React狀態管理專題:深入探討下Redux的三大原則

今天,我們將深入挖掘Redux的靈魂所在——其核心原則。通過理解單一事實來源(Single Source of Truth)、狀態的只讀性(Read-only State)以及如何通過純函數(Pure Functions)來執行狀態變化,我們不僅能夠更好地把握Redux的設計哲學,還能在實際開發中更加得心應手地應用它。這些原則不僅為Redux的強大功能奠定了基礎,也為我們提供了清晰、可靠的狀態管理方案。
前置知識簡介
在深入Redux之前,有幾項技術是必須要了解的:
- Node.js:一種能讓開發者在服務器端運行JavaScript代碼的運行時環境。對于搭建開發環境、管理依賴關系、構建基于Redux的客戶端或服務端應用來說,Node.js的理解是基礎。
- React:這是一個用于構建用戶界面的JavaScript庫。Redux通常與React一起使用,以更加可預測和高效地管理應用狀態。因此,深入理解React對于掌握Redux原則至關重要。
- React-Redux:它是Redux的官方React綁定庫,提供了一系列輔助函數,使React組件能夠無縫地與Redux存儲進行交互。了解React-Redux以及React組件與Redux存儲之間的數據流動對于實現和理解Redux的三大原則非常重要。
除了上述技術,對JavaScript生態系統的廣泛理解,包括使用npm或Yarn這樣的包管理器、使用Webpack進行打包、Babel進行代碼轉譯以確保跨環境兼容性、使用ESLint進行代碼檢查,都是搭建Redux應用的重要組成部分。
Redux三大原則
- 單一數據源:在Redux中,整個應用的狀態被存儲在一個對象樹中,并且這個對象樹只存在于唯一的一個存儲中。這樣的設計不僅使得狀態的管理變得更加可預測,而且也便于開發者進行狀態追蹤和調試。
- 狀態是只讀的:唯一改變狀態的方式是觸發一個動作(action),動作是一個用于描述已發生事件的普通對象。這種方式確保了視圖或網絡回調不能直接修改狀態,而是必須通過分發動作的方式,保證了數據流的清晰和一致性。
- 使用純函數來執行修改:為了描述動作如何改變狀態樹,你需要編寫reducers。Reducer是一種特殊的函數,根據舊的狀態和一個動作,返回一個新的狀態。關鍵在于,reducers必須是純函數,這意味著它們應該只計算下一個狀態,而不改變原始狀態。

