createApp
createApp()调用的是ensureRenderer,ensureRenderer方法内部有一个baseCeateRenderer方法,调用它可以得到渲染器renderer.
渲染器是一个对象:有render ,hydrate,createApp(此方法来自于createAppApi)属性.作用:首次渲染和获取应用程序的实例.
挂载做了:它只执行一次.做两件事:初始化(看到首次渲染的结果)和建立更新机制
为什么data必须为一个函数
组件被重复创建的时候,大家引用同一个对象,我变你也变.所以要是函数来返回全新的对象.
reactive
对象调用reactive做响应式处理.
reactive函数 若是做过响应式处理直接return,不是则创建响应式对象(使用createReactiveObject方法).
这个方法进行一系列边界判断(是不是对象,不是就报错;是不是已经是响应式了,是就直接返回;是不是已经存在代理对象,是就直接返回;数据类型是不是非法类型,是就不做响应式直接返回).然后就是利用 proxy做代理.
defineProperty一次只能拦截一个,所以需要遍历.且不能监听到新增和删除的,且不能对数组使用 proxy要拦截增删改查的操作(get,set,has,ownKeys,deleteProperty).判断要代理的对象是不是set,map数据类型,是就用collectionHandler方法,不是就用baseHandler方法.
响应式对象有可变的的只读的两种,可变的就需要做收集依赖(trake)
在get中触发trake方法,跟踪一个对象, 跟踪的类型,跟踪的key.
当第一次渲染执行, 正在调用的激活的函数(就是activeEffect就是根组件的更新函数)和target的key建立关系(利用weakMap).不再需要dep和obsever,watcher
proxy是一个懒处理,不会直接递归,直到用户访问这个数据的时候发现有深层次的嵌套才会向下递归处理.
ref
ref也是一个响应式对象,但是是一个包装对象,,内部有个属性为value.
如何创建ref对象, new一个RefImpl.会把传入的value用toReactive方法.
toReactive方法:如果是对象,调用reactive函数;如果是简单数据则利用class的get和set (就是class的存储器),之所以用这个是因为简单数据是不会像对象的属性那样有增加和删除操作的.
setup 和data共存问题
setup中的数据优先于data中的数据,组件初始化的时候会给组件实例的上下文做代理,先从setup中取的,然后再是data,再是props
diff
相对于vue2,vue3 diff算法新增了静态标记,只对动态节点进行比对.
用最长递增子序列来减少dom的移动
首先会头头匹配,复用相同的,直到找不到,尾尾同样的做法.
把剩余节点,新的vode列表做一个key与index的映射
然后遍历新表,在旧表里找到其对应的index,构成一个索引数组,然后找这个数组的最长递增子序列,不用挪动这个序列,挪动其他的节点.
-
旧列表(VNode):
[A,B,C,D]→ key 对应旧索引:A:0, B:1, C:2, D:3; -
新列表(VNode):
[B,D,A,C]→ key 对应新索引:B:0, D:1, A:2, C:3; -
得到索引数组:
arr = [1,3,0,2] -
最长递增子序列就是
[1,3],在新表里下标就是[0,1],所以AC需要挪动,A挪到D后,C挪到A后.
其余的,要是旧节点列表中没有的就新增,旧节点列表中多余的就删除
响应式
1 初始化阶段: 初始化阶段通过组件初始化方法形成对应的proxy对象,然后形成一个负责渲染的effect。
2 get依赖收集阶段:通过解析template,替换真实data属性,来触发get,然后通过stack方法,通过proxy对象和key形成对应的deps,将负责渲染的effect存入deps。(这个过程还有其他的effect,比如watchEffect存入deps中 )。
3 set派发更新阶段:当我们 this[key] = value 改变属性的时候,首先通过trigger方法,通过proxy对象和key找到对应的deps,然后给deps分类分成computedRunners和effect,然后依次执行,如果需要调度的,直接放入调度。
核心流程:创建 Proxy 代理对象 → 访问数据触发 get 收集依赖(track)→ 修改数据触发 set 触发更新(trigger)→ 执行副作用(更新视图 / 回调)。