Rematch: Redux 的从新设计

难道如今状态管理不是一个能够解决的问题吗?直观地说,开发人员彷佛知道一个隐藏的事实:状态管理的使用彷佛比须要的更困难。在本文中,咱们将探讨一些你可能一直在问本身的问题:javascript

  • 你是否须要一个用于状态管理的库?
  • Redux 的受欢迎程度是否值得咱们去使用? 为何或者为何不值得?
  • 咱们可否制定更好状态管理解决方案吗?若是能,要怎么作?

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!前端

状态管理须要一个库吗

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

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

clipboard.png

1. Component State (组件状态)

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

2. Relative State (关联状态)

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

3. Provided State (供给状态)

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

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

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

状态管理因为设计变动而变得复杂,并且一般很难判断哪些组件须要哪些状态。最直接的选择是从根组件提供全部状态,若是真要这么作的话,那么选用下一种方式会更好。数组

4. External State (外部状态)

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

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

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

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

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

clipboard.png

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

为何使用 Redux

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

clipboard.png

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

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

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

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

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

Redux 是否是太复杂了?

是的。有几个不能否认的迹象代表 API 须要改进,这些能够用下面的方程来总结

clipboard.png

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

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

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

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

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

从新设计Redux

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

1.初始化

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

clipboard.png

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

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

2. 简化 reducers

Redux 中的 reducers 能够经过一个转换,让咱们远离已经习惯但没必要要且冗长的 switch 语句。

clipboard.png

假设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?这难道不是一种拙劣的实践吗?

clipboard.png

如上图右边所示,难道咱们就不能只使用 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 中所涉及的概念进行合并分组,那么咱们能够得出下面这个更简单的模式。

clipboard.png

能够从 reducer 中自动肯定 action creator。 毕竟,在这种状况下,reducer 能够成为action creator

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

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

clipboard.png

如今,从 count.increment 中,咱们能够以一个 reducer 生成 action creator。

好消息:咱们能够有一个更好的 Redux

以上这些痛点就是咱们建立 Rematch 的缘由。

clipboard.png

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

clipboard.png

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

clipboard.png

在过去的几个月里,我一直在实际业务中使用 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 的分数板

clipboard.png

Redux 并无被抛弃,并且也不该该被抛弃。

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

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

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

clipboard.png

相关文章
相关标签/搜索