编程知识 cdmana.com

Source code series: vue3 in simple terms (1)

author : xu จุ๊บ, Reprint without authorization prohibited .

Preface

Vue.js 3.0 ( hereinafter referred to as Vue3), Officially released in 2020 year 09 month 18 Japan ,Vue3 Beta The version was released as early as April this year . I believe that some students have already started to experience . Or I've already read the official documents , The students who are not good at English are rich in Click here , This is a Chinese document .

I think you will be right Vue2 Source code , Or the realization of the core function has a certain understanding , This article is to learn from me Vue2 The way , Come and talk with you 、 Study Vue3 Source code , The current version is 3.0.2. If there's something wrong 、 What's missing , I hope you can correct me. 、 Add .


Project directory

Directory structure

Look back at Vue2 Source directory

├── src
  ├── compiler    #   Compile related modules 
  ├── core        #  vue Core code 
  ├── platforms   #   Platform related 
  ├── server      #   Server side rendering related 
  ├── sfc         #  vue Single file component 
  ├── shared      #   Content common methods 
 Copy code 

I want to see others Vue3 Directory structure within the source code

├──packages
  ├── compiler-core       
  ├── compiler-dom        
  ├── compiler-sfc       
  ├── compiler-ssr     
  ├── reactivity         
  ├── runtime-core       
  ├── runtime-dom         
  ├── vue
  ├── shared
  ├── ...            
 Copy code 

You can choose some to talk about the functions of these modules

  • compiler-core: Platform independent compilation module , For example, basic baseCompile Compile template file , baseParse Generate AST
  • compiler-dom: be based on compiler-core, Compiler module for browser , You can see that it's based on baseCompile,baseParse, Rewrote complie、parse
  • compiler-sfc: Used to compile vue Single file component
  • compiler-ssr: Server side rendering related
  • reactivity: vue Independent responsive modules
  • runtime-core: It's also a platform independent basic module , Yes vue All kinds of API, fictitious dom The renderer of
  • runtime-dom: be based on runtime-core, Runtime for browsers
  • vue: Import and export runtime-core, There are also compilation methods

You can see Vue3 Clear module splitting , Modules are relatively independent , We can quote reactivity This module , You can also quote compiler-sfc In our own development plugin To use it , for example vue-loader , vite All used .

monorepo

This is because Vue3 use monorepo It's the way to manage project code . differ Vue2 Code management , It's in a repo Manage multiple package, Every package Each has its own type declaration 、 unit testing . package It can also be released independently , Overall, it's easier to maintain 、 Publishing and reading .

From the entrance

establish Vue example

In retrospect Vue2 Create a b Vue example

import Vue from 'vue';
import App from './App.vue';

const vm = new Vue({
  /*  Options  */
}).$mount('#app');
 Copy code 

Vue3 Create an application instance in

import { createApp } from 'vue';
import App from './App.vue';

const app = Vue.createApp({
  /*  Options  */
});

const vm = app.mount('#app');
 Copy code 

You can see the difference between the two is ,Vue2 Each of them Vue Applications are all through new One Vue Constructor creates a new Vue example . And in the Vue3 What about China? , Every Vue Applications are all through createApp Function to create a new application instance .

The essence is that although there is no difference , But it has positive effects in other aspects , Next, let's take the example above , Suppose we need to register a global component , Or change the global configuration .

/* Vue2  How to do it  */
Vue.component('my-component', {
  /*  Options  */
});
Vue.directive('my-directive', {});
Vue.mixin({
  /* ... */
});

/* Vue3  How to do it  */
const app = Vue.createApp({
  /*  Options  */
});
app.component('my-component' /*  Components  */); //  Each method is chainable 
app.directive('my-directive' /*  Instructions  */);
app.mixin();
 Copy code 

Vue2 Use in Vue Of API、 Configuration can be changed globally Vue, It's very convenient , But for the same page through the same Vue Multiple constructor instances app The situation is very unfriendly . for example

const app1 = new Vue({}).$mount('#app1');
const app2 = new Vue({}).$mount('#app2');
 Copy code 