1.單一數據源的魅力:簡化數據管理與調試
Redux的核心之一是將整個應用的狀態集中存儲在一個被稱為“Redux store”的單一對象中。這個原則有幾個關鍵的好處:
- 簡化數據管理:將所有的狀態存儲在一個地方,使得狀態的讀取、更新變得非常直接和集中,從而極大地簡化了數據管理的復雜度。
- 便于調試:當應用出現問題時,你可以很容易地在一個地方找到應用的當前狀態,而不需要在多個組件或者模塊之間追蹤狀態的變化。
- 保證數據一致性:由于應用的所有狀態都來自于同一個源頭,因此可以有效地防止狀態不一致的情況出現,確保了應用的穩定性和可靠性。
Redux Actions:
在一個計數器應用中,我們通常會有增加(INCREMENT)和減少(DECREMENT)兩種操作。在Redux中,這兩種操作會被定義為動作(Actions):
// 定義增加和減少的動作類型
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';這些動作類型代表了觸發狀態變化的事件。在Redux中,改變狀態的唯一方式是通過分發(dispatch)一個動作。這樣做的好處是讓所有狀態的改變都可預測且可追蹤。在計數器應用的例子中,無論是增加還是減少計數,都會通過分發對應的動作來實現,這些動作最終會被送到reducer函數,根據動作的類型來更新狀態。
Reducer:精確控制狀態變化
在Redux架構中,Reducer扮演著至關重要的角色。它負責定義應用狀態如何響應不同的動作(Actions)并返回新的狀態。這個過程不僅保證了狀態的不可變性,而且也確保了應用狀態變化的可預測性。通過深入理解和合理利用Reducer,我們可以更加精確地控制應用的狀態變化,從而打造出既穩定又高效的應用。
以計數器應用為例,counterReducer函數展示了一個Reducer的基本結構:
export const counterReducer = (state = { value: 0 }, action) => {
switch (action.type) {
case INCREMENT:
return { value: state.value + 1 };
case DECREMENT:
return { value: state.value - 1 };
default:
return state;
}
};在這個例子中,Reducer接受當前的狀態和一個動作作為參數。根據動作的類型,Reducer決定如何更新狀態,并返回一個新的狀態對象。這里有幾點值得注意:
- 初始狀態:Reducer可以接受一個初始狀態,這里是{ value: 0 },代表計數器的起始值。
- 不可變性:在處理狀態更新時,Reducer遵循不可變性原則,即總是返回一個新的狀態對象,而不是修改當前的狀態。這有助于避免狀態管理中的一些常見問題,比如狀態污染和更新沖突。
- 動作處理:通過switch語句匹配動作類型,Reducer根據不同的動作來更新狀態。在我們的例子中,INCREMENT動作使計數器的值增加1,而DECREMENT動作則使其減少1。
Redux Store:應用狀態的核心
在Redux架構中,Store是連接應用與狀態管理的關鍵。它不僅保存了應用的狀態,還提供了一系列的方法來讓你能夠進行狀態的讀取、更新和監聽。通過合理配置Store,我們能夠使應用的數據流管理變得既清晰又高效。現在,我們就來詳細了解一下如何創建和配置Redux Store,以及它在應用中的作用。
創建Redux Store
創建Redux Store的過程非常直接。首先,你需要從Redux庫中引入createStore函數,然后使用這個函數來創建Store。這個過程需要一個Reducer作為參數,Reducer定義了狀態如何響應不同的動作并返回新的狀態。以我們之前提到的計數器應用為例,Store的配置過程如下:
// 引入Redux庫中的createStore函數
import { createStore } from 'redux';
// 引入之前定義的counterReducer
import { counterReducer } from './ReduxReducer';
// 使用counterReducer創建Redux store
const store = createStore(counterReducer);
// 導出store對象
export default store;在這個例子中,我們使用counterReducer來初始化Store,這意味著應用的狀態將根據counterReducer定義的規則來變化。
Store的作用和方法
創建了Store之后,它將成為應用狀態管理的中心。Store提供了幾個關鍵的方法,讓我們能夠與應用狀態進行交互:
- dispatch:這個方法用于分發動作,是觸發狀態變化的唯一方式。
- getState:通過這個方法,你可以獲取當前的應用狀態。
- subscribe:這個方法允許你添加一個狀態變化的監聽器,每當狀態變化時,監聽器會被調用。
通過這些方法,Redux Store成為了一個強大的工具,使得狀態管理變得既可控又靈活。無論是讀取當前狀態、更新狀態,還是監聽狀態的變化,Store都提供了簡單而有效的接口。
React組件
在使用Redux進行狀態管理的React應用中,將React組件與Redux的Store連接起來是一個至關重要的步驟。這不僅讓我們的組件能夠訪問應用狀態,還允許我們通過分發動作來更新這些狀態。接下來,我們通過一個計數器應用的例子,來深入了解如何實現React組件與Redux的連接。
首先,我們定義了一個React組件CounterApp,它負責渲染計數器的UI界面:
import React from 'react';
import { connect } from 'react-redux';
import { INCREMENT, DECREMENT } from './ReduxActions';
class CounterApp extends React.Component {
render() {
return (
<div>
<p>Counter Value: {this.props.value}</p>
<button onClick={() => this.props.increment()}>Increment</button>
<button onClick={() => this.props.decrement()}>Decrement</button>
</div>
);
}
}在這個組件中,我們通過this.props.value來顯示計數器的當前值,同時定義了兩個按鈕,用于觸發增加(Increment)和減少(Decrement)操作。
連接React組件與Redux
為了將CounterApp組件連接到Redux的Store,我們使用了react-redux庫中的connect函數。這個函數允許我們將Redux的狀態(state)映射到組件的屬性(props)上,以及將分發動作(dispatch actions)的函數映射到組件的屬性上:
// 將Redux狀態映射到組件的props上
const mapStateToProps = state => ({
value: state.value
});
// 將分發動作的函數映射到組件的props上
const mapDispatchToProps = dispatch => ({
increment: () => dispatch({ type: INCREMENT }),
decrement: () => dispatch({ type: DECREMENT })
});
// 連接組件
export const ConnectedCounterApp = connect(mapStateToProps, mapDispatchToProps)(CounterApp);通過mapStateToProps函數,我們將Redux的狀態中的value映射到了組件的value屬性上。通過mapDispatchToProps函數,我們創建了兩個函數increment和decrement,當調用這些函數時,會分別分發INCREMENT和DECREMENT類型的動作。

