您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
Vue3.0数据响应式原理详解
 
作者:方防访放
   次浏览      
 2021-3-1 
 
编辑推荐:
这篇文章主要介绍了Vue3.0数据响应式原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值。
本文来自于脚本之家,由火龙果软件Anna编辑推荐。

预备知识

ES6 Proxy,整个响应式系统的基础。

新的composition-API的基本使用,目前还没有中文文档,可以先通过这个仓库(composition-api-rfc)了解,里面也有对应的在线文档。

先把Vue3.0跑起来

先把vue-next仓库的代码clone下来,安装依赖然后构建一下,vue的package下的dist目录下找到构建的脚本,引入脚本即可。

下面一个简单计数器的DEMO:

<!DOCTYPE html>
<html lang="en">
<body>
<div id='app'></div>
</body>
<script src= z"./dist/vue.global.js"> </script>
<script>
const { createApp, reactive, computed } = Vue;

const RootComponent = {
template: `
<button @click="increment">
Count is: {{ state.count }}
</button>
`,
setup() {
const state = reactive({
count: 0,
})

function increment() {
state.count++
}

return {
state,
increment
}
}
}

createApp().mount(RootComponent, '#app')
</script>
</html>

template和之前一样,同样Vue3也支持手写render的写法,template和render同时存在的情况,优先render。

setup选项是新增的主要变动,顾名思义,setup函数会在组件挂载前(beforeCreate和created生命周期之间)运行一次,类似组件初始化的作用,setup需要返回一个对象或者函数。返回对象会被赋值给组件实例的renderContext,在组件的模板作用域可以被访问到,类似data的返回值。返回函数会被当做是组件的render。具体可以细看文档。

reactive的作用是将对象包装成响应式对象,通过Proxy代理后的对象。

上面的计数器的例子,在组件的setup函数中,创建了一个响应式对象state包含一个count属性。然后创建了一个increment递增的函数,最后将state和increment返回给作用域,这样template里的button按钮就能访问到increment函数绑定到点击的回调,count也能显示在按钮上。我们点击按钮,按钮上的数值就能跟着递增。

下面切入正题,我们就来探究下按钮上count值跟着响应式更新的原理

数据结构

首先列一下主要的一些数据结构,先列在这里,后面提到可以翻回来看看。

ReactiveEffect 一个Function对象,用于执行组件的挂载和更新。

interface ReactiveEffect {
(): any
isEffect: true
active: boolean
raw: Function // 具体执行的函数
deps: Array<Dep>
computed: boolean
scheduler: (run: Function) => void
onTrack: (event: DebuggerEvent) => void
onTrigger: ( event: DebuggerEvent) => void
onStop: () => void
}

targetMap 类似 {target -> key -> dep}的一个Map结构,用于缓存所有响应式对象和依赖收集。

export type Dep = Set<ReactiveEffect>
export type KeyToDepMap = Map<string | symbol, Dep>
export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()

Proxy代理拦截

reactive函数执行,会将传入的target对象通过Proxy包装,拦截它的get,set等,并将代理的target缓存到targetMap,targetMap.set(target, new Map())。

代理的get的时候会调用一个track函数,而set会调用一个triger函数。分别对应依赖收集和触发更新。

// Proxy get 简化
function get (target: any, key: string | symbol, receiver: any) {
// 通过key拿到原始值res
const res = Reflect.get (target, key, receiver)
// 过滤不需要代理的情况
// ...
// 依赖收集
track(target, OperationTypes.GET, key)
// 如果取到的值是个对象,将对象再代理包装一下
// Proxy只能代理对象第一层级
return isObject(res) reactive(res) : res
}

// Proxy set 简化
function set(
target: any,
key: string | symbol,
value: any,
receiver: any
): boolean {
// 一些不需要代理设置的场景
// ...

// 设置原始对象的值
const result = Reflect.set (target, key, value, receiver)
// 避免重复trigger的逻辑
// ...
// 触发通知更新
trigger(target, '更新的类型, 新增key或更新key', key)
return result
}

依赖收集和触发更新

组件在render阶段,视图会读取数据对象上的值进行渲染,此时便触发了Proxy的get,由此触发对应的track函数,记录下了对应的ReactiveEffect,也就是常说的依赖收集。

ReactiveEffect其实就可以看作是组件的更新(mount是特殊的update),数据的变更触发trigger,trigger遍历调用track收集的对应的数据的ReactiveEffect,也就是对应有关联的组件的更新。

trigger触发的组件的更新,在render阶段又触发了新一轮的track依赖收集,更新依赖。

// 简化的 track
function track(
target: any,
type: OperationTypes,
key?: string | symbol
) {
// 只有在依赖收集阶段才进行依赖收集
// 除了render,其他场景也可能会触发Proxy的get,但不需要进行依赖收集
// activeReactiveEffectStack栈顶包装了当前render的组件的mount和update的逻辑
const effect = activeReactiveEffectStack [activeReactiveEffectStack.length - 1]
// 如果effect为空,说明当前不在render阶段
if (effect) {
// ...
// =====>初始化对应{target -> key -> dep}的结构
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get (key as string | symbol)
if (!dep) {
depsMap.set (key as string | symbol, (dep = new Set()))
}
// <=====初始化对应{target -> key -> dep}的结构
// 依赖列表里如果没有,add
if (!dep.has(effect)) {
// 这里将effect作为依赖,缓存到依赖列表
dep.add(effect)
effect.deps.push(dep)
}
}
}

// 简化的trigger
function trigger(
target: any,
type: OperationTypes,
key?: string | symbol,
extraInfo?: any
) {
// 获取对应target在track过程中缓存的依赖
const depsMap = targetMap.get(target)

const effects: Set<ReactiveEffect> = new Set()
// 省略分类逻辑
depsMap.forEach(dep => {
// 将effect分类过滤添加到effects
})

const run = (effect: ReactiveEffect) => {
// 有个异步调度的过程,nextTick
scheduleRun (effect, target, type, key, extraInfo)
}

effects.forEach(run)
}

大致流程:

总结

现在的代码只有新特性的实现,而且ES6+TS的组合可读性大大提高,编辑器支持也很好,所以相对会好读很多。这里只是简单的理了一下vue 3.0 reactive的整体流程,细节还有很多地方值得学习,继续加油。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

 
   
次浏览       
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]
 
最新文章
如何设计高扩展的在线网页制作平台
electron入门心得
使用 Electron 构建桌面应用
VUE.JS组件化开发实践
深入理解JSCore
最新课程
HTML 5 + CSS3 原理与开发应用
Web前端高级工程师必备技能实战
Vue大型项目开发实战
React原理与实践
Vue.js进阶与案例实践
更多...   
成功案例
中交集团 构建Web自动化测试框架
某著名电信公司 Vue.js进阶与案例
国电通网络技术 HTML5+CSS3 +web前端框
移动通信 移动互联网应用开发原理
某电力行 android开发平台最佳
更多...