and Vue3 Only the application instance of the current configuration will be changed , Effectively avoid this problem .

Source entrance

The next step is from import { createApp } from 'vue'; This sentence begins to learn the source code .

stay Source code vue modular , We can see ,vue In fact, this module is basically introduced and exported runtime-dom,complier. Continue to runtime-dom Search for createApp.

export const createApp = ((...args) => {
  //  You can see the real createApp  The method is on the render properties 
  const app = ensureRenderer().createApp(...args)
  // ...
  const { mount } = app
  app.mount = (containerOrSelector: Element | string): any => {
    // ...
  }

  return app
}) as CreateAppFunction<Element>

/**
 * ensureRenderer  This is to execute createApp when , Only give renderer Render assignment , It's also an optimization point .
 *  Import only reactive,  No implementation createApp, Not execute  createRenderer,
 *  So when it comes to packing  tree-shaking  You can shake it off  runtime-core  This module .
 * */
function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}

 Copy code 

stay runtime-dom/index.ts It can be found in the createApp The definition of , There are three steps in the implementation , establish app Application example , rewrite mount Method , return app Application example .

And then we're going to derive this method runtime-core Take a look in the module createRenderer, And find the real createApp

// 1
export { createRenderer } from './renderer'

// 2
export function createRenderer (options) {
  return baseCreateRenderer(options)
}

// 3
function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
) {
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    forcePatchProp: hostForcePatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent
  } = options

  // ...

  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

// 4
export function createAppAPI(render, hydrate) {
  return function createApp(rootComponent, rootProps = null) {
    // ...
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent as ConcreteComponent,
      _props: rootProps,
      _container: null,
      _context: context,
      version,

      use (plugin) {
        // ...
        return app
      },
      mixin(mixin: ComponentOptions) {
        // ...
        return app
      },
      mount(rootContainer: HostElement, isHydrate?: boolean): any {
        if (!isMounted) {
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )

          vnode.appContext = context

          if (isHydrate && hydrate) {
            // ...
          } else {
            render(vnode, rootContainer)
          }
          isMounted = true
          app._container = rootContainer
          ;(rootContainer as any).__vue_app__ = app

          return vnode.component!.proxy
        }
      },
      // ...
    }
  }
 Copy code 
  • notes : I omit a lot of the above code and create pp Instance independent code , I think it's clearer to sort out the thinking and look at the source code than to read it line by line .

After four times, we finally runtime-core/apiCreateApp Found in createApp Method , as well as app example . You can see app example and Vue2 in Vue On the constructor API Basically consistent .app For example usecomponent It's all back in the end app example , Support chain writing .

from createApp Method call to app Instance creation process , In fact, we can roughly see runtime-dom How is this module based on runtime-core Build virtual for browsers dom Renderers .

/**
 *  stay  runtime-dom  in , call  runtime-core  Of  createRenderer  Method 
 *  And pass in  rendererOptions, This  rendererOptions  It actually contains the browser's DOM API,props
 *  for example  createElement、insertBefore  etc. , You can go  runtime-dom/nodeOps.ts  Look inside .
 * **/
function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}

/**
 *  It's coming into different environments  endererOptions, You can generate different environments render
 * **/
function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
) {
  //  These variables end up being  redner  in  patch  Service 
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    createElement: hostCreateElement,
    // ...
  } = options

  //  The corresponding browser environment is generated here  render
  const render: RootRenderFunction = (vnode, container) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }

  return {
    render,
    hydrate,
    //  In parametric form   Pass in  createApp  in ,  Finally supply  app Examples  mount  Use .
    createApp: createAppAPI(render, hydrate)
  }
 Copy code 

Let's keep looking at runtime-core/apiCreateApp in createApp Method

