【实用库小书】immutability-helper (01)

100 阅读6分钟

今天给大家来点不一样的干货:immutability-helper.js

github.com/kolodny/imm…

发现问题

当谈到React开发中的数据处理和状态管理时,不可变性(Immutability)变得至关重要。React组件中的状态数据应该在保持不可变的基础上进行更新,以确保更可预测、更可维护的代码。这正是immutability-helper.js库所涉及的关键概念,它为React应用的数据处理提供了强大的支持。本文将深入探讨 immutability-helper,让我们一起了解它与React之间的关系以及在现代React开发中的重要性。

immutability-helper 以不变性为核心概念,它的目的是在不改变原始数据源的情况下,让开发者能够修改数据的副本。它提供了一个强大的工具集,用于创建和管理数据结构,这些数据结构在保持不可变性的同时允许创建新的修改副本。

该库的作者描述 mutate a copy of data without changing the original source(即:改变该数据的副本而不是原始数据) 清晰地传达了它的用途。实际上,它让开发者可以在不破坏原始数据源的前提下,轻松地进行数据修改。另外值得一提的是,Facebook推出的 Immutable.js 库,它与immutability-helper有相似之处,但更加强大和复杂。你可以把immutability-helper看作是Immutable.js的一个简化版本。

引出问题

在我们深入探讨不可变性和数据操作的世界时,我们要明白这两个库的共同关键词是"不可变"。那么为什么要追求不可变性呢?从React的角度来解释可能会更加清晰。React组件通常包含各种状态,这些状态经常通过setState方法来更新,也就是说React组件的状态是可变的。但是初学React的人可能会误解"状态是可变的"这一概念。为什么会有这种误解呢?因为在React中,状态的变化实际上是状态的引用变化而不是状态值的变化。如果这还不够清晰,那么接下来我们将通过例子继续探讨这一概念。

image.png

举个栗子

假设我们开发一个简单的计数器应用,创建一个React组件,其中包含一个状态(state)变量 count,用于存储当前计数的值。现在,让我们看一下如何保持状态的不可变性。

import React, { Component } from 'react';

class Counter extends Component {
  constructor() {
    super();
    this.state = {
      count: 0
    };
  }

