[译] Immer 下的不可突变数据和 React 的 setState

Immer 下的不可突变数据和 React 的 setState

Immer 是为 JavaScript 不可突变性打造的一个很是棒的全新库。以前像 Immutable.js 这样的库,它须要引入操做你数据的全部新方法。前端

它很不错,可是须要复杂的适配器并在 JSON 和 不可突变 之间来回转换,以便在须要时与其余库一块儿使用。react

Immer 简化了这一点,你能够像往常同样使用数据和 JavaScript 对象。这意味着当你须要考虑性能而且想知道数据什么时候发生了变动,你可使用三个等号来作严格的全等检查以及证实数据的确发生了变动。android

你对 shouldComponentUpdate 的调用再也不须要使用双等或者全等去遍历整个数据并进行比较。ios

文章截图

注:此处为截图,原文为视频,建议看英文原文。git

对象展开运算符

在最新版本的 JavaScript 中,许多开发者依赖对象展开运算符来实现不可突变性。例如,你能够展开以前的对象并覆盖特定的属性,或者增长新的属性。它会在底层使用 Object.assign 并返回一个新对象。github

const prevObject = {
  id: "12345",
  name: "Jason",
};

const newObject = {
  ...prevObject,
  name: "Jason Brown",
};
复制代码

咱们的 newObject 如今会是一个彻底不一样的对象,因此任何全等判断(prevObject === newObject)将会返回 false。因此它彻底建立了一个新对象。name 属性也再也不是 Jason 而是会变成 Jason Brown,并且因为咱们没有对 id 属性进行任何操做,因此它会保持不变。后端

这也适用于 React,由于 React 只会合并最外层的属性,因此当你在 state 中有嵌套的对象时,你须要对以前的对象进行展开操做和更新。bash

让咱们看一个例子。能够看到咱们有两个嵌套的计数器,可是咱们只想更新其中的一个而不影响另外一个。函数

import React, { Component } from "react";

class App extends Component {
  state = {
    count: {
      counter: 0,
      otherCounter: 5,
    },
  };

  render() {
    return <div className="App">{this.state.count.counter}</div>;
  }
}

export default App;
复制代码

下一步在 componentDidMount 钩子中,咱们将设置一个间隔定时器来更新咱们嵌套的计数器。不过,咱们但愿保持 otherCounter 的值不变。因此,咱们须要使用对象展开运算符来把它从之前嵌套的 state 中带过来。post

componentDidMount() {
    setInterval(() => {
      this.setState(state => {
        return {
          count: {
            ...state.count,
            counter: state.count.counter + 1,
          },
        };
      });
    }, 1000);
  }
复制代码

这在 React 中是一个很是常见的场景。并且,若是你的数据是嵌套的很是深的,当你须要展开多个层级时,它会增长复杂性。

Immer Produce 基础

Immer 仍然容许使用突变(直接改变值)而彻底无需担忧如何去管理展开的层级,或者哪些数据咱们触及过以及须要维持不可突变性。

让咱们设置一个场景:你向计数器传递一个值来进行递增,与此同时,咱们还有一个 user 对象是不须要被触及的。

这里咱们渲染咱们的应用并传递增量值。

ReactDOM.render(<App increaseCount={5} />, document.getElementById("root"));
复制代码
import React, { Component } from "react";

class App extends Component {
  state = {
    count: {
      counter: 0,
    },
    user: {
      name: "Jason Brown",
    },
  };

  componentDidMount() {
    setInterval(() => {}, 1000);
  }

  render() {
    return <div className="App">{this.state.count.counter}</div>;
  }
}

export default App;
复制代码

咱们像以前那样设置了咱们的应用,如今咱们有一个 user 对象和一个嵌套的计数器。

咱们将导入 immer 并把它的默认值赋给 produce 变量。在给定当前 state 时,它将帮助咱们建立下一个 state。

import produce from "immer";
复制代码

接下来,咱们将建立一个叫作 counter 的函数,它接收 state 和 props 做为参数,这样咱们就能够读取当前的计数,并基于 increaseCount 属性更新咱们的下一次计数。

const counter = (state, props) => {};
复制代码

Immer 的 produce 方法接收 state 做为第一个参数,以及一个为下一个状态改变数据的函数做为第二个参数。

produce(state, draft => {
  draft.count.counter += props.increaseCount;
});
复制代码

若是你如今把他们放在一块儿。咱们就能够建立计数器函数,它接收 state 和 props 并调用 produce 函数。而后咱们按照对下一次状态指望的样子去改变 draft。Immer 的 produce 函数将为咱们建立一个新的不可突变状态。

const counter = (state, props) => {
  return produce(state, draft => {
    draft.count.counter += props.increaseCount;
  });
};
复制代码

咱们更新后的间隔计数器函数大概会是这样。

componentDidMount() {
    setInterval(() => {
      const nextState = counter(this.state, this.props);
      this.setState(nextState);
    }, 1000);
  }
复制代码

不过咱们只是触及过 countcounter,咱们的 user 对象上又发生了什么呢?对象的引用是否也发生了变化?答案是否认的。Immer 确切的知道哪些数据是被触及过的。因此,若是咱们在组件更新以后进行一次全等检测,咱们能够看到 state 中以前的 user 对象和以后的 user 对象是彻底相同的。

componentDidUpdate(prevProps, prevState) {
    console.log(this.state.user === prevState.user); // Logs true
  }
复制代码

当你考虑性能而使用 shouldComponentUpdate 时,或者相似于 React Native 中FlatList 那样,须要一种简单的方式来知道某一行是否已经更新时,这就很是的重要。

Immer 柯里化

Immer 可使得操做更加简单。若是它发现你传递的第一个参数是一个函数而不是一个对象,它就会为你建立一个柯里化的函数。所以,produce 函数返回另外一个函数而不是一个新对象。

当它被调用时,它会把第一个参数用做你但愿改变的 state,而后还会传递任何其余参数。

所以,它不只仅是能够建立一个计数器函数的(工厂)函数,就连 props 也会被代理。

const counter = produce((draft, props) => {
  draft.count.counter += props.increaseCount;
});
复制代码

得益于 produce 返回一个函数,咱们能够直接把它传递给 setState,由于 setState 有接收函数做为参数的能力。当你正在引用以前的状态时,你应该使用函数化的 setState(函数做为第一个参数)。在咱们的场景中,咱们须要引用以前的计数来把它增长到新的计数。它将传递当前的 state 和 props 做为参数,这也正是设置咱们的 counter 函数所须要的。

因此咱们的间隔计数器仅须要 this.setState 接收 counter 函数便可。

componentDidMount() {
    setInterval(() => {
      this.setState(counter);
    }, 1000);
  }
复制代码

总结

这显然是一我的为的示例,但具备普遍的现实应用。能够轻松比较仅更新了单个字段的一长串列表数据。大型嵌套表单只须要更新触及过的特定部分。

你再也不须要作浅比对或者深比对,并且你如今能够作全等检查来准确的知道你的数据是否发生了变化,然后决定是否须要从新渲染。


Originally published at Code.

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索