export function createAppAPI(render, hydrate) {
  return function createApp(rootComponent, rootProps = null) {
    // ...
    const app: App = (context.app = {
      // ...
      /**
       *   We create it in the project  app example , Again  mount  To a node 
       *   It's going to end up here ,App  Component as  rootComponent, render It's the render of the browser environment .
       * **/
      mount(rootContainer: HostElement, isHydrate?: boolean): any {
        if (!isMounted) {
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )
          vnode.appContext = context

          if (isHydrate && hydrate) {
            // ...
          } else {
            render(vnode, rootContainer)
          }
          isMounted = true
          app._container = rootContainer
          ;(rootContainer as any).__vue_app__ = app

          return vnode.component!.proxy
        }
      }
      // ...
    }
  }
 Copy code 

Of course , We're in the project .mount('#app') Not directly app Examples mount, here mount Method yes runtime-core It's nothing to do with the platform . in fact runtime-dom It has been rewritten mount, It's also for the browser environment .

But this one , This article won't go on , because mount The process is roughly to create Vnode, Rendering , Generate reality DOM, It's useful to Vue3 In response to the new , So we can start by looking at this, which can be used independently , Modules that don't have other baggage reactivity. ( Dig a hole for yourself , I'll tell you later -.-|)


Vue3 In response

Proxy

We know that Vue2 Inside, through Object.defineProperty API Changes in hijacking data , Depth traversal data The object in the function , Set for each property in the object gettersetter.

Trigger getter Will pass Dep Class does dependency collection operations , Collect current Dep.target, That is to say watcher.

Trigger setter, Will do distribution update operation , perform dep.notify Inform all kinds of information collected watcher to update , Such as computed watcheruser watcher Rendering watcher.


Vue3 use Proxy Refactoring the responsive part ,effect Side effect function Instead of watcher,

Proxy Of get handle in perform track() Used to track collection dependencies ( collect activeEffect, That is to say effect ),

set handle Internal execution trigger() To trigger a response ( Perform collected effect)

Independent response

I've mentioned it many times before ,reactivity It can be used independently , For example, we are in node Use in .

// index.js
const { effect, reactive } = require('@vue/reactivity');
// reactive  Define responsive data , Also is to use proxy  Set up  get、set handle
const obj = reactive({ num: 1 });

// effect  Define the side effect function 
effect(() => {
  console.log(obj.num);
});

//  modify num, trigger  Trigger response , perform  effect
setInterval(() => {
  ++obj.num;
}, 1000);
 Copy code 

node index.js, You'll see the script run all the time .

reactive

Then we will follow this code to see @vue/reactivity Inside reactive, effect Well . I'll just focus on what we care about right now , After the mainstream of this direction is finished, we will sort out the other methods .

Let's start with a brief introduction ReactiveFlags This enumeration , Because it will be used later

export const enum ReactiveFlags {
  SKIP = '__v_skip',  //  The value of this property is  true  The object of   Will be bypassed agent 
  IS_REACTIVE = '__v_isReactive', //  Get whether it is responsive 
  IS_READONLY = '__v_isReadonly', //  Whether it's read-only or not 
  RAW = '__v_raw' //  This property is applied to the original object 
}
 Copy code 

Enter the view reactive The theme of the method .

export function reactive(target: object) {
  //  If it's read-only, responsive data , It will return to itself directly 
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target, //  object 
    false,  //  Is it read-only 
    mutableHandlers, // proxy handle
    mutableCollectionHandlers  //  Aggregate data  proxy handle
  )
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  if (!isObject(target)) {
    //  Not object   Go straight back to 
    return target
  }

  //  If it is already a responsive object, it will directly return ,  Unless it is  readonly  Act on this responsive object 
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }

  //  A cache map,key yes  target object , value It's a reactive object 
  //  If the object has already created a responsive object , From   cache map Read back in 
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  //  with  skip  Mark  、 Frozen and so on to be non extensible , The type is not object array map set weakmap weakset  It's all off the white list , Don't create a proxy 
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }

  //  Use  proxy  To create responsive objects 
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  //  Deposit in   cache map  in 
  proxyMap.set(target, proxy)
  return proxy
}
 Copy code 

baseHandlers What operations are hijacked inside ?

