/

學習如何使用 Redux

學習如何使用 Redux

Redux 是一個狀態管理器,通常與 React 一起使用,但並不限於該庫。通過閱讀這個簡單且易於理解的指南,來學習 Redux。

為什麼需要 Redux

Redux 是一個狀態管理器,通常與 React 一起使用,但它並不限於該庫 - 它也可以與其他技術一起使用。React 有自己的狀態管理方式,你可以在 React 初學者指南 中了解有關在 React 中管理狀態的方法。

將狀態向上移動到樹中適用於簡單的情况,但在複雜的應用程序中,你可能會發現你需要將幾乎所有的狀態向上移動,然後再使用 props 將其向下傳遞。

React 在 16.3.0 版本中引入了上下文 API,從不同部分訪問狀態的用例中,這使得 Redux 變得多餘,因此可以考慮使用上下文 API 而不是 Redux,除非你需要 Redux 提供的特定功能。

Redux 是一種管理應用程序狀態並將其移至外部全局存儲區的方法。有一些概念需要掌握,但一旦掌握,Redux 可以非常簡單地解決問題。

Redux 在 React 應用程序中非常流行,但它不僅僅適用於 React:幾乎有任何流行框架的綁定。那麼,我將使用 React 做一些示例,因為這是它的主要用例。

何時應該使用 Redux?

Redux 適用於中大型應用程序,在使用 React 或其他庫的默認狀態管理時遇到問題時,應把它用於項目中。

簡單的應用程式不需要它(簡單的應用程式沒有問題)。

不可變的狀態樹

在 Redux 中,應用程序的整個狀態由一個名為狀態狀態樹的 JavaScript 對象表示。

我們稱其為不可變的狀態樹,因為它是只讀的:不能直接更改它。狀態只能通過分發動作來更改。

Actions

動作是一個以最小方式描述變更的 JavaScript 對象(僅帶有所需的信息):

1
2
3
4
5
6
7
8
9
{
type: 'CLICKED\_SIDEBAR'
}

// 例如,帶有更多數據的動作
{
type: 'SELECTED\_USER',
userId: 232
}

動作對象的唯一要求是具有type屬性,其值通常是一個字符串。

Action types 應該是常數

在簡單的應用程序中,動作類型可以定義為字符串。

當應用程序增長時,最好使用常數:

1
2
const ADD\_ITEM = 'ADD\_ITEM'
const action = { type: ADD\_ITEM, title: 'Third item' }

並將動作分離到自己的文件中,並導入它們:

1
import { ADD\_ITEM, REMOVE\_ITEM } from './actions'

Action creators

動作創建器是創建動作的函數:

1
2
3
4
5
6
function addItem(t) {
return {
type: ADD\_ITEM,
title: t
}
}

通常與觸發調度程序一起運行動作創建器:

1
dispatch(addItem('Milk'))

或通過定義動作調度程序函數來運行它:

1
2
const dispatchAddItem = i => dispatch(addItem(i))
dispatchAddItem('Milk')

Reducers

當動作被觸發時,必須發生某些事情,應用程序的狀態必須發生變化。這是reducer的工作。

什麼是 reducer

reducer是一個純函數,它根據先前的狀態樹和分發的動作計算下一個狀態樹:

1
;(currentState, action) => newState

純函數在不更改輸入或其他任何東西的情況下接收輸入並返回輸出。因此,reducer 返回一個完全替換先前狀態的全新狀態。

Reducer 不應該做什麼

reducer 應該是一個純函數,因此它應該:

  • 永遠不要更改它的參數
  • 永遠不要更改狀態,而是使用Object.assign({}, ...)創建一個新的狀態
  • 永遠不要生成副作用(不更改任何東西的 API 調用)
  • 永遠不要調用非純函數,這些函數根據與其輸入不同的因素改變其輸出(例如,Date.now()Math.random()

沒有強制執行,但你應該遵守這些規則。

多個 reducer

由於複雜應用的狀態可能非常廣泛,因此不只有一個 reducer,而有多個用於任何類型的動作。

reducer 的模擬

基本上,Redux 可以用這個簡單模型來簡化:

狀態

1
2
3
4
5
6
7
{
list: [
{ title: "First item" },
{ title: "Second item" },
],
title: 'Groceries list'
}

一系列的 actions

1
2
3
{ type: 'ADD\_ITEM', title: 'Third item' }
{ type: 'REMOVE\_ITEM', index: 1 }
{ type: 'CHANGE\_LIST\_TITLE', title: 'Road trip list' }

每個部分的 reducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const title = (state = '', action) => {
if (action.type === 'CHANGE\_LIST\_TITLE') {
return action.title
} else {
return state
}
}

const list = (state = [], action) => {
switch (action.type) {
case 'ADD\_ITEM':
return state.concat([{ title: action.title }])
case 'REMOVE\_ITEM':
return state.filter(item =>
action.index !== item.index)
default:
return state
}
}

整個狀態的 reducer

1
2
3
4
5
6
const listManager = (state = {}, action) => {
return {
title: title(state.title, action),
list: list(state.list, action)
}
}

Store

Store 是一個對象,它:

  • 保持 app 的狀態
  • 通過 getState() 公開狀態
  • 通過 dispatch() 允許我們更新狀態
  • 通過 subscribe() 允許我們(取消)註冊狀態變化監聽器

store 在應用程序中是唯一的

下面是如何創建 listManager app 的 store:

1
2
3
import { createStore } from 'redux'
import listManager from './reducers'
let store = createStore(listManager)

我可以使用服務器端數據初始化 store 嗎?

當然可以,只需傳遞起始狀態

1
let store = createStore(listManager, preexistingState)

獲取狀態

1
store.getState()

更新狀態

1
store.dispatch(addItem('Something'))

監聽狀態變化

1
2
3
4
5
const unsubscribe = store.subscribe(() =>
const newState = store.getState()
)

unsubscribe()

數據流

Redux 中的數據流始終是單向的。

在 store 上調用 dispatch(),傳遞一個 action。

store 負責將 action 傳遞給 reducer,生成下一個狀態。

store 更新狀態並通知所有的監聽器。tags:Redux, JavaScript