主要困惑的内容在两个 onClick 事件里,一个对于 state 是 immutable 的,另一个不是,但是效果是相同的。疑惑的点在于
import { useState } from "react" function App() { let [personList, setPersonList] = useState([{ name: "jack", age: 18 }]) const onClick1 = () => { let newState = [...personList] newState.push({ name: "kk", age: 1 }) // 这里相当于直接修改了 personList[0].name 上的值,对于 personList 这个 state 没有做到 immutable newState[0].name = "ddddddd" setPersonList(newState) } const onClick2 = () => { setPersonList([ { // 这里先复制一遍 personList[0],再复制,personList 这个 state 在过程中是只读的,是 immutable ...personList[0], name: "ddddddd", }, ...personList.slice(1), { name: "kk", age: 1 }, ]) } return ( <div className="App"> <button OnClick={onClick1}>add1</button> <button OnClick={onClick2}>add2</button> {personList.map((item, index) => { return <div key={index}>{item.name}</div> })} </div> ) } export default App
补充一下上述语境中 immutable 的定义,以常见的 immutable 库 immer 为例,可以看到执行函数,返回 new1 后,old 的值是不变的,这样可以认为 old 是 immutable 的
import { produce } from "immer" const old = [{ name: "jack", age: 18 }] const new1 = produce(old, (state) => { state[0].name = "ddddddd" state.push({ name: "kk", age: 1 }) }) console.log("原始值", old) // 原始值 [ { name: 'jack', age: 18 } ] console.log("新值", new1) // 新值 [ { name: 'ddddddd', age: 18 }, { name: 'kk', age: 1 } ]
1,2楼的回复应该是对的,我这补一下示例。Person1是PureComponent,Person是fc component,当直接传p.person时,按示例中的用法,p.person的引用对于2个props是没变的,PureComponent在这种情况下就不会刷新值。
这一方面说明确实和实际的用法有关,另一方面我觉得也可以说明react本身和immutable是没啥关系的
import React, { useState } from 'react' class Person1 extends React.PureComponent { constructor(props) { super(props) } render() { return <div>{this.props.person.name}</div> } } function Person({ person }) { return <div>{person.name}</div> } function App() { let [p, setP] = useState({ person: { name: 'jack', age: 18 }, }) const onClick1 = () => { let newState = { ...p } newState.person.name = 'kk' setP(newState) } return ( <div className="App"> <button OnClick={onClick1}>add1</button> {/* <button OnClick={onClick2}>add2</button> */} <Person person={p.person} /> <Person1 person={p.person} /> </div> ) } export default App
![]() | 1 chnwillliu 2022-04-22 04:12:20 +08:00 via Android ![]() 是否依赖 state 的 immutability 完全取决于你的 state 在 view 中具体是如何使用的。 你写一个 pure component 接受 person 对象作为 props, 然后放到你的 personList.map 里 render 试试。 |
![]() | 2 Yvette 2022-04-22 07:57:43 +08:00 ![]() 没看过源码,但按我的理解 setState 只是去 trigger 了一次 re-render ,只有 Pure Component 才会需要用到 immutability ,可以试试楼上说的验证一下。 |
![]() | 3 gogogo1203 2022-04-22 08:24:54 +08:00 react 做 shallow comparison let newState = [...personList] , 你以为你造出了一个 new object, 其实 spreading 是 shallow copy 。nested object 里面的还是同一个 referrence. |
![]() | 4 gogogo1203 2022-04-22 08:25:54 +08:00 如果是 nested object, 能用 immer 就用 immer 。 简单,不容易出现奇奇怪怪的 bug. |
![]() | 5 yazoox 2022-04-22 08:49:24 +08:00 @gogogo1203 immer 会帮助深拷贝 nested object? |
![]() | 6 gogogo1203 2022-04-22 09:18:25 +08:00 @yazoox 用 Immer 你就不用担心这问题了。还有个老办法就是 obj = JSON.parse(JSON.stringify(o)) , lodash 也有深拷贝的 func, lodash 还有 isEqual deep compare. |
7 jjwjiang 2022-04-22 10:56:36 +08:00 用 hooks 模式的时候,setState 以外的方法能够修改到真正的 state 吗? 说实话我觉得没必要纠结这一点,setState 之后就变成了新的 state |
8 yukinotech OP @gogogo1203 你说的道理是对的。Shallow comparison 就是用 === 比较,对象就比较引用,所以浅复制一个 obj ,虽然不能改变 obj 属性里面的引用,但是浅复制后的新 obj 引用就不是原来的了,丢入 setState 能触发新的 render 。这个过程和对象的属性是同一个 referrence 没什么关系吧。而且 onClick1 的注释也说得比较清楚了,不然为啥说 newState[0].name = "ddddddd"就是修改 personList[0].name |
9 yukinotech OP @jjwjiang 纠结主要有 2 点。1. state.xx = xx 这个操作本身,会不会导致 bug 2.因为一直有人把 react 和 immutable 绑定到一起,所以我想弄清楚对 react 的 render 来说 immutable 是必须的吗,还是只需要保证 newState!==oldState 即可 |
10 iseki 2022-04-22 15:33:53 +08:00 via Android 你把 mutable 的东西当 prop 传给 children 时就容易出问题了,想想下 children 里面 useEffect(...,[theObject]) |
11 yukinotech OP @iseki 嗯,有道理,所以还是和实际场景相关。这种场景 useEffect 依赖项可能就不会写 theObject ,而是 theObject.xxx ,或者上层组件 setState 的时候,自己{...一遍},再传给子组件。 但是确实和 1 ,2 楼说的,Pure Component 才会引起渲染的问题,附言 1 里补充了一下 |
12 AyaseEri 2022-04-22 19:41:23 +08:00 核心: React 做 diff 依赖数据变化(基本类型数据变化、对象引用变化)。 所以你的数据是不是 immutable ,React 其实不关心,但是你做出不改引用改内容的行为时,React 不对更新做保证。你可能被网上某些文章给误导了。 |
![]() | 13 gogogo1203 2022-04-22 19:45:35 +08:00 @yukinotech deeply nested obj ......................................... [aa:{ bb:[cc,ee]}] 你用 spreading 是浅拷贝,bb 还是原来的 obj. 你可能是没有碰过那些奇奇怪怪的 bug. reference vs value . |
![]() | 14 xiaoming1992 2022-04-22 20:28:06 +08:00 如果是我的话,就不会用 personList ,而是 setPersonList((prevList) => xxx) |
![]() | 15 ChefIsAwesome 2022-04-22 20:44:40 +08:00 ![]() react 不来就不依赖 immutable 那些东西。 正常的机制就是你 setState 之后,react 新旧 state 做一遍 deep diff 。react 有 shouldComponentUpdate 这个 api ,让你可以自己处理 diff 机制。 当初 react 面世之后,Clojure 有个基于 react ,叫 om 的项目。因为 Clojure 是函数式编程,数据是 immutable 的,在 shouldComponentUpdate 里比较两个 immutable 就是 === 这么判断,这就大大提高了运行速度。接下来就出现了各种用 js 实现 immutable 的库。函数式编程也在 js 圈子开始流行起来。 |
16 yukinotech OP @ChefIsAwesome 这也是困惑的点,因为网上现在一谈 react 就喜欢讲 immutable ,但是我自己实践就觉得 react 本身是不依赖 immutable 那些东西的,所以发个贴,问一下是不是我想错了。。 |