使用immer加快開發速度

本文參照源文檔 github.com/immerjs/imm…介紹使用immer v3

你們都知道,開發react項目時,推薦使用immutable的數據結構,這樣react就能很高效而且正確地檢測到數據變化用以肯定是否更新UI。javascript

市面上有幾款幫助你實現immutable操做的庫,immutable.js自己比較中規中矩,提供了一些方法,在必要時你能夠調用他們,然而筆者以爲他增長了使用負擔,要專門去記各個api,並且最重要的是,常常一不當心或者順手或者手賤就直接修改obj了!其實我以爲這纔是問題的關鍵,immer的出現就很好的解決了這一痛點。由於他的思路就是把你整個操做包裹起來,無論你是直接push數組仍是改obj.field,最後輸出的確定是新的對象。java

API

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

reducer的例子

當收到新的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

react的setState例子

/**
 * 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(柯里化,部分求值)的produce

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"複製代碼

異步producer

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差很少

相關文章
相關標籤/搜索