vue实现响应式数据的原理,vue3.0响应式原理
本文主要介绍vue3.x源代码分析的数据响应。在讲解的过程中,我们会对比Vue2.x的API特性,在使用上有哪些不同?有需要的朋友可以参考一下。
目录
前言数据响应的一般流程是什么?vue2.x数据响应和3.x响应对比一般流程图实现代码仓库依赖收集结束。
前言
如果你想念秋枫和冬天的雪,那么樱花一定会在春天盛开。最近一直在准备自己的考试。考完试,终于可以继续研究源代码写文章了,哈哈哈。研究过vue的人都知道,数据响应性在vue框架中极其重要。无论是写代码还是面试,数据响应能力都是核心内容。在vue3的官网文档中,作者说如果想让数据更有响应性,可以把数据放在reactive中。官方文件简单提了一下,刚开始不太懂。后来看了源代码才知道,在vue3中,response已经成为一个独立的模块,处理response的模块是反应式的;
什么是数据响应式
一开始用Vue的时候,和之前jq开发的一个很大的区别就是基本不需要手动操作dom,相关的dom会在数据中声明的数据状态发生变化后自动重新渲染。
换句话说,Vue知道哪个数据状态发生了变化,在哪里使用,需要相应地修改。
因此,实现数据响应有两个关键问题:
我如何知道数据已经更改?我如何知道数据更改后需要修改的地方?对于第一个问题,如何知道数据发生了变化?Vue3之前用的是ES5的一个API对象。定义属性VUE 3使用了ES6的代理,它检测被检测数据的变化,添加了getter和setter,这样你就可以知道数据什么时候被读取和修改了。
第二个问题是,如何知道数据发生变化后需要修改的地方?Vue已经收集了每个数据的相关依赖关系,这里的依赖关系实际上是一个对象,它保存了数据的旧值以及数据发生变化后需要执行的功能。当每个响应的数据发生变化时,它将遍历与通知对应的每个依赖项。收到通知后,依赖项会判断新旧值是否发生了变化。如果是,它将执行一个回调函数来响应数据更改(比如修改dom)。
数据响应式的大体流程
在vue3.0的响应式部分,我们需要找到的核心文件是vue3.0源代码的包中runtime-core下的src我们今天学习的线是沿着render线走的;
返回{
渲染,
水合物,
createApp: createAppAPI(渲染,水合物)
}
找到这个文件下的渲染函数,如下图;该函数用于将传入的vnode呈现到指定的容器中;
const render:rootdrenderfunction=(vnode,container)={
if (vnode==null) {
如果(容器。_vnode) {
卸载(容器。_vnode,null,null,true)
}
}否则{
补丁(容器。_vnode null,vnode,container)
}
flushPostFlushCbs()
集装箱。_vnode=vnode
}
看贴片法。如果你初始化,你将采取else if(shapeflagshapeflags.com部件)。
const patch: PatchFn=(
n1,
n2,
容器,
anchor=null,
parentComponent=null,
parent悬念=null,
isSVG=false,
优化=假
)={
//修补不同类型,卸载旧树
如果(n1!isSameVNodeType(n1,n2)) {
anchor=getNextHostNode(n1)
unmount(n1,parentComponent,parent悬念,true)
n1=空
}
if (n2.patchFlag===PatchFlags。保释){
优化=假
n2.dynamicChildren=null
}
const { type,ref,shapeFlag }=n2
开关(类型){
案例文本:
processText(n1,n2,容器,锚)
破裂
案例评论:
processCommentNode(n1,n2,容器,锚)
破裂
案例静态:
if (n1==null) {
mountStaticNode(n2,容器,锚,isSVG)
} else if (__DEV__) {
patchStaticNode(n1,n2,容器,isSVG)
}
破裂
案例片段:
processFragment(
n1,
n2,
容器,
锚,
parentComponent,
家长悬念,
isSVG,
最佳化的
)
破裂
默认值:
if (shapeFlag ShapeFlags .元素){
processElement(
n1,
n2,
容器,
锚,
parentComponent,
家长悬念,
isSVG,
最佳化的
)
} else if (shapeFlag ShapeFlags .组件){
//初始化走这个
进程组件(
n1,
n2,
容器,
锚,
parentComponent,
家长悬念,
isSVG,
最佳化的
)
} else if (shapeFlag ShapeFlags .瞬间移动){
;(类型为传送的类型)。流程(
n1为传送节点,
氮气作为传送节点,
容器,
锚,
parentComponent,
家长悬念,
isSVG,
优化,
内部构件
)
} else if(_ _ FEATURE _ suspension _ _ shape flag形状标志.悬念){
;(类型为悬架类型)。流程(
n1,
n2,
容器,
锚,
parentComponent,
家长悬念,
isSVG,
优化,
内部构件
)
} else if (__DEV__) {
警告(无效的虚拟节点类型:,type,`(${typeof type})`)
}
}
//设置引用
if (ref!=null parentComponent) {
setRef(ref,n1 n1.ref,parentComponent,parent悬念n2)
}
}
接下来查看过程组件方法,接下来走我们熟悉的安装组件
const processComponent=(
n1: VNode null,
n2: VNode,
容器:RendererElement,
anchor: RendererNode null,
父组件:组件内部实例空,
父挂起:suspendeboundary null,
isSVG:布尔型,
优化:布尔型
)={
if (n1==null) {
if (n2.shapeFlag ShapeFlags .COMPONENT _ keep _ ALIVE){
;(parentComponent!中强作为KeepAliveContext).激活(
n2,
容器,
锚,
isSVG,
最佳化的
)
}否则{
//初始化走挂载流程
安装组件(
n2,
容器,
锚,
parentComponent,
家长悬念,
isSVG,
最佳化的
)
}
}否则{
更新组件(n1,n2,优化)
}
}
进入安装组件方法,其中比较重要的情况为创建组件实例,设置组件为安装组件准备的;做选项处理用的;setupRenderEffec用于建立渲染函数副作用,在依赖收集的时候使用。
常量挂载组件:MountComponentFn=(
初始节点,
容器,
锚,
parentComponent,
家长悬念,
isSVG,
最佳化的
)={
//创建组件实例
const instance:组件内部实例=(初始vnode。component=createcomponent实例(
初始节点,
parentComponent,
父母悬念
))
if(_ _ DEV _ _ instance。类型。_ _ hmrId){
寄存器HMR(实例)
}
if (__DEV__) {
pushWarningContext(初始vnode)
开始测量(实例,‘坐骑’)
}
//为保持活跃注入渲染器内部
if (isKeepAlive(initialVNode)) {
;(instance.ctx作为KeepAliveContext).渲染器=内部
}
//解析设置上下文的属性和插槽
if (__DEV__) {
开始测量(实例, init `)
}
//安装组件:选项处理
安装组件(实例)
if (__DEV__) {
最终测量值(实例, init `)
}
//setup()是异步的。该组件依赖异步逻辑进行解析
//继续之前
if(_ _ FEATURE _ suspension _ _ instance。异步dep){
父母悬念父母悬念。注册Dep(实例,setupRenderEffect)
//如果这不是水合作用,就给它一个占位符
//TODO处理自定义回退
如果(!initialVNode.el) {
const占位符=(实例。subtree=createVNode(Comment))
processCommentNode(null,占位符,容器!锚)
}
返回
}
//建立渲染函数副作用:依赖收集
setupRenderEffect(
实例,
初始节点,
容器,
锚,
家长悬念,
isSVG,
最佳化的
)
if (__DEV__) {
popWarningContext()
最终测量值(实例,“安装”)
}
}
进入到设置组件函数里面,观看设置组件函数的内部逻辑,在这里面有属性插槽的初始化;在这里面可以看到setupStatefulComponent方法,它就是用来处理响应式的。
导出函数安装组件(
实例:ComponentInternalInstance,
isSSR=false
) {
isInSSRComponentSetup=isSSR
const { props,children,shapeFlag }=instance.vnode
const is stateful=shape flag shape flags .有状态组件
initProps(实例,道具,isStateful,isSSR)
初始化插槽(实例,子代)
const setupResult=isStateful
?setupStatefulComponent(实例isSSR)
:未定义
isInSSRComponentSetup=false
返回设置结果
}
进入方法setupStatefulComponent,其中常量组件=实例。类型作为组件选项用于组件配置。其中instance.proxy=新代理(instance.ctx,PublicInstanceProxyHandlers)用于代理,数据,$等都是在这里处理的。
函数setupStatefulComponent(
实例:ComponentInternalInstance,
isSSR:布尔型
) {
//组件配置
常量组件=实例。类型作为组件选项
if (__DEV__) {
if (Component.name) {
validateComponentName(组件。name,instance.appContext.config)
}
if (Component.components) {
常量名称=对象。按键(组件。组件)
对于(设I=0;一。姓名。长度;i ) {
validateComponentName(names[i],instance.appContext.config)
}
}
if (Component.directives) {
常量名称=对象。按键(组件。指令)
对于(设I=0;一。姓名。长度;i ) {
有效方向名称(名称[我])
}
}
}
//0.创建渲染代理属性访问缓存
instance.accessCache={}
//1.创建公共实例/渲染代理
//同时将它标记为生的,这样就不会被观察到
instance.proxy=新代理(instance.ctx,PublicInstanceProxyHandlers)
if (__DEV__) {
exposePropsOnRenderContext(实例)
}
//2.呼叫设置()
常量{设置}=组件
如果(设置){
常量设置上下文=(实例。设置上下文=
设置。长度1?createSetupContext(实例):空)
当前实例=实例
暂停跟踪()
const setup result=callWithErrorHandling(
设置,
实例,
错误代码。设置_功能,
[__DEV__?浅层只读(实例。道具):实例。道具,设置文本]
)
重置跟踪()
当前实例=空
if (isPromise(setupResult)) {
if (isSSR) {
//返回承诺,以便服务器呈现程序可以等待它
返回设置结果。然后((解析结果:未知)={
handleSetupResult(instance,resolvedResult,isSSR)
})
} else if(_ _ FEATURE _ suspension _ _){
//异步安装程序返回了承诺。
//在这里保释,等待重新进入。
instance.asyncDep=setupResult
} else if (__DEV__) {
警告(
`设置()返回了一个承诺,但是您正在使用的某视频剪辑软件版本`
`还不支持它。
)
}
}否则{
handleSetupResult(实例,设置结果,isSSR)
}
}否则{
//处理选项等事务
finishComponentSetup(实例isSSR)
}
}
因为在我们的例子中没有设置,所以我们将执行FinishComponentSetup (instance,ISSSR)来处理与选项api相关的事情。进入函数查看代码逻辑,会看到下面的代码。这部分代码用来处理与option API相关的事情,用来支持vue2.x的版本
//支持2.x选项
//支持选项API
if (__FEATURE_OPTIONS_API__) {
currentInstance=实例
应用选项(实例,组件)
当前实例=空
}
输入applyOptions方法;往下翻,你会看到这几行注释,很清楚的解释了vue2.x中各个选项的优先级,包括道具、注入、方法、数据等。
//选项初始化顺序(与Vue 2一致):
//- props(已经在此函数之外完成)
//-注射
//-方法
//-数据(推迟,因为它依赖于“此”访问)
//-计算得出
//- watch(推迟,因为它依赖于“此”访问)
继续往下看,你会看到这几行代码。我们这里不用混合形式,所以这一行代码,涉及到数据对应的公式,在resolveData方法里。
如果(!asMixin) {
if (deferredData.length) {
deferred data . foreach(dataFn=resolved ATA(instance,dataFn,publicThis))
}
if(数据选项){
//数据响应类型
resolveData(实例,数据选项,公共数据)
}
输入resolveData,可以看到const data=data fn . call(public this,public this)。这行代码用于获取数据对象。代码行instance.data=reactive(data)用于响应数据。其核心是反应性的,用来做反应性的处理。选项api,设置,最后采取响应式的方法,用这个方法做响应式的处理。
函数resolveData(
实例:ComponentInternalInstance,
dataFn: DataFn,
public this:ComponentPublicInstance
) {
if (__DEV__!isFunction(dataFn)) {
警告(
`数据选项必须是函数。`
不再支持使用普通对象
)
}
//获取数据对象
const data=datafn . call(publicThis,public this)
if (__DEV__ isPromise(data)) {
警告(
` data()返回了一个承诺-注意data()不能是异步的;如果你
`要在组件呈现之前执行数据提取,请使用` 2
`异步设置()暂记.
)
}
如果(!isObject(data)) {
__DEV__ warn(`data()应返回一个对象。`)
} else if(instance . data===EMPTY _ OBJ){
//响应数据
instance.data=reactive(data)
}否则{
//现有数据:这是mixin或extends。
扩展(实例.数据,数据)
}
}
进入反应式,观察代码逻辑;CreateReactiveObject用于处理数据。目标是最后要转换的东西。
返回createReactiveObject(
目标,
假的,
可变处理器,
可变集合处理程序
)
mutableHandlers中有一些get、set、deleteProperty等方法。MutableCollectionHandlers创建依赖集合等操作。
vue2.x数据响应式和3.x响应式对比
这里,我们先来回顾一下vue2.x是如何处理responsive的。DefineReactive用于截获每个键,以便可以检测到数据更改。这套处理方法是有问题的。当数据逐层嵌套时,会逐层递归执行,从而消耗大量内存。从这个角度来说,这一套处理方式并不友好。在Vue3中,defineReactive也用于拦截每个键。与此不同的是,vue3.x中的defineReactive使用了一层代理,相当于增加了另一层。Vue2.x需要递归对象化所有键,比较慢。阵列响应需要额外的实现。而且添加或删除属性是无法监控的,所以需要专门的api。现在,一个新的代理直接解决了所有问题。同时,以前的方法不知道映射、集合、类等数据结构。
大致流程图
那么我们来梳理一下反应过程中的顺序。
实现依赖收集
在实现响应的过程中,依赖收集与之密切相关,在setupRenderEffect函数中使用了effect函数来收集依赖。输入setupRenderEffect函数的内部。上面的代码里有这个函数,这里就不赘述了。我们继续往下看。当您进入该函数时,您将看到下面的代码。效果可以建立依赖关系:传入效果的回调函数和响应数据之间;效果相当于vue2中的dep,然后vue3中没有守望者。
instance . update=effect(function component effect(){
如果(!instance.isMounted) {
let vnode hook:vnode hook null undefined
const { el,props }=initialVNode
const { bm,m,parent }=实例
继续阅读,你会看到下面的代码。subTree是当前组件vnode,其中renderComponentRoot方法用于实现渲染组件的根。
const subTree=(instance . subTree=rendercomponentrout(instance))
至此,vue3.0的响应式部分将告一段落。
代码仓库
手写的vue3.0短版实现数据响应,已经上传到个人仓库,有兴趣的可以看看。喜欢的可以关注一下,哈哈哈。关注我,你在编程的道路上会多一个朋友。https://gitee.com/zhang-shichuang/xiangyingshi/tree/master/
结尾
Vue的数据响应能力经常在面试中被问到。原理就是看源代码。看源代码的时候,难免会有枯燥的时候,但是坚持就是胜利。后面会分享vue的编译过程和react相关的源代码知识。
关于vue3.x源代码分析的数据响应这篇文章到此为止。更多关于vue3.x数据响应的信息,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。