React的老手们早就知道为何要用不可变数据了,可是为了防止新手们看不懂,因此仍是要解释一下什么是不可变数据,不可变数据指的其实就是当你修改一个数据的时候,这个数据会给你返回一个新的引用,而本身的引用保持不变,有点像是常常用到的数组的map方法:javascript
const arr1 = [1, 2, 3];
const arr2 = arr1.map(item => item * 10);
console.log(arr1 === arr2)
//false
复制代码
这样的话每次修改数据,新返回的数据就和原来不相等了。java
若是数据变动,节点类型不相同的时候会怎样呢?React 的作法很是简单粗暴,直接将 原 VDOM 树上该节点以及该节点下全部的后代节点 所有删除,而后替换为新 VDOM 树上同一位置的节点,固然这个节点的后代节点也全都跟着过来了。react
这样的话很是浪费性能,父组件数据一变化,子组件所有都移除,再换新的,因此才有了shouldComponentUpdate这个生命周期(Vue的小伙伴请放心,Vue原理和React不太同样,因此没这毛病),这个函数若是返回false的话子组件就不会更新,可是每次在这个函数里面写对比会很麻烦,因此有了PureComponent和Memo,可是只提供了浅比较,因此这时候不可变数据就派上用场了,每次修改数据都和原数据不相等的话,就能够精确的控制更新。git
Facebook早就知道React这一缺陷,因此历时三年打造了一个不可变数据的immutable.js。它内部实现了一套完整的 Persistent Data Structure,还有不少易用的数据类型。像Collection、List、Map、Set、Record、Seq。有很是全面的map、filter、groupBy、reduce``find函数式操做方法。同时 API 也设计的和JS对象、数组等相似。
不过功能虽全,可是若是咱们仅仅只是为了优化浅对比防止子组件过分刷新的话,引入这么大的一个库就未免有些大材小用了,并且学习成本也是须要考虑在内的,因此要为你们介绍一下今天的主角:轻量、易用、简洁又能够快速上手的immer.js!github
immer这玩意来头可不小,他的创造者就是大名鼎鼎的Mobx做者,听过Mobx的人应该都知道,它与Redux相比更简洁、更轻量、同时也更加易学,因此immer也一样的继承了这些优势:轻量、简洁、易上手、而且使用起来也很是的舒服,不会产生容易把immutable数据类型与原生JS数据类型搞混的状况。它的核心思想就是利用Vue3源码中大量运用的Proxy代理,几乎以最小的成本实现了JS的不可变数据结构,解决了许多平常开发中的棘手问题,相信看完个人文章你必定会喜欢上它的!
首先第一步就是先进行安装:shell
npm i -S immer
复制代码
或者npm
yarn add immer
复制代码
import produce from 'immer';
const array = [{value: 0}, {value: 1}, {value: 2}];
const arr = produce(array, draft => {
draft[0].value = 10;
});
console.log(arr === array);
//false
复制代码
解释一下:produce是生产的意思(你想起啥名都行,可是官网喜欢这么叫,我就跟着这么起名),这个函数第一个参数是你想要改变的数据对象,第二个参数是一个函数,这个函数的参数draft是草稿的意思,表明的就是你想要改变的那个数据对象,而后在函数体内你就正常想怎么改就怎么改,produce运行完的结果就是一个全新的对象啦!怎么样是否是超级简洁超级好用呢?数组
const array = [{value: 0}, {value: 1}, {value: 2}];
const arr = produce(array, draft => {});
console.log(array === arr);
// true
复制代码
const array = [{value: 0}, {value: 1}, {value: 2}];
const producer = produce((draft) => {
draft[0].value = 10;
});
const arr = producer(array);
console.log(array === arr);
// false
复制代码
这样虽然结果同样,可是却加强了可复用性,甚至能够进行再次封装来造成一个高阶函数:数据结构
const array = [{value: 0}, {value: 1}, {value: 2}];
const producer = (state, fn) => produce(fn)(state);
const arr = producer(array, draft => { draft[0] = 666 });
console.log(array, arr);
// [{…}, {…}, {…}]
// [666, {…}, {…}]
复制代码
const array = [{value: 0}, {value: 1}, {value: 2}];
const producer = (state, fn) => produce(fn)(state);
const arr = producer(array, draft => [666, ...draft]);
console.log(array, arr);
// [{…}, {…}, {…}]
// [666, {…}, {…}, {…}]
复制代码
咱们发现返回值就是新数据的结果!因此咱们能够清楚的得知:在没有返回值时数据是根据函数体内对draft参数的操做生成的。有返回值的话返回值就会被当作新数据来返回。函数
因为React Hooks的异军突起,致使如今不少组件都使用函数来进行编写,数据就直接写在useState中,可是有了useImmer,你之后就能够用它来代替useState啦!
仍是老规矩,先安装:
npm install immer use-immer
复制代码
或
yarn add immer use-immer
复制代码
定义数据: const [xxx, setXxx] = useImmer(…)
修改数据: setXxx(draft => {})
能够看到用法和setState几乎没啥太大区别,接下来咱们经过一个小案例来继续深刻useImmer的用法:
import React from "react";
import { useImmer } from "use-immer";
export default function () {
const [person, setPerson] = useImmer({
name: "马云",
salary: '对钱没兴趣'
});
function setName(name) {
setPerson(draft => {
draft.name = name;
});
}
function becomeRicher() {
setPerson(draft => {
draft.salary += '$¥';
});
}
return (
<div className="App"> <h1> {person.name} ({person.salary}) </h1> <input onChange={e => { setName(e.target.value); }} value={person.name} /> <br /> <button onClick={becomeRicher}>变富</button> </div> ); } 复制代码
use-immer对useReducer进行了增强封装,一样也几乎没什么学习成本,再改编一下官网小案例👇
import React from "react";
import { useImmerReducer } from "use-immer";
const initialState = { salary: 0 };
function reducer(draft, action) {
switch (action.type) {
case "reset":
return initialState;
case "increment":
return void draft.salary++;
case "decrement":
return void draft.salary--;
}
}
export default function () {
const [state, dispatch] = useImmerReducer(reducer, initialState);
return (
<> 期待工资: {state.salary}K <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "decrement" })}>-</button> <button onClick={() => dispatch({ type: "reset" })}>重置</button> </> ); } 复制代码