前言:我相信大部分做过React的都知道Immutable这个东西,不知不觉我们就用到这个,但很难有人彻底的讲清楚Immutable的作用,React为什么要强调Immutable,究竟Immutable给React框架带来的意义是什么?我以前刚学React的时候也有这个困惑。为了让初学着少走弯路,我们这次来深入探究一下Immutable这个概念。
一. 谈谈什么是数据的不可变性?
1.我们都知道js数据类型分两大类:基本数据类型存储在栈内存、引用类型值存储在堆内存
-
基本数据类型: 值是不可变的,javascript中的原始值(undefined、null、布尔值、数字和字符串)是不可更改的。
-
引用数据类型:对象或数组,引用类型是可以直接改变其值的。
//不可变的类型 let str = "abc"; console.log(str[1]="f");
//可变类型 let a = [1,2,3]; a[1] = 5; console.log(a[1]); // 5
所以你会发现在JS中引用类型会带来共享问题、以及数据可变性问题。
2 .React是一种函数式编程提倡使用纯函数,排斥数据共享以及可变性。 那我们继续追一下React这样设计的原因。
React追求数据不可变性的主要的好处之一是为了加速了diff算法中reconcile的过程,React只需要检查object的reference有没有变即可确定数据有没有变。
举个例子:在使用useMemo,useState,shouldComponentUpdate 时,react只会去shallow compare props,只是去检查props的reference有没有变,如果变了,就reRender(重新渲染),简单来讲是这样的:Object.keys(prevProps).some(key => prevProps[key] !== nextProps[key]),只对比对象一层,如果是深对象,就无法判断了。
const [todos, setTodos] = useState([{o:1,a:2}]);
const onClick = () => {
todos[0].a = 3;
setTodos(todos);//不能reRender
}
所以上面的做法是错的。setTodos后,往往不会reRender 。 所以一般新手都会很困惑。
正确的做法是这样,浅拷贝出来后再修改,如果是更深的对象,需要深拷贝后再修改。
const onClick = () => {
let list =[...todos]
list[0].a=3
setTodos(list);
}
简单讲就是React要求useState 和 useReducer等这些hook 在update state的时候要返回一个新的 reference。
二. 那么我们日常开发中如何保证其不可变性呢?
1. 使用纯函数的操作
宗旨就是:保持其纯函数的特性。见如下例子:
//比如数组-增加元素
let arr1= []
const addArr1 =(arr,ele)=> {arr.push(ele); return arr;}//这样改变了原有数组不妥
const addArr2 = (arr,ele)=> arr1.concat(ele) // 不会改变原数组 纯函数 Object.assign //对象的操作
2.像我上面的例子根据情况使用深拷贝或浅拷贝,先copy一份,改变reference后再增删改。
使用自己封装一个deepclone
const deepClone = (obj, hash= new WeakMap()) => {
if (hash.has(obj)) {
return hash.get(obj)
}
// 暂只考虑object、array
const type = getDataType(obj)
let o
// 根据类型初始化
if (type === ARRAY) {
o = []
} else if (type === OBJECT) {
o = {}
} else {
// 基本类型不可变
return obj
}
if (o) {
hash.set(obj, o)
}
if (type === ARRAY) {
for (let i = 0; i < obj.length; i++) {
o.push(deepClone(obj[i], hash))
}
return o
} else if (type === OBJECT) {
for (const key in obj) {
o[key] = deepClone(obj[key], hash)
}
return o
}
}
或使用lodash等这些第三方库实现。
3. 使用facebook提供的immutable.js库。
在immutableJs对对象和数组,有单独对应的数据类型Map和List 。 Map中的set,setIn,merge等操作都是返回一个新的reference的数据,像List的push, unshift等操作,也都是返回新的数据,它不可对数据进行可变修改,保持数据不可变性,才是我们的目标。
像上面的例子如果我们用immutableJs可以这么玩
import { fromJs } from 'immutable'
const [todos, setTodos] = useState(fromJs([{o:1,a:2}]));
const onClick = () => {
setTodos(todos.get(0).set('a',3));
}
三.总结
React中immutable的核心思想就是不改变"历史记录",函数式编程所说的"数据不可变"其实就是求你不要改变"历史记录",你可以创建新的"历史"但是不应该去改变"历史"。
但Immutable 不是 React 的核心。React 唯一需要的是知道 state 何时改变了,但 React 无法监控深层次的 state 对象变化。那如何避免程序员不小心改变了深层次的对象但 React 不知道呢?那就引导你们使用 Immutable,使得不能修改对象,只能创建新对象,这样就能保证 React 一定能监控到变化,这就Immutable的最终目的。谢谢大家的阅读。