// reactivity/baseHandlers.ts => mutableHandlers
//  Here we choose the normal object handlers Come and see 
export const mutableHandlers: ProxyHandler<object> = {
  get, //  To access the properties of an object handler
  set, //  Set the properties of the object handler
  deleteProperty, // Delete object properties handler
  has, //  in the light of  in  Operator handler
  ownKeys //  On the object getOwnPropertyNames、getOwnPropertySymbols、keys  Methods such as handler
};
 Copy code 

Use Proxy The advantage is , I believe many people even if they haven't seen the source code , They all know something about . for example Proxy Make up for Object.defineProperty Need recursive objects , Set... For each property settergetter, There's no way to hijack some other operations , Arrays also need hack, Additional methods are required to handle new attributes ,Map、Set 、weakMap Such as the data structure can not be responsive .

summary

Some of the above , It's using Proxy The optimization , We'll see what other optimizations have been made in the code implementation later . So far we know the first sentence in the example , Execute first reactive, use proxy Proxy for the original object we passed in , Back to this proxy, It's called responsive objects . Finally, we assign the returned responsive object to obj.

effect

And then the order of the above example , Let's look at this sentence again

  effect(() => {
    console.log(obj.num);
  });
 Copy code 

We give effect A function is passed in the method () => { console.log(obj.num); } , Function to access the responsive object obj Of num attribute . So let's see effect The source code. .

export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  if (isEffect(fn)) {
    // 1.  If  fn  Yes  effect  Function label , It points to the original function , Hereunder  createReactiveEffect  You can see raw、_isEffect The definition of 
    fn = fn.raw
  }
  // 2.  Create a responsive side effect function 
  const effect = createReactiveEffect(fn, options)
  if (!options.lazy) {
    // 3.  perform effect,  Does this look like  computed watcher  Of  lazy  attribute  , if true Not immediately ,
    effect()
  }
  // 4.  The return is wrapped in  fn  Of  effect  function 
  return effect
}

let uid = 0

