Remath-Redux-的重新设计

52次阅读

共计 4545 个字符,预计需要花费 12 分钟才能阅读完成。

难道现在状态管理不是一个可以解决的问题吗? 直观地说,开发人员似乎知道一个隐藏的事实: 状态管理的使用似乎比需要的更困难。在本文中,我们将探讨一些你可能一直在问自己的问题:

  • 你是否需要一个用于状态管理的库?
  • Redux 的受欢迎程度是否值得我们去使用?为什么或者为什么不值得?
  • 我们能否制定更好状态管理解决方案吗? 如果能,要怎么做?

状态管理需要一个库吗

作为前端开发人员,不仅仅是布局,开发的真正艺术之一是知道如何管理存储状态。简而言之: 状态管理是复杂的,但又并非那么复杂。

让我们看看使用 React 等基于组件的视图框架 / 库时的选项:

1. Component State (组件状态)

存在于单个组件内部的状态。在 React 中,通过 setState 方法更新state

2. Relative State (关联状态)

从父级传递给子级的状态。在 React 中,将 props 作为属性传递给子组件。

3. Provided State (供给状态)

状态保存在根 provider (提供者) 组件中,并由 consumer (消费者) 在组件树的某个地方访问,而不考虑组件之间的层级关系。在 React 中,通过 context API 可以实现。

大多数的状态都是存在于视图中的,因为它是用来反映用户界面的。那么,对于反映底层数据和逻辑的其它状态,又属于谁呢?

将所有内容都放在视图中可能会导致关注点的分离: 它将与 javascript 视图库联系在一起,使代码更难测试,而且可能最大的麻烦是: 必须不断地思考和调整存储状态的位置。

状态管理由于设计变更而变得复杂,而且通常很难判断哪些组件需要哪些状态。最直接的选择是从根组件提供所有状态,如果真要这么做的话,那么选用下一种方式会更好。

4. External State (外部状态)

状态可以移出视图库。然后,库可以使用 提供者 / 消费者模式 连接以保持同步。

也许最流行的状态管理库是Redux。在过去的两年里,它变得越来越受欢迎。那么为什么这么喜欢一个简单的库呢?

Redux 更具性能?答案是否定的。事实上,为了每一个必须处理的新动作(action),都会稍微慢一些。

Redux 是否更简单?当然不是。

简单应当是纯 javascript:比如 TJ Holowaychuk 在 twitter 上说

那么为什么不是每个人都使用 global.state={}?

为什么使用 Redux

在表层之下,Redux 与 TJ 的根对象 {} 完全相同——只是包装在了一系列实用工具的管道 (pipeline) 中。

在 Redux 中,不能直接修改状态。只有一种方法:派发(Dispatch)一个 动作(Action)到管道中,管道会自动根据动作去更新状态。

沿着管道有两组侦听器:中间件 (middleware)订阅(subscriptions)。中间件是可以侦听传入的动作的函数,支持诸如“logger”,“devtools”或“syncWithServer”侦听器之类的工具。订阅是用于广播这些状态更改的函数。

最后,合成器(Reducer)函数负责把状态变更拆分成更小、更模块化、更容易管理的代码块。

和使用一个全局对象相比,Redux 确实简化了开发过程。

将 Redux 视为一个带有更新前 / 更新后钩子的全局对象,以及能够以简单的方式合成新状态。

Redux 是不是太复杂了?

是的。有几个不可否认的迹象表明 API 需要改进,这些可以用下面的方程来总结

time_saved来表示你开发自己的解决方案所花费的时间,time_invested相当于阅读文档,学习教程和研究不熟悉的概念所花费的时间。

Redux 是一个拥有陡峭学习曲线的小型库。虽然有不少开发者能够克服深入学习函数式编程的困难并从 Redux 获益良多,但是也有很多开发者望而却步,宁愿重新使用 jQuery。

使用 jQuery 你不需要理解“monad”是什么,你也不需要为了使用 Redux 去理解函数组合。

使用 jQuery 你不需要理解“comonad”是什么,你也不需要为了使用 Redux 去理解函数组合。

任何框架或者库的目的都应该是把复杂的事物抽象得更加简单。

重新设计 Redux

我认为 Redux 值得重写,至少有以下 6 个方面可以改进得更友好。

1. 初始化

让我们来看看一个基本的 Redux 初始化过程,如下图左边所示:

许多开发人员在第一步后就在这里暂停,茫然地盯着深渊。什么是 thunkcompose?一个函数能做到这些吗?

如果 Redux 是基于配置而不是函数组合的话,那么像右边那样的初始化过程明显看起来更加合理。

2. 简化 reducers

Redux 中的 reducers 可以通过一个转换,让我们远离已经习惯但不必要且冗长的 switch 语句。

