vue3.o源码分析

[[toc]]

前言

调研的预览版本,很多api还没完善,但大致架构已经成形, 暂时还不能用于生产。

优势

  • 函数式编程,函数对ts兼容性好【react-hooks】
  • static tree hoisting 功能 (检测静态语法,进行提升) 【diff】
  • 基于 Proxy 实现的数据变更检测 【不需要递归了,节省内存】
  • 支持 Fragments 【 react 空标签,当根元素用】
  • 支持 Portals 【react 允许在DOM的其它位置进行渲染】
  • 同时支持 Composition API 和 Options API 【单文件兼容vue2.o】
  • Custom Renderer API 【自定义渲染器API】

源码目录

├── packages
│ ├── compiler-core # 所有平台的编译器(weex也是基于这个)
│ ├── compiler-dom # 针对浏览器的编译器
│ ├── compiler-ssr # ssr
│ ├── compiler-sfc # 针对单文件
│ ├── reactivity # 数据响应式系统
│ ├── runtime-core # 渲染器,一些核心的api
│ ├── runtime-dom # 针对浏览器的runtime,包括处理原生DOM
│ ├── runtime-test # 专门为测试写的runtime
│ ├── server-renderer # 用于SSR
│ ├── shared # 帮助方法
│ ├── template-explorer
│ └── vue # 构建vue

composition-api

Composition API纯粹是添加的,不会影响/弃用任何现有的2.x API,它是可以单独导入到项目中的

动机

  • Vue 3 使用ts实现了类型推断,新版api全部采用普通函数,在编写代码时可以享受完整的类型推断(避免使用装饰器)
  • 解决了多组件间逻辑重用问题 (解决:高阶组件、mixin、作用域插槽)
    • 如果项目过于复杂的时候,mixin中的代码和外部组件的代码存在命名冲突的时候会被覆盖,而且如果有相同的生命周期函数也会被覆盖,所以会导致代码难以维护,容易出现bug
    • 代码分散

// 安装

yarn add @vue/composition-api

// 使用

import Vue from ‘vue’;

import VueCompositionApi from ‘@vue/composition-api’;

Vue.use(VueCompositionApi);

reactive

Object.defineProperty()

let oldProtoMehtods = Array.prototype;
let proto = Object.create(oldProtoMehtods);
// 针对数组
['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'].forEach(method => {
proto[method] = function () { // 函数劫持,把内部的函数重写,数组方法还是继续调用老的方法
updateView();
observer(...arguments);
oldProtoMehtods[method].call(this, ...arguments)
}
});

function observer(target) {
if (!isObject(target)) return target;

if (Array.isArray(target)) {
target.__proto__ = proto; // 同上

// 给数组中的每一项进行observer
for (let i = 0; i < target.length; i++) {
observer(target[i])
}
return
}

for (let key in target) {
defineReactive(target, key, target[key])
}
}

function defineReactive(target, key, value) {
observer(value); // 有可能对象类型是多层,递归劫持

Object.defineProperty(target, key, {
get() {
// 在get 方法中收集依赖
return value
},
set(newVal) {
console.log('set方法', newVal);
if (newVal !== value) {
updateView(); // 在set方法中触发更新
observer(newVal); // 防止传进来是一个二级对象,不能对以后的数据监测
}
}
})
}


let data = {ary: [1, 2, {age: 22}]};
let result = observer(data);

data.ary.push({age: 33});
data.ary.push([1, 2, 3]);
data.ary[4].push(1);

data.ary[4].splice(1, 1, 22222);
// data.ary[4][1] = 2222; 不会触发更新,值会改变

console.log(data.ary);

缺点

  • 无法监听数组的变化
  • 需要递归深度遍历,浪费内存
// 无法响应式
vm.items[indexOfItem] = newValue
vm.items.length = newLength

// 解决办法
vm.$set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)

vm.items.splice(newLength)

Proxy



effect

ref

下面是自己提炼的的核心代码,源码分析看这

// ref
const convert = (val) => {
return isObject(val) ? reactive(val) : val
};
function ref(rawValue) {
let value = convert(rawValue);

const result = {
__v_isRef: true,
get value() {
return value
},
set value(newVal) {
console.log('视图更新');
value = convert(newVal);
}
};
return result
}
// ---------------
// toRefs
function toRefs(object) {
const ret = {};
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}

function toRef(object, key) {
return {
__v_isRef: true,
get value() {
return object[key]
},
set value(newVal) {
object[key] = newVal
}
}
}

computed

Beta升级使用

写demo可以,公司正式项目不建议,后期少不了小改动

安装3.o新版本

yarn add vue@next vue-router@next vuex@next
yarn add @vue/compiler-sfc@next eslint-plugin-vue@next vue-cli-plugin-vue-next -D

逻辑复用

import { ref, onMounted, onUnmounted } from 'vue'

export function useMousePosition() {
const x = ref(0)
const y = ref(0)

function update(e) {
x.value = e.pageX
y.value = e.pageY
}

onMounted(() => {
window.addEventListener('mousemove', update)
})

onUnmounted(() => {
window.removeEventListener('mousemove', update)
})

return { x, y }
}
// 在组件中使用:
import { useMousePosition } from './mouse'
export default {
setup() {
return { ...useMousePosition() }
}
}

参考文档

vue-next
vue-cli-plugin-vue-nex
vue-composition-api文档
vue3的编译工具
尤雨溪在Vue3.0Beta直播里聊到了这些
juejin
segmentfault