在react开发过程中,大家应该对state的处理感受颇深。就算使用了hooks进行开发,如果数据层级较多,分成多个useState使用起来会显得很累赘,并且代码量巨大。但是使用一个state的时候,数据层级过深又会导致一个不小心由于引用关系视图不能进行更新。
虽然react官方已经有了useReducer来在某些场景替代useState,但是开发使用redux的时候依然会有类似的烦恼,于是就有了react-addons-update。
(来自react官方的示例)
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
不过react-addons-update已经被弃用,随之用来取代的是immutability-helper。两个库大体差距并不大,但是后者会多一个很重要的扩展功能,这也是immutability-helper更加强大的地方。(这里我贴上immutability-helper的库地址,同时由于这个库之前没有特别好的翻译,这里是本人翻译的文档地址)
import update, { extend } from 'immutability-helper';
extend('$addtax', function(tax, original) {
return original + (tax * original);
});
const state = { price: 123 };
const withTax = update(state, {
price: {$addtax: 0.8},
});
assert(JSON.stringify(withTax) === JSON.stringify({ price: 221.4 }));
于是,我们就可以用immutability-helper来改写我们原本看起来很繁琐的state。
我们在这里用一个比较直观易懂的例子,我们来写一个只通过redux处理的登录注册逻辑。
首先我们确定一下我们数据结构, 大体是这个样子:
const database = {
loginUser: { // 这里是登录用户信息,id和用户名
userId: number,
name: string
},
userInfo: [ // 这里是所有用户信息,也就是相当于用户表
{
id: number,
username: string,
password: string
},
...
]
}
于是,不使用immutability-helper的reducer就可以写成这个亚子:
const user = (
// 初始state
state = {
loginUser: {
userId: -1, // 当无用户登录时,默认id为-1
name: '' // 当无用户登录时,默认用户名为空
},
userInfo: [] // 没有用户注册时,userInfo为空
}
},
action
) => {
switch(action.type) {
case 'LOGIN': // 登录action
return { // 纯函数原则,返回新的state
...state, // 展开state,保留其他数据
loginUser: action.loginUser // 通过action拿到登录信息
};
case 'REGISTER':
return {
...state,
userInfo: [
...state.userInfo,
action.userInfo
]
};
case 'CHANGE_USER_INFO':
return {
...state,
userInfo: userInfo.map(item => { // 通过map方法来修改数据
if (item.id === action.id) {
return {
...item,
...action.changeUserInfo
}
} else {
return item
}
})
};
default:
return state;
}
};
此处就只用这么三个小实例来说明,可以发现,在type为LOGIN的时候其复杂程度还不算很高,但当数据层级逐渐变深的时候,就可以发现需要每一次都展开数据保留其完整性,而且还需要一层一层向下查找,引用一句immutability-helper文档中的原话:
This is not only annoying, but also provides a large surface area for bugs. 他不仅很烦人,同时还为出现bug提供了大面积的代码。
因此,我们是时候来正式看看immutability-helper到底为我们做了什么了。
由于初始化等与不使用的时候并无差别,所以我们索性这次拆开分别看几种type并只看返回的部分。
// LOGIN
update(state, { // update会直接返回纯新引用的数据
loginUser: {
userId: {
$set: action.userId
},
name: {
$set: action.name
}
}
/*
当然这种其实也是可以的
loginUser: {
$set: action.loginUser
}
*/
})
可以看到对于这种层级比较浅的数据使用起来并无太大差距,可以说只是为我们省去了展开state这么一个操作。
那我们继续看REGISTER:
update(state, {
userInfo: {
$push: [
action.userInfo
]
}
})
很明显,在REGISTER这里就已经很明显要简洁的多了,我们省去了展开state,还省去了展开state.userInfo。只需要一个简单的$push,就可以直接将数据加进去,如果这还不够爽,那我们继续看CHANGE_USER_INFO:
update(state, {
userInfo: {
[state.userInfo.findIndex(item => item.id === action.id)]: {
$set: action.userInfo
}
}
})
或者
update(state, {
userInfo: userInfo => {
return update(userInfo, {
[userInfo.findIndex(item => item.id === action.id)]: {
$set: action.userInfo
}
});
}
})
或者...
你有很多方式可以实现,这里只不过用了两种不太一样的方式来实现。我相信不论从代码可阅读性以及代码量等方面,都已经有了明显的优势。我认为无需再继续举例,通过上面的例子已经足够体现数据层级再深入的时候,写起来是会多么舒爽了。更何况,我这里还没有说明扩展指令的操作,就已经这么舒爽了。
所以,不要再为了处理state而头疼,快来投入更简单更快捷的immutability-helper的怀抱吧(滑稽