react hook
使用的是Object.is
来进行的比较,这个比较是一个浅比较。这也意味着对于一个对象,直接修改对象里面的值,是不会触发组件的重渲染的。但是如果我们使用深clone,又会引起性能问题,那怎么办呢?
import React, { useEffect, useMemo, useState } from 'react';
import update from 'immutability-helper';
import { Button } from 'antd';
import Child from './Child';
import { cloneDeep } from 'lodash';
const Test = () => {
const [data, setData] = useState({
info: {
name: 'tom',
age: 12,
},
score: {
exam1: [99, 98, 89],
exam2: [78, 85, 33],
},
});
function handleClick() {
// TODO: 点击按钮,更新第一个考试的英语成绩
}
const examStr = useMemo(() => {
const exam1 = data.score.exam1;
return (
<div>
<p>语文: {exam1[0]}</p>
<p>数学: {exam1[1]}</p>
<p>英语: {exam1[2]}</p>
</div>
);
}, [data.score.exam1]);
return (
<div>
<Button onClick={handleClick}>更新数据</Button>
<div>{examStr}</div>
<Child child={data.info}></Child>
</div>
);
};
export default Test;
来看上面的代码,我们需要点击按钮的时候更新exam1
数组的第三项数据,这时候应该如何实现呢?
data.score.exam1.push(100);
setData(data);
data.score.exam1[2] = Math.random() * 100;
setData({
...data,
});
import { cloneDeep } from 'lodash';
data.score.exam1[2] = Math.random() * 100;
setData(cloneDeep(data));
我们通过对data进行深复制,返回一个新的对象,通过这种方式是可以实现数据更新成功,但是也会引发一个新的问题,就是本来我们只需要更新exam1
,但是缺导致info
也变成了一个新的对象,引起Child
组件的重新渲染。
data.score.exam1[2] = Math.random() * 100;
setData({
...data,
score: {
...data.score,
exam1: [...data.score.exam1],
},
});
为了更新一个数组的某一项的值,我们尝试了上面的四种方式,其中有两种是成功的,但是只有最后一种方式是比较好的,使用最后一种在更新数据的同时,尽可能的降低了对其他数据引用的破坏,但是我们示例数据只有三层,在代码中我们使用了三次...
扩展运算符,如果层级更深,这样更新就会变得特别麻烦了。
如何能达到即优雅又高效的去变更数据,我这里使用到了immutability-helper
setData((data) => {
return update(data, {
score: {
exam1: {
2: {
$set: Math.random() * 10,
},
},
},
});
});
使用immutability-helper
可以按需去调整数据,而且它只会去调整需要修改的数据,复用未修改的数据,和实现方式四的效果是一致的。
const [data, setData] = useState<any[]>([1,2]);
setData((data) => {
// data 值为 [1,2,3,4]
return update(data, {
// $push的参数必须是一个数组
$push: [3, 4],
});
});
const [data, setData] = useState<any[]>([3,4]);
setData((data) => {
// data 值为 [1,2,3,4]
return update(data, {
// $unshift的参数必须是一个数组
$unshift: [1,2],
});
});
const [data, setData] = useState<any[]>([3,4]);
setData((data) => {
// data 值为 [3,6,5]
return update(data, {
// $splice的参数必须是一个二维数组
$splice: [[1,1,6,5]],
});
});
const [data, setData] = useState<any[]>([
{
user: [
{
name: 'superfeng',
},
],
},
]);
setData((data) => {
//修改name的值
return update(data, {
0: {
user: {
0: {
name: {
$set: '冯超',
},
},
},
},
});
});
const [data, setData] = useState<any[]>([
{
user: [
{
name: 'superfeng',
sex: '男',
},
],
},
]);
// 将sex从对象中删除
setData((data) => {
return update(data, {
0: {
user: {
0: {
$unset: ['sex'],
},
},
},
});
});
const [data, setData] = useState<any[]>([
{
user: [
{
name: 'superfeng',
sex: '男',
},
],
},
]);
setData((data) => {
return update(data, {
0: {
user: {
0: {
$merge: {
age: 16,
},
},
},
},
});
});
const [data, setData] = useState<any[]>([
{
user: [
{
name: 'superfeng',
sex: '男',
},
],
},
]);
setData((data) => {
return update(data, {
0: {
user: {
0: {
$apply: (user: any) => {
return Object.assign({}, user, { age: 15 });
},
},
},
},
});
});
虽然使用immutabilty-helper
可以按需更新数据,但是对于层级比较多的数据来说,书写也是很麻烦的,有没有更好的方式去更新数据呢?我封装了一个这样的函数
const toImmutability = (path: string, value: any) => {
const arrReg = /\[(\d+)\]/;
const keys = path.split('.').filter((item) => item);
const result = {} as any;
let current = result;
const len = keys.length;
keys.forEach((key: string, index: number) => {
const matches = key.match(arrReg);
if (matches && matches.length) {
const idx = parseInt(matches[1]);
current[idx] = index === len - 1 ? value : {};
current = current[idx];
} else {
current[key] = index === len - 1 ? value : {};
current = current[key];
}
});
return result;
};
使用这个函数,我们就可以通过下面的方式来更新数据了
setData((data) => {
return update(
data,
toImmutability('[0].user.[0].name', {
$set: '冯超',
})
);
});
toImmutability
函数的第一个参数是一个字符串,这个字符串是由需要更新数据的路径拼接而成的,数组使用[index]
来表示,然后在函数内解析这个字符串,构建需要更新的对象就可以了