vue3源码学习
使用最新的vue3.0 输入命令 vue add vue-next
****************
option api 和 compositi on api的区别
1, a 传统对象api, b hooks api 函数式变成 增强复用
2, b 使用了ref computed 很好的支持了 tree-shaking(摇树)
3, b 取代了 mixins
4, hooks 每次都产生一个hooks闭包,b第一次会产生一个闭包,其他用响应式实现
****************
import {reactive, ref , computed, onMounted, watchEffect} from 'vue';
reactive 把对象变成响应式
ref 把基本数据类型变成响应式
computed 计算属性
onMounted
watchEffect 数据响应后,通知给组件更新 这个函数执行 意味着
数据变了 vdom的diff 开始最小化操作dom
****************
proxy 不支持IE11 需要polyfill 提供支持
@vue/reactivity 是一个新的工具包,vue依赖这个包实现
composition = @vue/reactivity + 生命周期
ref/reactive 的代码实现
ref
// 手动实现一个 ref
let activeEffect // 临时存储依赖改变的方法
// 工具类
class Dep{
constructor(){
this.subs = new Set(); // 依赖容器
}
depend(){ // 收集依赖
if(activeEffect){
this.subs.add(activeEffect);
}
}
notofy(){ // 数据变化 触发effect 执行
this.subs.forEach(effect=>effect())
}
}
const dep = new Dep(); // vue3 中这个将变成一个大的map对象
// ref 实现 一个 基础数据绑定的实现
function ref(ininVal){
let _v = ininVal;
let state = {
get value(){
// 获取值, 收集依赖
dep.depend();
return _v;
}, // 子面量的get写法
set value(val){
// 通知函数执行effect ,改变值
_v=val;
dep.notofy(); // 执行effect 修改dom
}
}
return state;
}
let state = ref(0);
function effect(fn){
console.log(fn)
activeEffect = fn // 先往临时变量存个方法
fn(); // 然后调用get 就可以收集到依赖
//watcheffect 执行dom的更新
}
// 传入一个方法 ,实际vue3执行的不是我们的这个方法,是某个关键方法
effect(()=>{ // 先执行一次
console.log(state.value)
})
setInterval(()=>{
state.value++;
},1000)
手工实现一个reactive
// 配合index.html 手动实现一个 reactive
// WeakMap key 必须是对象 ,万一key的弱引用对象被删除则会被自动清除,
// 设计有助于防止内存泄漏, 有一种使用场景是和dom进行绑定;
let targetMap = new WeakMap();
const effectStack = [];
function track(target,key){ // 收集依赖 ,因为 reactive可能有多个,一个又有多个属性在变化
const effect = effectStack[effectStack.length - 1];
if(effect){
let depMap = targetMap.get(target);
if(!depMap){
depMap=new Map();
targetMap.set(target,depMap);
}
let dep = depMap.get(key);
if(!dep){
dep= new Set();
depMap.set(key, dep);
}
dep.add(effect);
}
}
function trigger(target,key,info){ // 触发更新
let depMap = targetMap.get(target);
if(!depMap){ // 如果执行的话没有依赖就中断
return
}
const effects = new Set();
const computedRenders = new Set();
let deps = depMap.get(key);
deps.forEach(effect => {
if(effect.computed){
computedRenders.add(effect)
}else{
effects.add(effect);
}
});
// 两者处理的其他的东西不一样
computedRenders.forEach(computed=>computed());
effects.forEach(effect=>effect());
}
function effect(fn,options={}){ // 副作用
// {lazy:false, computed:false} = options
// lazy是否是第一次 ,第一次不执行,否则执行, flag
// computed 是否是计算属性
// computed 是一个特殊的 effect
// fn 是用户自定义的方法
let e = createReactiveEffect(fn,options); // 获取effect
if(!options.lazy){
e();
}
return e;
}
function createReactiveEffect(fn,options){
const effect = function effect(...args){
return run(effect,fn,args );
}
effect.deps = []; // 清理依赖和缓存
effect.computed = options.computed;
effect.lazy = options.lazy;
return effect
}
function run(effect,fn,args){
if(effectStack.indexOf(effect)===-1){
try{
effectStack.push(effect); // 维护
return fn(...args);
}finally{
effectStack.pop(); // 维护清理掉
}
}
}
let baseHandler = {
get(target,key){ // 使用 reflect.get 更合理 ,把 目标的get 方法重写
const res = target[key];
track(target,key); // 收集依赖
return res;
},
set(target,key,val){ // 使用 reflect.set 更合理 ,把 目标的set 方法重写
const info = {oldVal: target[key],newVal:val};
target[key] = val;
trigger(target,key,info); // 需要更新时,把所有的值都传递出去
},
// has, ownkey 等都可以拦截
}
function reactive(target){ // 变成响应式的
let observed = new Proxy(target,baseHandler)
return observed;
}
function computed(fn){ // 基于effect去实现的
const runner = effect(fn,{computed:true,lazy:true});
return {
effect: runner,
get value(){
return runner();
}
}
}
index.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<div id='app'>
</div>
<button id='btn'>btn</button>
</div>
</body>
<script src='./reactive.js'></script>
<script>
const root = document.getElementById('app');
const btn = document.getElementById('btn');
let obj = reactive({
name : '八神',
age : 18
})
let double = computed(()=>obj.age*2);
effect(()=>{
console.log('数据变了',obj.age);
root.innerHTML = `<h2>${obj.name}</h2><h3>今年${obj.age}</h3><h4>双倍年龄是${double.value}岁</h4>`
})
btn.addEventListener('click',()=>{
obj.age+=1;
},false);
</script>
</html>
html
***********************************
.性能对比 2和3
1, proxy 取代 defineproperty
2, vdom 重写 a. 静态标记 b. 纯静态节点会被标记 ,重新渲染的时候根本不会动
5, event 等)
动态节点, 会维护在一个数组内(block内部)
为什么 vue 和react 需要编译
3, diff 算法
vue2 的时候是 双端比较 比较两个对象 只算出需要更新的部分
vue3加入了 最长递增子序列
jsx 动态性太好 整体渲染(vue2的静态标记也不多)但是静态标记就会不起作用,
还是要多用 template
.compiler
编译原理
ast --》 transform --》 generate
template parse -》 ast 抽象语法树
transform 做优化 v-if 扩展 在这个模块 加上vue 语义
generate 生成执行代码 render 函数渲染
.静态标记
vdom 其实template是编译成了一个大的对象,json的每个属性上都说明了自己是谁是干嘛的
有没有孩子节点(静态标记代码: 1,text,2,class 3, props,4, fullprops
静态标记 对 性能做到了极致. 灵感来源于prepack.io
.ssr 对比
把dom 全部静态节点变成了字符串, 只有一个buffer 不停的往里面推送字符串
Vue2把template重新计算编译了 其实是性能浪费
.自定义render
****************
vue 的watch 知道component层面, component内部使用component内部使用diff算法
Diff 算法 有16.6毫秒的瓶颈 会倒是卡顿, 把 diff算法的应用缩小到了componnet上 就从设计上很好的解决了这个瓶颈问题
React 全部都是diff算法 , 也存在 16.6毫秒的问题, 这个时候他是通过fiber架构取解决这个问题的,把树变成了链表, diff 算法 到了16.6毫秒 后 马上把 处理器还给浏览器,让浏览器继续动画等内容 , 过了之后再继续回来dom diff计算, 备胎 算法(利用空闲时间来计算 diff )
*************************
参考大圣老师的讲解整理