preface
If you miss autumn maple and winter snow, the cherry blossoms in spring will be in full bloom. Recently, I have been preparing for my own exam. After the exam, I can finally continue to study the source code and write articles. Hahaha. Those who have studied Vue know that data response is extremely important in Vue framework. Data response is the core content, whether writing code or interviewing. In the official website document of vue3, the author said that if you want to make the data more responsive, you can put the data in reactive. The official document mentioned here, and the author didn't understand it at first. Later, after reading the source code, I knew that in vue3, the response has become a separate module, and the module dealing with the response is reactive;
General flow of data response
In vue3 0, the core file we need to find is vue3 0 in the src under the runtime core in the packages of the source code; The line we are studying today goes along the render line;
return { render, hydrate, createApp: createAppAPI(render, hydrate) }
Find the render function under this file, as shown below; This function is used to render the incoming vnode to the specified container;
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 }
Check the patch method, and else if (shapeflag & shapeflags. Component) will be used for initialization
const patch: PatchFn = ( n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false ) => { // patching & not same type, unmount old tree if (n1 && !isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense, true) n1 = null } if (n2.patchFlag === PatchFlags.BAIL) { optimized = false n2.dynamicChildren = null } const { type, ref, shapeFlag } = n2 switch (type) { case Text: processText(n1, n2, container, anchor) break case Comment: processCommentNode(n1, n2, container, anchor) break case Static: if (n1 == null) { mountStaticNode(n2, container, anchor, isSVG) } else if (__DEV__) { patchStaticNode(n1, n2, container, isSVG) } break case Fragment: processFragment( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) break default: if (shapeFlag & ShapeFlags.ELEMENT) { processElement( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } else if (shapeFlag & ShapeFlags.COMPONENT) { // Initialize this processComponent( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } else if (shapeFlag & ShapeFlags.TELEPORT) { ;(type as typeof TeleportImpl).process( n1 as TeleportVNode, n2 as TeleportVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals ) } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ;(type as typeof SuspenseImpl).process( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals ) } else if (__DEV__) { warn('Invalid VNode type:', type, `(${typeof type})`) } } // set ref if (ref != null && parentComponent) { setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2) } }
Next, look at the processComponent method, and then go to the familiar mountComponent
const processComponent = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean ) => { if (n1 == null) { if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { ;(parentComponent!.ctx as KeepAliveContext).activate( n2, container, anchor, isSVG, optimized ) } else { // Initialize the mount process mountComponent( n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } } else { updateComponent(n1, n2, optimized) } }
Enter the mountComponent method, where the more important instance is to create a component instance and setupComponent is prepared for installing components; For option processing; Setuprenderexffec is used to establish the side effects of rendering functions and is used when relying on collection.
const mountComponent: MountComponentFn = ( initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) => { // Create component instance const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance( initialVNode, parentComponent, parentSuspense )) if (__DEV__ && instance.type.__hmrId) { registerHMR(instance) } if (__DEV__) { pushWarningContext(initialVNode) startMeasure(instance, `mount`) } // inject renderer internals for keepAlive if (isKeepAlive(initialVNode)) { ;(instance.ctx as KeepAliveContext).renderer = internals } // resolve props and slots for setup context if (__DEV__) { startMeasure(instance, `init`) } // Installing components: options handling setupComponent(instance) if (__DEV__) { endMeasure(instance, `init`) } // setup() is async. This component relies on async logic to be resolved // before proceeding if (__FEATURE_SUSPENSE__ && instance.asyncDep) { parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect) // Give it a placeholder if this is not hydration // TODO handle self-defined fallback if (!initialVNode.el) { const placeholder = (instance.subTree = createVNode(Comment)) processCommentNode(null, placeholder, container!, anchor) } return } // Side effects of creating rendering functions: dependency collection setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ) if (__DEV__) { popWarningContext() endMeasure(instance, `mount`) } }
Go to the setupComponent function and watch the internal logic of the setupComponent function, where there is the initialization of the attribute slot; Here, you can see the setupStatefulComponent method, which is used to handle the response type.
export function setupComponent( instance: ComponentInternalInstance, isSSR = false ) { isInSSRComponentSetup = isSSR const { props, children, shapeFlag } = instance.vnode const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT initProps(instance, props, isStateful, isSSR) initSlots(instance, children) const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) : undefined isInSSRComponentSetup = false return setupResult }
Enter the method setupStatefulComponent, where const component = instance Type as componentoptions is used for component configuration. Where instance Proxy = new proxy (instance.ctx, publicinstanceproxyhandlers) is used for proxy. data, $etc. are processed here.
function setupStatefulComponent( instance: ComponentInternalInstance, isSSR: boolean ) { // Component configuration const Component = instance.type as ComponentOptions if (__DEV__) { if (Component.name) { validateComponentName(Component.name, instance.appContext.config) } if (Component.components) { const names = Object.keys(Component.components) for (let i = 0; i < names.length; i++) { validateComponentName(names[i], instance.appContext.config) } } if (Component.directives) { const names = Object.keys(Component.directives) for (let i = 0; i < names.length; i++) { validateDirectiveName(names[i]) } } } // 0. create render proxy property access cache instance.accessCache = {} // 1. create public instance / render proxy // also mark it raw so it's never observed instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers) if (__DEV__) { exposePropsOnRenderContext(instance) } // 2. call setup() const { setup } = Component if (setup) { const setupContext = (instance.setupContext = setup.length > 1 ? createSetupContext(instance) : null) currentInstance = instance pauseTracking() const setupResult = callWithErrorHandling( setup, instance, ErrorCodes.SETUP_FUNCTION, [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext] ) resetTracking() currentInstance = null if (isPromise(setupResult)) { if (isSSR) { // return the promise so server-renderer can wait on it return setupResult.then((resolvedResult: unknown) => { handleSetupResult(instance, resolvedResult, isSSR) }) } else if (__FEATURE_SUSPENSE__) { // async setup returned Promise. // bail here and wait for re-entry. instance.asyncDep = setupResult } else if (__DEV__) { warn( `setup() returned a Promise, but the version of Vue you are using ` + `does not support it yet.` ) } } else { handleSetupResult(instance, setupResult, isSSR) } } else { // Processing options and other transactions finishComponentSetup(instance, isSSR) } }
Since there is no setup in our case, we will execute finish component setup (instance, isssr) to handle things related to the optional API. Enter this function to view the code logic, and you will see the following code. This part of the code is used to handle things related to the optional API and to support vue2 Version of X.
// support for 2.x options // Support options API if (__FEATURE_OPTIONS_API__) { currentInstance = instance applyOptions(instance, Component) currentInstance = null }
Enter the applyOptions method; Turn down and you will see these lines of notes, which clearly explain vue2 The priority of each option in X, including props, inject, methods, data, etc.
// options initialization order (to be consistent with Vue 2): // - props (already done outside of this function) // - inject // - methods // - data (deferred since it relies on `this` access) // - computed // - watch (deferred since it relies on `this` access)
If you continue to look down, you will see these lines of code. We don't use the mixed form, so this line of code, including the code related to the data corresponding formula, is in the resolveData method.
if (!asMixin) {
if (!asMixin) { if (deferredData.length) { deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis)) } if (dataOptions) { // Data response resolveData(instance, dataOptions, publicThis) }
Enter resolveData and you can see const data = datafn Call (publicthis, publicthis), this line of code is used to get the data object. instance. The line data = reactive (data) is used for responsive processing of data. The core is reactive, which is used for responsive processing. Either the optional api or the setup, the final method is the reactive method, which is used for responsive processing.
function resolveData( instance: ComponentInternalInstance, dataFn: DataFn, publicThis: ComponentPublicInstance ) { if (__DEV__ && !isFunction(dataFn)) { warn( `The data option must be a function. ` + `Plain object usage is no longer supported.` ) } // Get data object const data = dataFn.call(publicThis, publicThis) if (__DEV__ && isPromise(data)) { warn( `data() returned a Promise - note data() cannot be async; If you ` + `intend to perform data fetching before component renders, use ` + `async setup() + <Suspense>.` ) } if (!isObject(data)) { __DEV__ && warn(`data() should return an object.`) } else if (instance.data === EMPTY_OBJ) { // Responsive processing of data instance.data = reactive(data) } else { // existing data: this is a mixin or extends. extend(instance.data, data) } }
Enter reactive and observe the code logic; The createReactiveObject is used to process data. Among them, target is the final thing to be transformed.
return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers )
mutableHandlers contains some get, set, deleteProperty and other methods. mutableCollectionHandlers creates operations such as dependency collection.
vue2.x data response and 3 X-response comparison
Here, let's review vue2 How x handles responsive. It uses defineReactive to intercept each key to detect data changes. This set of processing method is problematic. When the data is nested one layer at a time, it will recurse layer by layer, thus consuming a lot of memory. From this point of view, this set of treatment is not friendly. Vue3 also uses defineReactive to intercept each key. The difference is that in vue3 The defineReactive in X uses proxy as a layer of proxy, which is equivalent to adding a layer of level. Vue2. X needs to recurse all keys of the object, which is slow. Array responses require additional implementation. Moreover, adding or deleting attributes cannot be monitored, and special APIs need to be used. Now, a new proxy directly solves all the problems. At the same time, the previous set of methods did not know the data structures such as Map,Set and Class.
General flow chart
Then let's sort out the sequence of the response process
Implement dependency collection
In the process of implementing responsive, dependency collection is closely related to it. In setuprenderexfect function, effect function is used for dependency collection. Enter the inside of the setuprenderexfect function. There is this function in the above code. We won't repeat it one by one here. Let's continue to look at it. Enter the function and you will see the following code. Effect can establish a dependency relationship: between the callback function of the incoming effect and the responsive data; Effect is equivalent to dep in vue2, and there is no watcher in vue3.
instance.update = effect(function componentEffect() { if (!instance.isMounted) { let vnodeHook: VNodeHook | null | undefined const { el, props } = initialVNode const { bm, m, parent } = instance
If you continue to look down, you will see the following code. subTree is the current component vnode, and the renderComponentRoot method is used to implement the root of the rendering component.
const subTree = (instance.subTree = renderComponentRoot(instance))
Here, vue3 The responsive part of 0 is coming to an end
Code warehouse
Handwritten vue3 0 simple version of the implementation of data response, has been uploaded to the personal warehouse, interested can see. If you like, you can pay attention, hahaha. Pay attention to me, you have one more friend on the road of programming. https://gitee.com/zhang-shichuang/xiangyingshi/tree/master/
ending
vue's data response is often asked during the interview. The principle is to look at the source code. There will inevitably be boring times when reading the source code, but persistence is victory. Later, we will share the compilation process of vue and the source code knowledge related to react.