手写vue3
Vue3目录介绍
- packages
- reactivity: 响应式
- runtime-core: 运行时核心
- runtime-dom: DOM环境的运行
- shared: 公共函数
- scripts: 脚本
- rollup.config.js: rollup编译文件
- tsconfig.json:
包的关系
graph RL
reactivity-->runtime-core --> runtime-dom --> Vue
compiler-core --> compiler-dom --> Vue
reactivity
响应式api
api | 作用 | dep |
---|---|---|
reactive | 响应式 | 深层 |
readonly | 只读 | 深层 |
shallowReactive | 响应式 | 浅层 |
shallowReadonly | 只读 | 浅层 |
graph RL
reactive--> proxy
proxy --> get --> track
proxy --> set --> trigger
API
- 通过高阶函数,只需要传入深浅层和对应的proxy的get,set处理即可实现4个方法
export function reactive<T = object>(obj: T) {
return createReactiveObject(obj, false, reactiveHandler);
}
// 对象的任何层都只读
export function readonly<T = object>(obj: T) {
return createReactiveObject(obj, true, readonlyHandler);
}
// 对象的浅层响应式,深层不响应
export function shallowReactive(obj) {
return createReactiveObject(obj, false, shallowReactiveHandler);
}
// 对象的浅层只读,深度只读
export function shallowReadonly(obj) {
return createReactiveObject(obj, true, shallowReadonlyHandler);
}
createReactiveObject
- 都是采用proxy代理响应式
- 只能代理对象
- 通过reactiveMap和readonlyMap的防止重复代理
- 使用weakMap的作用可以在target被销毁是,weakMap能够同时垃圾回收
// 缓存已经处理过的对象:在target销毁时能被垃圾回收
const reactiveMap = new WeakMap();
const readonlyMap = new WeakMap();
function createReactiveObject(target, isReadonly, baseHandlers) {
// 只能处理是对象类型
if (!isObject(target)) {
return target;
}
const map = isReadonly ? readonlyMap : reactiveMap;
if (map.has(target)) {
// 返回已经缓存
return map.get(target);
}
const proxy = new Proxy(target, baseHandlers);
map.set(target, proxy);
return proxy;
}
handlers
- 主要是实现4个api不同的get和set
- 封装get和set高阶函数,可以传入不同参数
export const reactiveHandler = {
get: createGet(),
set: createSet(true)
};
export const shallowReactiveHandler = {
get: createGet(false, true),
set: createSet(true)
};
// readonly的设置都返回报错
export const readonlyHandler = {
get: createGet(true),
set: () => {
console.log('error');
}
};
export const shallowReadonlyHandler = {
get: createGet(true, true),
set: () => {
console.log('error');
}
};
createGet
- 创建handler的get
- track的作用是此对象和此属性收集依赖
/**
* createGet
* @param isReadonly 是否只读
* @param shallow 是否浅层
* @returns function
*/
function createGet(isReadonly = false, shallow = false) {
return function (target, key, receiver) {
// 类比 target[key]
const result = Reflect.get(target, key, receiver);
if (!isReadonly) {
// 非只读,需要收集依赖
track(target, key, TrackOptType.GET);
}
if (shallow) {
// 浅层的就结束
return result;
}
// 如果是对象,且非浅层的,还是继续递归调用readonly和reactive
if (isObject(result)) {
return isReadonly ? readonly(result) : reactive(result);
}
// 非对象就直接返回
return result;
};
}
createSet
- 设置会判断操作类型:新增或修改
- 新增:
- 对象:设置的key不是对象本身已有的属性
- 数组:key为索引,且索引值大于数组本身长度
- 修改:新旧值不一样则是修改
- 新增:
- trigger:通知依赖触发更新操作
/**
* 创建set
* @param shallow 是否浅层
* @returns
*/
function createSet(shallow = false) {
return function (target, key, value, receiver) {
// 获取原来值
const oldValue = Reflect.get(target, key);
// 设置后会返回一个是否成功
const result = Reflect.set(target, key, value, receiver);
/**
* 新增
* 数组:并且设置索引,并且设置的索引比当前数组的长度还长
* 对象:设置的key不是对象本身已有的属性
* 修改
* 新旧值不一样则是修改
*/
const hasKey = isArray(target) && isInteger(key)
? Number(key) < target.length - 1
: hasOwn(target, key);
if (!hasKey) {
// 通知:新增操作
trigger(target, TriggerOpt.ADD, key, value);
} else if (hasChanged(oldValue, value)) {
// 通知:修改操作
trigger(target, TriggerOpt.SET, key, value, oldValue);
}
// 返回修改结果
return result;
};
}
了解effect
在了解trigger和track之前,先了解effect
其作用:默认会执行一次,在effect执行函数用到的响应式变量(reactive,readonly,shallowReactive,shallowReadonly,ref,shallowRef包装过)会被收集依赖,当这边变量发生变化是会再次执行此函数
const state = reactive({ hello: 'world', other: 1 })
// 默认会执行此函数fn
effect(()=>{ // fn
// 使用累reactive包装后的变量
app.innerHTML = state.hello
})
setTimeout(() => {
state.hello = 'vue'
// 修改后会默认调用fn函数
}, 1000);
effect:副作用
export function effect(fn, options: EffectOptions = {}) {
// 创建响应式
const effect = createReactiveEffect(fn, options);
// 如果没有设置lazy,会立即执行一次
if (!options.lazy) {
effect();
}
return effect;
}
createReactiveEffect:创建响应式副作用
// 存放当前的effect,方便track将target,key和对应的effect关联起来
let activeEffect;
// 存放一个栈形结构,保证activeEffect指向正确
const effectStack = [];
let uid = 0;
function createReactiveEffect(fn, options) {
const effect = function createEffect() {
// 保证不再重复
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect);
activeEffect = effect;
// 其实还是调用传进来的函数
return fn();
} finally {
// 保证effect永远正确
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
};
effect.id = uid++;
effect.__isEffect = true;
effect.raw = fn;
effect.options = options;
return effect;
}
存在effectStack的原因
effect(()=>{ // effect1
state.name =1
effect(()=>{ // effect2
state.age=2
})
state.address=3 // 此处应该存effect1,如果没有当一个effect执行完后重制为数组最后一个,此时存的就是effect1
})
track: 收集依赖
对象,key和effect需要关联起来
/**
* 收集依赖,将target,key关联起来
* @param target 对象
* @param key 属性
* @param type 类型
* @returns
*/
export function track(target, key, type) {
// 此处的activeEffect是跟effect在同一文件。
// 主要利用js是单线程的机制
// 如果当前使用值不在effect里面使用是不需要收集
if (!activeEffect) {
return;
}
/***
存储的数据结果
Weakmap {
[ target ]: Map {
[ key ] : Set [ effect1, effect2 ]
}
}
*/
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let depMap = depsMap.get(key);
if (!depMap) {
depMap = new Set();
depsMap.set(key, depMap);
}
const dep = depMap.has(activeEffect);
if (!dep) {
depMap.add(activeEffect);
}
}
trigger: 触发更新
- 通过target获取此对象收集的依赖
- 修改数组长度
- 触发收集length的effect
- 触发收集索引大于length的effect
- 修改
- 触发收集对应key的effect
- 如果是数组,且是新增操作
- 触发收集length的effect
/**
* 触发更新
* @param target 对象
* @param type 更新类型
* @param key 属性
* @param newValue 新值
* @param oldValue 旧值
* @returns
*/
export function trigger(target, type, key: string, newValue?, oldValue?) {
// 此对象没被依赖收集就不需要处理
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
// 要执行的effect存到新的集合
const effects = new Set();
const add = (effectToAdd) => {
if (effectToAdd) {
effectToAdd.forEach(effect => {
effects.add(effect);
});
}
};
// 修改数组的长度
if (isArray(target) && key === 'length') {
depsMap.forEach((dep, key) => {
// 更改长度
// length收集的effects要执行
// 大于新设置长度的索引收集的effects也要执行
if (key === 'length' || (isNumber(key) && Number(key) > newValue)) {
add(dep);
}
});
} else {
if (key !== undefined) {
// 修改
add(depsMap.get(key));
}
switch (type) {
case TriggerOpt.ADD:
// 数组:新增操作,length收集的effects要执行
if (isArray(target) && isInteger(key)) {
add(depsMap.get('length'));
}
}
}
// 要执行的effects依次执行
effects.forEach((effect: any) => {
// effect不一定都是立即执行的,可能做一下其他事情,比如computed
if (effect.options?.scheduler) {
effect.options.scheduler(effect);
} else {
effect();
}
});
}
ref
使用
const name = ref('hello')
const age = ref({ num: 10 })
effect(() => {
app.innerHTML = name.value + '---' + age.value.num
})
setTimeout(() => {
name.value = 'world'
}, 1000);
setTimeout(() => {
age.value.num = 20
}, 2000);
特点
- 针对reactive等不支持对象的形式支持
- 支持除了普通类型也是支持引用类型
- 使用
.value
- 使用的Object.defineProperty去实现对value的响应式
ref/shallowRef
// 传进来的是普通值
export function ref(value: string | number | boolean | object) {
return createRef(value);
}
// 传进来的是普通值
export function shallowRef(value) {
return createRef(value, true);
}
createRef
function createRef(value, isShallow = false) {
return new RefImpl(value, isShallow);
}
// 如果是对象则采用reactive
const convert = val => isObject(val) ? reactive(val) : val;
class RefImpl {
// 存放处理过的值
public _value;
// 存放原值
public _rawValue;
private isShallow;
// 增加标识
public _v_isRef = true;
public constructor(value, isShallow) {
// 如果是深度且是对象,只用reactive去处理响应式
this._value = isShallow ? value : convert(value);
// 保存原值
this._rawValue = value;
this.isShallow = isShallow;
}
get value() {
// 收集此对象的 value 字段的依赖
track(this, 'value', TrackOptType.GET);
return this._value;
}
set value(newValue) {
// 判断是否有改变
if (hasChanged(newValue, this._rawValue)) {
this._rawValue = newValue;
// 如果非浅层,则判断是否是对象,如果是对象则进行调用reactive
this._value = this.isShallow ? newValue : convert(newValue);
// 触发此对象 设置 value 字段的更新
trigger(this, TriggerOpt.SET, 'value', newValue, this._value);
}
}
}
toRef
使用
将响应式对象的某个属性转成
ref
const person = reactive({ name: 'allen', age: 10 })
const name = toRef(person, 'name')
effect(() => {
app.innerHTML = name.value
})
setTimeout(() => {
name.value = 'tom'
}, 1000)
实现
- 只是做一个层代理
class ObjectRefImpl {
public _v_isRef = true;
public target;
public key;
constructor(target, key) {
this.target = target;
this.key = key;
}
get value() {
return this.target[this.key];
}
set value(newValue) {
this.target[this.key] = newValue;
}
}
// 传进来的是响应式对象
// 将一个响应式对象的属性变成ref
// 代理了对象的属性
export function toRef(target, key) {
return new ObjectRefImpl(target, key);
}
toRefs
// 传进来的是响应式对象
export function toRefs(target) {
const ret = isArray(target) ? new Array(target.length) : {};
for (const key in target) {
ret[key] = toRef(target, key);
}
return ret;
}
computed
使用
const age = ref(18)
const myAge = computed(() => {
console.log('runner')
return age.value + 10
})
// 第一次获取,会走函数
// runner
console.log(myAge.value) // 28
// 这次computed依赖项没有修改,则不走函数,直接读缓存的值
console.log(myAge.value) // 28
// 修改age,computed依赖项发生改变,但不会立即执行,需要等待下次取值才会走函数
age.value = 10
// 还没更新
// {"_dirty":true,"_value":28}
console.log(JSON.stringify(myAge))
// 因为依赖项发生改变,会重新执行
// runner
// 20
console.log(myAge.value)
computed
- getterOrOptions可以是函数/对象
type Getter<T = unknown> = () => T;
type GetterOption<T = unknown> = {
get: Getter<T>;
set: () => void;
};
type GetterOrOptions<T> = Getter<T> | GetterOption<T>;
export function computed<T = unknown>(getterOrOptions: GetterOrOptions<T>) {
let getter;
let setter;
if (isFunction(getterOrOptions)) {
getter = getterOrOptions;
setter = () => {
console.log('no change');
};
} else if (isObject(getterOrOptions)) {
getter = (getterOrOptions as GetterOption).get;
setter = (getterOrOptions as GetterOption).set;
}
return new ComputedRefImpl(getter, setter);
}
ComputedRefImpl
- computed基于effect
- 通过_dirty是否使用缓存值还是重新计算
- computed的值通过
.value
- computed本身函数默认不执行,只有获取值是去调用
- 但computed依赖项没有发生改变时,不去走计算函数,直接使用缓存的值
- 但依赖项发生改变,不会立即执行。下次获取获取值是才会重新计算
// effect的执行由自己控制什么时候执行
class ComputedRefImpl {
// 需不需要重新计算的标识符
public _dirty = true;
// 存储最新的值
public _value;
public setter: any;
public effect;
constructor(getter, setter) {
this.setter = setter;
this.effect = effect(getter, {
// 不是立即执行
lazy: true,
// 调度
scheduler: () => {
if (!this._dirty) {
// 设置下次get的时候执行重新获取最新的值
// 修改this._dirty,在下次获取时就可以去获取新值
this._dirty = true;
// 触发收集这个依赖的effect
trigger(this, TriggerOpt.SET, 'value');
}
}
});
}
get value() {
if (this._dirty) {
// 缓存起来,不是每个获取都是去走effect的
this._value = this.effect();
this._dirty = false;
}
// 收集依赖
track(this, 'value', TrackOptType.GET);
return this._value;
}
set value(newValue) {
this.setter(newValue);
}
}
runtime-dom
- 将dom操作等平台相关操作作为参数传入给createRenderer,是runtime-core是与平台无关
- 重写mount的原因是进行一些平台相关业务操作
// 渲染用到的方法:
// 跟平台相关的api
// 创建dom,修改dom属性等
const rendererOptions = { ...nodeOps, patchProp };
export function createApp(rootComponent, rootProps = null) {
const app: any = createRenderer(rendererOptions).createApp(rootComponent, rootProps);
const { mount } = app;
// 重写mount,执行一些跟平台相关的操作
app.mount = function (container) {
// 获取真实dom
container = nodeOps.querySelector(container);
// 清空
container.innerHTML = '';
// 才是真正的挂载
mount(container);
};
return app;
}
// 导出runtime-core
export * from '@vue/runtime-core';
runtime-core
结合runtime-dom调用
createRenderer(rendererOptions).createApp(rootComponent, rootProps);
createRenderer
runtime-core/renderer.ts
// 创建渲染器
export function createRenderer(rendererOptions) {
// 核心代码: 在此函数内能获取到平台相关的操作方法,减少传递
// 。。。
// 渲染
const render = (vnode, container) => {
patch(null, vnode, container);
};
return {
// 调用createAppApi返回createApp,同时传递render
createApp: createAppApi(render)
};
}
createAppApi
runtime-core/createAppApi
export function createAppApi(render) {
// 根组件,根组件的props
return function createApp(rootComponent, rootProps) {
const app = {
_props: rootProps,
_component: rootComponent,
_container: null,
mount(container) {
// 创建虚拟节点
const vnode = createVNode(rootComponent, rootProps);
// 调用render
render(vnode, container);
app._container = container;
}
};
return app;
};
}
createVNode 创建VNode
// 目前只考虑元素和组件
export function createVNode(type, props, children = null) {
// 标识:为后期patch比较提供便利
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: (isObject(type) ? ShapeFlags.STATEFUL_COMPONENT : 0);
const vnode = {
// 是否是vnode
_v_isVnode: true,
type,
props,
children,
key: props && props.key,
// 真实的节点
el: null,
// 实例
component: null,
shapeFlag
};
// 根据children标识
normalizeChildren(vnode, children);
return vnode;
}
// 设置children的类型
function normalizeChildren(vnode, children) {
let type = 0;
if (children === null) { }
else if (isArray(children)) {
type = ShapeFlags.ARRAY_CHILDREN;
} else {
type = ShapeFlags.TEXT_CHILDREN;
}
vnode.shapeFlag |= type;
}
render 渲染
// 调用patch
patch(null, vnode, container);
patch: 核心中的核心
挂载和更新组件/元素都走此方法
- n1为null是代表走挂载流程
- 否则走更新流程
- 如果新旧VNode的类型不一样,则把旧VNode干掉,重新走挂载流程
- 根据VNode不同类型做不同操作
/**
* 打补丁:挂载和更新的功能
* @param n1 旧节点
* @param n2 新节点
* @param container 挂载容器
* @param anchor 当前元素的参照物
*/
const patch = (n1, n2, container, anchor = null) => {
const { shapeFlag, type } = n2;
// 如果有旧vnode,且不是相同类型:直接把旧节点干掉,走挂载的流程
if (n1 && !isSameVNodeType(n1, n2)) {
// 删除以前,换成新的
anchor = hostNextSibling(n1);
unmount(n1);
n1 = null;
}
// 根据不同类型,做不同操作
switch (type) {
case TEXT:
// 文本节点,特殊处理
processText(n1, n2, container);
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
// 元素
processElement(n1, n2, container, anchor);
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
// 组件
processComponent(n1, n2, container);
}
}
};
processText:处理文本节点
- n1为null时,走挂载流程
- n1不为null时,走更新流程
- hostCreateText/hostInsert/hostSetText等方法则是调用createRenderer传递的参数
const processText = (n1, n2, container) => {
if (n1 === null) {
// 挂载
// 新建文本节点
n2.el = hostCreateText(n2.children);
// 插入父容器
hostInsert(n2.el, container);
} else {
// 更新:复用旧节点的元素
const el = n2.el = n1.el;
// 比较新旧文本节点的children是否一致
if (n2.children !== n1.children) {
// 如果不同只重新设置文本内容
hostSetText(el, n2.children);
}
}
};
processElement:处理Element元素
const processElement = (n1, n2, container, anchor = null) => {
if (n1 === null) {
// 元素挂载
mountElement(n2, container, anchor);
} else {
// 元素更新
patchElement(n1, n2, container, anchor);
}
};
mountElement:挂载元素
const mountElement = (vnode, container, anchor = null) => {
const { props, shapeFlag, type, children } = vnode;
// 创建元素
const el = (vnode.el = hostCreateElement(type));
// 处理属性
if (props) {
for (const key in props) {
hostPatchProp(el, key, null, props[key]);
}
}
// 处理元素children
// 1. children为文本节点
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
// 文本内容
hostSetElementText(el, children);
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// 2. 元素数组
mountChildren(children, el);
}
// 挂载到容器上
hostInsert(el, container, anchor);
};
mountChildren: 挂载子节点(文本/Element/组件节点)
递归调用patch
// 挂载子节点
const mountChildren = (children, el) => {
for (let i = 0; i < children.length; i++) {
// 标准化子VNode
// 如果是字符串:则生成文本节点
const child = normalizeVNode(children[i]);
// 调用patch进行分别挂载
patch(null, child, el);
}
};
patchElement: 处理Element更新
const patchElement = (n1, n2, container, anchor = null) => {
// 复用dom节点
const el = (n2.el = n1.el);
// 更新属性
patchProps(n1.props, n2.props, el);
// 更新子节点
patchChildren(n1, n2, el);
};