返回介绍

酸爽的 Immutable

发布于 2025-02-17 12:51:31 字数 4754 浏览 0 评论 0 收藏 0

第二个值得重视的点是,Redux 架构下状态并非只是一个普通的 tree,而是一棵不可变的 tree。

回想一下前面我们设计的状态 tree,你可能会觉得可以直接在应用的代码里直接更新 tree:修改映射的值,或删除数组元素等。然而,这并不是 Redux 允许的。

一个 Redux 应用的状态树是不可变的数据结构。这意味着,一旦你得到了一棵状态树,它就不会在改变了。任何用户行为改变应用状态,你都会获取一棵映射应用改变后新状态的完整状态树。

这说明任何连续的状态(改变前后)都被分别存储在独立的两棵树。你通过调用一个函数来从一种状态转入下一个状态。

这么做好在哪呢?第一,用户通常想一个 undo 功能,当你误操作导致破坏了应用状态后,你往往想退回到应用的历史状态,而单一的状态 tree 让该需求变得廉价,你只需要简单保存上一个状态 tree 的数据即可。你也可以序列化 tree 并存储起来以供将来重放,这对 debug 很有帮助的。

抛开其它的特性不谈,不可变数据至少会让你的代码变得简单,这非常重要。你可以用纯函数来进行编程:接受参数数据,返回数据,其它啥都不做。这种函数拥有可预见性,你可以多次调用它,只要参数一致,它总返回相同的结果(冪等性)。测试将变的容易,你不需要在测试前创建太多的准备,仅仅是传入参数和返回值。

不可变数据结构是我们创建应用状态的基础,让我们花点时间来写一些测试项来保证它的正常工作。

为了更了解不可变性,我们来看一个十分简单的数据结构:假设我们有一个计数应用,它只包含一个计数器变量,该变量会从 0 增加到 1,增加到 2,增加到 3,以此类推。

如果用不可变数据来设计这个计数器变量,则每当计数器自增,我们不是去改变变量本身。你可以想象成该计数器变量没有“setters”方法,你不能执行 42.setValue(43)

每当变化发生,我们将获得一个新的变量,它的值是之前的那个变量的值加 1 等到的。我们可以为此写一个纯函数,它接受一个参数代表当前的状态,并返回一个值表示新的状态。记住,调用它并会修改传入参数的值。这里看一下函数实现和测试代码:

//test/immutable_spec.js

import {expect} from 'chai';

describe('immutability', () => {

      describe('a number', () => {

        function increment(currentState) {
              return currentState + 1;
        }

        it('is immutable', () => {
              let state = 42;
              let nextState = increment(state);

              expect(nextState).to.equal(43);
              expect(state).to.equal(42);
        });

      });
});

可以看到当 increment 调用后 state 并没有被修改,这是因为 Numbers 是不可变的。

我们接下来要做的是让各种数据结构都不可变,而不仅仅是一个整数。

利用 Immutable 提供的 Lists ,我们可以假设我们的应用拥有一个电影列表的状态,并且有一个操作用来向当前列表中添加新电影,新列表数据是添加前的列表数据和新增的电影条目合并后的结果,注意,添加前的旧列表数据并没有被修改哦:

//test/immutable_spec.json

import {expect} from 'chai';
import {List} from 'immutable';

describe('immutability', () => {

      // ...

      describe('A List', () => {

        function addMovie(currentState, movie) {
              return currentState.push(movie);
        }

        it('is immutable', () => {
              let state = List.of('Trainspotting', '28 Days Later');
              let nextState = addMovie(state, 'Sunshine');

              expect(nextState).to.equal(List.of(
                'Trainspotting',
                '28 Days Later',
                'Sunshine'
              ));
              expect(state).to.equal(List.of(
                'Trainspotting',
                '28 Days Later'
              ));
        });
      });
});

如果我们使用的是原生态 js 数组,那么上面的 addMovie 函数并不会保证旧的状态不会被修改。这里我们使用的是 Immutable List。

真实软件中,一个状态树通常是嵌套了多种数据结构的:list,map 以及其它类型的集合。假设状态树是一个包含了movies列表的 hash map,添加一个电影意味着我们需要创建一个新的 map,并且在新的 map 的movies元素中添加该新增数据:

//test/immutable_spec.json

import {expect} from 'chai';
import {List, Map} from 'immutable';

describe('immutability', () => {

      // ...

      describe('a tree', () => {

        function addMovie(currentState, movie) {
              return currentState.set(
                'movies',
                    currentState.get('movies').push(movie)
              );
        }

        it('is immutable', () => {
              let state = Map({
                movies: List.of('Trainspotting', '28 Days Later')
              });
              let nextState = addMovie(state, 'Sunshine');

              expect(nextState).to.equal(Map({
                movies: List.of(
                      'Trainspotting',
                      '28 Days Later',
                      'Sunshine'
                )
              }));
              expect(state).to.equal(Map({
                    movies: List.of(
                      'Trainspotting',
                      '28 Days Later'
                )
              }));
        });
      });
});

该例子和前面的那个类似,主要用来展示在嵌套结构下 Immutable 的行为。

针对类似上面这个例子的嵌套数据结构,Immutable 提供了很多辅助函数,可以帮助我们更容易的定位嵌套数据的内部属性,以达到更新对应值的目的。我们可以使用一个叫 update 的方法来修改上面的代码:

//test/immutable_spec.json

function addMovie(currentState, movie) {
      return currentState.update('movies', movies => movies.push(movie));
}

现在我们很好的了解了不可变数据,这将被用于我们的应用状态。 Immutable API 提供了非常多的辅助函数,我们目前只是学了点皮毛。

不可变数据是 Redux 的核心理念,但并不是必须使用 Immutable 库来实现这个特性。事实上, 官方 Redux 文档 使用的是原生 js 对象和数组,并通过简单的扩展它们来实现的。

这个教程中,我们将使用 Immutable 库,原因如下:

  • 该库将使得实现不可变数据结构变得非常简单;
  • 我个人偏爱于将尽可能的使用不可变数据,如果你的数据允许直接修改,迟早会有人踩坑;
  • 不可变数据结构更新是持续的,意味着很容易产生性能平静,特别维护是非常庞大的状态树,使用原生 js 对象和数组意味着要频繁的进行拷贝,很容易导致性能问题。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文