接下來,我們將深入總結下如何通過Redux Store、Redux Actions以及React組件之間的互動,來體現這一原則。
Redux Store的單一真理源
在Redux架構中,所有的應用狀態都被存儲在一個稱為Store的對象中。這個Store通過counterReducer來管理計數器的狀態,其中的value屬性表示當前的計數值。這種集中式的狀態管理方式不僅簡化了狀態的訪問和更新,還使得應用狀態的變化變得可預測和可追蹤。
Redux Actions:狀態變化的唯一途徑
在Redux中,狀態的任何變化都必須通過分發(dispatch)動作(Action)來實現。在我們的計數器應用例子中,INCREMENT和DECREMENT動作被用來分別增加和減少計數值。這些動作是改變狀態的唯一途徑,確保了狀態變化的一致性和可控性。
React組件:連接數據與界面
通過react-redux庫提供的connect函數,React組件可以直接連接到Redux的Store。組件的屬性(props)通過mapStateToProps和mapDispatchToProps函數分別映射到Store的狀態和分發動作的函數。這樣,組件不僅可以直接訪問到應用的狀態(即單一數據源),還可以通過分發動作來更新這些狀態。這種設計保證了組件的數據和行為都嚴格依賴于Redux Store,強化了單一數據源的概念。
2.狀態的不變性(state):Redux中的只讀狀態原則
在Redux架構中,狀態的不變性(即只讀狀態)是其核心原則之一。這個原則確保了一旦狀態被定義,它就不能被直接改變。任何對狀態的修改都必須通過分發動作(dispatching actions)來進行,而這些動作將被Reducer處理,從而產生一個全新的狀態。這種方法不僅使狀態變化變得可預測,而且極大地簡化了調試過程,使得開發者更容易理解應用狀態隨時間的演變,以及動作如何影響應用的數據流。
一個待辦事項應用的例子
讓我們通過一個待辦事項(To-do List)應用的例子來更深入地理解這一原則。在這個應用中,應用的狀態用來表示任務列表,遵循著只讀原則。
Redux動作(Actions)
首先,定義一個動作類型ADD_TASK,用于添加新的任務:
// 定義添加任務的動作類型
export const ADD_TASK = 'ADD_TASK';Redux Reducer
接著,通過Reducer來管理任務的狀態:
// 管理待辦事項的Reducer
export const tasksReducer = (state = { tasks: [] }, action) => {
switch (action.type) {
case ADD_TASK:
// 通過展開原有任務列表并添加新任務來返回一個新狀態
return { tasks: [...state.tasks, action.payload] };
default:
// 如果不匹配任何動作類型,返回當前狀態
return state;
}
};在這個例子中,tasksReducer接收當前狀態和一個動作作為參數。基于動作類型ADD_TASK,它通過將新任務添加到當前任務列表的副本中來返回一個新的狀態,從而遵守了狀態的不變性原則。這意味著原始的任務列表狀態保持不變,確保了狀態的可預測性和可追蹤性。
創建Redux Store
首先,我們通過Redux的createStore函數創建了一個Store,該Store使用了tasksReducer來管理待辦事項的狀態:
import { createStore } from 'redux';
import { tasksReducer } from './ReduxReducer';
const store = createStore(tasksReducer);
export default store;這個Store將作為應用的單一真理源,負責存儲和管理待辦事項列表的狀態。
定義React組件
接著,我們定義了一個React組件TodoApp,用于展示待辦事項列表并提供添加新任務的功能:
import React from 'react';
import { connect } from 'react-redux';
import { ADD_TASK } from './ReduxActions';
class TodoApp extends React.Component {
constructor() {
super();
this.state = { newTask: '' };
}
handleInputChange = (event) => {
this.setState({ newTask: event.target.value });
};
handleAddTask = () => {
const newTask = this.state.newTask;
this.props.addTask(newTask);
this.setState({ newTask: '' });
};
render() {
return (
<div>
<h1>Tasks</h1>
<ul>
{this.props.tasks.map((task, index) => (
<li key={index}>{task}</li>
))}
</ul>
<input
type="text"
value={this.state.newTask}
onChange={this.handleInputChange}
/>
<button onClick={this.handleAddTask}>Add Task</button>
</div>
);
}
}在這個組件中,我們通過輸入框接收新任務,并在點擊按鈕時通過addTask方法分發ADD_TASK類型的動作來更新Redux Store中的狀態。
連接React組件與Redux Store
最后,我們通過connect函數將TodoApp組件連接到Redux Store。mapStateToProps函數將Redux Store中的狀態映射到組件的props,使得組件能夠訪問待辦事項列表;mapDispatchToProps函數則將分發動作的方法映射到組件的props:
const mapStateToProps = state => ({
tasks: state.tasks
});
const mapDispatchToProps = dispatch => ({
addTask: newTask => dispatch({ type: ADD_TASK, payload: newTask })
});
export const ConnectedTodoApp = connect(mapStateToProps, mapDispatchToProps)(TodoApp);通過這種方式,TodoApp組件既可以訪問Redux Store中的狀態,也可以通過分發動作來更新狀態,實現了React組件與Redux狀態管理的無縫連接。