const effectStack = [] // effect Stack , Remember  Vue2  Inside   Global storage  Watcher  Is it a stack ?
function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }

    if (!effectStack.includes(effect)) {
      //  This is an optimization , Remove the  deps  In all  dep  Inside  effect, With the back  track  Li Gei  effect deps Add... Again  dep, It's like eliminating unnecessary dependencies , I'll talk about it in detail later  
      cleanup(effect)
      try {
        //  Open to allow collection   That is to say, set this variable shouldTrack by  true
        enableTracking()
        //  Here's the stack ,  Set up activeEffect, Execute the original function 
        effectStack.push(effect)
        activeEffect = effect
        return fn() //  Here we execute the original function , It's a reference to what we wrote in the function   The value of the responsive object , Triggered by the responsive object  get handler
      } finally {
        //  Finally out of the stack , Stop collecting ,activeEffect  Refer back to the previous effect, Here's the nested relationship between the two effect Have effect 
        effectStack.pop()
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  //  Here is effect Related properties of 
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true
  effect.active = true
  effect.raw = fn
  effect.deps = [] // effect  Yes  dep  Two way dependence of 
  effect.options = options
  return effect
}

function cleanup(effect: ReactiveEffect) {
  // deps  It's a   The array is wrapped in  Set The data structure of the set    [Set1(...), Set2(...), ...],  every last  Set  Namely ,targetMap Inside dep
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}
 Copy code 

summary

So far we know , In the example, execute effect(fn), Will create 、 perform 、 And return a effect function , Execute this effect Function time , Turn on the collection switch , Push into the big picture effectStack In the stack , Set global activeEffect The pointer points to itself , And execute the incoming fn, This triggers obj Of get handler. After execution fn after , Execute the destack , Stop collecting ,activeEffect Point to the previous one in the stack effect.

get, Rely on collection

It's about executing incoming fn, This triggers obj Of get handler, So let's see get Source code .

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW &&
      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) {
      return target
    }
    //  Remember  ReactiveFlags  Enumerate the values in it ,  The corresponding values are obtained through the above judgments , Because they are all private properties , Get the value and return it directly , There's no need to go down 

    const targetIsArray = isArray(target)
    // ['includes', 'indexOf', 'lastIndexOf']  Array changes , The results of this approach may also change , therefore  get  There's special treatment in it 
    //  for example   perform arr.includes('xx')  when , Can track  arr  Array each subscript 
    if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    //  utilize Reflect Map return value 
    const res = Reflect.get(target, key, receiver)

    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : key === `__proto__` || key === `__v_isRef`
    ) {
      //  Judging native methods, etc , Go straight back to , No track 了 
      return res
    }

    if (!isReadonly) {
      // track  Rely on collection operations 
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      //  This is for shallow response , such as  shallowReactive  Method , I'll give you a brief introduction later 
      return res
    }

    if (isRef(res)) {
      //  here  ref  yes  reactivily  There's another one inside api, Implementation is relatively simple , You can create a responsive object for the base type , for example  num = ref(0)  Will create a  value by 0 Responsive objects for , isRef  Is to judge whether it is  ref  value 
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    if (isObject(res)) {
      //  You can see ha , Child objects also need to be recursively hijacked , But here, compared to  Vue2  There's an optimization point ,
      // Vue2 If the property is still an object   Array etc. , Then immediately traverse the child object to do hijacking 
      //  and  Vue3  When you access this property , Find that the value is an object and then turn it into a responsive object 

      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
 Copy code 

The above is complete get handler 了 , You can find get In the operation, the first is to ReactiveFlags The values in this enumeration are hijacked , Then evaluate the special methods in the array separately 、 Handle , And then use Reflect This is a good partner , Again track To do things related to dependency collection , Last result returned . Let's have a look track.

// ./effect.ts

//  Let's first look at two variables used 
activeEffect //  Be similar to  Vue2  Inside  Dep.target,  One watcher.  Here is the current active effect
shouldTrack //  Judge whether to collect at present  ,effect The internal execution of the function starts by setting this variable as true
targetMap //  Take the original object as the key  , Value is also a weakMap,map The property name is key,effect The set is value
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) {
    return
  }

  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  //  All these judgments , cache , It will eventually form  targetMap  Like the data structure 
  /** 
   *  targetMap = {
   *    [target]: {
   *      [key]: new Set([ effect,... ])
   *    }
   *  }
  **/

  if (!dep.has(activeEffect)) { // effect  Execution time , take  activeEffect  Point to your 
    dep.add(activeEffect)
    //  Currently active  effect  It also stores  dep aggregate , This is actually cooperation  effect  Inside  cleanup  Method   Remove unwanted dependencies 
    activeEffect.deps.push(dep)
  }
}
 Copy code 

Why do you say activeEffect.deps.push(dep) It's cooperation cleanup Remove unwanted dependencies , Take a look at the following example

//  example 2:  One more.  count  attribute 
const obj = reactive({num: 1, count: 0});

effect(() => {
  if (obj.num === 1) {
    ++obj.num
    //  We are only in  num  by  1  It's time to visit again  count
    console.log('count', obj.count)
  }
  console.log('num', obj.num)
},);

setTimeout(() => {
  //  Because the first time I visited  count,  therefore   We modify  count  Will execute  effect
  console.log(' The first revision  count')
  obj.count = 2
}, 1000);

setTimeout(() => {
  //  Last execution effect When ,cleanup Remove all dependencies , But because  num  by  2
  //  Function does not access  count , Didn't go to  track count, There will be no new  activeEffect.deps.push(dep)  This operation 
  //  So modify  count  They don't execute  effect
  console.log(' Second revision  count')
  obj.count = 3
}, 1000);

setTimeout(() => {
  // num  Every time I visit , So normal trigger response 
  console.log(' The third revision  num')
  obj.num = 3
}, 1000);
 Copy code 

In this case, for the first time effect, It's automatic ,effect.deps The value would be [dep, dep], Namely num Of dep aggregate , as well as count Of dep aggregate .

Then modify count Trigger execution effect, Execute first cleanup, Will clear num and count dep In the assembly effect,

