Previous articles:

  • Vue3-vuex-typescript trampling tour

preface

Vue3 is finally here. It’s a matter of opinion whether it works or not, but for now, I prefer react

Reasons for favoring React (personal opinion only)

  • 1. React has perfected hooks; Vue3 borrows from the React hooks, but not completely
  • 2.Fiber scheduling mechanism, excellent one
  • 3. Vue2 GC is not very good because template compilation using with(CTX), vue3 has been fixed and is worth 👍
  • 4. Vue data stream management does not like the react is rigorous, some would say, vue is very strict, I can do well, but in the actual project development, in order to achieve certain requirements (especially take over other people’s code), you have to write some syntactic sugar type code, serious damage of top-down data flow, increase the risk of a certain project quality, I’ve been cheated a few times, and I can’t make fun of it
  • Vue doesn’t support typescript as well as React. See the vue3-vuex-typescript pit tour
  • React abstract common components. High-order components are more flexible and convenient than VUE. JSX is more flexible and easy to control than Template
  • 7. Vue ecosystem is inferior to React ~
    • Antd Vue is maintained by individuals, while ANTD React is maintained by a large team of technical communities, especially corporate projects. Sustainability is the foundation
    • In practice, both Recoil and Mobx are better than Vuex

Vuex can only be used on VUE, because VUex is mounted on VUE and listening data is based on new VUE

Recoil can only be used with React, because recoil has four apis based on Provide and useEffect in React, which are very simple and effective.

Mobx can be used by anyone, because it is based on Observable and mobx has its own data flow management functions, such as a store, which can manage its own flow. For example, the constructor can perform some basic operations on data, such as when, autorun hook to listen to data, in the actual application, the data flow can be completely managed in mobx, outside only reference and call store method, data flow management, clear, see the effect.

Anyway, we still have to learn, so let’s get started…

vue3 VS vue2

1. The source code structure has changed

  • All vue2 methods are mounted on vue instances, and are packaged into artifacts whether you use them or not. The listening data is based on the browser Object. Observer capabilities, and the Observer API is not exposed

  • Vue3 is a change from code structure, written in typescript. Each module can be used externally as a separate function, which is tree-shaking, and Reactive etc can be exposed for external use

Packages directory structure

  • Reactivity Reactive data processing
    • Reactive Ref Effect can be referenced separately without reference to vUE
  • Runtime-core Platform-independent Runtime (VNode, renderer vue component instance…)
  • Runtime-dom Specifies the runtime of the browser
  • Complier-core platform-independent compiler (render)
  • Complier-dom is compiled for the browser
  • Complier-sfc compilation handles single files
    • A vue file contains three parts: SFC breaks them up for processing separately, template => vue, script => js, and style => CSS
  • The complier-SRr server-side rendering compiler
  • Server-renderer Server rendering
  • Shared Internal utility functions and constants are shared
  • Size-check private package, used to check the runtime size after tree-shaking
  • Template-explorer online template compiler
  • Vue main entrance

2. Template compilation

AST analysis tool

vue2

  • Template => AST => traverse the AST (look for static nodes and tag them) => render => VNode => diff
    • Diff from top to bottom, without distinguishing between dynamic and static templates, is expensive
    • Vue2 uses with because the browser does not recognize the dynamic string {{greeting}}.

vue2 template-explorer

    • Vue2 is optimized at compile time, react is optimized at runtime with JSX => react. CreateElment
    • Template compilation takes up a lot of performance. In the process of compiling template into AST, the template is implemented through regular matching, which degrades performance due to the backwardness of regular matching
For example: match /xf{1,3}fz/ XFFZ XFFFZ XFFFFZ keeps backtracking until the match, namely the regular greed featureCopy the code
  • OptionsAPI Configuration API
new Vue({
  data: {},
  mounted: {},
  methods: {}
})
Copy the code

vue3

  • Template => AST=> iterate over AST=> generate new AST=> render
    • Vue3 does not use with because vue3 is compiled using @babel/ Parser

Vue 3 Template Explorer

    • Vue3 is optimized for static compilation: static analysis is done at compile time, and static nodes that can be promoted are pushed out of the rendering function so that nodes do not need to be created again

