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

相关文章
相关标签/搜索