06-mini-vue readonly
·
实现 readonly 功能
- 实现 readonly 的代码很简单
export function readonly (raw) {
return new Proxy(raw, {
get(target,key) {
const res = Reflect.get(target,key)
return res
},
set() {
return true // ✅
}
})
}
- 根据 TDD(Test Driven Development) 的思维,我们进行优化,抽离 get
// 仅可以抽离 reactive 的 getter
function get(target,key) { // ✅
const res = Reflect.get(target,key)
// 依赖收集
track(target,key)
return res
}
- 上面的代码仅可以抽离 reactive 里面的 get , 而 readonly 里面的 get 是不需要进行 track 的,因为它本来也不会更改也不会有 trigger
所以我们想到可以使用高阶函数,外面的函数返回里面的函数,外面函数的参数控制是否使用track,里面的函数接收 target,key, value
function createGetter(isReadonly = false) { // ✅
return function get (target, key, value) {
if(!isReadonly) { // ✅
track(target, key)
}
return Reflect.get(target, key)
}
}
export function reactive (raw) {
return new Proxy(raw, {
get: createGetter(), // ✅
set(target, key, value) {
const res = Reflect.set(target, key, value)
trigger(target, key)
return res
}
})
}
export function readonly (raw) {
return new Proxy(raw, {
get: createGetter( true ), // ✅
set() {
return true
}
})
}
- 我们把 reactive 和 readonly 的 get 方法抽离成了 createGetter, 我们还可以把 reactive 的 set 方法抽离出一样
的形式
function createSetter() { // ✅
return function set(target, key, value) {
const res = Reflect.set(target, key, value)
trigger(target, key)
return res
}
}
export function reactive (raw) {
return new Proxy(raw, {
get: createGetter(),
set: createSetter() // ✅
})
}
- 我们观察发现,proxy 里面的 handler 也可以进行抽离,于是我们把这一块抽离到 baseHandlers.ts 文件
// baseHandlers.ts
import { track, trigger } from "./effect"
function createGetter(isReadonly = false) { // ✅
return function (target, key, value) {
if(!isReadonly) {
track(target, key)
}
return Reflect.get(target, key)
}
}
function createSetter() { // ✅
return function set(target, key, value) {
const res = Reflect.set(target, key, value)
trigger(target, key)
return res
}
}
export const mutableHandlers = { // ✅
get: createGetter(),
set: createSetter()
}
export const readonlyHandlers = { // ✅
get: createGetter( true ),
set() {
return true
}
}
// reactive.ts
import { mutableHandlers, readonlyHandlers } from "./baseHandlers"
export function reactive (raw) {
return new Proxy(raw, mutableHandlers) // ✅
}
export function readonly (raw) {
return new Proxy(raw, readonlyHandlers) // ✅
}
- 后来我们发现 new Proxy 也可以进行抽离出公共的部分,抽离以后更易读, 可复用
export function reactive (raw) {
return createActiveObject(raw, mutableHandlers) // ✅
}
export function readonly (raw) {
return createActiveObject(raw, readonlyHandlers) // ✅
}
function createActiveObject(raw, baseHandlers) { // ✅
return new Proxy(raw, baseHandlers)
}
- 针对 baseHandlers.ts 文件中的,get set 我们没有必要每次拦截都进行创建,可以使用缓存的技术
export const mutableHandlers = {
get: createGetter(),
set: createSetter()
}
// 修改后,每次初始化创建一次,以后拦截都会进行复用
const get = createGetter() // ✅
const set = createSetter() // ✅
export const mutableHandlers = { // ✅
get,
set
}
- 实现 readonly 的警告功能
export const readonlyHandlers = {
get: readonlyGet,
set(target, key, value) {
console.warn(`${key}不可以修改,因为 ${target}是readonly`) // ✅
return true
}
}
- 测试
import { isReadonly, reactive, readonly } from "../reactive"
describe('readonly',()=>{
it('readonly',()=>{
// not set
const original = reactive({ foo: 1, bar: { baz: 2 }})
const wrapped = readonly(original)
expect(wrapped).not.toBe(original)
expect(wrapped.foo).toBe(1)
})
it('warn when call set',()=>{
// 验证 console.warn 有没有被触发
// mock
console.warn = jest.fn()
const user = readonly({
age: 10
})
user.age = 11
expect(console.warn).toHaveBeenCalled()
})
test('readonly nested',()=>{
const original = { foo: 1, bar: { baz: 2 }}
const wrapped = readonly(original)
expect(wrapped).not.toBe(original)
expect(isReadonly(wrapped)).toBe(true)
expect(isReadonly(original)).toBe(false)
expect(isReadonly(wrapped.bar)).toBe(true)
expect(isReadonly(original.bar)).toBe(false)
expect(wrapped.foo).toBe(1)
})
})
更多推荐
所有评论(0)