In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article is about the sample analysis of the rendering system in vue3. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.
Thinking
Before we start today's article, you can think about this:
How are vue files converted to DOM nodes and rendered to browsers?
When the data is updated, what is the whole update process?
Vuejs has two phases: compile time and run time.
Compile time
The .vue file we usually write during development cannot be run directly in the browser, so in the webpack compilation phase, the .vue file needs to be compiled through vue-loader to generate the corresponding js code, and the template template corresponding to the vue component will be converted into a render function by the compiler.
Run time
Next, when the compiled code actually runs in the browser, it executes the render function and returns VNode, the so-called virtual DOM, and finally renders the VNode as a real DOM node.
After understanding the rendering idea of the vue component, let's start from the source code of Vue.js 3.0 (hereinafter referred to as vue3) to gain an in-depth understanding of the whole rendering process of the vue component.
Prepare for
This paper mainly analyzes the rendering system of vue3. In order to facilitate debugging, we directly debug and analyze the source code by introducing vue.js files.
Download vue3 source code
# Source code address (recommended for ssh download) https://github.com/vuejs/vue-next# or download the author's note-taking version https://github.com/AsyncGuo/vue-next/tree/vue3_notes
Generate vue.global.js file
Npm run dev# bundles... / vue-next/packages/vue/src/index.ts → packages/vue/dist/vue.global.js...# created packages/vue/dist/vue.global.js in 2.8s
Start the development environment
Npm run serve
Test code
Static node {{title}} click const Item = {props: ['msg'], template: `{{msg}}`} const app = Vue.createApp ({components: {Item}, setup () {return {title: Vue.ref (0)}}, methods: {add () {this.title + = 1}}) }) app.mount ('# app')
Create an application
From the above test code, we can see that vue3 and vue2 are mounted differently. Vue3 is created through the entry function createApp. Next, let's take a look at the specific implementation of createApp:
/ / entry file: / vue-next/packages/runtime-dom/src/index.tsconst createApp = ((... args) = > {console.log ('createApp input parameter:',.. ARGs); / / create application const app = ensureRenderer (). CreateApp (.. ARGs); const {mount} = app; / / rewrite mount app.mount = (containerOrSelector) = > {/ /...}; return app;}))
EnsureRenderer
First, create a web renderer through ensureRenderer. Let's take a look at the implementation:
/ / methods for updating attributes const patchProp = () = > {/ /...} / / methods for manipulating DOM const nodeOps = {insert: (child, parent, anchor) = > {parent.insertBefore (child, anchor | | null)}, remove: child = > {const parent = childxss _ clean] if (parent) {parent.removeChild (child)}} The parameters required by the renderer on the web side are set const rendererOptions = extend ({patchProp}, nodeOps). Let renderer;// delays the creation of rendererfunction ensureRenderer () {return (renderer | | (renderer = createRenderer (rendererOptions);}
As you can see here, by delaying the creation of the renderer, when we only rely on responsive packages, we can remove the rendering-related code through tree-shaking, greatly reducing the size of the package.
CreateRenderer
As you can see from ensureRenderer, the real entry is this createRenderer method:
/ / vue-next/packages/runtime-core/src/renderer.tsexport function createRenderer (options) {return baseCreateRenderer (options)} function baseCreateRenderer (options, createHydrationFns) {/ / General DOM operation method const {insert: hostInsert, remove: hostRemove .} = options / / the core flow of rendering / / caching the introverted function through closure / / = const patch = () = > {} / / Core diff procedure const processElement = () = > {} / / processing element const mountElement = () = > {} / / mounting element const mountChildren = () = > {} / / mounting child node const processFragment = () = > {} / / processing fragment node const processComponent = () = > {} / / processing component const mountComponent = () = > {} / / Mount component const setupRenderEffect = () = > {} / / run the render function with side effects const render = () = > {} / / render the mount flow / /. / / = = the introverted function / / = = return {render of the line 2000 + Hydrate, / / server rendering related createApp: createAppAPI (render, hydrate)}}
Next, let's skip the implementation of these introverted functions (when the rendering process is used later, we will analyze it in detail), and take a look at the specific implementation of createAppAPI:
CreateAppAPI
Function createAppAPI (render, hydrate) {/ / actually create app entry return function createApp (rootComponent, rootProps = null) {/ / create vue application context const context = createAppContext (); / / installed vue plug-in const installedPlugins = new Set (); let isMounted = false Const app = (context.app = {_ uid: uid++, _ component: rootComponent, / / Root component use (plugin,... options) {/ /. Return app}, mixin (mixin) {}, component (name, component) {}, directive (name, directive) {}, mount (rootContainer) {}, unmount () {}, provide (key, value) {}); return app;};}
As you can see, the createApp function returned by createAppAPI is the real entry point for creating an application. In createApp, the context of the vue application is created, initializes the app, binds the application context to the app instance, and finally returns app.
Here is a noteworthy point: the use, mixin, component, and directive methods on the app object all return the app application instance, which the developer can call chained.
/ / always use createApp (App) .use (Router) .use (Vuex) .component ('component', {}) .mount ("# app")
At this point, the app application instance has been created. Print and view the app application created under:
Summarize the process of creating an app application instance:
Create the corresponding renderer on the web side (delayed creation, tree-shaking)
Execute the baseCreateRenderer method (cache the introverted function through the closure, the main process of the subsequent mount phase)
Execute the createAppAPI method (1. Create application context; 2. Create app and return)
Mounting phase
Next, when we execute app.mount, we start to mount the component. The app.mount we call is the overridden mount method:
Const createApp = (... args) = > {/ /. Const {mount} = app; / / cache the original mount method / / rewrite mount app.mount = (containerOrSelector) = > {/ / get container const container = normalizeContainer (containerOrSelector); if (! container) return; const component = app._component / / determine that if the passed root component is not a function & the root component has no render function & there is no template, set the container content to the root component's template if (! isFunction (component) & &! component.render & &! component.template) {component.template = container [XSS _ clean];} / clear the container content [XSS _ container] ='' / / execute cached mount method const proxy = mount (container, false, container); return proxy;}; return app;})
After the mount method overridden by the web side is executed, the real mount of the component begins, that is, the mount method on the app application returned by createAppAPI is called:
Function createAppAPI (render, hydrate) {/ / actually create the entry return function createApp of app (rootComponent, rootProps = null) {/ /. Const app = (context.app = {/ / Mount the root component mount (rootContainer, isHydrate, isSVG) {if (! isMounted) {/ / create the vnode const vnode = createVNode (rootComponent, rootProps) corresponding to the root component; / / there is an application context in the root vnode vnode.appContext = context / / render the virtual vnode node into a real node, and mount render (vnode, rootContainer, isSVG); isMounted = true; / / record the root component container of the application app._container = rootContainer; rootContainer.__vue_app__ = app; app._instance = vnode.component; return vnode.component.proxy }}); return app;};}
To sum up, what does the mount method mainly do?
Create the vnode corresponding to the root component
The root component vnode binds the application context context
Render vnode into a real node and mount it
Record mount statu
Careful students may have found that the mount method here is a standard cross-platform rendering process that abstracts vnode, and then uses rootContainer to achieve platform-specific rendering. For example, in a browser environment, it is a DOM object and other specific values on other platforms. This is why when we call the creataApp method of the runtime-dom package, we rewrite the mount method to improve the rendering logic for different platforms.
Create vnode
When it comes to vnode, more people may associate it with high performance, mistakenly thinking that the performance of vnode must be higher than that of manually operating DOM, but it is not. The bottom layer of vnode is also to operate DOM, on the contrary, if the patch process of vnode is too long, it will also lead to stutters on the page. On the other hand, the proposal of vnode is an abstraction of the native DOM, which will be abstracted in the processing of cross-platform design. For example: server rendering, Mini Program rendering, weex platform.
Next, let's take a look at the process of creating a vnode:
Function _ createVNode (type, props, children, patchFlag,...): VNode {/ / normalize class & style / / for example: class= [], class= {}, style= [], etc. Need to normalize if (props) {/ /...} / / get the vnode type const shapeFlag = isString (type)? 1 / * ELEMENT * /: isSuspense (type)? 128 / * SUSPENSE * /: isTeleport (type)? 64 / * TELEPORT * /: isObject (type)? 4 / * STATEFUL_COMPONENT * / : isFunction (type)? 2 / * FUNCTIONAL_COMPONENT * /: 0 Return createBaseVNode ()} function createBaseVNode (type, props = null, children = null,...) {/ / the default structure of vnode const vnode = {_ _ v_isVNode: true, / / whether it is vnode _ _ v_skip: true, / / skip responsive data type, / / create the first parameter of vnode props, / / DOM parameter children, component: null / / component instance (instance) Create a shapeFlag, / / type tag through createComponentInstance, and in the patch phase, perform the corresponding rendering process by matching shapeFlag.} / / standardize the child node if (needFullChildrenNormalization) {normalizeChildren (vnode, children);} / / collect the dynamic child node or child block to the parent block tree if (isBlockTreeEnabled > 0 & &! isBlockNode & & currentBlock & & (vnode.patchFlag > 0 | | shapeFlag & 6 / * COMPONENT * /) & vnode.patchFlag! = = 32 / * HYDRATE_EVENTS * /) {currentBlock.push (vnode);} return vnode;}
With the above code, we can summarize what is done in the vnode creation phase:
Normalize class & style (for example: class= [], class= {}, style= [], etc.)
Tag the type of vnode shapeFlag, that is, the vnode type corresponding to the root component (type is the root component rootComponent, and the root component is in object format, so shapeFlag is 4)
Standardize child nodes (children is empty at initialization)
Collect dynamic descendant nodes or offspring block to parent block tree (here is the new concept introduced by vue3: block tree, which is limited, so I won't elaborate on it in this article)
Here, we can print to see the vnode structure corresponding to the root component at this time:
Render vnode
Get the vnode corresponding to the root component through createVNode, and then execute the render method. The render function here is the render function cached by baseCreateRenderer through closure:
/ / the render method actually called is the render method function baseCreateRenderer () {const render = (vnode, container) = > {if (vnode = = null) {if (container._vnode) {/ / uninstall component unmount ()}} else {/ / normal mount patch (container._vnode | | null, vnode, container)}
When the passed vnode is null& and the old vnode exists, uninstall the component
Otherwise, mount normally
After the mount is complete, execute the component life cycle in batch
Bind the vnode to the container so that the subsequent update phase uses the old and new vnode for patch
⚠️: next, the whole rendering process will be performed in the introverted function of baseCreateRenderer, the core function.
Patch
Next, let's look at the implementation of the patch function in the render process:
Const patch = (N1, / / old vnode N2, / / new vnode container, / / mounted container.) = > {/ /. Const {type, ref, shapeFlag} = N2 switch (type) {case Text: / / processing text processText (N1, N2, container, anchor) break case Comment: / / comment node processCommentNode (N1, N2, container, anchor) break case Static: / / static node if (N1 = = null) {mountStaticNode (N2, container, anchor) IsSVG)} break case Fragment: / / fragment node processFragment (N1, N2, container,...) Break default: if (shapeFlag & 1 / * ELEMENT * /) {/ / processing DOM elements processElement (N1, N2, container,...);} else if (shapeFlag & 6 / * COMPONENT * /) {/ / processing component processComponent (N1, N2, container,...) } else if (shapeFlag & 64 / * TELEPORT * /) {type.process (N1, N2, container,...);} else if (shapeFlag & 128 / * SUSPENSE * /) {type.process (N1, N2, container,...);}
Analyzing the patch function, we will find that the patch function will take different processing logic by judging the difference between type and shapeFlag. Today we mainly analyze the processing of component types and ordinary DOM elements.
ProcessComponent
When initializing rendering, type is object and the corresponding value of shapeFlag is 4 (bit operation 4-6), that is, the processing method of the corresponding processComponent component:
Const processComponent = (N1, N2, container,...) = > {if (N1 = = null) {if (n2.shapeFlag & 512 / * COMPONENT_KEPT_ALIVE * /) {/ / activate component (cached component) parentComponent.ctx.activate (N2, container,...);} else {/ / mount component mountComponent (N2, container,...) }} else {/ / Update component updateComponent (N1, N2, optimized);}}
If N1 is null, the component is mounted; otherwise, the component is updated.
MountComponent
Next, let's move on to the implementation of the mountComponent function of the mount component:
Const mountComponent = (initialVNode, container,...) = > {/ / 1. Create a component instance const instance = (/ / mount the component instance to the component attribute of the component vnode initialVNode.component = createComponentInstance (initialVNode, parentComponent, parentSuspense)); / / 2. Set the component instance setupComponent (instance); / / 3. Set up and run the render function setupRenderEffect (instance, initialVNode, container,...) with side effects
After omitting the code that has nothing to do with the main process, you can see that the mountComponent function does three main things:
Create a component instance
Function createComponentInstance (vnode, parent, suspense) {const type = vnode.type; / / bind application context const appContext = (parent? Parent.appContext: vnode.appContext) | | emptyAppContext / / default value of the component instance const instance = {uid: uid$1++, / / component unique id vnode, / / vnode type of the current component, / / vnode node type parent, / / instance instance appContext of the parent component, / / Application context root: null, / / Root instance next: null, / / if the current component mounted is null, it will be set to instance.vnode, and the next time update The rendering vnode of the updateComponentPreRender subTree: null, / / component will be generated by the component's render function. After creation, synchronize update: null, / / the component content is mounted or updated to the view's execution callback, and after creation, synchronize the scope: new EffectScope (true / * detached * /), render: null, / / component's render function In the setupStatefulComponent phase, assign proxy: null, / / is a proxy proxy ctx field. When using this internally, point to it / / local resovled assets / / resolved props and emits options / / emit / / props default value / / inheritAttrs / / state / / suspense related / / lifecycle hooks} {instance.ctx = createDevRenderContext (instance);} instance.root = parent? Parent.root: instance; instance.emit = emit.bind (null, instance); return instance;}
The createComponentInstance function is mainly used to initialize the component instance and return, print and view the instance content of the next root component:
Set up component instance
Function setupComponent (instance, isSSR = false) {const {props, children} = instance.vnode; / / determine whether it is a state component const isStateful = isStatefulComponent (instance); / / initialize component properties, slots initProps (instance, props, isStateful, isSSR); initSlots (instance, children); / / mount setup information const setupResult = isStateful? SetupStatefulComponent (instance, isSSR): undefined; return setupResult;}
The logic of setupComponent is also very simple. First, initialize the components props and slots to mount to the component instance instance, and then determine whether to mount the setup information (that is, the composition api of vue3) according to the component type vnode.shapeFlag===4.
Function setupStatefulComponent (instance, isSSR) {const Component = instance.type; / / create attribute access cache for rendering context instance.accessCache = Object.create (null); / / create rendering context proxy instance.proxy = markRaw (new Proxy (instance.ctx, PublicInstanceProxyHandlers)); const {setup} = Component / / determine whether the component exists setup if (setup) {/ / determine whether the setup has parameters, if so, create a setup context and mount the component instance / / for example: setup (props) = > {} const setupContext = (instance.setupContext = setup.length > 1? CreateSetupContext (instance): null); / / execute the setup function const setupResult = callWithErrorHandling (setup, instance, 0 / * SETUP_FUNCTION * /, [shallowReadonly (instance.props), setupContext]); handleSetupResult (instance, setupResult, isSSR);} else {finishComponentSetup (instance, isSSR);}}
Determine whether the component has the setup function set:
If the setup function is set, the setup function is executed and the type of return value is determined. If the return type is a function, the value of the component instance render is set to setupResult, otherwise it is the value of the component instance setupState
When function handleSetupResult (instance, setupResult, isSSR) {/ / judges the setup return value type if (isFunction (setupResult)) {/ / the return value is a function, then the render method instance.render = setupResult;} else if (isObject (setupResult)) {/ / the return value of the component instance is regarded as the object, then setupState instance.setupState = proxyRefs (setupResult) of the component instance } else if (setupResult! = = undefined) {warn$1 (`setup () should return an object. Received: ${setupResult = null? 'null': typeof setupResult} `);} finishComponentSetup (instance, isSSR);}
Set the render method of the component instance, analyze the finishComponentSetup function, and render function can be set in three ways:
If the return value of setup is a function type, then instance.render = setupResult
If the component has a render method, then instance.render = component.render
If a template template exists in the component, then instance.render = compile (template)
Render optimization level of component instance: instance.render = setup () | | component.render | | compile (template)
Function finishComponentSetup (instance,...) {const Component = instance.type; / / bind the render method to the component instance if (! instance.render) {if (compile & &! Component.render) {const template = Component.template; if (template) {/ / compile template through the compiler to generate the render function Component.render = compile (template,...) }} instance.render = (Component.render | | NOOP);} / / support for 2.x options.}
After we have set up the component, we can see what has changed in the content of instance:
At this time, the data, proxy, render and setupState of the component instance instance have been bound to the initial values.
Set up and run render functions with side effects
Const setupRenderEffect = (instance, initialVNode, container,...) = > {/ / create responsive side effect function const componentUpdateFn = () = > {/ / first rendering if (! instance.isMounted) {/ / rendering component spanning subtree vnode const subTree = (instance.subTree = renderComponentRoot (instance)); patch (null, subTree, container,...); initialVNode.el = subTree.el; instance.isMounted = true } else {/ / Update}}; / / create render effcet const effect = new ReactiveEffect (componentUpdateFn, () = > queueJob (instance.update), instance.scope / / track it in component's effect scope); const update = (instance.update = effect.run.bind (effect)); update.id = instance.uid; update ();}
Next, to continue to execute the setupRenderEffect function, we will first create a rendering effect (the responsive system also includes other side effects: computed effect and watch effect), bind the side effect execution function to the update attribute of the component instance (the update process will trigger the update function again), and immediately execute the update function to trigger the first update.
Function renderComponentRoot (instance) {const {proxy, withProxy, render,...} = instance; let result; try {const proxyToUse = withProxy | | proxy; / / execute the render method of the instance and return vnode, and then standardize vnode / / execute the render method, proxyToUse will be called, that is, get result = normalizeVNode (render.call (proxyToUse, proxyToUse,...)) of PublicInstanceProxyHandlers will be triggered;} return result;}
At this point, the renderComponentRoot function executes the instance's render method, that is, the function bound to the instance render method in the setupComponent phase, while standardizing the vnode returned by render and returning it as a subtree vnode.
Similarly, we can print and view the contents of the subtree vnode:
At this time, some students may begin to wonder why there are two vnode trees. What's the difference between these two vnode trees?
InitialVNode
InitialVNode is the vnode of the component, which describes the entire component object. The component vnode defines some properties related to the component: data, props, life cycle, and so on. A subtree vnode is generated through the rendering component vnode.
Sub tree
The subtree vnode is generated through the render method of the component vnode, which is actually the description of the component template template, that is, the DOM vnode that is actually to be rendered to the browser.
Once the subTree is generated, you then continue to mount the subTree node to the container through the patch method. Next, let's continue with the analysis. You can take a look at the screenshot of subTree above: the type value of subTree is Fragment. Recall the implementation of the patch method:
Const patch = (N1, / / old vnode N2, / / new vnode container, / / mounted container...) = > {const {type, ref, shapeFlag} = N2 switch (type) {case Fragment: / / fragment node processFragment (N1, N2, container,...) Break default: / /...}}
Fragment is one of the new features mentioned by vue3. In vue2, multiple node components are not supported, while vue3 is officially supported. If you think about it, it is actually a single root node component, but the underlying layer of vue3 is wrapped in Fragment. Let's take a look at the implementation of processFragment:
Const processFragment = (N1, N2, container,...) = > {/ / create the text node at the beginning and end of the fragment const fragmentStartAnchor = (n2.el = N1? N1.el: hostCreateText (''); const fragmentEndAnchor = (n2.anchor = N1? N1.anchor: hostCreateText (''); if (N1 = = null) {hostInsert (fragmentStartAnchor, container, anchor); hostInsert (fragmentEndAnchor, container, anchor); / / mount child node array mountChildren (n2.children, container,...);} else {/ / Update}}
Next, continue to mount the array of child nodes:
Const mountChildren = (children, container,...) = > {for (let I = start; I)
< children.length; i++) { const child = (children[i] = optimized ? cloneIfMounted(children[i]) : normalizeVNode(children[i])); patch(null, child, container, ...); }}; 遍历子节点,patch每个子节点,根据child节点的type递归处理。接下来,我们主要看下type为ELEMENT类型的DOM元素,即processElement: const processElement = (n1, n2, container, ...) =>{if (N1 = = null) {/ / mount DOM element mountElement (N2, container,...)} else {/ / Update}} const mountElement = (vnode, container,...) = > {let el; let vnodeHook; const {type, props, shapeFlag,...} = vnode; {/ / create DOM node and bind to el of current vnode el = vnode.el = hostCreateElement (vnode.type,...) } / / insert parent node hostInsert (el, container, anchor);}
Create a DOM node and mount it to vnode.el, then mount the DOM node to container, continue the recursive processing of other vnode, and finally mount the entire vnode to the browser view, thus completing the whole process of rendering vue3 for the first time. In the mountElement method, it is mentioned that hostCreateElement and hostInsert are the processing methods corresponding to the parameters passed in the initial creation of the renderer, which completes the entire cross-platform initial rendering process.
Update proc
After analyzing the whole process of vue3's first rendering, how does vue3 update the rendering after the data is updated? The next stage of the analysis and update process will involve the knowledge of vue3's responsive system (due to limited space, we will not expand more responsive knowledge and look forward to a more detailed analysis of the following chapters).
Dependency collection
Recall that during the setupComponent phase of setting up the component instance for the first rendering, the rendering context proxy is created, and in the subTree generation phase, the render method of the component vnode is executed through the renderComponentRoot function, and the get of the PublicInstanceProxyHandlers of the rendering context proxy is triggered to achieve dependency collection.
Function setupStatefulComponent (instance, isSSR) {. / / create rendering context proxy instance.proxy = markRaw (new Proxy (instance.ctx, PublicInstanceProxyHandlers));} function renderComponentRoot (instance) {const proxyToUse = withProxy | | proxy; / / when the render method is executed, proxyToUse is called, that is, get result = normalizeVNode (render.call (proxyToUse, proxyToUse,...) of PublicInstanceProxyHandlers is triggered; return result;}
We can look at the contents of the render method of the component vnode at this time:
Or print to view the contents of the render method:
(function anonymous () {const _ Vue = Vueconst {createVNode: _ createVNode, createElementVNode: _ createElementVNode} = _ Vueconst _ hoisted_1 = / * # _ PURE__*/_createElementVNode ("div", null, "static node",-1 / * HOISTED * /) const _ hoisted_2 = ["onClick"] return function render (_ ctx, _ cache) {with (_ ctx) {const {createElementVNode: _ createElementVNode, toDisplayString: _ toDisplayString, resolveComponent: _ resolveComponent, createVNode: _ createVNode Fragment: _ Fragment, openBlock: _ openBlock, createElementBlock: _ createElementBlock} = _ Vue const _ component_item = _ resolveComponent ("item") return (_ openBlock (), _ createElementBlock (_ Fragment, null, [_ hoisted_1, _ createElementVNode ("div", null, _ toDisplayString (title), 1 / * TEXT * /), _ createElementVNode ("button", {onClick: add}, "click", 8 / * PROPS * /, _ hoisted_2) _ createVNode (_ component_item, {msg: title}, null, 8 / * PROPS * /, ["msg"])], 64 / * STABLE_FRAGMENT * /)}})
Take a closer look at the first parameter of render, _ ctx, that is, the incoming rendering context proxy proxy. When you access the title field, the get method of PublicInstanceProxyHandlers is triggered. What is the logic of PublicInstanceProxyHandlers?
/ / handler implementation of proxy rendering context const PublicInstanceProxyHandlers = {get ({_: instance}, key) {const {ctx, setupState, data, props, accessCache, type, appContext} = instance; let normalizedProps / / attribute if whose key value does not start with $(key [0]! ='$') {/ / priority to determine from the cache where the current attribute needs to be obtained / / performance optimization: which type the cache attribute should be obtained from, to avoid the cost of triggering hasOwn every time const n = accessCache [key] If (n! = = undefined) {switch (n) {case 0 / * SETUP * /: return setupState [key]; case 1 / * DATA * /: return data [key]; case 3 / * CONTEXT * /: return ctx [key]; case 2 / * PROPS * /: return props [key] / / default: just fallthrough}} / / get attribute values in the following order: setupState = > data = > props = > ctx = > failed else if (setupState! = = EMPTY_OBJ & & hasOwn (setupState, key)) {accessCache [key] = 0 / * SETUP * /; return setupState [key] } else if (data! = = EMPTY_OBJ & & hasOwn (data, key)) {accessCache [key] = 1 / * DATA * /; return data [key];} else if ((normalizedProps = instance.propsOptions [0]) & & hasOwn (normalizedProps, key)) {accessCache [key] = 2 / * PROPS * /; return props [key] } else if (ctx! = = EMPTY_OBJ & & hasOwn (ctx, key)) {accessCache [key] = 3 / * CONTEXT * /; return ctx [key];} else if (shouldCacheAccess) {accessCache [key] = 4 / * OTHER * /;}}, set () {}, has () {}}
Next, let's take key as an example to briefly introduce the logic of get:
First of all, determine whether the key value has started with $, and it is obvious that title has the logic of no.
See if it exists in the accessCache cache.
Performance optimization: which type of cache attributes should be obtained to avoid the overhead of triggering * * hasOwn** every time?
Finally, the processing logic of set and has of ctxPublicInstanceProxyHandlers is obtained in the same order: setupState = > data = > props = > ctxPublicInstanceProxyHandlers.
If it exists, set the cache accessCache first, and then get the corresponding value of title from setupState.
The point is that when you access setupState.title, the process that triggers proxy's get has two stages:
First trigger the get of the proxy corresponding to setupState, and then get the value of title to determine whether it is Ref?
Yes: continue to get ref.value, that is, trigger the dependency collection process of ref type
No: return directly, that is, a normal data type, without dependency collection
/ / the proxy prxoy// setting process of setupState is set when the component instance is set: setupComponent= > setupStatefulComponent= > handleSetupResultinstance.setupState = proxyRefs (setupResult) export function proxyRefs (objectWithRefs) {return isReactive (objectWithRefs)? ObjectWithRefs: new Proxy (objectWithRefs, {get: (target, key, receiver) = > {return unref (Reflect.get (target, key, receiver))}, set: (target, key, value, receiver) = > {}})} export function unref (ref) {return isRef (ref)? Ref.value: ref}
When ref.value is accessed, dependency collection for ref is triggered. So let's first analyze the implementation logic of Vue.ref ().
/ / call Vue.ref (0) Thus triggering the process of createRef / / omitting other extraneous codes function ref (value) {return createRef (value, false)} function createRef (rawValue) {return new RefImpl (rawValue) Implementation of false)} / / ref class RefImpl {constructor (value) {this._rawValue = toRaw (value) this._value = toReactive (value)} get value () {trackRefValue (this) return this._value}} function trackRefValue (ref) {if (isTracking ()) {if (! ref.dep) {ref.dep = new Set ()} / / add side effects Conduct dependency collection dep.add (activeEffect) activeEffect.deps.push (dep)}}
Analyzing the implementation of ref, you will find that when you access ref.value, the value method of the RefImpl instance is triggered, which triggers trackRefValue for dependency collection dep.add (activeEffect). Then who is the activeEffect at this time?
Recall the implementation of the setupRenderEffect phase:
Const setupRenderEffect = (instance, initialVNode, container,...) = > {/ / create responsive side effect function const componentUpdateFn = () = > {}; / / create rendering effcet const effect = new ReactiveEffect (componentUpdateFn, () = > queueJob (instance.update), instance.scope); const update = (instance.update = effect.run.bind (effect)); update ();} / / create an implementation of the effect class, class ReactiveEffect {run () {try {effectStack.push ((activeEffect = this)) / /. Return this.fn ()} finally {}
When the update function is executed (that is, the run method of rendering the effect instance), the global activeEffect is set to the current rendering effect, that is, the activeEffect collected by dep.add (activeEffect) is the rendering effect, thus achieving dependency collection.
We can print out the contents of setupState and verify our analysis:
Through the screenshot, we can see that the side effect of title collection at this time is to render effect. Careful students have found that the fn method in the screenshot is the componentUpdateFn function. Execute fn () to continue mounting children.
Dispatch updates
After analyzing the dependency collection phase, let's take a look at how vue3 dispatches updates.
When we click the button to execute this.title + = 1, the set method of PublicInstanceProxyHandlers is also triggered, and the trigger order of set is the same as that of get: setupState= > data= > other judgments that are not allowed to be modified (for example, props, reserved fields starting with $)
/ / handler implementation of proxy rendering context const PublicInstanceProxyHandlers = {set ({_: instance}, key, value) {const {data, setupState, ctx} = instance; / / 1. Update the attribute value of setupState if (setupState! = = EMPTY_OBJ & & hasOwn (setupState, key)) {setupState [key] = value;} / / 2. Update the property value of data else if (data! = = EMPTY_OBJ & & hasOwn (data, key)) {data [key] = value;} / /. Return true;}}
Set setupState [key] to continue to trigger the set method of setupState:
Const shallowUnwrapHandlers: ProxyHandler = {set: (target, key, value, receiver) = > {const oldValue = target [key] / / oldValue is of ref type & if value is not ref, execute if (isRef (oldValue) & &! isRef (value)) {oldValue.value = value return true} else {/ otherwise, return return Reflect.set (target, key, value, receiver)}} directly
When setting the value of oldValue.value, continue to trigger the set method of ref, determine whether there is dep in ref, and execute the side effect effect.run (), so as to dispatch updates and complete the update process.
Class RefImpl {set value (newVal) {newVal = this._shallow? NewVal: toRaw (newVal) if (hasChanged (newVal, this._rawValue)) {this._rawValue = newVal this._value = this._shallow? NewVal: toReactive (newVal) triggerRefValue (this, newVal)}} / / determine whether ref has a dependency, and then dispatch update function triggerRefValue (ref) {ref = toRaw (ref) if (ref.dep) {triggerEffects (ref.dep)}} / / dispatch update function triggerEffects (dep) {for (const effect of isArray (dep)? Dep: [... dep]) {if (effect! = = activeEffect | | effect.allowRecurse) {/ / execute side effect effect.run ()}}
Summary
In summary, we have analyzed the whole rendering process and update process of vue3. Of course, we only analyze the main rendering process, and the complexity of the complete rendering process is more than this, such as the optimization implementation based on block tree, the diff optimization of patch phase and the optimization of responsive phase in the update process.
The original intention of this article is to provide you with the analysis of the outline of the entire rendering process of vue3, with an overall impression, and then to analyze and understand more detailed points, there will be more ideas and direction.
Finally, a complete rendering flow chart is attached and shared with you.
Thank you for reading! This is the end of this article on "sample Analysis of rendering system in vue3". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, you can share it for more people to see!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.