先来看看什么叫可变状态
let objA = { name: '爸爸' };
let objB = objA;
objB.name = '妈妈';
console.log(objA.name); // objA 的name也变成了妈妈
代码解析: 我们明明只修改代码objB的name,发现objA也发生了改变。这个就是可变状态。
可变状态的缺点:
- 间接修改了其他对象的值,在复杂的代码逻辑中,会造成代码隐患。
解决方案:
- 深度拷贝
- JSON.parse(JSON.stringify(objA)) 。 我常用的方案
- immutable-js
不可变状态immer.js
案例分析:
const {produce} = require('immer')
// const {produce} = require('./immer')
let baseState = {
home:{name:'爸爸',arr:[1]},
b:{}
}
let nextState = produce(baseState, (draft) => {
draft.home.name = '妈妈';
})
console.log(baseState.home === nextState.home); // false
console.log(baseState.home.arr === nextState.home.arr) // true
console.log(baseState.b === nextState.b); // true
特点:
- 子节点被修改,那么父节点,或者父父节点被重新创建。
- 兄弟节点或者其他与修改节点无关的节点,兄弟节点是被复用的节点,不会重新创建。
其形成特点如下:
immer.js 原理讲解
前置知识proxy的讲解
let baseState = {
home:{name:'爸爸',arr:[1]},
b:{}
}
let p = new Proxy(baseState,{
get(tartget, key) {
console.log('get', tartget, key)
return tartget[key]
},
set(tartget,key,value){
console.log('set', tartget, key, value)
tartget[key]=value
}
})
p.home = 'mama1'
// 打印结果
// set { home: { name: '爸爸', arr: [ 1 ] }, b: {} } home mama1
p.home.name = 'mama1'
// 打印结果
// get { home: { name: '爸爸', arr: [ 1 ] }, b: {} } home
结论:
- 赋值的时候会触发,set的方法。
- proxy 不会深度代码,只代理一层,如果想深度代理的话,需要递归代理。
immer 源码实现流程
- 辅助方法。
var is = {
isObject: (val) => Object.prototype.toString.call(val) === '[object Object]',
isArray: (val) => Array.isArray(val),
isFunction: (val) => typeof val === 'function'
}
- 对象进行浅复制。用于产生草稿对象。
// 浅复制
function createDraftState(baseState) {
if (is.isArray(baseState)) {
return [...baseState];
} else if (is.isObject(baseState)) {
return Object.assign({}, baseState);
} else {
return baseState;
}
}
- 对象进行代理,并增加标识位,用户判断是否发生修改。
var internal = {
draftState: createDraftState(baseState),
mutated: false
}
- set 方法实现
function set(target, key, value) {
internal.mutated = true;
let {draftState}= internal;
draftState[key] = value;
valueChange && valueChange()
return true
}
- get 方法实现
function get(target, key) {
if (key === INTERNAL) {
return internal
}
const value = target[key];
if (is.isObject(value) || is.isArray(value)) {
if (key in keyToProxy){
return keyToProxy[key];
}else{
keyToProxy[key] = toProxy(value,()=>{
internal.mutated = true;
const proxyOfChild = keyToProxy[key];
var {draftState} = proxyOfChild[INTERNAL];
internal.draftState[key] = draftState;
valueChange && valueChange()
})
return keyToProxy[key];
}
}
return internal.mutated ? internal.draftState[key] : baseState[key];
}
思路梳理完毕,接下来组装所有代码。
var is = {
isObject: (val) => Object.prototype.toString.call(val) === '[object Object]',
isArray: (val) => Array.isArray(val),
isFunction: (val) => typeof val === 'function'
}
// 定义一个唯一变量
const INTERNAL = Symbol('INTERNAL');
// 对外暴露的核心方法
function produce(baseState, producer) {
// 代理传入的初始对象
const proxy = toProxy(baseState);
producer(proxy) // 将代理对象
const internal = proxy[INTERNAL];
return internal.mutated ? internal.draftState : baseState;
}
function toProxy(baseState,valueChange) {
var keyToProxy = {};
var internal = {
draftState: createDraftState(baseState), // 创建一个草稿对象
mutated: false // 标记位,标记节点是否修改了。
}
return new Proxy(baseState, {
get(target, key) {
if (key === INTERNAL) { // 用户获取草稿对象
return internal
}
const value = target[key];
// 判断是不是,对象或者数组,如果是,则进行递归代理
if (is.isObject(value) || is.isArray(value)) {
// 如果已经代理过了,就不进行代理了,直接返回。
if (key in keyToProxy){
return keyToProxy[key];
}else{
// 递归代理
keyToProxy[key] = toProxy(value,()=>{//子节点发生变化,通知父节点的回掉函数
// 子节点,改变了,父节点也要标记改变了。
internal.mutated = true;
const proxyOfChild = keyToProxy[key];
// 拿到子节点的草稿对象,
var {draftState} = proxyOfChild[INTERNAL];
// 父节点执行,字节点的草稿对象。
internal.draftState[key] = draftState;
// 然后在通知父节点。
valueChange && valueChange()
})
return keyToProxy[key];
}
}
// 如果没有发生修改,则返回原始对象,修改了,则返回草稿对象
return internal.mutated ? internal.draftState[key] : baseState[key];
},
set(target, key, value) {
// 进行复制操作毕竟发生了修改。
internal.mutated = true;
// 如果发生了修改,不能修改原始对象,只能修改草稿对象。
let {draftState}= internal;
// 修改草稿对象
draftState[key] = value;
//通知父节点进行父节点变更。
valueChange && valueChange()
return true
}
})
}
// 浅复制
function createDraftState(baseState) {
if (is.isArray(baseState)) {
return [...baseState];
} else if (is.isObject(baseState)) {
return Object.assign({}, baseState);
} else {
return baseState;
}
}
module.exports = {
produce
}
代码已经实现,但是感觉不是特别好理解。我这里举一个生活动比较常见的例子。希望能帮助大家理解。
小A,有一个父亲,有一个爷爷。 有一天小b告诉小a,你不是你爸爸亲生的(相当于修改了小a)。这个时候小a去找它新的爸爸,找到新的爸爸后,新爸爸带它去见新的页面,重新组建了一个新的家庭关系。(上诉代码就是这个实现逻辑)。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!