vue 整体流程
vue 的三个 core Reactivity Module 创建 JS 响应对象,并观察其变化,当使用这些对象的代码运行时,他们会被跟踪,当响应式对象变化时,代码会运行。
Compiler Module 将 HTML 模板编译成渲染函数
Render Module 三个阶段
Render Phase
渲染阶段,调用 render 函数,返回一个虚拟DOM节点
Mount Phase
挂载阶段,使用 虚拟DOM节点并调用 DOM API 来创建网页
Patch Phase
补丁阶段,新旧虚拟节点对比,并更新网页变化部分
总的流程 我们拥有 html template 和 设定的响应式对象
Compiler Module 会将 template 转换为一个 render 函数
Reactivity Module 初始化响应式对象
Renderer Module (渲染)会调用 render 函数(而 render 函数引用了响应式对象),观察响应式对象的变化,render 函数返回一个虚拟 DOM 节点
Renderer Module(挂载)调用 mount 函数,使用 虚拟 DOM 创建页面
如果响应式对象发生任何变化,render 会再次调用 render 函数,创建一个新的虚拟 DOM,新旧虚拟节点传入 Patch 函数,新旧虚拟节点对比,根据需要更新网页
compiler 和 render 在 js 中使用 render 在 js 中使用 render 创建元素
h 函数会调用 vnode函数 从而创建 vnode 对象
1 2 3 4 5 6 7 8 9 <script > import {h} from 'vue' render ( ) { return h ('div' , { id : 'foo' onClick : this .onClick }, 'hello' ) } </script >
从 template 到 页面实例 1 2 3 4 5 <div class ="app" > <div class ="red" > <span > hello</span > </div > </div >
vue 在内部做的是:
compiler 将 template 转为 render 函数,renderer 调用 render 函数,返回一个虚拟 DOM
1 2 3 4 5 6 7 import { h } from 'vue' render ( ) { return h ('div' , { class : 'red' }, [ h ('span' , null , ['hello' ]) ]) }
虚拟 DOM 转为真实 DOM 并挂载到容器(父组件)上
1 mount(vdom, document.getElementById('app'))
写一个简单的 mount 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function mount (vnode, container ) { const el = document .createElement (vnode.tag ) if (vnode.props ) { for (const key in vnode.props ) { const value = vnode.props [key] el.setAttribute (key, value) } } if (vnode.children ) { vnode.children .forEach (child => { if (typeof vnode.children === 'string' ) { el.textContent = vnode.children } else { vnode.children .forEach (child => { mount (child, el) }) } }) } container.appendChild (el) }
写一个简单的 patch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 function patch (n1, n2 ) { if (n1.tag === n2.tag ) { const el = n2.el = n1.el const oldProps = n1.props || {} const newProps = n2.props || {} for (const key in newProps) { const oldValue = oldProps[key] const newValue = newProps[key] if (newValue !== oldValue) { el.setAttribute (key, newValue) } } for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute (key) } } const oldChildren = n1.children const newChildren = n2.children if (typeof newChildren === 'string' ) { if (typeof oldChildren === 'string' ) { if (newChildren !== oldChildren) { el.textcontent = newChildren } } else { el.textContent = newChildren } } else { if (typeof oldChildren === 'string' ) { el.innerHTML = '' newChildren.forEach (child => { mount (child, el) }) } else { const commonLength = Math .min (newChildren.length , oldChildren.length ) for (let i = 0 ; i < commonLength; i++) { patch (oldChildren[i], newChildren[i]) } if (newChildren.length > oldChildren.length ) { newChildren.slice (oldChildren.length ).forEach (child => { mount (el, child) }) } else if (newChildren.length < oldChildren.length ) { oldChildren.slice (newChildren.length ).forEach (child => { el.removeChild (child) }) } } } } else { } }
写一个简单的 Dep 有点像 ref 的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 let activeEffectclass Dep { constructor (value ) { this .subscribers = new Set () this ._value = value } get value () { this .depend () return this ._value } set value (newValue ) { this ._value = newValue this .notify () } depend ( ) { if (activeEffect) { this .subscribers .add (activeEffect) } } notify ( ) { this .subscribers .forEach (effect => { effect () }) } }function watchEffect (effect ) { activeEffect = effect effect () activeEffect = null }
1 2 3 4 5 6 7 8 9 10 11 12 13 const dep = new Dep ('hello' )watchEffect (() => { console .log (dep.value ) }) dep.value = 'changed'
很多问题中的一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const ok = new Dep (true )const msg = new Dep ('hello' )watchEffect (() => { if (ok.value ) { console .log (msg.value ) } else { console .log ('false branch' ) } }) ok.value = false msg.value = 'changed'
理论上是什么都不会打印,因为 ok 已经被修改为 false 了,副作用函数 console.log(msg.value) 不应该存在了,可以理解为下面这样:
1 2 3 watchEffect (() => { console .log ('false branch' ) })
所以当修改 msg 时,不应该触发这个副作用函数,但实际还是触发了。
写一个简单的 reactive 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 let activeEffectclass Dep { subscribers = new Set () depend ( ) { if (activeEffect) { this .subscribers .add (activeEffect) } } notify ( ) { this .subscribers .forEach (effect => { effect () }) } }function watchEffect (effect ) { activeEffect = effect effect () activeEffect = null }const targetMap = new WeakMap ()function getDep (target, key ) { let depsMap = targetMap.get (target) if (!depsMap) { depsMap = new Map () targetMap.set (target, depsMap) } let dep = depsMap.get (key) if (!dep) { dep = new Dep () depsMap.set (key, dep) } return dep }const reactiveHandlers = { get (target, key, receiver ) { const dep = getDep (target, key) dep.depend () return Reflect .get (target, key, receiver) }, set (target, key, value, receiver ) { const dep = getDep (target, key) const result = Reflect .set (target, key, value, receiver) dep.notify () return result }, has ( ){ }, ownKeys ( ) { } }function reactive (raw ) { return new Proxy (raw, reactiveHandlers) }
1 2 3 4 5 6 7 8 9 10 11 12 13 const state = reactive ({ count : 0 })watchEffect (() => { console .log (state.count ) }) state.count += 1