  increment = () => {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

export default Counter;

在这个示例中,我们使用了 this.setState 方法,但它并不直接修改 this.state.count 的值。相反,它接受一个函数,该函数返回一个新的状态对象,只更新我们想要更改的属性 count。这确保了React状态的不可变性。

现在,让我们看一个可变的方式,这是不推荐的:

import React, { Component } from 'react';

class Counter extends Component {
  constructor() {
    super();
    this.state = {
      count: 0
    };
  }

  increment = () => {
    this.state.count++; // 直接修改状态
    this.forceUpdate(); // 强制组件重新渲染
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

export default Counter;

在这个示例中,我们直接修改了 this.state.count 的值,这破坏了React状态的不可变性。为了更新UI,我们甚至使用了 forceUpdate。这种开发方式我们并不鼓励,甚至是禁止这么写,否则Team Leader就提着大刀过来了...

在上面的计数器示例中,我们展示了如何在React中保持状态的不可变性,以确保更可预测和可维护的代码。但在大型和复杂的应用程序中,处理嵌套的状态对象和深层次的更新可能变得非常繁琐。这时,immutability-helper.js库派上了用场,为React开发者提供了一种更便捷的方式来处理不可变数据。

使用immutability-helper.js,我们可以轻松地进行状态更新,而不必手动复制和克隆状态对象,或编写复杂的更新逻辑。以下是如何使用immutability-helper.js来实现计数器的状态更新:

import React, { Component } from 'react';
import update from 'immutability-helper';

class Counter extends Component {
  constructor() {
    super();
    this.state = {
      count: 0
    };
  }

  increment = () => {
    this.setState(update(this.state, {
      count: { $set: this.state.count + 1 }
    }));
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

export default Counter;

在这个例子中,我们使用了immutability-helper.js的 update 函数来更新状态。它接受两个参数:要更新的状态对象和一个描述更新操作的对象。这里,我们使用 $set 操作符来设置新的 count 值,而不必手动创建新的状态对象。

这个例子展示了immutability-helper.js的价值:简化了状态更新,减少了不可变性维护的工作,使代码更加清晰和易于管理。对于复杂的应用程序和深层次的状态对象,这个库尤其有用,能够提高开发效率和代码质量。此外值得一提的是 immutability-helper会输出一个全新的数据对象并且只会更新有关的那条数据,剩下的通过【地址引用】的方式引入到新的数据对象中,这样会最大限度的使用数据和内存。

immutability-helper工作方式

接下来我们开始介绍该库使用方式。通过在update方法在使用指令就可以实现对数据的修改。
immutability-helper的指令组成也很简单:$+关键字。而且这个关键字我们仍然很熟悉。它支持的指令一共有:

  1. $push
  2. $unshift
  3. $splice
  4. $set
  5. $unset
  6. $toggle
  7. $merge
  8. $apply
  9. $add
  10. $remove

指令介绍

$push

push,顾名思义和数组有关,其实就是向源数组中增加一个元素并且输出一个内容想通了的新数组。看例子:

const update = require("immutability-helper");
const state1 = ["x"];
const state2 = update(state1, { $push: ["y"] }); // ['x', 'y']

console.log(state1, state2);
console.log(`state1===state2: ${state1===state2}`);

输出结果 image.png

同时也来熟悉下update方法的使用:update方法接受两个参数,第一个是源数据,这个很好理解;第二个是操作线(笔者YY出来的),用来描述我们如何去操作源数据,key是指令,value是指令所需要的数据

$unshift

unshift的作用就是向源数组的开头批量添加元素

const update = require("immutability-helper");
const state1 = ["x"];
const state2 = update(state1, { $unshift: ["y","Z"] });

console.log(state1, state2);

输出结果 image.png

$splice

splice的作用就是向源数组中添加/删除元素。参数接受多个数组,每个数组为一组操作。每一组与实际splice方法的参数相同。

const update = require("immutability-helper");
const state1 = [0, 1, 2, 4];
const state2 = update(state1, {
    $splice: [
        [3, 1, 3, 4, 5, 6, 7]
    ]
});

console.log(state1, state2);

输出结果 image.png

$set

set指令被用来改成字面量对象中的某个key的值。

const update = require("immutability-helper");
const data = { 'id': 0, name: 'xiaoming' };
const data2 = update(data, { name: { $set: 'Miss Li' } });
console.log(data, data2);

输出结果 image.png

$toggle

toggle意为切换,该方法是用来对Boolean对象进行切换,比如True切换为False

const update = require("immutability-helper");
const data = [true, false];
const data2 = update(data, { $toggle: [0] });
console.log(data, data2);

输出结果 image.png

本篇先介绍这5个指令方法。剩下的5个我们下一篇继续。
接下来我们尝试从上面这5个指令中找出相应指令去解决我们日常开发中列表数据更新的问题:
首先是列表的数据:

[
    {'id':0,name:'xiaoming'},
    {'id':1,name:'lilei'},
    {'id':2,name:'hanmeimei'},
]

可以分析出我们需要对两中对象进行处理,一个是数组,一个是字面量对象。
然后假如我们修改了id=1的那条记录的‘name’属性,‘name=张伟’,该怎么做?
首先第一步我们要找到id=1的那条记录的index,这里是1;然后是确定需要更改的字段和更改后的值

const update = require("immutability-helper");
const data = [
    { 'id': 0, name: 'xiaoming' },
    { 'id': 1, name: 'lilei' },
    { 'id': 2, name: 'hanmeimei' },
]
const data2 = update(data, { 1: { name: { $set: '张伟' } } });
console.log(data[1]['name'], data2[1]['name']);
console.log(`data===data2: ${data===data2}`);

输出结果 image.png 这样就实现了在源数据的基础上更改了值并且输出一个与之地址完全不同数组。

image.png

咱们下期见...