Vue源码学习(3) -- 响应式系统(2)
前言
本文为Vue3源码学习第三篇。,上文学习了reactive
以及简易的effect
。本文主要学习readonly
,isReactive
,isReadonly
, isProxy
,shallowReactive
,shallowReadonly
等功能的实现
1. Readonly
1.1 文档详情
查看Vue3文档响应式Api可得
Type
function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>>
复制代码
Details
readonly
是深层的代理,任何被访问的属性都是只读的。他们和reactive
相同,接收一个对象(响应式或者纯对象)或者ref
并返回原始值的只读代理
Example
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// works for reactivity tracking
console.log(copy.count)
})
//变更original会触发copy的监听器
original.count++
// 变更copy将会失败并导致警告
copy.count++ // warning!
复制代码
1.2、源码实现
在实现readonly
之前,首先进行测试代码的编写
//readonly.spec.ts
describe("happy path readonly",()=>{
it("should make nested values readonly",()=>{
const original = {
foo: 1,
}
const wrapped = readonly(original)
expect(wrapped).not.toBe(original)
expect(wrapped.foo).toBe(1)
});
it("should call console.warn when set", () => {
console.warn = jest.fn();
const user = readonly({
age: 10,
});
//readonly响应式对象的property是只读的
user.age = 11;
//修改readonly响应式对象的property的值会调用console.warn发出警告
expect(console.warn).toHaveBeenCalled();
});
})
复制代码
readonly
与reactive
的区别在于readonly
的对象是只读的。
所以在readonly
的实现上与reactive
的实现基本一致,区别在于是否进行依赖收集。readonly
是只读的,可以不进行依赖收集
代码实现
//reactive.ts
export function readonly(obj){
//返回Porxy实例
return new Porxy (obj,{
//对原始对象的get进行代理
get(target,key){
const res =Reflect.get(target,key)
return res
},
set(target,key,newVal){
//readonly是只读的,set会报错
console.warn(`"${String(key)}"set失败,因为${target}是readonly类型`)
return true;
}
})
复制代码
执行yarn test readonly
命令运行readonly
的测试,可以看到测试通过,这样就完成了readonly
最基础的实现
重构的思考
reactive
和 readonly
在代码层面的实现过程中有大量的重复代码,例如均返回Proxy
对象,getter
和setter
方法也有重复代码。所以对重复代码进行抽离,提高代码可读性
创建baseHandlers.ts
文件,用于构造Proxy
的getter
,setter
,相关的代码并抽离公共代码和使用全局变量进行缓存
//baseHadnler.ts
//生成getter方法工具函数
function createGetter(isReadonly = false){
return function(target,key){
const res = Reflect.get(target,key)
//判断是否为reactive/readonly,reactive进行依赖收集,readonly不需要收集
if(!isReadonly){
//track(target,key)
}
return res
}
//生成setter方法工具函数
function createSetter(){
return function (target,key,newVal){
const res = Reflect.set(target,key,newVal)
//触发依赖
trigger(target,key)
return res
}
//reactive
const reactiveGet = createGetter()
const reactiveSet = createSetter()
export const reactiveHandlers = {
get:reactiveGet,
set:reactiveSet
}
//readonly
const readonlyGet = createGetter(true)
export const readonlyHandlers = {
get:readonlyGet,
set(target,key,newVal){
console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal);
return true;
}
}
复制代码
对reactive.ts
文件进行重构
//reactive.ts
//工具函数
function creactReactiveObject(target,mutableHandlers){
if (!isObject(target)) {
console.warn(`target${target}必须是一个对象`);
return target;
}
return new Proxy(obj,mutabHandlers)
}
//reactive
export function reactive(obj){
return createReactiveObject(obj,reactiveHandlers)
}
//readonly
export function readonly(obj){
return createReactiveObject(obj,readonlyHandlers)
}
复制代码
重构完成
2、isReactive、isReadonly、isProxy
2.1、isReactive
2.1.1、文档详情
Type
function isReactive(value: unknown): boolean
复制代码
Detials
检查对象是否由reactive
或则shallowReactive
创建的proxy
代理
Example
import { reactive, isReactive } from 'vue'
export default {
setup() {
const state = reactive({
name: 'John'
})
console.log(isReactive(state)) // -> true
}
}
复制代码
如果代理是readonly
创建的,但包裹了reactive
创建的另一个代理,他也会返回true
import { reactive, isReactive, readonly } from 'vue'
export default {
setup() {
const state = reactive({
name: 'John'
})
// 从普通对象创建的只读 proxy
const plain = readonly({
name: 'Mary'
})
console.log(isReactive(plain)) // -> false
// 从响应式 proxy 创建的只读 proxy
const stateCopy = readonly(state)
console.log(isReactive(stateCopy)) // -> true
}
}
复制代码
执行yarn test reactive
命令运行reactive
的测试,可以看到测试通过,这样就实现了isReactive
。
2.1.2、源码实现
在实现isReactive
之前,首先进行测试代码的编写
// reactive.ts
describe("reactive",()=>{
it("happy path",()=>{
const original = { foo: 1 };
const observed = reactive(original);
//isReactive
expect(isReactive(observed)).toBe(true);
expect(isReactive(original)).toBe(false);
})
})
复制代码
在设计getter的时候 传入了一个isRaedonly的参数 默认false,我们利用obj[__v_isReactive]
,调用getter
函数,在getter
函数中进行判断,如果纯如的key ===__v_isReactive
,则表明是reactive
返回!isReadonly
代码实现
// basehandlers.ts
function createGetter(isReadonly = false){
return function (target,key){
//当property名为__v_isReactive时,表明正在调用 isReactive,直接返回 !isReadonly
if(key==="__v_isReactive"){
return !isReadonly
}
/*其他代码*/
}
复制代码
reactive.ts
// reactive.ts
exprot function isReactive(obj){
return !! obj["__v_isReactive"]
}
复制代码
2.2、isReadonly
2.2.1 文档详情
isReadonly
的效果跟isReactive
类似:判断代理对象是不是readonly
类型。
type
function isReadonly(value: unknown): boolean
复制代码
2.2.2、源码实现
测试代码
//readonly.spec.ts
describe('readonly', () => {
it('should make values readonly', () => {
const original = { foo: 1 }
const wrapped = readonly(original)
console.warn = jest.fn()
expect(wrapped).not.toBe(original)
// 对 readonly 响应式对象调用 isReactive 返回 false
expect(isReactive(wrapped)).toBe(false)
// 对 readonly 响应式对象调用 isReadonly 返回 true
expect(isReadonly(wrapped)).toBe(true)
// 对普通对象调用 isReadonly 返回 false
expect(isReadonly(original)).toBe(false)
expect(wrapped.foo).toBe(1)
wrapped.foo = 2
expect(wrapped.foo).toBe(1)
expect(console.warn).toBeCalled()
})
})
复制代码
代码实现
// basehandlers.ts
function createGetter(isReadonly = false){
return function (target,key){
/*其他代码*/
//当property名为__v_isReadonly时,表明正在调用 isReadonly,直接返回 isReadonly
if(key==="__v_isReadonly"){
return isReadonly
}
/*其他代码*/
}
复制代码
// reactive.ts
exprot function isReactive(obj){
return !! obj["__v_isReadonly"]
}
复制代码
执行yarn test readonly
命令运行readonly
的测试,可以看到测试通过,这样就实现了isReadonly
。
2.3、 isProxy
2.3.1、 文档详情
检查对象是否是由reactive
或readonly
创建的 proxy。 type
function isProxy(value: unknown): boolean
复制代码
2.3.2、 源码实现
测试代码
//reactive.spec.ts
describe("reactive", () => {
it("happy path", () => {
const original = { foo: 1 };
const observed = reactive(original);
/*其他代码*/
//isProxy
expect(isProxy(observed)).toBe(true);
expect(isProxy(original)).toBe(false);
});
})
复制代码
//readonly.spec.ts
describe("readonly", () => {
it("should make nested values readonly", () => {
/*其他代码*/
//isProxy
expect(isProxy(wrapped)).toBe(true);
expect(isProxy(original)).toBe(false);
}
})
})
复制代码
满足isReactive和isReadonly任意一个就是proxy
代码实现
//reactive.ts
export function isProxy(obj){
return isReactive(obj) === true || isReadonly(obj) === true
}
复制代码
分别执行yarn test reactive
和yarn test readonly
命令运行reactive
和readonly
的测试,可以看到测试均通过,这样就实现了isProxy
。
2.4、 代码重构
isReactive
和isReadonly
的实现中使用到的特殊 property 的名为字符串,需要对其进行优化,创建并导出枚举类型ReactiveFlags
用于保存这两个字符串:
//baseHandler.ts
//用于保存 isReactive 和 isReadonly 中使用的特殊 property 的名
export cosnt enum ReactvieFlags {
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly'
}
function createGetter(isReadonly = false) {
return function (target, key) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
}
/* 其他代码 */
}
}
复制代码
//reactive.ts
export function isReactive(value): boolean {
return !!value[ReactiveFlags.IS_REACTIVE]
}
export function isReadonly(value): boolean {
return !!value[ReactiveFlags.IS_READONLY]
}
复制代码
3、 完善reactive
和readonly
——响应式嵌套
reactive
和readonly
的响应式转换是“深层”的,会影响所有嵌套的 property,即嵌套的 property 也应该是响应式的。
分别在reactive.spec.ts
和readonly.spec.ts
测试文件中添加代码
// reactive.spec.ts
describe("reactive", () => {
it("nested reactive", () => {
const original = {
nested: {
foo: 1,
},
arr: [{ bar: 2 }],
};
const observed = reactive(original);
// 嵌套对象是响应式的
expect(isReactive(observed.nested)).toBe(true)
expect(isReactive(observed.arr)).toBe(true)
expect(isReactive(observed.arr[0])).toBe(true)
});
})
复制代码
//readonly.spec.ts
describe("readonly", () => {
it("should make nested values readonly", () => {
const original = { foo: 1, bar: { baz: 2 } };
const wrapped = readonly(original);
expect(wrapped).not.toBe(original);
expect(wrapped.foo).toBe(1);
//isReadonly
expect(isReadonly(wrapped)).toBe(true);
expect(isReadonly(original)).toBe(false);
//isProxy
expect(isProxy(wrapped)).toBe(true);
expect(isProxy(original)).toBe(false);
//嵌套
expect(isReadonly(wrapped.bar)).toBe(true);
});
})
复制代码
代码实现
//baseHandlers.ts
//工具函数,用于判读是对象或者数组
function isObject(value){
return typeof value === "object" && res !== null
}
export function createGetter(isReadonly = false){
return function (traget,key){
/*其他代码*/
const res = Reflect.get(target,key)
//判断res是否是对象或数组
if(isObject(res)){
return isReadonly ? readonly(res) :reactive(res)
}
/*其他代码*/
}
复制代码
分别执行yarn test reactive
和yarn test readonly
命令运行reactive
和readonly
的测试,可以看到测试均通过,这样就进一步完善了reactive
和readonly
的实现。
4、shallowReadonly
和 shallowReacitve
的实现
文档描述
shallowReative
type
function shallowReactive<T extends object>(target: T): T
复制代码
details 与reactive
不同,shallowReactive
只追踪自身property的响应性,但不执行嵌套对象的生层次的响应式转换(暴漏原始值)
exmpale
const state = shallowReactive({
foo:1,
nested:{
bar:2
}
})
//改变state本身的property是响应式的
state.foo ++ //响应式
//但是不转换嵌套对象的响应式
isReactive(state,nested) //false
state.nested.bar ++ //非响应式
复制代码
与reactive
不同,任何使用ref
的 property 都不会被代理自动解包
shallowReadonly
type
function shallowReadonly<T extends object>(target: T): Readonly<T>
复制代码
details 与readonly
不同,shallowReadonly
之追踪自身的 property 我i只读,但不执行嵌套对象的深度只读转换(暴露原始值)
exmaple
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2
}
})
// 改变 state 本身的 property 将失败
state.foo++
// ...但适用于嵌套对象
isReadonly(state.nested) // false
state.nested.bar++ // 适用
复制代码
源码实现
shallowReactive
编写测试代码
//shallowReactive.spec.ts
describe("shallwoReactive",()=>{
test("should not make non-reactive properties reactive",()=>{
const state = shallowReactive({
foo:1,
nested:{
bar :2
}
})
expect(isReactive(state)).toBe(true)
expect(isReactive(state.nested)).toBe(false)
});
test("happy shallwoReactive",()=>{
const state = shallowReactive({
foo:1,
nested:{
bar:2
}
})
let nextState;
effect(()=>{
nextState = props.foo+ 1
})
expect(nextProps).toBe(2)
state.foo ++
expect(nextPorps.toBe(3))
})
})
复制代码
功能实现 shallowReactive
与reactive
区别在于对于内层嵌套对象响应式不同,shallowReactive
内层不是响应式对象。
对createGetter
传入一个isShallow
参数,为true
不进行内部嵌套对象的响应式操作,为false
则执行
//baseHanlders.ts
function createGetter(isReadonly=false,isShallwo = false){
return function (target,key){
/*其他代码*/
const res = Reflect.get(target,key)
if(!isReadonly){
track(target,key)
}
//是否为shallow
if(shallow){
return res
}
if (isObject(res)) {
//递归调用
// isReadonly == true -> 表明是readonly对象 :是reactive对象
return isReadonly ? readonly(res) : reactive(res);
}
return res
}
// shallowReactive
const shallowReactiveGet = functon createGetter(false,true)
const shallowReactiveSet = function createSetter()
export const shallowReactiveHandlers = {
get: shallowReactiveGet,
set,
};
复制代码
//reactive.ts
export function shallowReactive(obj){
return createReactiveObject(obj,shallowReactiveHandlers)
复制代码
shallowReadonly
编写测试代码
//shallowReadonly.spec.ts
describe("shallowReadonly", () => {
test("should not make non-reactive properties reactive", () => {
const props = shallowReadonly({ n: { foo: 1 } });
expect(isReadonly(props)).toBe(true);
expect(isReadonly(props.n)).toBe(false);
});
it("should call console.warn when set", () => {
console.warn = jest.fn();
const user = shallowReadonly({
age: 10,
});
user.age = 11;
expect(console.warn).toHaveBeenCalled();
});
});
复制代码
代码实现 shallowReadonly
功能的实现,其实在shallowReactive
实现过程中已经实现,区别在于createGetter
函数的参数不同
// baseHandlers.ts
//shallowReadonly
const shallowReadonlyGet = funtion createGettr
r(true,true)
export const shallowReadonlyHandlers = {
get: shallowReadonlyGetter,
set: function (target, key, newVal) {
console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal);
return true;
},
};
复制代码
//reactive.ts
//shallowReadonly
export function shallowReadonly(obj){
return createReactiveObject(obj,shallowReadonlyHandlers)
复制代码
分别执行yarn test shallowRreactive
和yarn test shallowReadonly
命令运行shallowRreactive
和shallowReadonly
的测试,可以看到测试均通过,这样就实现了shallowRreactive
和shallowReadonly
。