主要困惑的内容在两个 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 1
是否依赖 state 的 immutability 完全取决于你的 state 在 view 中具体是如何使用的。
你写一个 pure component 接受 person 对象作为 props, 然后放到你的 personList.map 里 render 试试。 |
2
Yvette 2022-04-22 07:57:43 +08:00 1
没看过源码,但按我的理解 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 1
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 那些东西的,所以发个贴,问一下是不是我想错了。。
|