假设 reduceraction类型匹配,那么我们可以对参数进行反转,这样每个 reducer 都是一个接受 stateaction 的纯函数。也许更简单,我们可以标准化 action 并仅传入 state 和有效负载(payload)。

3. 使用 Async/Await 代替 Thunks

thunk 通常用于在 Redux 中创建异步 action。在许多方面,thunk 的工作方式看起来更像是一个聪明的黑客,而不是官方推荐的解决方案。我们一步一步来看:

  1. 你派发一个 action(dispatch an action),它实际上是一个函数而不是预期的对象。
  2. thunk 中间件检查每个动作,看看它是否是一个函数。
  3. 如果是,中间件调用该函数,并传入一些 store 的方法:dispatchgetState

怎么会这样?一个简单的 action 到底是作为一个动态类型的对象、一个函数,还是一个 Promise?这难道不是一种拙劣的实践吗?

如上图右边所示,难道我们就不能只使用 async/await?

4. 两种 action

仔细想想,其实有两种 action

1.reducer action: 触发 reducer 并改变状态。

2.effect action:触发异步 action,这可能会调用 reducer 操作,但异步函数不会直接更改任何状态。

将这两种类型的 action 区分开来,将比上面的 thunk 用法更有帮助,也更容易理解。

5. 不再有 action 类型(action.type)变量

为什么我们的标准实践要把 action creator 和 reducer 区分开来呢?能否只用其中一个呢?改变其中一个又是否会影响到另一个?

action creator 和 reducer 是同一枚硬币的两面。

const ACTION_ONE = ‘ACTION_ONE’是分离 action creators 和 reducers 的一个冗余产物。应将两者视为一体,并且不再需要文件导出类型的字符串。

6.reducers 即 action creators

按照使用方式,把 Redux 中所涉及的概念进行合并分组,那么我们可以得出下面这个更简单的模式。

可以从 reducer 中自动确定 action creator。毕竟,在这种情况下,reducer 可以成为 action creator

使用一个基本的命名约定,下面是可预测的:

  1. 如果 reducer 命名为 increment,那么 type 就是 increment。更好的做法是加上命名空间 “count/increment”
  2. 每个 action 都通过 payload 键来传递数据。

现在,从 count.increment 中,我们可以以一个 reducer 生成 action creator。

好消息:我们可以有一个更好的 Redux

以上这些痛点就是我们创建 Rematch 的原因。

Rematch 对 Redux 进行了封装,提供更简单的 API,但又不失任何可配置性的特点

请参见下面的一个完整的 Rematch 示例:

在过去的几个月里,我一直在实际业务中使用 Rematch。作为证明,我会说:状态管理从未变得如此简单、高效。

Redux 与 Rematch 的对比

Redux 是一个出色的状态管理工具,有键全的中间件生态与出色的开发工具。

Rematch 在 Redux 的基础上构建并减少了样板代码和执行了一些最佳实践。

说得清楚点,Rematch 移除了 Redux 所需要的这些东西:

  • 声明 action 类型
  • action 创建函数
  • thunks
  • store 配置
  • mapDispatchToProps
  • sagas

让 Redux 与 Rematch 作对比有助于让理解更加清晰。

Rematch

1.model

import {init} from '@rematch/core'

const count = {
  state: 0,
  reducers: {upBy: (state, payload) => state + payload
  }
}

init({model: { count}
})

2.View

import {connect} from 'react-redux'

// Component

const mapStateToProps = (state) => ({count: state.count})

const mapDispatchToProps = (dispatch) => ({countUpBy: dispatch.count.upBy})

connect(mapStateToProps, mapDispatchToProps)(Component)

Redux(最佳实践)

1.store

import {createStore, combineReducers} from 'redux'
// devtools, reducers, middleware, etc.
export default createStore(reducers, initialState, enhancers)

2.Action Type

export const COUNT_UP_BY = 'COUNT_UP_BY'

3.Action Creator

import {COUNT_UP_BY} from '../types/counter'

export const countUpBy = (value) => ({
  type: COUNT_UP_BY,
  payload: value,
})

4.Reducer

import {COUNT_UP_BY} from '../types/counter'

const initialState = 0

export default (state = initialState, action) => {switch (action.type) {
    case COUNT_UP_BY:
      return state + action.payload
    default: return state
  }
}

5.view

import {countUpBy} from '../actions/count'
import {connect} from 'react-redux'

// Component

const mapStateToProps = (state) => ({count: state.count,})

connect(mapStateToProps, { countUpBy})(Component)

Rudex 与 Rematch 的分数板

Redux 并没有被抛弃,而且也不应该被抛弃。

只是,我们应该以更低的学习成本,更少的样板代码和更少的认知成本,来拥抱 Redux 背后的简单哲学。

你的点赞是我持续分享好东西的动力,欢迎点赞!

欢迎加入前端大家庭,里面会经常分享一些技术资源。

正文完
 0