浅谈 TypeScript - immutable

1,758 阅读3分钟

在我们最基础的印象里 JavaScript 的对象都是引用关系,这对于我们的应用来说,可能会有无法预期的结果,比如:

const f = {
  a: 1
}
function g(r) {
  r.a = 2
}
g(f)
console.log(f.a) // 2

这影响了外部定义的对象,有时候我们不可预知的错误,就是如此。那么,是否有更好的解决方案来解决这个对象不可变的事情呢?immutable 这个库就能很好的解决这个问题,虽然它总是出现在 React 的应用场景下,但它并不一定需要 React ,这是一个可以在其他环境中也可以运行的库。使用 immutable 创建的 Data ,一旦创建就不可变,任何的操作都会返回一个新的 Data,它的实现利用了 Persisten Data Structure 为我们提供了一个很不错的方案。

immutable.js 提供了各种的数据类型,比如 CollectionListMap等等,其 API 也与我们熟悉的 Object 或 Array 的操作类似,这一点对于去理解它的人来说,非常的平滑。

让我们先来看一个例子:

const newList = immutable.List([
  {
    id: "1",
    name: "xww",
  },
  {
    id: "2",
    name: "icepy",
  },
]);

const newMap = immutable.Map({id: "3", name: "ts"});

创建 immutable 对象就如上述一样的简单,现在,我们可以看一下渲染一个列表,如图:

点击 SET LIST DATA 渲染一个列表

public clickListHandler = () => {
  const newList = immutable.List([
    {
      id: "1",
      name: "xww",
    },
    {
      id: "2",
      name: "icepy",
    },
  ]);
  this.setState({
    list: newList,
  });
}

// render

<div>
  {
    list.map((v: IimmList | undefined) => {
      if (v) {
        return (
          <p key={v.id}>{v.name}</p>
        );
      }
      return null;
    })
  }
</div>

在 TypeScript 的世界里 List 是一个范型,需要指定一个类型,如:

interface IimmList {
  id: string;
  name: string;
}

interface IState {
  list: immutable.List<IimmList>;
}

接下来,我们想改变第一个元素的值:

public clickList0Handler = () => {
  const { list } = this.state;
  if (list.size > 0) {
    const list0 = list.get(0);
    if (list0.name === "xww-icepy") {
      return;
    }
    const newList = list.set(0, {
      id: "3",
      name: "xww-icepy",
    });
    this.setState({
      list: newList,
    });
  }
}

如图:

其实,我们可以看到很多操作的 API 都非常的和 Array 像,并且和预期的结果一样,每一次的操作都会返回一个新的 immutable Data ,既然如此,我们还可以利用 React 本身的特性来进行优化,比如:

public shouldComponentUpdate(nextProps: Readonly<{}>, nextState: Readonly<IState>, nextContext: any) {
  if (this.state.list !== nextState.list || immutable.is(this.state.list, nextState.list)) {
    return true;
  }
  return false;
}

immutable Data 的比较与我们以往的比较有些不一样,它比对的是 immutable Data 的 hashCode,如果我们的数据源嵌套很深,我们还可以使用游标(Cursor)来处理这个问题,这是一个很有趣的事情。

import Immutable from 'immutable';
import Cursor from 'immutable/contrib/cursor';

let data = Immutable.fromJS({ a: { b: { c: 1 } } });
// 让 cursor 指向 { c: 1 }
let cursor = Cursor.from(data, ['a', 'b'], newData => {
  // 当 cursor 或其子 cursor 执行 update 时调用
  console.log(newData);
});

cursor.get('c'); // 1
cursor = cursor.update('c', x => x + 1);
cursor.get('c'); // 2

通过上述的一些知识点分享,我们应该对 immutable Data 有一个基础的认识,再结合 React 本身的特性,我们可以对于我们的应用程序做出优化的可能性。

如果你的数据使用 Redux 来驱动,搭配 immutable 稍微有些复杂,因为本身 Redux 对于数据的驱动有它自己的定义,比如 selec 来做检索,Action 来更新数据等等,两者的结合还是需要花费一番心思的。