前言
对于MOBX
,可能并不会陌生。而immer.js
也是该开源项目作者的一大力作,用于immutable
管理的实践。而今天,会对其做一个简单的小分享。
谈谈为什么会选择immer.js
来作为immutable
的解决方案,以及一些项目上的小实践。
immer的优缺点
在这里总结一些优缺点,其实对比是相当明显的,比时下immutable.js
来说更加的接地气,实用。
优点
- 上手快,学习成本较低
- 原生语法实现,没有额外的方言依赖
- 体积小,方便紧张的空间内实用
- api精炼,理解较为容易
缺点
- 浏览器需要支持
proxy
语法糖,否则会实用defineProperty
代替 - ES5后备实现的速度大约是代理实现速度的两倍,在某些情况下更糟。
用大白话来说就是,如果你需要在ie10
上开发项目,那还是别用了。在这里也侧面突出了两个api
的一个生产速度。在2021
年的今天,ie10
用户应该很少了吧。
工作模式
用过immutable.js
大家都知道,它的实现方式是自己维护了一套自有的数据结构。虽然解决了问题,但也面临很多的问题。
开发者需要在兼顾原生类型的时候,还要抽时间学习对应的数据结构的使用。对于轻度用户来说,无疑是非常鸡肋的事情。
而immer
的话,没有额外的学习负担,更加的贴合应用场景,且改造代码也非常的快捷和优雅。基本的实现思路就如下图所表达的一样,我们所做的更改只是在当前数据
的代理
上,一旦你的更改结束了,那么就会基于更改产生新的对象
,这样就非常方便的在隔离沙箱中修改一个数据,且没有过多的副作用
。
如何使用
在immer
中,主要的操作是由produce
函数来进行的,produce
需要接收两个参数。
produce(currentState, producer: (draftState) => void): nextState
- 当前数据:
元数据
- 草稿函数:
代理函数
使用起来也非常简单。下面就简单的分享下immer
在React
中是如何工作的一些小实例。
为什么在React使用
在这里,我也谈谈为什么使用immer
吧。
在之前,我们也知道React
如何判断视图更新是依赖于浅比较
,如下图事例。
当我们是基本数据类型
的时候,看起来并没有什么问题,这个时候数据比较是不想等的。a
var a = 1
var b = a
b = 2
// a = 1, b = 2
console.log(a === b) // false
但是,如果是引用类型呢?
var a = { x: 1 }
var b = a
b.x = 2
// a = { x: 2 }, b = { x: 2 }
console.log(a === b) // true
这个时候,我们a
和b
都指向了同一片内存单元,当我们在一处引用中修改了属性值,所有引用的数据都变了。另一个就是在浅比对的时候,对象的指向如果一样,那么React
就不会刷新render
,哪怕你的值已经修改了。因此,大多数时候,都是用一以下当时进行对象数组的更新的。
this.setState({
...state,
count: 2
})
以上方式产生一个新的内存引用进行设置,来确保引用类型数据的新值
和当前值
相对来说是没有瓜葛的。虽然解决了问题,但其实从解决的方式来说非常的暴力。所以,为了更加精准的管理数据,引入了immer
对引用状态进行管理,减少不必要的状态变化和意外的render
渲染。
在最后面留下了一个小问题,小伙伴们可以思考下。本文就不做赘述了。嘿嘿
Class组件
在class组件
中的state
更新是通过触发setState
来进行的,对于对象状态的更改形式非常的简单,参考官网的demo
,对于状态来说非常的简单。在produce
中直接对数据进行更改,是不是有vuex
风味了,嘿嘿。
从代码开发的角度来讲,更加接地气。
EthicalAds: A privacy-focused ad network for developers. Publishers & Advertisers wanted!
Ad by EthicalAds
egghead.io lesson 8: Using Immer with useState. Or: useImmer
Deep updates in the state of React components can be greatly simplified as well by using immer. Take for example the following onClick handlers (Try in codesandbox):
/**
* Classic React.setState with a deep merge
*/
onBirthDayClick1 = () => {
this.setState(prevState => ({
user: {
...prevState.user,
age: prevState.user.age + 1
}
}))
}
/**
* ...But, since setState accepts functions,
* we can just create a curried producer and further simplify!
*/
onBirthDayClick2 = () => {
this.setState(
produce(draft => {
draft.user.age += 1
})
)
}
上述例子中,其实很多同学会看不懂,为什么没有原始数据呢?那么怎么知道你要代理的数据来源呢?
对于这个问题就要思考于函数柯里化(currying)
了,得益于函数的柯里化(currying)
。其作用非常的朴素,就是将接收多个参数的函数变成为接收一个参数且返回值为剩余参数的函数。
这样理解的话,由于默认的setState
会传入一个state
参数,因此柯里化
之后,只需要传递一个草稿函数就可以完成状态的更改了。
函数组件
对于无状态组件,immer
将其可能用到State
抽离成为了hook
,名字是use-immer
。
- 如何使用?查看文档
useImmer
useImmer
用于state
的对象处理,其实本质上是一个自定义hook
,对useState
进行了一层包装。对于状态的更改,只需要和class
一样传递一个函数就可以了。
import { useImmer } from 'use-immer';
const [person, setPerson] = useImmer({
name: "wangly",
});
// button click event ...
const handleClick = () => {
setPerson(state => {
state.name = 'wangly19 yes!!!'
})
}
实现原理
export function useImmer(initialValue: any) {
const [val, updateValue] = useState(initialValue);
return [
val,
useCallback(updater => {
updateValue(produce(updater));
}, [])
];
}
对于state
的使用方式更加的简介和易懂,且不需要关注其他函数改变副作用的担忧,在开发的时候能够明细修改的数据,不必刻意去成产一个新对象,将这些事情更多交给统一化处理,出错也更加容易定位。
其次,对于useReducer
来说,和useState
一半,也有一个自定义的hook
来管理这个内容。使用方式也相对来说更加好理解。
参考以下事例,通过dispatch
调用不同的action
操作时,进行不同的更改操作。
import { useImmerReducer } from "use-immer";
const initialState = { count: 0 };
function reducer(draft, action) {
switch (action.type) {
case "reset":
return initialState;
case "increment":
return void draft.count++;
case "decrement":
return void draft.count--;
}
}
代码实例
点击查看
其他
由于项目使用的是umi-cli
,那么必不可少的会使用dva
来管理项目的部分状态,如果需要体验immer
,只需要在配置文件中加上声明就可以享受immer
的数据流了。
import { defineConfig } from 'umi';
import routes from './routes'
export default defineConfig({
hash: true,
antd: {
},
dva: {
immer: true
},
history: {
type: 'browser'
},
locale: {
// default zh-CN
default: 'zh-CN',
antd: true,
},
routes
})
性能
对于性能来说,从官方给出一些对比数据来看,immer
在大多数的场景下看起来消费差距并不是很大。相对一观之即可。
# wangly19 @ wangly19s-MacBook-Pro in ~/Desktop/项目/immer on git:master o [11:42:31]
$ yarn test:perf
yarn run v1.22.5
$ cd __performance_tests__ && babel-node add-data.js && babel-node todo.js && babel-node incremental.js
# add-data - loading large set of data
just mutate: 0ms
just mutate, freeze: 1ms
handcrafted reducer (no freeze): 0ms
handcrafted reducer (with freeze): 0ms
immutableJS: 65ms
immutableJS + toJS: 34ms
seamless-immutable: 40ms
seamless-immutable + asMutable: 48ms
immer (proxy) - without autofreeze * 10000: 27ms
immer (proxy) - with autofreeze * 10000: 29ms
immer (es5) - without autofreeze * 10000: 93ms
immer (es5) - with autofreeze * 10000: 65ms
# todo - performance
just mutate: 1ms
just mutate, freeze: 195ms
deepclone, then mutate: 175ms
deepclone, then mutate, then freeze: 368ms
handcrafted reducer (no freeze): 19ms
handcrafted reducer (with freeze): 19ms
naive handcrafted reducer (without freeze): 19ms
naive handcrafted reducer (with freeze): 42ms
immutableJS: 5ms
immutableJS + toJS: 164ms
seamless-immutable: 52ms
seamless-immutable + asMutable: 143ms
immer (proxy) - without autofreeze: 64ms
immer (proxy) - with autofreeze: 74ms
immer (proxy) - without autofreeze - with patch listener: 87ms
immer (proxy) - with autofreeze - with patch listener: 84ms
immer (es5) - without autofreeze: 257ms
immer (es5) - with autofreeze: 257ms
immer (es5) - without autofreeze - with patch listener: 2439ms
immer (es5) - with autofreeze - with patch listener: 2715ms
# incremental - lot of small incremental changes
just mutate: 0ms
handcrafted reducer: 72ms
immutableJS: 17ms
immer (proxy): 988ms
immer (es5): 3520ms
immer (proxy) - single produce: 9ms
immer (es5) - single produce: 3ms
✨ Done in 67.52s.
参考资料
- immerjs github
- immerjs docs
- immutabl-维基百科
- immutablejs
后话
immer
本身来说并没有性能屏障,哪怕在文档的后面贴出了性能测试也并没有达到很大的提升。只能说在某些场景会优于现今的解决方案。如果在项目中存在困扰,不妨可以试试能不能更好的解决问题。
对于引用类型来说,很容易发生一些小意外,对于我来说,引入immer
更多的是解决不可变数据(immutable)
带来的负担,在开发的时候不需要因为一些隐式方法而改变数据导致BUG
,哪怕这类问题出现的几率很小,但如果出现一次的话,排查起来是非常困难的。
在最后,祝大家2021年,牛气冲天。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!