> For the complete documentation index, see [llms.txt](https://zg-zhang.gitbook.io/note/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://zg-zhang.gitbook.io/note/shi-zhan-bi-ji/cloud-music-web-app-v1.0/optimization.md).

# 项目的优化方案 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 属性
```
