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

相關文章
相關標籤/搜索