perform fn When , Because only by visiting num,num Of dep I'll add effect, but count Of dep Not anymore . and effect.deps It's just push num Of dep.

So I'll change it later count Not when It won't trigger effect 了 .

summary

Finally, back to the mainstream , In the first example, we're passing in fn Medium visits obj.num , Trigger get handler, It will pass. Reflect.get Get the value 1, next track our num attribute , collect effect, Last targetMap The structure will be as follows

targetMap = {
  [obj]: {
    'num' : new Set([effect])
  }
}
 Copy code 

set, Distribution updates

The last section relies on the collection process and the end of the process . We talked about the modification num Will trigger effect perform , In fact, it's the distribution of updates , That is to say perform set handler. Let's see set handler Source code

// ./baseHandlers.ts

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 1.  First old value 
    const oldValue = (target as any)[key]
    if (!shallow) {
      value = toRaw(value) // toRaw  Is to take the original object , Here if value It's a reactive object , Then it goes all the way to the original object 
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        //  If the old value is  Ref  The response object , And the updated value is not , That updates the old responsive object  value  Property value is enough , There's no need to implement the trigger
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    
    // 2.  Judge the present  set  Of  key  Does existence exist and  target On 
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)

    // 3. Reflect.set  evaluation  
    const result = Reflect.set(target, key, value, receiver)

    //  Here, the original data is operated on the prototype chain ,Reflect.set After modification , I'll come in again , So with this judgment 
    if (target === toRaw(receiver)) {
       // 4.  By having no current  key  Whether it exists or not depends on  add  Of  triger, still  set  Of  trigger,set There will be one more oldvalue
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}
 Copy code 

set I'm done with the logic of , The notes are roughly marked as 4 Step , Take a look at the last step trigger

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  //  From before  track  What's stored in it  targetMap  Take out the corresponding  depsMap
  const depsMap = targetMap.get(target)

  if (!depsMap) {
    //  No dependence , Go straight back to , It doesn't trigger back effect Implementation 
    return
  }

  const effects = new Set<ReactiveEffect>()
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) { //  This is containing  effects  Of  Set aggregate 
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.allowRecurse) {
          // add  The method is to put  effect  Unified   Collect to  effects  In this collection 
          effects.add(effect)
        }
      })
    }
  }

  // ...
  add(depsMap.get(key)) //  hold dep Put it in effects in 
  // ...

  const run = (effect: ReactiveEffect) => {
    if (effect.options.scheduler) {
      //  A scheduler , You can sort it out 、 duplicate removal 、 Put in  nexttick  Asynchronous execution in 
      //  We can also customize this scheduler 
      effect.options.scheduler(effect)
    } else {
      //  Otherwise, execute directly 
      effect()
    }
  }

  //  Start execution 
  effects.forEach(run)
}
 Copy code 

summary

thus , We understand the process of distribution update , According to the first example , We modify num , Trigger set handler, hold targetMap in num Corresponding dep out . adopt add Method to dep Inside effect Add to effects In this big collection . Finally, execute run Method traversal execution effects Inside effect.

summary

Only this and nothing more , Let's study Vue3 The beginning is the end . Because I'm a beginner , We can't do everything , I can't understand the function of every code sentence , Can only grasp the most concerned about , The most basic process . It's like a tree , After we get to know the basic backbone , To continue to explore other branches .

For example, this time we focus on the analysis of the response equation, and there are many other API,RefshallowReactive,、readonly shallowReadonly wait , But I think we're going through reactive Understand the whole responsive Side effect function , Rely on collection 、 After distributing the updated Foundation , Look back at this API It's going to be a lot easier .

ps: This time we learned about the basic response , You can learn more Vue3 Rendering logic ,composition API etc. , Later, I will also write down the learning process of other modules , There is something wrong , Also please correct me , thank you !

版权声明
本文为[Zhiyun health front end team]所创,转载请带上原文链接,感谢
https://cdmana.com/2020/12/20201224152607715A.html

Scroll to Top