本文参照源文档 github.com/immerjs/imm…介绍使用immer v3
你们都知道,开发react项目时,推荐使用immutable的数据结构,这样react就能很高效而且正确地检测到数据变化用以肯定是否更新UI。javascript
市面上有几款帮助你实现immutable操做的库,immutable.js自己比较中规中矩,提供了一些方法,在必要时你能够调用他们,然而笔者以为他增长了使用负担,要专门去记各个api,并且最重要的是,常常一不当心或者顺手或者手贱就直接修改obj了!其实我以为这才是问题的关键,immer的出现就很好的解决了这一痛点。由于他的思路就是把你整个操做包裹起来,无论你是直接push数组仍是改obj.field,最后输出的确定是新的对象。java
produce(currentState, producer: (draftState) => void): nextState
react
第一个参数为你准备要改的对象,第二个参数是个回调,这个回调函数的参数就是他给你复制的一个临时对象,因此你能够对这个作任何操做。最后produce返回新的对象(下一状态),而currentState任然保持不变。git
import produce from "immer"
const baseState = [
{
todo: "Learn typescript",
done: true
},
{
todo: "Try immer",
done: false
}
]
const nextState = produce(baseState, draftState => {
draftState.push({todo: "Tweet about it"})
draftState[1].done = true
})复制代码
因此你在开发时只须要专一于逻辑便可,没必要纠结再怎么保证不可变性。在下面redux的reducer里体现的更明显。github
当收到新的products以后,把这些products按id加入总的state里面typescript
const byId = (state, action) => {
switch (action.type) {
case RECEIVE_PRODUCTS:
return {
...state,
...action.products.reduce((obj, product) => {
obj[product.id] = product
return obj
}, {})
}
default:
return state
}
}复制代码
使用immer后:json
import produce from "immer"
const byId = (state, action) =>
produce(state, draft => {
switch (action.type) {
case RECEIVE_PRODUCTS:
action.products.forEach(product => {
draft[product.id] = product
})
}
})复制代码
你能够看到,一个加id和对象到另外一个对象里面的操做是多简单。并且这里你不用处理默认的状况,由于producer啥都不作的话会返回原对象。redux
/**
* 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
})
)
}复制代码
对于依赖以前的值的setState,得按第一种方式写,而用produce就能够直接+=。(注意这里produce的用法是produce((draft)=>{}),你不须要再传baseState了,其实用的是下面的语法)api
currying的意思是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数且返回结果的新函数的技术, 好比func(a,b,c)变成func(a)(b)(c),redux里的connect就是这个样子的,记得吗。另外Currying是我的名。
当你给produce传入的第一个参数是回调函数的话,produce返回的是一个预绑定回调函数的函数,这个函数接收一个baseState做为参数。数组
好比前面的例子,produce返回的就是(prevState)=>{}这个函数,因此它能够直接放在setState里面。另外一个例子:
// mapper will be of signature (state, index) => state
const mapper = produce((draft, index) => {
draft.index = index
})
// example usage
console.dir([{}, {}, {}].map(mapper))
//[{index: 0}, {index: 1}, {index: 2}])复制代码
这样咱们就能够改前面的reducer例子代码更少:
import produce from "immer"
const byId = produce((draft, action) => {
switch (action.type) {
case RECEIVE_PRODUCTS:
action.products.forEach(product => {
draft[product.id] = product
})
return
}
})复制代码
produce生成的函数是接受state做为传入值的,到produce里面就是draft了。另外你能够传第二个参数去初始化state:
import produce from "immer"
const byId = produce(
(draft, action) => {
switch (action.type) {
case RECEIVE_PRODUCTS:
action.products.forEach(product => {
draft[product.id] = product
})
return
}
},
{
1: {id: 1, name: "product-1"}
}
)复制代码
返回undefined
前面说过,默认producer啥都不作的话会返回baseState,然而你显式地return undefined其实也会返回baseState。若是你真想返回undefined,须要返回一个预约义的token:nothing
import produce, {nothing} from "immer"
const state = {
hello: "world"
}
produce(state, draft => {})
produce(state, draft => undefined)
// Both return the original state: { hello: "world"}
produce(state, draft => nothing)
// Produces a new state, 'undefined'复制代码
import produce from "immer"
import {produce} from "immer"
const {produce} = require("immer")
const produce = require("immer").produce
const produce = require("immer").default
import unleashTheMagic from "immer"
import {produce as unleashTheMagic} from "immer"复制代码
import produce from "immer"
const user = {
name: "michel",
todos: []
}
const loadedUser = await produce(user, async function(draft) {
draft.todos = await (await window.fetch("http://host/" + draft.name)).json()
})复制代码
包大小4.35k,速度跟immutablejs差很少