vue3.o源码分析

# 前言

调研的预览版本,很多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
1
2
3
4
5
6
7
8
9
10
11
12
13

# 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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

缺点

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

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

vm.items.splice(newLength)
1
2
3
4
5
6
7
8
9

# Proxy


1

# 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
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 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() }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 参考文档

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