我厌倦了 Redux,那就造个轮子 Rectx 第二集: immutable 痛点分析

#1

仓库地址:仓库

前言

完全没想到,竟然会有第二集!第一集在这里:我厌倦了Redux,那就造个轮子:Rectx

我们在前面的第一集中讲到了我如何厌倦 Redux 以及,我如何对这个问题进行了规避。利用了新的 Context API,我在此之上,进行了一系列的封装,封装出了一个还算可行的状态容器:Controller,用于保管我们的组件状态和书写管理状态的业务逻辑。

Rectx 的痛点还有哪些?


## 只支持 16.3 版本 React
Rectx **[发音类似 rick text]** 有一个最大的毛病就是,我们对 `Context API` 的支持非常的严重,也就是说,想要玩 `Rectx` 的玩家,不得不升级到 `16.3` 版本才能玩耍,这是俨然成为最大痛点之一。

因此,我引入了一个 polyfill 使得我们的 Rectx 能够在 React > 15 版本 都可以使用。

你没有看错,我们在React > 15 版本 都可以使用!
你没有看错,我们在React > 15 版本 都可以使用!
你没有看错,我们在React > 15 版本 都可以使用!

没有单元测试


在第一集中,我向大家介绍了 Rectx 的使用以及其简单的概念。但是一个库,没有单元测试,谁都不敢用在生产环境中。于是,我给 Rectx 加上了大约 400 行的测试代码,保证了其期望功能的完整性和正确性。

Immer 的引入
Immer 是一个非常巧的库,他的作用实际上是使得 immutable 概念的库( Redux 之流)拥有了一个 mutable 的 API ,学习它的成本极其低下,因为这个库只有一个 API。

什么是 immutable ?


在介绍为什么引入 Immer 之前,我们不妨来回想一下最原始的 Reducer。Reducer 是一个极其简单的概念,通过他我们能够轻松的改变一个 Store 的状态:
const Reducer = (state, action) =>{
    return {...state, fuck: action.shit}
}

如果是数组,那就搞成:

const Reducer = (state, action) =>{
    const newArray = action.shit.map((i,idx)=>{
         if(idx === 1) return {...i, fuck:'shit}
         return i
    })
    return {...state, fuck: newArray}
}

这样就能够更新了。这里为什么要返回一个新的数组的究其原因,让我来带大家一起实现一个超级简单的 Redux 你就能够明白:

Redux 使用的是一个 发布/订阅 模型,通过 subscribe 函数,把某个函数注册到 Store 中去,当执行 dispatch 的时候,就会将 Store 的全局状态进行广播,具体怎么广播呢?答案就是遍历所有的 subscribe 进来的函数。

然而,我们总不能没发送一个 dispatch 就要更新一次,那样代价也太大了,尤其是「当状态没有发生改变的时候,我们更新有个屁用?」

这里的没有改变,说的就是 Store 了。

//核心代码
const store = reducer(this.store, action)
if(store !== this.store)
....

在这里我们看到,Store 如果还是原来的那个 Store 那么就不会更新。你在 reducer 中直接修改 store 是不可能对 store 进行更新的,所以我们必须要每次:

{...state,....}

这就是 immutable 的核心了,这种模式的好处,简单来说就是实现起来非常简单。

immutable 的最大毛病


immutable 的最大毛病就是在更新深层次的嵌套 object 以及性能上,深层次怎么说呢,看例子:
const fuckDeep = {
    shit:{
        canNotMakeUp:[ {
            haha:[ {hello:1},{world:2} ]
        } ]
    }

//我们要更新fuckDeep.shit.canNotMakeup[0].haha[0].hello的值

有时候,我们的数据结构就是这样,我们想通过修改 :

fuckDeep.shit.canNotMakeup[0].haha[0].hello = 'oh shit'

这么做是不可能的,我们的做法就是对其进行 递归深拷贝,制造一个层次一样的新对象,并且带着 ‘oh shit’ 的值。

由此,因为我们可能要对对象进行大量的操作,所以我们必须要每次 递归深拷贝 执行这些操作,这就带来了严重的性能问题。

有没有办法优雅的解决这样的问题?

人类是聪明的,解决的方案其实早就有了,那就是 immutable.js 这玩意,这玩意是 fb 出的,哈哈,不能不说,这玩意又有自己的一套 API ,学起来也是够费力的,他的作用大约就是让你能够使用这样的形式:

fuckDeep.shit.canNotMakeup[0].haha[0].hello = 'oh shit'

但是,因为 API 太多了,所以大家都懒得用(就像原来的 Context API 那样)

那么这时候,immer 出现了,他只有一个 API:

const fuckDeep = {
    shit:{
        canNotMakeUp:[ {
            haha:[ {hello:1},{world:2} ]
        } ]
    }

const newState = product(fuckDeep, (draft) => {
      draft.shit.canNotMakeup[0].haha[0].hello = 'oh shit'
})

newState.shit.canNotMakeup[0].haha[0].hello === 'oh shit' //true
newState === fuckDeep ? // false

没错了,就是这么简单。

集成到 Rectx

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider, Controller, Listen } from 'rectx'

class LikeController extends Controller {
    state = {
        isLike: false,
        isMount: false
    }

    handleClick = () => {
        this.setState((draft)=>{
              draft.isLike = true
        })
    }
}

const Like = () => (
    <Listen
        to={[LikeController]}
    >
        {like => (
            <div>
                <button onClick={() => like.handleClick()}>Click me</button>
                <div>{like.state.isLike ? 'I love you' : 'I hate you'}</div>
            </div>
        )}
    </Listen>
)


ReactDOM.render(
    <Provider>
        <Like />
    </Provider>,
    document.getElementById('root')
)

值得注意的是:

 handleClick = () => {
        this.setState((draft)=>{
              draft.isLike = true
        })
    }

我们在 setState 的时候,使用的是函数形式,这个函数你不需要返回任何东西,你只需要修改里面的状态,爱怎么改怎么改。

当 setState 完毕以后,你 Listen 的函数,就会进行更新

总结一下

第二集中我们着重处理了几大痛点:

  • 版本问题:现在的 Rectx 可以使用到 React >15 的版本中
  • 修改 State 的痛苦:引入 immer ,对其进行封装,轻松愉快的解决了问题
  • 单元测试:没有单元测试,连我自己都他妈不敢用!!!

Yeah ,到此为止~谢谢大家观看

仓库地址:仓库 喜欢的给点:sparkles:哦~

#2

试了一下第一集中的真实例子:https://l970jx93pz.codesandbox.io/,很慢,点击按钮,有很明显的延迟,为什么?如果一个这么简单的实现都这么慢,怎么可能实用?

#3

你要注意看一下这个例子,例子里是一个:await 函数。

#4

果然,降低其中的延迟值,效果好多了,有了一试的欲望。
这几天正在试NextJS-Starter, 发现它有时候也很慢,所以就怕那种看上去很美丽,但是不实用或很容易用错了的东西。