前言
你并不一定需要 Redux 。 如果你的應用場景還不夠復雜,引入 Redux 只會徒增成本。
Flux 設計模式
學習 Redux 之前需要了解 Flux 設計模式。 Flux 模式是 Facebook 提出的一種架構模式,其中最重要的思想是單向數據流。
- Action: 規范化所有的動作。數據的修改、UI 的變化都由 Action 觸發.
- Dispatcher: Action 的分發器,將 Action 傳遞給 Store
- Store:存放數據、狀態和邏輯處理。
- View: 根據 Store 的變化進行渲染
Redux
本篇篇幅有限,主要介紹 Redux 中的一些核心概念幫助快速理解上手。網上有很多資料都詳細介紹了 Redux 的各個部分。React 小書 這份文檔詳細介紹了 React 和 Redux 的原理
Store & Action & Reducer & Dispatcher
Redux 采用單一數據源的模式,整個工程全局只有一個 Store。Store 內存儲全局數據和狀態,我們用 State 來表示。State 對外是只讀的。State 的修改只能通過接收不同的 Action 來觸發。我們寫一些純函數來處理不同的 Action,修改完值后返回一個新的 State,這種純函數叫 Reducer。
public reducer(state, action) {
switch (action.type) {
case type1:
return state1;
case type2:
return state2;
default:
return state;
}
}
Store 會有一個 dispatch 方法,用于分發 action 到對應的 reducer 上。
store.dispatch(action);
以上即是 Redux 最核心的一些概念,分別和 Flux 的設計圖的概念一一對應。但是僅有這些東西,在工程實踐上仍然不是很方便,下面我們再看下怎么把這些東西分別應用到業務中的各個場景下。
集成
yarn add redux react-redux
創建 Store
全局只有一個 Store,所以需要在進入應用之前創建 Store 對象,并作用到全局。
import { createStore } from 'redux';
const store = createStore(reducer);
react-redux 庫提供了一個 Provider 組件,專門負責將 Store 作用于全局。
import { Provider } from 'react-redux';
function Application() {
return (
<Provider store={store}>
<AppContaienr />
</Provider>
);
}
創建 Reducer
創建 store 對象時需要關聯上對應的 reducer. 前面說過,reducer 就是用于修改 state 的純函數.
export default (state = initialState, action: Action): State => {
switch (action.type) {
case ACTION_TYPE_INIT:
return initialState;
case ACTION_TYPE_ADD_TASK:
return {
...state,
tasks: addTask(state.tasks, action)
};
case ACTION_TYPE_EDIT_TASK:
return {
...state,
tasks: changeTask(state.tasks, action)
}
}
return state;
}
業務邏輯比較龐大的時候,我們不可能把所有 action 判斷都放在一個 reducer 內,可以根據類型拆分成很多個 reducer。redux 提供了一個 combineReducers 的工具來做 reducer 的合并。
import {createStore, combineReducers} from 'redux';
import * as reducers from '../reducers';
const reducer = combineReducers(reducers);
const store = createStore(reducer);
Action
Action 其實只是一個普通對象,不過按照約定俗成的習慣,每個 action 都會有一個 type 屬性。
export interface Action {
type: string;
[prop: string]: any;
}
根據不同的場景創建不同的 Action 對象扔給 Store 做分發即可觸發 State 的需改。State 的變化最終又會反饋到 UI 的變化上。但是每次都手動 new 一個 Action 對象很麻煩,而且容易造成代碼耦合。我們可以設置一些函數專門用來創建不同的 action。
export function addTask(name: string, startTime: number, endTime: number, desc?: string, ): Action {
return {
type: ACTION_TYPE_ADD_TASK,
name,
startTime,
endTime,
desc,
} as Action;
}
這類函數叫 ActionsCreators.
connect 函數
在前文中提到過 Smart 組件和 Dumb 組件。在開發中我們盡量將組件 Dumb 化,Smart 組件用于組合 Dumb 組件和實現業務邏輯。但是現在我們已經將業務邏輯遷移到了 reducer 中了,也就沒有了 Smart 組件存在的必要了。
剩下需要做的就是怎么將 Store 中的 State 與 Dumb 組件中的 props 關聯起來。這里需要用到 react-redux 中的 connect 輔助函數。
connect([mapStateToProps], [mapDispatchToProps])
connect 函數接受兩個可選參數。mapStateToProps 和 mapDispatchToProps.
mapStateToProps
Component 的渲染需要由父組件傳遞 props, props 的真實值存儲在 State 中,所以這里實現一個 mapStateToProps 函數,設置好當前 Component 所需要的 props 分別對應 state 中的哪些屬性。
(state: State) => ({ tasks: state.tasks })
mapDispatchToProps
當 Component 觸發了一些操作需要修改 State 時,需要反向發起 Action。 但是對于 Dumb 組件來說,它只需要關心把對應的數據傳遞出去即可。所以 Component 接受的應該是一個 function,觸發操作只需要調用 function。 而 function 是由外部實現,封裝一個 action,并調用 dispatch,這就是 mapDispatchToProps 函數。
dispatch => ({
addTask: (name: string, startTime: number, endTime: number) => dispatch(addTaskAction(name, startTime, endTime))
})
再看下完整一點的代碼:
interface Prop {
tasks: Array<Task>,
navigation: NavigationScreenProp<NavigationState>,
addTask: (name: string, startTime: number, endTime: number) => void;
}
class TodosScreen extends React.Component<Prop> {
...
}
export default connect((state: State) => ({
tasks: state.tasks
}), dispatch => ({
addTask: (name: string, startTime: number, endTime: number) => dispatch(addTaskAction(name, startTime, endTime))
}))
(TodosScreen);
Redux 中間件
Redux 的中間件提供的是位于 action 被發起之后,到達 reducer 之前的擴展點。我們可以通過集成第三方或自己實現的中間件來擴展功能。
const createStoreWithMiddleware = applyMiddleware(thunk, logger)(createStore); //創建中間件
const store = createStoreWithMiddleware(reducer);
可以根據需求加入不同類型的中間件。
參考
- Redux 中文文檔
- React 小書
- 以上代碼已傳至 Github: ReactNativeTodos