51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

学习React:认知React中的不可变性和状态

React强制实施状态不可变性,这意味着不能直接更改有状态值。 这实际上可以释放,并帮助消除整个类 bug。 亚伦·鲍威尔 (@斜杠) 谈论不可变性、为什么很重要,以及如何在React中管理它。

不变性对应的英文单词是 Immutability,它不是 React 中的概念,但它对写一个正确的 React 程序至关重要。不考虑生命周期函数 shouldComponentUpdate 对组件重新渲染造成的影响,当组件的 state 发生变化时,组件将被重新渲染。

你可曾遇到过这样一种情况------你自认为改变了 state 的值,但是组件没有重新渲染?本文将揭露其中的缘由并介绍怎么编写符合 Immutability 原则的代码。

{#_label1}

什么是 immutable {#heading-1}

immutable 指不发生变化,这意味着创建新的值去替换原来的值,而非改变原来的值,与 immutable 相反的概念是 mutable,下面用代码演示 mutable 和 immutable。

let user = {name: 'Bela'}
user.name = 'CI' // 改变user的name属性
user.age = 12 // 给user新增age属性
user = {name: 'Bela'} // 用新的值替换原来的值

上述代码第 2 行和第 3 行都属于改变原来的值,只有第四行是新建一个对象,用新对象替换原来的对象。下面调用函数进一步说明 immutable 与 mutable。

function addAgeMutable(user: User) {
    user.age = 12 // 修改原来的
    return user
}
function addAgeImmutable(user: User) {
    const other = Object.assign({}, user) // 创建新的
    other.age = 12
    return other
}
let user1Original = {name: 'Bella'}
let user1New = addAgeMutable(user1Original) // 用 mutable 的方式
let user2Original = {name: 'Bella'}
let user2New = addAgeImmutable(user2Original) // 用 immutable 的方式
console.log('user1Original 与 user1New 相同吗?',user1Original === user1New) // true
console.log('user2Original 与 user2New 相同吗?',user2Original === user2New) // false

上述 addAgeMutable 函数直接在入参上新增 age 属性,但 addAgeImmutable 函数没有改变入参,而是新建了一个对象,在新对象上添加age属性。

总结一下,immutable 是指不修改原来的;mutable 是指在原来的基础上修改。通过 mutable 的方式修改变量会导致修改前后变量的引用不变。某些操作数组的方法会让原来的数组发生变化,比如:push/pop/shift/unshift/splice,这些方法是 mutable 的,而有一些操作数组的方法不会让原来的数组发生变化,而是返回一个新组件,比如:slice/concat,这些函数是 immutable 的。字符串、布尔值和数值操作都不改变原来的值,而是创建一个新的值。

{#_label2}

React 与 Immutability {#heading-2}

在 React 程序中,组件的 state 必须具备不变性,接下来演示修改state的正确与不正确的方式。为了说明state的组成结构,先定义个State接口,代码如下:

interface State {
  user: User
  hobbies: string[]
  time: string
}

从上述接口可以看出,组件有三个状态,分别为:user、hobbies 和 time,它们的数据类型各不相同。

{#_lab2_2_0}

修改 state 的错误案例 {#heading-3}

下面罗列的案例试图用 mutable 的方式修改 state,这些做法全部是错误的。

// 案例一
this.state.user.age = 13
// 案例二
this.setState({
    user: Object.assign(this.state.user, {age: 13})
})
// 案例三
this.setState({
    hobbies: this.state.hobbies.reverse(),
})
// 案例四
this.state.hobbies.length = 0
this.setState({
    hobbies: this.state.hobbies,
})
  • 案例一: 直接修改 user 的内部结构,修改前后 user 的引用不变。

  • 案例二: 错误使用 Object.assign,Object.assign 将第二个参数的属性合并到第一个参数上,然后将第一个参数返回,这意味着案例二还是修改了user的内部结构,修改前后user的引用不变。

  • 案例三: 使用reverse将数组翻转,它翻转的是原数组,翻转前后数据的引用不变。

  • 案例四: 修改hobbies的长度,修改前后hobbies的引用一样。

上述四个案例都不符合数据一旦创建就不发生变化的原则,由于调用了 setState 方法,所以对于用 React.Component 创建的组件而言,不会发生故障,对于用 React.PureComponent 创建的组件,会引发故障,即:界面不更新。

{#_lab2_2_1}

修改 state 的正确案例 {#heading-4}

下面罗列的案例与错误案例一一对应,它们通过 immutable 的方式修改 state。

// 案例一
this.setState({
    user: {...this.state.user, age: 13}
})
// 案例二
this.setState({
    user: Object.assign({},this.state.user, {age: 13})
})
// 案例三
this.setState({
    hobbies: [...this.state.hobbies].reverse()
})
// 案例四
this.setState({
    hobbies: []
})

上述案例都是新建一个值,用新的值替换原来的值,符合数据一旦创建就不发生变化的原则。

{#_label3}

总结 {#heading-5}

在 react 应用中,更新 state 必须满足 Immutability 原则,因为 React.memo、PureComponent shouldComponentUpdate 和 React Hooks 通过浅比较确定 state 是否发生变更,如果变更 state 的方式不满足 Immutability 原则,它们会认为 state 的值没有变化。

在更新 state 并重新渲染时,React 会将类组件的 this.setState 与函数组件的 useState、useReducer hooks 区别对待。在函数组件中,React 要求所有 hooks 更新状态必须返回一个新的引用作为状态值,如果 React 发现状态更新来自 hook,它会检查该值的引用是否与以前的引用相同,如果相同,它将退出该函数组件的渲染流程,最终用户界面不更新。使用 this.setState 更新类的 state,React 并不关心状态的引用是否变化,只要在类组件中调用 this.setState,该组件一定会重新渲染。


赞(0)
未经允许不得转载:工具盒子 » 学习React:认知React中的不可变性和状态