代碼案例通過以下方式遵循"狀態只讀"原則:
Redux Reducer:
tasksReducer定義了狀態如何響應動作而被修改。它通過返回一個新的狀態對象而不是直接修改現有狀態對象來遵循只讀原則。
Redux Store和Dispatch:
Redux Store是用tasksReducer創建的。當需要添加一個新任務時,會分發一個攜帶負載(新任務)的ADD_TASK動作。這個動作被reducer處理后,會創建一個新的狀態。
渲染:
任務列表基于從Redux Store獲取的當前狀態在組件中顯示。只讀的本質確保了UI反映了最新的狀態,而無需直接操作。
3.通過純函數完成修改
Redux依賴于稱為reducer的純函數來指定應用狀態如何響應動作。純函數是指給定相同輸入時,總是返回可預測的輸出而不會引起任何副作用的函數。純函數確保狀態變化是一致且可復現的,并且不會產生副作用,如修改外部變量或與DOM交互。
讓我們考慮一個簡單的計數器應用,來舉例說明使用純函數(reducer)管理Redux中的狀態變化。
// Redux動作
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// Redux Reducer
const cReducer = (state = { count_num: 0 }, action) => {
switch (action.type) {
case INCREMENT:
return { count_num: state.count_num + 1 };
case DECREMENT:
return { count_num: state.count_num - 1 };
default:
return state;
}
};
// Redux Store
const { createStore } = Redux;
const store = createStore(cReducer);
// 訂閱Redux Store變化
store.subscribe(() => {
console.log('Current Count:', store.getState().count_num);
});
// 分發動作
store.dispatch({ type: INCREMENT });
store.dispatch({ type: INCREMENT });
store.dispatch({ type: DECREMENT });counterReducer是一個純函數,它接受當前狀態和一個動作作為參數。根據動作類型,它返回一個新狀態。在這個例子中,對于INCREMENT,它將計數增加1;對于DECREMENT,它將計數減少1。Redux store被創建,然后使用store.subscribe()方法來訂閱Redux store中的變化。每當分發一個動作并修改狀態時,subscribe中的回調函數就會執行。在這個例子中,它將當前計數記錄到控制臺。使用store.dispatch()方法將動作分發到Redux store。每分發一個動作,cReducer就根據當前狀態和動作類型計算新狀態。store訂閱確保在每個動作之后,更新的計數被記錄到控制臺,提供對狀態變化的可見性。
代碼通過以下方式遵循通過純函數完成修改的原則:
Redux Reducer:
counterReducer是一個純函數,它接受當前狀態和一個動作作為參數,并返回一個新狀態而不改變原始狀態。它遵循不變性原則,確保可預測性和可追蹤性。
Redux Store和Dispatch:
Redux store使用cReducer創建。動作(INCREMENT和DECREMENT)被分發到store,觸發reducer基于分發的動作創建一個新狀態。
訂閱Store變化:
使用store.subscribe()方法來監聽Redux store中的變化。每次動作分發后,新的計數被記錄到控制臺,展示了更新的狀態而沒有直接修改。
純函數的可預測性簡化了測試和調試。開發者可以獨立地隔離并測試reducer,確保狀態變化正如所期望的,為更加穩健和可預測的代碼庫做出貢獻。
輸出:
執行提供的代碼將在控制臺中產生以下輸出:
Current Count: 1
Current Count: 2
Current Count: 1輸出顯示了使用Redux中的純函數(reducer)對計數狀態進行順序修改的結果。每個動作分發觸發reducer創建基于之前狀態和動作類型的新狀態,而不改變原始狀態。
小節
Redux是JavaScript中廣泛使用的狀態管理庫,通常與React結合使用,以在應用程序中有效地處理狀態。熟悉Node.js、React、React-Redux以及Webpack和Babel等工具對于在應用程序中使用Redux至關重要。
Redux的三個關鍵原則包括確保中心化且可訪問的存儲、執行不可變性以實現可預測性、以及利用純函數(reducers)來實現狀態之間的轉換的概念。
單一真理源原則促進了中心存儲或Redux store中的統一狀態,簡化了Redux驅動應用程序中的調試和測試過程。
Redux中的不可變性確保應用程序的狀態不能被直接修改。這一概念對于可預測性、調試和維護一致的狀態轉換至關重要。
狀態只讀原則通過防止直接修改狀態來執行不可變性和防止副作用,這在復雜應用中增強了可預測性和可追蹤性。
Redux中的純函數或reducer在確保可預測的狀態變化中起著重要作用。這些函數是確定性的,對于相同的輸入總是產生相同的輸出,并且它們沒有副作用。
通過純函數完成修改原則涉及使用reducer從舊狀態創建新狀態,確保Redux應用程序中一致且可預測的狀態轉換。
結束
隨著我們對Redux核心原則的深入探討,相信你對如何在應用中有效管理狀態有了更加深刻的認識。但理論總是服務于實踐的,接下來我們將進一步探索如何將Redux與React結合使用,通過一個實際的案例,讓這些原則和概念在真實世界中生根發芽。
























