本文參照源文檔 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差很少