react immutable 相关困惑

2022-04-22 03:59:02 +08:00
 yukinotech

主要困惑的内容在两个 onClick 事件里,一个对于 state 是 immutable 的,另一个不是,但是效果是相同的。疑惑的点在于

  1. 两种写法是否都可以,第一种写法会不会导致 bug(比如 react18 cm 模式下)
  2. 如果两个写法都可以,是不是说明 react 本身并不强依赖 state 的 immutable ,只需要让 newState!==oldState ,也就是引用改变(非 js 基础类型),然后 newState 丢到 setState 里面去 react 都能正常 render
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 } ]
1944 次点击
所在节点    React
16 条回复
chnwillliu
2022-04-22 04:12:20 +08:00
是否依赖 state 的 immutability 完全取决于你的 state 在 view 中具体是如何使用的。

你写一个 pure component 接受 person 对象作为 props, 然后放到你的 personList.map 里 render 试试。
Yvette
2022-04-22 07:57:43 +08:00
没看过源码,但按我的理解 setState 只是去 trigger 了一次 re-render ,只有 Pure Component 才会需要用到 immutability ,可以试试楼上说的验证一下。
gogogo1203
2022-04-22 08:24:54 +08:00
react 做 shallow comparison
let newState = [...personList] , 你以为你造出了一个 new object, 其实 spreading 是 shallow copy 。nested object 里面的还是同一个 referrence.
gogogo1203
2022-04-22 08:25:54 +08:00
如果是 nested object, 能用 immer 就用 immer 。 简单,不容易出现奇奇怪怪的 bug.
yazoox
2022-04-22 08:49:24 +08:00
@gogogo1203 immer 会帮助深拷贝 nested object?
gogogo1203
2022-04-22 09:18:25 +08:00
@yazoox 用 Immer 你就不用担心这问题了。还有个老办法就是 obj = JSON.parse(JSON.stringify(o)) , lodash 也有深拷贝的 func, lodash 还有 isEqual deep compare.
jjwjiang
2022-04-22 10:56:36 +08:00
用 hooks 模式的时候,setState 以外的方法能够修改到真正的 state 吗?

说实话我觉得没必要纠结这一点,setState 之后就变成了新的 state
yukinotech
2022-04-22 14:53:50 +08:00
@gogogo1203 你说的道理是对的。Shallow comparison 就是用 === 比较,对象就比较引用,所以浅复制一个 obj ,虽然不能改变 obj 属性里面的引用,但是浅复制后的新 obj 引用就不是原来的了,丢入 setState 能触发新的 render 。这个过程和对象的属性是同一个 referrence 没什么关系吧。而且 onClick1 的注释也说得比较清楚了,不然为啥说 newState[0].name = "ddddddd"就是修改 personList[0].name
yukinotech
2022-04-22 14:57:23 +08:00
@jjwjiang 纠结主要有 2 点。1. state.xx = xx 这个操作本身,会不会导致 bug 2.因为一直有人把 react 和 immutable 绑定到一起,所以我想弄清楚对 react 的 render 来说 immutable 是必须的吗,还是只需要保证 newState!==oldState 即可
iseki
2022-04-22 15:33:53 +08:00
你把 mutable 的东西当 prop 传给 children 时就容易出问题了,想想下 children 里面 useEffect(...,[theObject])
yukinotech
2022-04-22 15:44:43 +08:00
@iseki 嗯,有道理,所以还是和实际场景相关。这种场景 useEffect 依赖项可能就不会写 theObject ,而是 theObject.xxx ,或者上层组件 setState 的时候,自己{...一遍},再传给子组件。

但是确实和 1 ,2 楼说的,Pure Component 才会引起渲染的问题,附言 1 里补充了一下
AyaseEri
2022-04-22 19:41:23 +08:00
核心: React 做 diff 依赖数据变化(基本类型数据变化、对象引用变化)。
所以你的数据是不是 immutable ,React 其实不关心,但是你做出不改引用改内容的行为时,React 不对更新做保证。你可能被网上某些文章给误导了。
gogogo1203
2022-04-22 19:45:35 +08:00
@yukinotech deeply nested obj ......................................... [aa:{ bb:[cc,ee]}] 你用 spreading 是浅拷贝,bb 还是原来的 obj. 你可能是没有碰过那些奇奇怪怪的 bug. reference vs value .
xiaoming1992
2022-04-22 20:28:06 +08:00
如果是我的话,就不会用 personList ,而是 setPersonList((prevList) => xxx)
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 圈子开始流行起来。
yukinotech
2022-04-22 20:59:48 +08:00
@ChefIsAwesome 这也是困惑的点,因为网上现在一谈 react 就喜欢讲 immutable ,但是我自己实践就觉得 react 本身是不依赖 immutable 那些东西的,所以发个贴,问一下是不是我想错了。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/848476

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX