const [user, setUser] = useState( {id: '123', name: '张三'} );
如上方示例,在react引用state时,无论state的值是什么类型,都要将存在state的js值视为只读的,?也就是不允许修改,如果直接修改了state的值(例如user.name = '李四'),这个操作就制造了一个mutation。在react使用state时,要遵循state的值是不可变的原则!
直接修改state对象的某个值,并没有调用state的更新函数setXXX,因此react无法识别修改的内容,不会触发重新渲染。为了避免制造mutation而造成的无法重新渲染问题,需要调用setXXX替换原有的值,包括对象。例如setUser({ id: '123',?name: '李四' })。
通过setXXX替换对象需要将对象原有的所有属性都写一遍,如果对象的属性特别多,就可以使用展开语法来实现,代码更简洁,例如:setUser({ ...user, name: '李四' })
理解:...user语法复制了user对象的所有属性,但是name属性使用 '李四' 覆盖
嵌套对象理解:
对象实际没有嵌套之说,他们都是互相独立的,所谓的对象嵌套,只不过是属性的指向而已。
例如:const user = { name: '张三' , size: {?weight: 78, height: 180 } }
实际上看到的是两个不同的对象:user = { name: '张三' , size:?sizeObj },sizeObj = {?weight: 78, height: 180 }
复制多层嵌套对象:
按照上面展开语法的方式,复制对象可能要写成:
setUser({
???????? ...user, //?复制其他字段
? ? ? ? ?name: '李四', //?替换name字段
? ? ? ? size: {
? ? ? ? ? ? ? ? ... user.size, //?复制其他size被嵌套对象的字段
? ? ? ? ? ? ? ? height: 182 //?替换height字段
????????}
})
如果嵌套较深或者特别多的情况下,代码看起来也不简洁,这时候,就可以使用Immer库。
使用方式:
(1)安装依赖:npm install use-immer
(2)引入import { useImmer } from 'use-immer',替换掉import { useState } from 'react'
(3)自定义Immer(替换原自定义state):const [user, updateUser] = useImmer( {id: '123', name: '张三', size: { weight: 78, height: 180} } );
(4)修改对象属性写法:
updateUser(draft => {
? ? ? ? draft.name = '李四';
? ? ? ? draft.size.height = 182;
})
理解:
Immer提供了一个draft特殊类型的对象,会记录你用它进行的操作。这样用Immer看似直接修改了对象的属性,实际上Immer 会弄清楚draft对象的哪些部分被改变了,并会依照你的修改创建出一个全新的对象。
state数组类型值的mutation:
state更新数组与对象一样,也要视为只读(不建议使用push、pop、splice等会改变原数组的方法),因为数组也是一个对象。
通过展开语法复制新的数组示例:
setUsers(?[
?????? ...users, // 新数组包含原数组的所有元素
????????{ id: 99, name: ‘麻五’} // 并在末尾添加了一个新的元素
?]);
可以配合展开语法修改数组的方法:
以上方法均不改变原数组,返回一个新数组,可以配合展开语法来实现state数组类型的值修改
与对象一样,如果数组内部嵌套了较深层次的对象,更改起来也比较麻烦,通常有两种方式:
(1)调整数据结构,尽量做到扁平,避免嵌套太深
(2)如果涉及业务层面不能修改数据结构,就使用Immer库来修改
Immer库修改示例:
updateUsers(draft => {
const targetUser = draft.find(a => a.id === '123');
targetUser.name = '麻五';
});