Such as:

<div>
    <section>
      <span> {{ name }}</span>
    </section>
</div>-- Dynamic node<span>{{ name }}</span> ---
Copy the code

When the name changes, instead of diff recursively from top to bottom, just direct the update through dynamicChildren, which greatly improves compilation performance

1.block tree

const vnode = {
  tag: 'div'.children: [{tag: 'section'.children: [{tag: 'span'.children: ctx.name,
          patchFlag: 1 // Dynamic textContent},],},],// This array collects all dynamic child nodes under the current VNode
  dynamicChildren: [{tag: 'span'.children: ctx.name, patchFlag: 1},],}Copy the code

However, there are cases where directional update mode cannot be used, such as V-if, v-for, which recreates a layer of blocks

2. Static nodes are promoted

3. Static attribute enhancement

4. Event caching

5. If the number of static nodes exceeds 6, static nodes will be stringed.

    • When the template is compiled, it iterates over the string based on the state machine
  • ComppositionAPI: Hooks API
createApp({
  setup() {}
}).mount('#app')
Copy the code

3. Responsive systems

vue2

  • Object. DefinePrototype, which can’t directly listen for new keys, deep nesting (recursive), arrays (resulting in multiple get/set triggers)

vue3

  • It is expensive to create an instance. Every time you create an instance, you need to expose a lot of things on this. Every exposed attribute needs to be defined in Object.defineProperty

    • Proxy can be understood as a layer of “interception” in front of the target object. All external access to the object must pass this layer of interception. Therefore, it provides a mechanism for filtering and rewriting external access, which can be translated as “Proxy”.
    • The Proxy can monitor the changes of the object itself and perform corresponding operations after the changes. You can track objects, and it’s also useful for data binding

    var proxy = new Proxy(target, handler);

    The new Proxy() parameter generates a Proxy instance, the target parameter represents the target object to intercept, and the handler parameter is also an object that is used to customize the interception behavior. Note that for the Proxy to work, you must operate on the Proxy instance (in this example, the Proxy object). Instead of operating on the target object

  • Based on proxy, you can listen for new keys, but can not directly listen for deep nesting and array through GET, which requires additional processing

    • Array, by overriding array methods, intercepting these methods to gain the ability to listen
    • Object deep nesting uses reactive only when a key is obtained to solve the performance problem of deep nesting
new Proxy(target, baseHandler);
const baseHandler = {
  get(target, key) {
    // Collect dependencies
    track(target, key);
    const res = Reflect.get(target, key);
    return typeof res === 'object' ? reactive(res) : res;
  },
  set(target, key, val) {
    const res = Reflect.set(target, key, val);
    // Trigger the update
    return res;
  },
  apply (target, ctx, args) {
    return Reflect.apply(...args);
  }
};
Copy the code

Vue3 overall process

import { createApp } from 'vue'
import App from './app'
const app = createApp(App)
app.mount('#app')
Copy the code

App mounting process

1. Call createApp(App) to generate an application instance, and then call mount

2. In the mount stage, first create vnodes according to the entry component App, and then trigger the render(Reactive +effect) function. The whole render stage is the Patch VNode stage. It is divided into initial mount and update component mount

3. For the stage of internal patch, different node types will be treated differently, mainly components and elements. Components will continue to be created and mounted

Data /props hangs on CTX

CreateApp => App

    • Run-time/dom/ SRC /index: createApp -> ensureRenderer -> createRenderer
export const createApp = ((. args) = > {
  // Create an app instance object
  constapp = ensureRenderer().createApp(... args)// Override the mount method (which can be implemented independently of other platforms)
  const { mount } = app
  app.mount = (containerOrSelector) = > {
    // ... 
    // Container processing
    const proxy = mount(container)
    ontainer.removeAttribute('v-cloak')
    container.setAttribute('data-v-app'.' ')
    return proxy
  }
  return app
}) 

// Dynamically load render, so that import can guarantee the tree shake
function ensureRenderer() {
  return renderer || (renderer = createRendere(rendererOptions))
}
Copy the code
    • runtime-core/src/renderer: createRenderer -> baseCreateRenderer => {

    render, hydrate, createApp: createAppAPI(render, hydrate)

}

Node differs from Element: All Element elements are node elements. Node is not necessarily an Element, because a text node is not an element. An element is a node

 // Create logic
export function createRenderer(options) {
  return baseCreateRenderer(options)
}

// Return render and createApp methods
function baseCreateRenderer(options,createHydrationFns) {
  const{dom manipulation methods... } = options/** Patch method, accept the new and old VNode nodes and corresponding containers, sibling nodes of the node, parent components, compression optimization, etc., the whole patch process is actually the corresponding VNode depth-first traversal process; The result is a complete tree */
  const patch: PatchFn = (n1,n2,container,anchor = null,parentComponent = null,
  parentSuspense = null,isSVG = false,optimized = false) = > {
    // Old vnodes exist, and the new VNode type is different, destroy the corresponding old node
    if(n1 && ! isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense,true)
      // Set n1 to null to ensure that the entire node mount logic is followed
      n1 = null
    }

    // The node is of an unoptimized type
    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }

    /** Patch has two internal cases: 1. Mount N1 is null 2. Update * /
    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text:// Process text
      case Comment:// Handle comments
      case Static:// Handle static nodes
      case Fragment:
      default: //elemment handles DOM elements
        if (shapeFlag & ShapeFlags.ELEMENT) {
          // Element processing logic
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          // Component processing logic
          /** Determine whether to mount or update the component, mount the mountComponent and update updateComponent STEP: 2. For first mounted mountedComponent inside, 1. Create the component instance according to the component VNode. 2. Set the component instance, call the setup method, and handle options, etc. 3. Trigger and run a render function with side effects, internally using Effect and render calls STEP: 3. Render real logic 1. Generate the corresponding subTree, since the component will be the case of other sub-components, this process is the process of calling render, and then generate the corresponding subTree 2. Render the corresponding subTree into the container by performing the corresponding patch and then entering the function again. If the patch is element, the render task is triggered */
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          / / processing TELEPORT
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          / / deal with SUSPENSE}}// set ref
    if(ref ! =null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
    }
  }
  
  // Process text
  const processText = (n1, n2, container, anchor) = > {}
  // Handle comments
  const processCommentNode: ProcessTextOrCommentFn (n1,n2,container,anchor)
  // Handle static
  const mountStaticNode = (n2,container,anchor,isSVG)
  
  const processFragment = (...) {}
  // Handle element: 1. Initialize the mount for the first time. 2
  const processElement = (n1, n2) = > {
   if (n1 == null) {
      // Initialize the mount node element
      mountElement(n2,container,...)
    } else {
      // The process of updating a nodepatchElement(n1, n2, ...) }}// Mount the element node
  const mountElement = (vnode,container,...) = > {
    // Create a DOM element node and call the platform method of options directly
    el = vnode.el = hostCreateElement(vnode.type,isSVG,props && props.is)
    // Process text nodes
    hostSetElementText(el, vnode.children as string);
    // The child nodes are arrays
    mountChildren(vnode.children,el,null,parentComponent,...)
    // Handle props, class style event, etc
    hostPatchProp(...)
    
    // Insert the created element into the node
    hostInsert(el, container, anchor)
    // 
    queuePostRenderEffect(() = >{ vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode) needCallTransitionHooks && transition! .enter(el) dirs && invokeDirectiveHook(vnode,null, parentComponent, 'mounted')
      }, parentSuspense)
  }
  
  // When the child node is an array, the patch needs to be traversed
  const mountChildren = (children,container,anchor,...) = > {
    patch(....)
  }
  // Update the element node, mainly to update props and child nodes, and internally to update the dynamic patch
  const patchElement()
  // Changes triggered by dynamic Black Tree
  const patchBlockChildren = (.) = > {}
  // Update props of a node, such as class style, event, etc
  const patchProps = (.) = > {}
  
  // The component processing logic determines whether the sub-component needs to be updated. If so, the sub-component's side effect rendering function is recursively executed, otherwise the VNode is directly updated
  const processComponent() {
    // Initialize the mount of the component
    if{mountComponent(...) }else {
      / / updateupdateComponent(...) }}// Mount the component
  const mountComponent = (...) {
    // 1. Create a component instance
    const instance = (initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent,
      parentSuspense
    ))
    // 2. Set the component instance
    setupComponent(instance)
    // 3. Set up and run the render function with side effectsSetupRenderEffect (instance,...). }// Update component: DOM diff updates the DOM to trigger
  const updateComponent = (n1: VNode, n2: VNode,...) = > {
    const instance = (n2.component = n1.component)!
    // Determine whether a new child component is needed according to the old and new child component VNode
    if (shouldUpdateComponent(n1, n2, optimized)) {
      // Update next of the corresponding component instance to the new VNode
      // Call the render function of the child component
      instance.update()
    } else {
      // There is no need to update, just copy the properties of the old and new components}}// Run the render function with side effects
  const setupRenderEffect = (instance,...) = > {
    // Create a responsive side effect rendering function that re-executes the internal logic of componentEffect when the data inside the component changes, and mounts it to the update method of the component instance for subsequent updates
    instance.update = effect(function componentEffect() {
      if(! instance.isMounted) {// Initialize render the first time the component is mounted
        // 1. Generate the corresponding subTree
        // 2. Render the corresponding subTree into the container
        
        // Render component generates subtree VNode,
        const subTree = (instance.subTree = renderComponentRoot(instance))
      } else {
        // Render the corresponding subTree into the container. The subTree may be Element, text, Component, etc
        path(null,subTree,container,...) }})}// Update the component instance information, such as the component props, the Vnode reference, and the component instance that the Vnode points to
  const updateComponentPreRender = (instance,nextVNode,...) = > {}
  If the old node is text 1. If the new node is text, replace 2 directly. New node is array, empty text, add array 2. If the old node is empty 1. If the new node is text, add text 2. If the new node is empty, do not handle 3. If the old node is an array 1. If the new node is text, delete the node and add text 2. New node is empty, delete old node 3. New node is array, need full diff child node */
  const patchChildren: PatchChildrenFn = () = > {}
  // diff, the child node of the array is changed, which is handled by updating, deleting, adding, or moving
  const patchKeyedChildren = () = > {}
  
  // Render and mount processes
  const render = (vnode, container) = > {
    / / unloading
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null.null.true)}}else {
      // Create or update the component
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs();
    // Cache the VNode node (already rendered)
    container._vnode = vnode
  }
  
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}
Copy the code

— runtime-core/src/apiCreateApp: createAppAPI

export function createAppAPI(render,hydrate) {
  // Accepts two arguments, the object and props of the root component, which are null by default
  return function createApp(rootComponent, rootProps = null) {
    // Create the application context
    const context = createAppContext();
    /** ctx = { app, config: { isNativeTag: NO, performance: false, globalProperties: {}, optionMergeStrategies: {}, isCustomElement: NO, errorHandler: undefined, warnHandler: undefined }, mixins: [], components: {}, directives: {}, provides: Object.create(null) } */
    // All plug-ins
    const installedPlugins = new Set(a);let isMounted = false
    
    / / app instance
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent,
      _props: rootProps,//props
      _container: null.// Mount the container
      _context: context,
      version,
      get config() {
        return context.config
      },
      // Mount the middleware through use,
      use(plugin, ... options){ installedPlugins.add(plugin) plugin.install(app, ... options)return app;
      },
      / / use mixins
      mixin(mixin) {
        context.mixins.push(mixin)
        return app
      },

      // Define the component on the app instance
      component(name, component) {
        if(! component) {return context.components[name]
        }
        context.components[name] = component
        return app
      },
      // Define directives
      directive(name, directive) {
        if(! directive) {return context.directives[name]
        }
        context.directives[name] = directive
        return app
      },


      // Mount the component core rendering logic
      mount(rootContainer, isHydrate) {
        if(! isMounted) {// No mount, create VNode of root node
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          );
          // Bind context
          vnode.appContext = context

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            // Instantiate trigger render, use the renderer to render VNode, pass in VNode and container
            render(vnode, rootContainer)
          }
          
          isMounted = true
          
          app._container = rootContainer

          returnvnode.component! .proxy } },/ / unloading
      unmount() {
        if (isMounted) {
          render(null, app._container)
        }
      }
      provide(key, value) {
        context.provides[key as string] = value
        return app
      }
  }
}
Copy the code

