一. immer
介绍
immer
是一个高性能更新数据状态的工具库。对于状态有更新部分会创建新的副本对象,然后基于副本对象做数据更新,对于状态没有更新部分则是直接复用。
javascript
import { produce } from 'immer'
const state = {
name: 'zhansgan',
age: 10,
}
const nextState = produce(state, draft => {
draft.name = 'lisi'
})
二. immer
原理
核心原理是使用Proxy
对象,监听get
和set
事件。
当触发get
事件回调时,会创建key
对应value
的proxy
对象。
当触发set
事件回调时,会创建当前state
副本对象,然后更新副本对象的属性值。
2.1 定义produce
- 创建
state
对应的proxy
对象 - 调用
recipe
方法,执行更新逻辑 - 调用
finalize
方法,返回state
副本
javascript
export function produce(base, recipe) {
draft = createProxy(base)
recipe(draft)
return finalize(draft)
}
2.2 定义createProxy
通过Proxy.revocable
方法获取proxy
对象。该方法具体用法参考文档
javascript
// 创建state副本
function prepareCopy(state) {
if (!state.copy_) {
state.copy_ = shallowCopy(state.base_)
}
}
const traps = {
get(state, prop) {
if (prop === DRAFT_STATE) return state
const source = latest(state)
const value = source[prop]
// 如果非对象或者数组类型,直接返回value
if (!isDraftable(value)) return value
const current = state.base_[prop]
if (current === value) {
prepareCopy(state)
return (state.copy_[prop] = createProxy(value))
}
return value
},
set(state, prop, value) {
if (!state.modified_) {
state.modified_ = true
prepareCopy(state)
}
const source = latest(state)
const current = source[prop]
if (Object.is(current, value)) return true
state.copy_[prop] = value
return true
},
}
function createProxy(base) {
let state = {
base_: base, // 原始state
copy_: null, // state副本
draft_: null, // state proxy
revoke_: null,
modified_: false, // 记录当前state会否有修改过
}
const { proxy, revoke } = Proxy.revocable(state, traps)
state.draft_ = proxy
state.revoke_ = revoke
return proxy
}
2.3 定义finalize
返回state
副本对象,需要注意如果副本对象有属性值是proxy
对象,需要递归处理。
javascript
function finalize(draft) {
const state = draft[DRAFT_STATE]
if (!state.modified_) {
state.revoke_()
return state.base_
}
const result = state.copy_
each(result, (key, value) => {
if (isDraft(value)) result[key] = finalize(value)
})
state.revoke_()
return result
}
三. immer
使用准则
3.1 不要使用draft
对象进行查询操作
当有查询诉求时,应当基于原始对象进行查询,而不该使用draft
对象,因为会触发get
事件回调,会创建新的proxy
对象,会造成更多的性能开销。
javascript
const remove = produce((list, element) => {
const index = list.indexOf(element) // this won't work!
const index = original(list).indexOf(element) // do this instead
if (index > -1) list.splice(index, 1)
})
const values = [a, b, c]
remove(values, a)
四. 总结
immer
将原始state
转换成proxy
对象,当修改state
数据时,可以通过set
和get
事件细粒度创建修改数据部分的副本对象,基于副本对象做数据更新,而数据没有变更部分则直接复用,通过这种机制实现数据高效更新。代码仓库
创作不易,如果文章对你有帮助,那点个小小的赞吧,你的支持是我持续更新的动力!