# 项目的优化方案 immutable实战

> 优化方案都会基于 shouldComponentUpdate

总结起来就是：想在对 React 组件进行性能优化时，需要检测 state 或 props 的变化来判断是否需要 render， 如果使用浅比较，存在更新对线属性时引用没变的问题，但只要解决这个问题，浅比较依然是好方案。 因此 immutable 的出现解决的就是有变化就返回新引用，所以浅比较 + immutable 就是性能优化的利器。 后面出现的 Immer 是比 immutable 更好的方案

## 优化方案一：PureComponent （memo）进行浅层比较

PureComponent 浅比较部分的核心代码：

```javascript
function shallowEqual(objA: mixed, objB: mixed): boolean {

    // 下面的 is 相当于 === ，只是对 + 0 和 - 0，以及 NaN 和 NaN 的情况进行了特殊处理
    // 第一关：基础数据类型直接比较出结果
    if (is (objA, objB)) {
        return true;
    }

    // 第二关：只要有一个不是对象数据类型就返回 false
    if (
        typeof objA !== 'object' ||
        objA === null ||
        typeof objB !== 'object' ||
        objB === null
    ) {
        return false;
    }

    // 第三关：在这里已经可以保证两个都是对象数据类型，比较两者的属性类型
    const KeysA = Object.keys(objA);
    const KeysB = Object.keys(objB);

    if (KeysA.length !== KeysB.length) {
        return false;
    }

    // 第四关；比较两者的属性是否相等，值是否相等
    for (let i = 0; i < KeysA.length; i++) {
        if (
            !hasOwnProperty.call(objB, KeysA[i]) ||
            !is (objA [KeysA [i]], objB [KeysA[i]])
        ) {
            return false;
        }
    }

    return true;
}
```

这里只是浅比较，在下面的情况下会判断失灵

```jsx
// 调用 state.a.push("2")
state: {a: ["1"]} -> state: {a: ["1", "2"]}
```

上面情况中 a 数组已经发生了改变，但是浅层比较会表示没有改变，因为数组的引用没有变

一旦属性的值为引用类型的时候，浅比较就失灵了

由于 JS 引用赋值的原因，这种浅比较方式仅仅适用于无状态组件或状态数据非常简单的组件，对于大量的应用型组件，它是无能为力的

## 优化方案二：shouldComponentUpdate 中进行深层对比

为了解决方案一带来的问题，我们现在不做浅层对比了，我们把 props 中所有的属性和值进行递归比对

```javascript
function deepEqual(objA: mixed, objB: mixed): boolean {

    // 第一关：保证两者都是基本数据类型，基础数据类型直接比较出结果
    // 对象类型就不比了
    if (objA == null && objB == null) return true;
    if (
        typeof objA !== 'object' &&
        typeof objB !== 'object' &&
        is (objA, objB)
    ) {
        return true;
    }

    // 第二关：只要有一个不是对象数据类型就返回 false
    if (
        typeof objA !== 'object' ||
        objA === null ||
        typeof objB !== 'object' ||
        objB === null
    ) {
        return false;
    }

    // 第三关：在这里已经可以保证两个都是对象数据类型，比较两者的属性数量
    const KeysA = Object.keys(objA);
    const KeysB = Object.keys(objB);

    if (KeysA.length !== KeysB.length) {
        return false;
    }

    // 第四关：比较两者的属性是否相等，值是否相等
    for (let i = 0; i < KeysA.length; i++) {
        if (
            !hasOwnProperty.call(objB, KeysA[i]) ||
            !is (objA [KeysA [i]], objB [KeysB[i]])
        ) {
            return false;
        } else {
            if (!deepEqual(objA[KeysA[i]], objB[KeysA [i]])) {
                return false;
            }
        }
    }

    return true;
}
```

当访问到对象的属性值的时候，将属性值再进行递归对比，这样就达到了深层对比的效果

但是如果属性有一万条的时候，只有最后一个属性发生了改变的极端情况下，我们就迫不得已将一万条属性都遍历，这是浪费性能的

## 优化方案三：immutable 数据类型 + SCU (memo) 浅层对比

> 回到问题的本质，无论是直接用浅层对比，还是进行深层对比，我们最终是想知道组件的 props 或 state 数据有无发生改变

这样的情况下：immutable 数据应运而生

### 什么是 immutable 数据？有什么优势？

immutable 数据是一种利用结构共享形成的持久化数据结构，一旦有部分被修改，将会返回一个全新的对象，并且原来相同的节点会直接共享

immutable 对象数据内部采用的是多叉树的结构，凡是有节点被改变，那么它和与它相关的所有上级节点都更新

![immutable](https://github.com/zg-zhang/note/tree/a4fb09e10271a95b20f49da69725b90f7aaccf9d/.gitbook/assets/immutable.png)

只更新父节点，比直接对比所有的属性强太多，并且更新后返回了一个全新的引用，即使是浅对比也能感知到数据的变化

因此，采用 immutable 既能够最大效率地更新数据结构，又能够和现有的 PureComponent, memo 顺利对接，感知到状态的变化，是提高 React 渲染性能的极佳方案

immutable 对象和 JS 对象要注意转换，不能混用，这个注意适当的时候调用 toJS 或者 fromJS 即可

### 项目中涉及的 immutable 方法

* fromJS

它的功能是将 JS 对象转换为 immutable 对象

会经常在 redux 的 reducer 文件中看到这个 api，是 immutable 库当中导出的方法

```javascript
import {fromJS} from 'immutable'

const immutableState = fromJS({
    count: 0
})
```

* toJS

和 formJS 功能相反，用来将 immutable 对象转换为 JS 对象

但是这个方法并没有在 immutable 库中直接导出，而是需要让 immutable 对象调用

```javascript
const jsObj = immutableState.toJS();
```

* get / getIn

用来获取 immutable 对象属性

```javascript
// JS 对象
let jsObj = {a: 1};
let res = jsObj.a;
// immutable 对象
let immutableObj = fromJS(jsObj);
let res = immutableObj.get ('a')

// JS 对象
let jsObj = {a: {b: 1}};
let res = jsObj.a.b;
// immutable 对象
let immutable = fromJS(jsObj);
let res = immutableObj.getIn (['a', 'b']); // 注意传入的是一个数组
```

* set

用来对 immutable 对象的属性赋值

```javascript
let immutable = fromJS ({a: 1});
immutable.set ('a', 2);
```

* merge&#x20;

新数据与旧数据的对比，旧数据中不存在的属性直接添加，旧数据中已存在的数据用新数据中的覆盖

```javascript
let immutableObj = fromJS ({a: 1});
immutableObj.merge({
    a: 2,
    b: 3
}); // 修改了 a 属性， 增加了 b 属性
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://zg-zhang.gitbook.io/note/shi-zhan-bi-ji/cloud-music-web-app-v1.0/optimization.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