Data response analysis

  • Reactive: Generates complex (Object, array, etc.) data and returns it as a reactive data proxy
  • Ref: Returns the basic (number, string, etc.) data proxy
  • Effect (FN) : listens for data change handlers

When initialized, fn is executed, collecting dependencies => render the page

Render => VNode => patch => mount

When reactive data in FN changes, Effect re-performs FN => updates => renders the page

Render => VNode => patch => diff => update, e.g

const state = reactive({ count: 1 })
effect(() = > {
  const count = state.count;
  console.log(count);
})
Copy the code

reactive

  • Get: Collect depends on track
  • Set: triggers the update trigger
// Convert raw data to response data
export const proxyMap = new WeakMap<Target, any> () /export function reactive(target: object) {
  // Read only data, return directly
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  // Create reactive data
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

function createReactiveObject(target, isReadonly, mutableHandlers, mutableCollectionHandlers) {
  // If it is not Object, return target, which means that the data you are listening for must be {}.
  // ...
  // If the data is already responsive and not read-only, return it directly
  // ...
  // If the original data already has proxy data, find the corresponding proxy data from proxyMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) { return existingProxy }
  // If not found, process proxy data
  const proxy = new Proxy(target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers)
  // Insert proxy into proxyMap for retention
  proxyMap.set(target, proxy);
  return proxy
}
// ------- handler --------
// mutableHandlers
export const base: ProxyHandler<object> = {
  get:createGetter,// Collect dependencies
  set, // Publish a subscription to trigger an update
  deleteProperty,
  has,
  ownKeys
}
function createGetter() {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // If it is an array, special handling is required, because array changes can cause multiple sets and get fires
    const targetIsArray = isArray(target);
    // Array calls built-in methods instead of methods on the prototype chain
    if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    
    // Other types
    const res = Reflect.get(target, key, receiver)
    
    // If it is an object
    /** data: { bar: { foo: 1 }, car: { price: 100 } } 1. When data.bar is executed, only bar is converted to Reactive 2. Foo is converted to reactive */ when data.bar.foo is executed
    if (isObject(res)) {
      // If the object is reactive, it is recursive, but it does not recurse all the keys in the first place
      return isReadonly ? readonly(res) : reactive(res)
    }
    
    // Start collecting dependencies instead of read-only data
    if(! isReadonly) { track(target, TrackOpTypes.GET, key) }returnres; }}// Override array, override method directly, avoid multiple trigger get/set problem
