Vue3.0预览版发布
Vue3.0预览版源码已经发布 可以直接看 github源码。 github 搜索 vue-next
vue-next 目前不支持IE11
解决的问题
- Vue 3 使用ts实现了类型推断,新版api全部采用普通函数,在编写代码时可以享受完整的类型推断(避免使用装饰器)
- 解决了多组件间逻辑重用问题 (解决:高阶组件、mixin、作用域插槽)
- Composition API 使用简单
先看看效果
<div id="container"></div>
<script src="vue.global.js"></script>
<script>
function usePosition(){
let state = Vue.reactive({x:0,y:0});
function update(e) {
state.x= e.pageX
state.y = e.pageY
}
Vue.onMounted(() => {
window.addEventListener('mousemove', update)
})
Vue.onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return Vue.toRefs(state);
}
const App = {
setup(){ // Composition API 使用的入口,相当于v2的created
const state = Vue.reactive({name:'666'}); // 定义响应数据
const {x,y} = usePosition(); // 使用公共逻辑,绑定数据
Vue.onMounted(()=>{
console.log('当组挂载完成')
});
Vue.onUpdated(()=>{
console.log('数据发生更新')
});
Vue.onUnmounted(()=>{
console.log('组件将要卸载')
})
function changeName(){
// 更改数据
state.name = '123';
}
return { // 返回上下文,可以在模板中使用
state,
changeName,
x,
y
}
},
template:`
<button @click="changeName">{{state.name}} 鼠标x: {{x}} 鼠标: {{y}}</button>
`
}
// 挂载vue实例 react的风格
Vue.createApp().mount(App,container);
</script>
目录分析
在vue-next目录中,packages目录中包含着Vue3.0所有功能
-
compiler
compiler-core主要功能是暴露编译相关的API以及baseCompile方法 compiler-dom基于compiler-core封装针对浏览器的compiler (对浏览器标签进行处理) -
runtime
runtime-core 虚拟 DOM 渲染器、Vue 组件和 Vue 的各种API runtime-test将DOM结构格式化成对象,方便测试 runtime-dom 基于runtime-core编写的浏览器的runtime (增加了节点的增删改查,样式处理等),返回render、createApp方法 -
reactivity
单独的数据响应式系统,核心方法reactive、effect、 ref、computed -
vue
整合 compiler + runtime -
在目录中官方给出了基础的响应式数据的测试文件,我们可以来分析实现原理
const app = {
template:`<div>{{count}}</div>`,
data(){
return {count:100}
},
}
let proxy = Vue.createApp().mount(app,container);
setTimeout(()=>{
proxy.count = 200;
},2000)
vue3和vue2 数据响应式的区别
vue2 数据响应
对象拦截,给对象的属性增加set 和 get方法,
因为核心是defineProperty所以还需要对数组的方法进行拦截,
自定义实现数组的方法,通过call来对数组原型方法劫持更改
// 获得原始数组的原型,和它上面的方法
let oldProtoMehtods = Array.prototype;
let proto = Object.create(oldProtoMehtods);
['push','pop','shift','unshift'].forEach(method=>{
Object.defineProperty(proto,method,{
get(){
update();
// 方法劫持
oldProtoMehtods[method].call(this,...arguments)
}
})
})
function observer(target){
// 如果不是对象数据类型直接返回即可
if(typeof target !== 'object'){
return target
}
if(Array.isArray(target)){
// 吧劫持后的数组原型给当前data
Object.setPrototypeOf(target,proto);
// 给数组中的每一项进行observr
for(let i = 0 ; i < target.length;i++){
observer(target[i])
}
// 是数组 不走下面的对象处理了
return
};
// 重新定义key
for(let key in target){
defineReactive(target,key,target[key])
}
}
function update(){
console.log('update view')
}
function defineReactive(obj,key,value){
observer(value); // 有可能对象类型是多层,递归劫持
Object.defineProperty(obj,key,{
get(){
// 在get 方法中收集依赖,发布订阅模式
return value
},
set(newVal){
if(newVal !== value){
// 更改的时候也要触发劫持
observer(value);
update(); // 在set方法中触发更新
}
}
})
}
let obj = {name:'666'}
observer(obj);
obj.name = '123';
可以看到
-
Object.defineProperty缺点
- 无法监听数组的变化
- 默认深度遍历,浪费内存
vue3 数据响应
vue3使用了ES6中的 Proxy、Reflect;
还有ES6中为我们提供的 Map、Set两种数据结构;
以及其他的一些东西
先用一下
let p = Vue.reactive({name:'666'});
Vue.effect(()=>{ // effect方法会立即被触发,里面的函数会被执行一次
console.log(p.name);
})
p.name = '123'; // 修改属性后会再次触发effect方法,让里面的函数在执行一次
- reactive实现
const toProxy = new WeakMap(); // 存放被代理过的对象
const toRaw = new WeakMap(); // 存放已经代理过的对象
function reactive(target){
// 创建响应式对象
return createReactiveObject(target);
}
function isObject(target) {
return typeof target === "object" && target !== null;
}
function hasOwn(target,key){
return target.hasOwnProperty(key);
}
function createReactiveObject(target){
// 判断target是不是对象,不是对象不必继续
if(!isObject(target)){
return target;
}
// 获取里面是否存在
let observed = toProxy.get(target);
if(observed){ // 判断是否被代理过
return observed;
}
if(toRaw.has(target)){ // 判断是否要重复代理
return target;
}
// Reflect 是一个es6内置的对象,它提供拦截 JavaScript 操作的方法,和proxy搭配使用,具有返回值
const handlers = {
get(target,key,receiver){ // 取值
console.log('获取')
let res = Reflect.get(target,key,receiver);
// 方便取值不用.value 这种方式
if(res._isRef){
return res.value
}
track(target,'get',key); // 依赖收集
// 懒代理,只有当取值时再次做代理,vue2.0中一上来就会全部递归增加getter,setter
return isObject(res) ? reactive(res) : res;
},
set(target,key,value,receiver){ // 更改 、 新增属性
// 更改、新增属性
let oldValue = target[key]; // 获取上次的值
let hadKey = hasOwn(target,key); // 看这个属性是否存在
let result = Reflect.set(target, key, value, receiver);
if(!hadKey){ // 新增属性
console.log('更新 添加')
trigger(target,'add',key); // 触发添加
}else if(oldValue !== value){ // 修改存在的属性
console.log('更新 修改')
trigger(target,'set',key); // 触发修改
}
// 当调用push 方法第一次修改时数组长度已经发生变化
// 如果这次的值和上次的值一样则不触发更新
return result;
},
deleteProperty(target,key){ // 删除属性
console.log('删除')
const result = Reflect.deleteProperty(target,key);
return result;
}
}
// 开始代理
observed = new Proxy(target,handlers);
toProxy.set(target,observed); //吧要代理的对象放进去 存放被代理过的对象
toRaw.set(observed,target); // 做映射表 存放已经代理过的对象,进来的时候直接取用
return observed;
}
let p = reactive({name:'666'});
console.log(p.name); // 获取
p.name = '165'; // 设置
delete p.name; // 删除
剩下的就是在get和set的时候实现依赖收集和释放,
track的作用是依赖收集,收集的主要是effect里面的方法,
我们先来实现effect原理,之后再完善 track和trigger方法
-
effect
- effect意思是副作用,此方法默认会先执行一次。
- 如果数据变化后会再次触发此回调函数。
function effect(fn) {
// 创建响应式的effect,返回一个函数来执行,
// 所以他下面是一个高阶函数
const effect = createReactiveEffect(fn);
// 针对computed 做修改
// 如果是lazy 则不立即执行, 缓存起来,在调用的时候执行
if(!options.lazy){
effect();
}
// 再返回这个函数 方便再次调用
return effect;
}
const activeReactiveEffectStack = []; // 存放响应式effect
function createReactiveEffect(fn) {
// 响应式的effect,高阶函数
const effect = function() {
return run(effect, fn);
};
// 吧方法设置给effect,后面来判断
effect.scheduler = options.scheduler;
return effect;
}
// 具体实现
function run(effect, fn) {
try {
activeReactiveEffectStack.push(effect);
// 先让fn执行,执行时会触发get方法,
// 可以将effect存入对应的key属性
return fn();
} finally {
// 可能不是一个函数,会失败但是最终都会被清理掉 pop
activeReactiveEffectStack.pop(effect);
}
}
- 当调用fn()时可能会触发get方法,此时会触发track
- 也可能不会触发,那么久不会执行effect,没有意义
const targetMap = new WeakMap();
function track(target,type,key){
// 查看是否有effect,获取数组最后一项
const effect = activeReactiveEffectStack[activeReactiveEffectStack.length-1];
if(effect){
// 看这个对象里面是否存在传进来的对象
let depsMap = targetMap.get(target);
if(!depsMap){ // 不存在map
targetMap.set(target,depsMap = new Map());
}
// 在这个对象里面再去取值,这次放入set数组
let dep = depsMap.get(target);
if(!dep){ // 不存在set
depsMap.set(key,(dep = new Set()));
}
if(!dep.has(effect)){
dep.add(effect); // 将effect添加到依赖中
}
}
}
// 当更新属性时会触发trigger执行,找到对应的存储集合拿出effect依次执行
function trigger(target,type,key){
const depsMap = targetMap.get(target);
if(!depsMap){
return
}
let effects = depsMap.get(key);
if(effects){
effects.forEach(effect=>{
// 针对computed实现
// 如果有scheduler 说明不需要执行effect
if(effect.scheduler){
// 将dirty设置为true,下次获取值时重新执行runner方法
effect.scheduler();
}else{
// 否则就是effect 正常执行即可
effect();
}
})
}
// 处理如果当前类型是增加属性,如果用到数组的length的effect应该也会被执行
if (type === "add") {
let effects = depsMap.get("length");
if (effects) {
effects.forEach(effect => {
effect();
});
}
}
}
vue3的响应式核心已经被实现,有兴趣的可以去查看源码 reactive
ref和computed的实现
- ref可以将原始数据类型也转换成响应式数据, 需要通过.value属性进行获取值
function convert(val) {
return isObject(val) ? reactive(val) : val;
}
function ref(raw) {
raw = convert(raw);
const v = {
_isRef:true, // 标识是ref类型
get value() {
track(v, "get", "");
return raw;
},
set value(newVal) {
raw = newVal;
trigger(v,'set','');
}
};
return v;
}
// 这样修改之后,在上面的get()就要加上.value了
get(){
// ...
let res = Reflect.get(target, key, receiver);
if(res._isRef){
return res.value
}
// ...
}
- computed 实现也是基于 effect 来实现的, 特点是computed中的函数不会立即执行,多次取值是有缓存机制
- 用法
let a = reactive({name:'666'});
let c = computed(()=>{
console.log('执行次数')
return a.name +'123';
})
// 不取不执行,取n次只执行一次
console.log(c.value);
console.log(c.value);
- 实现
function computed(getter){
let dirty = true;
const runner = effect(getter,
// 标识这个effect是懒执行
{
// 懒执行
lazy:true,
// 当依赖的属性变化了,调用此方法,而不是重新执行effect
scheduler:()=>{
dirty = true;
}
}
);
let value;
return {
_isRef:true,
get value(){
if(dirty){
value = runner(); // 执行runner会继续收集依赖
dirty = false;
}
return value;
}
}
}
// 同时 我们需要对effect trigger 都做修改
完整代码,可以被引入直接用于数据双向绑定的reactive方法
const toProxy = new WeakMap(); // 存放被代理过的对象
const toRaw = new WeakMap(); // 存放已经代理过的对象
function reactive(target){
// 创建响应式对象
return createReactiveObject(target);
}
function isObject(target) {
return typeof target === "object" && target !== null;
}
function hasOwn(target,key){
return target.hasOwnProperty(key);
}
function createReactiveObject(target){
// 判断target是不是对象,不是对象不必继续
if(!isObject(target)){
return target;
}
// 获取里面是否存在
let observed = toProxy.get(target);
if(observed){ // 判断是否被代理过
return observed;
}
if(toRaw.has(target)){ // 判断是否要重复代理
return target;
}
// Reflect 是一个es6内置的对象,它提供拦截 JavaScript 操作的方法,和proxy搭配使用,具有返回值
const handlers = {
get(target,key,receiver){ // 取值
console.log('获取')
let res = Reflect.get(target,key,receiver);
// 方便取值不用.value 这种方式
if(res._isRef){
return res.value
}
track(target,'get',key); // 依赖收集
// 懒代理,只有当取值时再次做代理,vue2.0中一上来就会全部递归增加getter,setter
return isObject(res) ? reactive(res) : res;
},
set(target,key,value,receiver){ // 更改 、 新增属性
// 更改、新增属性
let oldValue = target[key]; // 获取上次的值
let hadKey = hasOwn(target,key); // 看这个属性是否存在
let result = Reflect.set(target, key, value, receiver);
if(!hadKey){ // 新增属性
console.log('更新 添加')
trigger(target,'add',key); // 触发添加
}else if(oldValue !== value){ // 修改存在的属性
console.log('更新 修改')
trigger(target,'set',key); // 触发修改
}
// 当调用push 方法第一次修改时数组长度已经发生变化
// 如果这次的值和上次的值一样则不触发更新
return result;
},
deleteProperty(target,key){ // 删除属性
console.log('删除')
const result = Reflect.deleteProperty(target,key);
return result;
}
}
// 开始代理
observed = new Proxy(target,handlers);
toProxy.set(target,observed); //吧要代理的对象放进去 存放被代理过的对象
toRaw.set(observed,target); // 做映射表 存放已经代理过的对象,进来的时候直接取用
return observed;
}
function effect(fn) {
// 创建响应式的effect,返回一个函数来执行,
// 所以他下面是一个高阶函数
const effect = createReactiveEffect(fn);
// 针对computed 做修改
// 如果是lazy 则不立即执行, 缓存起来,在调用的时候执行
if(!options.lazy){
effect();
}
// 再返回这个函数 方便再次调用
return effect;
}
const activeReactiveEffectStack = []; // 存放响应式effect
function createReactiveEffect(fn) {
// 响应式的effect,高阶函数
const effect = function() {
return run(effect, fn);
};
// 吧方法设置给effect,后面来判断
effect.scheduler = options.scheduler;
return effect;
}
// 具体实现
function run(effect, fn) {
try {
activeReactiveEffectStack.push(effect);
// 先让fn执行,执行时会触发get方法,
// 可以将effect存入对应的key属性
return fn();
} finally {
// 可能不是一个函数,会失败但是最终都会被清理掉 pop
activeReactiveEffectStack.pop(effect);
}
}
const targetMap = new WeakMap();
function track(target,type,key){
// 查看是否有effect,获取数组最后一项
const effect = activeReactiveEffectStack[activeReactiveEffectStack.length-1];
if(effect){
// 看这个对象里面是否存在传进来的对象
let depsMap = targetMap.get(target);
if(!depsMap){ // 不存在map
targetMap.set(target,depsMap = new Map());
}
// 在这个对象里面再去取值,这次放入set数组
let dep = depsMap.get(target);
if(!dep){ // 不存在set
depsMap.set(key,(dep = new Set()));
}
if(!dep.has(effect)){
dep.add(effect); // 将effect添加到依赖中
}
}
}
// 当更新属性时会触发trigger执行,找到对应的存储集合拿出effect依次执行
function trigger(target,type,key){
const depsMap = targetMap.get(target);
if(!depsMap){
return
}
let effects = depsMap.get(key);
if(effects){
effects.forEach(effect=>{
// 针对computed实现
// 如果有scheduler 说明不需要执行effect
if(effect.scheduler){
// 将dirty设置为true,下次获取值时重新执行runner方法
effect.scheduler();
}else{
// 否则就是effect 正常执行即可
effect();
}
})
}
// 处理如果当前类型是增加属性,如果用到数组的length的effect应该也会被执行
if (type === "add") {
let effects = depsMap.get("length");
if (effects) {
effects.forEach(effect => {
effect();
});
}
}
}
function convert(val) {
return isObject(val) ? reactive(val) : val;
}
function ref(raw) {
raw = convert(raw);
const v = {
_isRef:true, // 标识是ref类型
get value() {
track(v, "get", "");
return raw;
},
set value(newVal) {
raw = newVal;
trigger(v,'set','');
}
};
return v;
}
function computed(getter){
let dirty = true;
const runner = effect(getter,
// 标识这个effect是懒执行
{
// 懒执行
lazy:true,
// 当依赖的属性变化了,调用此方法,而不是重新执行effect
scheduler:()=>{
dirty = true;
}
}
);
let value;
return {
_isRef:true,
get value(){
if(dirty){
value = runner(); // 执行runner会继续收集依赖
dirty = false;
}
return value;
}
}
}