V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yukinotech
V2EX  ›  React

react immutable 相关困惑

  •  
  •   yukinotech · 2022-04-22 03:59:02 +08:00 · 2093 次点击
    这是一个创建于 972 天前的主题,其中的信息可能已经有所发展或是发生改变。

    主要困惑的内容在两个 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 } ]
    
    第 1 条附言  ·  2022-04-22 15:33:59 +08:00

    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
    
    第 2 条附言  ·  2022-04-22 16:02:14 +08:00
    再补一条
    class 中会有这个问题的是 PureComponent ,FC 中对标的应该就是 React.memo(),这下就对上了
    chnwillliu
        1
    chnwillliu  
       2022-04-22 04:12:20 +08:00 via Android   ❤️ 1
    是否依赖 state 的 immutability 完全取决于你的 state 在 view 中具体是如何使用的。

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

    说实话我觉得没必要纠结这一点,setState 之后就变成了新的 state
    yukinotech
        8
    yukinotech  
    OP
       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
        9
    yukinotech  
    OP
       2022-04-22 14:57:23 +08:00
    @jjwjiang 纠结主要有 2 点。1. state.xx = xx 这个操作本身,会不会导致 bug 2.因为一直有人把 react 和 immutable 绑定到一起,所以我想弄清楚对 react 的 render 来说 immutable 是必须的吗,还是只需要保证 newState!==oldState 即可
    iseki
        10
    iseki  
       2022-04-22 15:33:53 +08:00 via Android
    你把 mutable 的东西当 prop 传给 children 时就容易出问题了,想想下 children 里面 useEffect(...,[theObject])
    yukinotech
        11
    yukinotech  
    OP
       2022-04-22 15:44:43 +08:00
    @iseki 嗯,有道理,所以还是和实际场景相关。这种场景 useEffect 依赖项可能就不会写 theObject ,而是 theObject.xxx ,或者上层组件 setState 的时候,自己{...一遍},再传给子组件。

    但是确实和 1 ,2 楼说的,Pure Component 才会引起渲染的问题,附言 1 里补充了一下
    AyaseEri
        12
    AyaseEri  
       2022-04-22 19:41:23 +08:00
    核心: React 做 diff 依赖数据变化(基本类型数据变化、对象引用变化)。
    所以你的数据是不是 immutable ,React 其实不关心,但是你做出不改引用改内容的行为时,React 不对更新做保证。你可能被网上某些文章给误导了。
    gogogo1203
        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 .
    xiaoming1992
        14
    xiaoming1992  
       2022-04-22 20:28:06 +08:00
    如果是我的话,就不会用 personList ,而是 setPersonList((prevList) => xxx)
    ChefIsAwesome
        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 圈子开始流行起来。
    yukinotech
        16
    yukinotech  
    OP
       2022-04-22 20:59:48 +08:00
    @ChefIsAwesome 这也是困惑的点,因为网上现在一谈 react 就喜欢讲 immutable ,但是我自己实践就觉得 react 本身是不依赖 immutable 那些东西的,所以发个贴,问一下是不是我想错了。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2895 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 14:40 · PVG 22:40 · LAX 06:40 · JFK 09:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.