const arrayInstrumentations: Record<string.Function> = {}
// Override the method of getting data
(['includes'.'indexOf'.'lastIndexOf'] as const).forEach((key) = > {
  // Get the original method of the array
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function (this: unknown[], ... args: unknown[]) {
	// Get the raw data of the proxy data
    const arr = toRaw(this)
    // Collect dependencies
    for (let i = 0, l = this.length; i < l; i++) {
      track(arr, ‘get’, i + ' ')}// Execute for the first time, possibly with reactive data
    const res = method.apply(arr, args) 
    if (res === -1 || res === false) {
      // If the data is proxied, find the original data and reprocess it
      return method.apply(arr, args.map(toRaw))
    } else {
      return res
    }
})
(['push'.'pop'.'shift'.'unshift'.'splice'] as const).forEach((key) = > {
 / /...
}

function createSetter(shallow = false) {
  return function set(target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ) :boolean {
    // Get the original value
    const oldValue = (target as any)[key]
    if(! shallow) {// handle the case of the corresponding ref data
      value = toRaw(value)
      if(! isArray(target) && isRef(oldValue) && ! isRef(value)) {// when ref, value needs to be modified to trigger internal methods for the corresponding data
        oldValue.value = value
        return true}}else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    // Determine whether the key is newly added or modified
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // Trigger the change logic
    const result = Reflect.set(target, key, value, receiver)

    // receiver is a Proxy or an object that inherits from Proxy,
    // We need to deal with the case of the prototype chain, because if the prototype chain also inherits a proxy, modifying the properties on the prototype chain via reflect.set fires two setters
    if (target === toRaw(receiver)) {
      if(! hadKey) {//trigger Sends a notification
        trigger(target, 'add', key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, 'set', key, value, oldValue)
      }
    }
    return result
  }
}
Copy the code

effect

  • Effect (() => state.name) procedure
    • Initialization execution, fn => wrapper createReactiveEffect(FN) => activeEffect
    • Fn () => Triggers get => track to collect dependencies
/** * effect wraps the function-dependent logic and returns activeEffect ** fn which function to execute * options configuration item */
export function effect<T = any> (fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ) :ReactiveEffect<T> {
  // If fn is already an effect wrapped function, point directly to the original function
  if (isEffect(fn)) {
    fn = fn.raw
  }
  /** * Create a wrapper logic * because dependencies need to be collected at the time the data is fetched, the processing logic should be assigned to reactiveEffect * before execution
  const effect = createReactiveEffect(fn, options)
  // Determine if lazy is required. If not, execute it once
  if(! options.lazy) { effect() }// Return the wrapped function
  return effect
}

/ / stack effect
const effectStack: ReactiveEffect[] = [

// Wrap the function
function createReactiveEffect<T = any> (fn: () => T, options: ReactiveEffectOptions) :ReactiveEffect<T> {
  const effect = function reactiveEffect() :unknown {
    // If effect is not activated and there is no scheduling option, then fn is executed directly
    if(! effect.active) {return options.scheduler ? undefined : fn()
    }
    // During initialization, effectStack collects effects before fn
    EffectStack can be used to avoid this problem, since effect is triggered by modifying the corresponding data during fn execution
    if(! effectStack.includes(effect)) {// console.log(effect.deps, 'dependencies during current data execution ')
      // Clean up the dependent effects in effect.dep to avoid multiple rendering,
      
      Const state = reactive({show:true, name:' XFZ ', default: 'girl'}); // Start the listener, rely on the collection, collect the show and name fields, map to effect, Effect (function(){if(state.show){console.log(state.name)} else {console.log(state.default)}}) // Modify show Name state. Name = 'zc' // will print girl again if you do not clear all dependencies in the dep
      
      cleanup(effect)

      try {
        // Enable dependency collection
        enableTracking()
        / / effect into the stack
        effectStack.push(effect)
        // Set the active effect
        activeEffect = effect
        // Execute FN logic to trigger the collection of internal data dependencies and start collecting ActiveEffects
        return fn(); // fn() => Trigger GET => track to collect dependencies
      } finally {
        / / out of the stack
        effectStack.pop()
        // Restore the previous state
        resetTracking()
        // point to the last effect on the stack
        activeEffect = effectStack[effectStack.length - 1]}}}asReactiveEffect effect.id = uid++ effect.allowRecurse = !! options.allowRecurse effect._isEffect =true // indicates an effect function
  effect.active = true // Active state
  effect.raw = fn // The original function
  effect.deps = [] // effect dependency. An effect may have multiple listeners
  effect.options = options
  return effect
}

// Clear the corresponding dependency execution
function cleanup(effect: ReactiveEffect) {
  const { deps } = effect; // An effect may have multiple data dependencies
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0}}let shouldTrack = true // Whether dependencies should be collected
const trackStack: boolean[] = [] // Control the state of multiple nesting of collection dependencies

// Suspend dependent collection
export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}
// Start dependency collection
export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}
// Fall back to the previous dependent collection
export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}
Copy the code

Track collection dependency

{[' target1 ']: depsMap}
const targetMap = new WeakMap<any, KeyToDepMap>();

/** * Collection depends on * target raw data * type Trigger type, such as get, add * key to obtain a specific key value */
export function track(target: object.type: TrackOpTypes, key: unknown) {
  ActiveEffect What needs to be done after data changes
  // The dependency should not be collected, or there is no effect to activate
  if(! shouldTrack || activeEffect ===undefined) {
    return
  }
  /* {target< original data >:{key< corresponding key>:[effect]< current key data, all related monitoring places, a key, may be used in multiple places, there is a key pair for each effect>}} */
  let depsMap = targetMap.get(target)
  if(! depsMap) {// Each target corresponds to a depsMap
    targetMap.set(target, (depsMap = new Map()))}let dep = depsMap.get(key)
  if(! dep) {// depsMap maintains a set of corresponding keys to the DEP
    depsMap.set(key, (dep = new Set()))}if(! dep.has(activeEffect)) {// Collect the currently active effects as dependencies
    dep.add(activeEffect)
    // Collect the DEP as a dependency for the currently active effect. Other effects may be triggered within the current effect
    activeEffect.deps.push(dep) 
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}
Copy the code

Trigger Triggers updates and sends notifications

export function trigger(target, type, key, newValue, oldValue, oldTarget) {
  // Get the dependency set of the corresponding original data
  const depsMap = targetMap.get(target)
  if(! depsMap) {// Not collected, return directly
    return
  }
  // Create an effect collection to run
  const effects = new Set<ReactiveEffect>()
  // Define the function to iterate over to add effect
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) = > {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect= > {
        if(effect ! == activeEffect || effect.allowRecurse) { effects.add(effect) } }) } }// The corresponding action is triggered to modify, delete, add, and add all dependencies to effects
  if (type === TriggerOpTypes.CLEAR) {
    depsMap.forEach(add) 
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) = > {
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if(key ! = =void 0) {
      add(depsMap.get(key))
    }

    switch (type) {
      case TriggerOpTypes.ADD:
        if(! isArray(target)) { add(depsMap.get(ITERATE_KEY))if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          add(depsMap.get('length'))}break
      case TriggerOpTypes.DELETE:
        if(! isArray(target)) { add(depsMap.get(ITERATE_KEY))if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          add(depsMap.get(ITERATE_KEY))
        }
        break}}// Define the execution function
  const run = (effect: ReactiveEffect) = > {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }

    // If there is a scheduling function, execute the scheduling function first
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      // If not, execute directly
      effect()
    }
  }

  effects.forEach(run);// Execute effect
}
Copy the code

ref

// Create ref data, handle basic type data listening
export function ref(value? : unknown) { 
  return createRef(value)
}
function createRef(rawValue: unknown, shallow = false) {
  // If it is ref, return it directly
  if (isRef(rawValue)) {
    return rawValue
  }
  / / create the ref
  return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
  constructor(private _rawValue: T, public readonly _shallow = false) {
    this._value = _shallow ? _rawValue : convert(_rawValue) // Handle data transformations, and if complex data, modify it to Reactive
  }
  // Trigger the entire track process by setting.value
  get value() {
    // Collect dependencies
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      // send notifications
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}
Copy the code

Template compilation

compile -> parse -> AST -> render

export function compile(template, options {
  return baseCompile(template, extend({}, parserOptions, options, {...})
}
export function baseCompile(template,options) {
  //The AST is generated through baseParse compilationconst ast = isString(template) ? baseParse(template, options) : template
  //Transform (AST,extend({}, options, {... }));return ast;
}

Copy the code

scheduling

  • Maintain a promise queue, join the task by push, and flush performs the task
const queue: SchedulerJob[] = []

export function nextTick(
  this: ComponentPublicInstance | void, fn? : () = >void
) :Promise<void> {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}
---------------------------------------------
export function queueJob(job: SchedulerJob) {
  if((! queue.length || ! queue.includes( job, isFlushing && job.allowRecurse ? flushIndex +1: flushIndex )) && job ! == currentPreFlushParentJob ) { queue.push(job) queueFlush() } }function queueFlush() {
  if(! isFlushing && ! isFlushPending) { isFlushPending =true
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}
Copy the code

The end of the

I don’t want to read it anymore. I hope I don’t use it in the future. I’ll study React instead

Subsequent articles

  • React source code

Join us at ❤️

Bytedance Xinfoli team

Nice Leader: Senior technical expert, well-known gold digger columnist, founder of Flutter Chinese community, founder of Flutter Chinese community open source project, well-known developer of Github community, author of dio, FLY, dsBridge and other well-known open source projects

We look forward to your joining us to change our lives with technology!!

Recruitment link: job.toutiao.com/s/JHjRX8B