一、框架设计基本概念
1、编程范式之命令式编程
关注过程 的一种编程范式,他描述了完成一个功能的 详细逻辑与步骤
2、编程范式之声明式编程
不关注过程,只关注结果 的范式
3、命令式 VS 声明式
声明式的框架本质是由命令式的代码去实现的
-
性能: 命令式 > 声明式
-
可维护性:命令式 < 声明式
可维护性指: 阅读、修改、删除、增加
4、企业应用开发与设计原则
- 项目成本: 命令式 < 声明式
由开发周期决定
- 开发体验: 命令式 < 声明式
5、框架的设计过程是一个不断在可维护性和性能取舍的过程
vue设计原则:在保证可维护性的基础上,尽可能减少性能的损耗
6、.vue中的html是真实的html吗
不是,这些标签上的指令,浏览器不能识别,vue做了处理
- 编译时:compiler
- 运行时:runtime
- 运行时 + 编译时:runtime + compiler
7、运行时
利用 render 把 vnode 渲染成真实 dom 节点
8、编译时
把 template 中的 html 的节点 编译成 render 函数
9、运行时 + 编译时
- dom 渲染是如何进行的
- 初次渲染(挂载)
- 更新渲染(打补丁)
- 全删,全加
- 对比新旧差异,再删、加(选这个)
- 纯运行时:只能使用复杂的js对象
- 纯编译时:分析差异的过程在编译时进行,损失灵活性,例svelte
- 运行时 + 编译时:保持灵活性的基础上,尽量进行性能优化,达到平衡
10、副作用
对数据进行 setter 或 getter 操作时,产生的一系列后果,可能会有多个副作用
11、vue3框架设计概述
-
响应性:reactivity
通过 reactive 传入一个被代理对象(target)得到代理对象(proxyTarget),当代理对象产生setter或getter行为,都会产生副作用
-
编译器:complier 把template转换成render函数
-
运行时:runtime 通过render函数渲染成真实dom
12、为什么说vue3对ts支持比较好
vue 内部声明了很多ts类型
二、Vue 3源码结构 - 搭建框架雏形
1、源码结构
bash
复制代码
├── packages
│ ├── compiler-core # 与平台无关的编译器实现的核心函数包
│ ├── compiler-dom # 浏览器相关的编译器上层内容
│ ├── compiler-sfc # 单文件组件的编译器
│ ├── compiler-ssr # 服务端渲染相关的编译器实现
│ ├── global.d.ts # ts 相关一些声明文件
│ ├── reactivity # 响应式核心包
│ ├── runtime-core # 与平台无关的渲染器相关的核心包
│ ├── runtime-dom # 浏览器相关的渲染器部分
│ ├── runtime-test # 渲染器测试相关代码
│ ├── server-renderer # 服务端渲染相关的包
│ ├── sfc-playground # 单文件组件演练场
│ ├── shared # 工具库相关
│ ├── size-check # 检测代码体积相关
│ ├── template-explorer # 演示模板编译成渲染函数相关的包
│ └── vue # 包含编译时和运行时的发布包
1、源码debugger
- 下载 vue.js/core 代码
- 修改打包命令,开启soutceMap
"build": "node scripts/build.js -s" - 注释git相关代码,否则build失败
全局搜索
const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7) 注释,并注释引用commit变量的地方
4. 执行npm run build,使用/packages/vue/dist/vue.global.js,
5. 通过 live server 启动
6. 在控制台源代码debugger代码
2、如何阅读源代码
- 摒弃边缘情况,仅阅读核心逻辑
- 跟随一条主线
三、响应系统-响应系统的核心设计原则
影响视图变化的数据称为响应数据
1、vue 2 的响应性核心 API:Object.defineProperty
只能监听指定对象上指定属性的getter行为和setter行为,因此有缺陷——不能监听数组和对象的变化,不能监听新增的属性的变化
- 为指定对象的指定属性设置属性描述符
- 当想要修改对象的指定属性时,可以使用原对象进行修改
- 通过属性描述符,只有被监听的指定属性,才可以触发getter和setter
2、vue3的响应性核心 API:proxy
- 传入被代理对象,得到一个代理对象,同时拥有被代理对象中所有的属性
- 当想要修改对象的指定属性时,我们应该使用代理对象进行修改
- 代理对象的任何一个属性都可以触发handler的getter和setter
3、proxy的最佳拍档reflect
Reflect.get(target, propertyKey, receiver如果
target对象中指定了getter,receiver则为getter调用时的this值。 当我们期望代理对象的 getter 和setter时,不应该使用 target[key], 因为当被代理对象的属性有 get set标记时,this指向的是被代理对象,应该使用 reflect,借助他的get set方法,将this执行代理对象
四、响应系统 - 初见 reactivity 模块
创建 proxy; 收集 effect 的依赖; 触发收集的依赖
1、reactive
- 1.触发了 createReactiveObject 函数,里面构建了 proxy ,才可以监听被代理对象的getter行为和setter行为,这个监听通过baseHandlers里完成
2、effect
- 1.生成 ReactiveEffect 实例,
- 2.类ReactiveEffect 接收一个 fn,fn必须暴露getter行为,才能完成依赖收集
- 2.类ReactiveEffect 除了有fn,在 run 对全局变量 activeEffect 赋值,触发 fn 方法,从而激活getter行为,触发track,构建了一个targetMap,完成当前指定对象,指定属性,依赖收集的工作,也就是建立 targetMap 和 activeEffect 之间的联系
- dep.add(activeEffect)
- activeEffect.deps.push(dep)
3、触发依赖
- 1.触发setter行为,触发trigger,通过targetMap,tartget和key拿到effect,调用fn,完成依赖触发
4、特点
- 只支持对象和数组(引用数据类型)
- 重新分配一个新对象、传入函数会丢失响应性
- 解构时会丢失响应性,需使用toRefs
五、响应式系统 - ref
1、复杂数据类型
-
触发 createRef 函数,返回 RefImpl 类型的实例
-
该实例中,根据__v_isShallow传入数据类型分开处理
- 复杂数据类型:转化为 reactive 返回的 proxy 实例
- 简单类型:不做处理
-
当触发getter行为,执行trackRefValue,当activeEffect为真时,执行trackEffects完成依赖收集,返回this._value
-
触发setter行为时,实际上触发了一次 get value,触发了 reactive setter行为
2、简单数据类型
- 响应性依赖于 get value,set value
- 区别在于,set value时,触发triggrRefValue,执行triggerEffects
特点
- 支持基本数据类型+引用数据类型
- 需要使用
.value访问属性 - 重新分配一个新对象、传入函数不会丢失响应性
- 解构对象时会丢失响应性,需使用toRefs
六、响应式系统 - computed
- 计算属性的实例,本质上是 ComputedRefImpl 的实例
- ComputedRefImpl 中通过 dirty 变量来控制 run 的执行和 triggerRefValue 的触发
- 想要访问计算属性的值必须通过 .value,因为他内部 和 ref 一样是通过 get value 实现的
- 每次 .value 都会触发 trackRefValue 即 依赖收集
- 在依赖触发时,先触发 computed的 effect 再触发 非computed的effect
过程: 依赖值的setter触发了computed effect 的scheduler,执行了triggerRefValue,触发了 render effect,触发了计算属性值的 getter ,计算出最新的 计算属性值 ,完成更新渲染