React+Redux入坑指南
Redux
原理
1. 單一資料來源
all states => Store
- 隨著元件的複雜度上升(包括互動邏輯和業務邏輯),資料來源逐漸混亂,導致元件內部資料呼叫十分複雜,會產生資料冗餘或者混用等情況。
- Store 的基本思想是將所有的資料集中管理,資料通過 Store 分類處理更新,不再在元件內放養式生長。
2. 單向資料流
dispatch(actionCreator) => Reducer => (state, action) => state
- 單向資料流保證了資料的變化是有跡可循且受控制的。
- 通過繫結 Store 可以確定唯一資料來源。
- actionCreator 通過 dispatch 觸發,使元件內事件呼叫邏輯清晰,具體的事件處理邏輯不用放在元件寫,保持 view 層的純淨。
- Reducer 通過判斷不同的 actionType 處理不同資料更新,保證資料有秩序更新。
React + Redux
Action
- actionType 定義操作型別
- actionCreator 定義操作具體執行函式
1. Action 基礎寫法
- actionType 提供給 Reducer 判斷動作型別
- actionCreator 為可呼叫的執行函式,必須返回 actionType 型別
// actionType
export const ACTION_TYPE = `ACTION_TYPE`;
// actionCreator
let actionCreator = (config) => {
return {
type: ACTION_TYPE, // 必須定義 type
config // 傳遞引數 => reducer
}
}
2. Action 非同步解決方法
- redux-thunk 中間層做資料非同步轉換
- redux-saga 使用 ES6 generator / yield
2.1 redux-thunk 使用方法
-
redux-thunk 配置
redux-thunk 為獨立工具,需要另外安裝,通過 redux 提供的中介軟體 applyMiddleware ,繫結到 store 中。
import { createStore, applyMiddleware } from `redux`;
import thunk from `redux-thunk`;
import reducers from `../reducers`;
let store = createStore(
reducers,
applyMiddleware(thunk)
);
-
Action 使用 redux-thunk
獲取資料方法在非同步獲取資料後需要再次呼叫接收方法接收資料。
// 接收方法
let receiveSomething = (res) => {
return {
type: RECEIVE_SOME,
res
}
}
// 獲取資料方法
export let fetchSomething = (args) => {
return dispatch => {
return fetch(args).then((res) => {
return dispatch(receiveSomething(res))
})
}
}
Reducer
- 引入 Action 中定義好的 actionType
-
傳入 初始資料 和 actionType 後,返回更新資料
`(initialState, action) => newState`
Reducer 基礎寫法
1.依據不同執行 ActionType 直接更新狀態
import { ACTION_A, ACTION_B } from `../actions`;
let initialState = { ... }
function example(state = initialState, action) {
switch(action.type) {
case ACTION_A:
return Object.assign({}, state, action.config)
case ACTION_B:
return Object.assign({}, state, action.config)
}
}
2.對 Action 傳遞的資料多加一層處理
let doSomething = (config) => {
let { a, b } = config;
// do something with a, b
return { a, b }
}
function example(state = initialState, action) {
switch(action.type) {
case ACTION_TYPE:
return Object.assign({},
state,
doSomething(action.config))
}
}
3.合併多個 Reducer
通過 redux 提供的 combineReducers 將不同處理邏輯的 reducer 合併起來。
import { combineReducers } from `redux`;
export default combineReducers({
reducerA,
reducerB
});
// or
export let reducer = (state = initialState, action) {
a: processA(state.a, action),
b: processB(state.b, action)
}
Store
1. 將 Store 繫結 React
使用 react-redux 提供的 Provider 可以將 Store 注入到 react 中。
Store 將合併後的 reducers 通過 createStore 建立,此外下面示例程式碼還使用中介軟體加入了一層 react-thunk 處理。
import ReactDOM from `react-dom`;
import { createStore, applyMiddleware } from `redux`;
import { Provider } from `react-redux`
import thunk from `redux-thunk`;
import reducers from `./reducers`;
let store = createStore(
reducers,
applyMiddleware(thunk)
);
ReactDOM.render((
<Provider store={store}>
// ...
</Provider>
), document.querySelector(`#app`));
2. 將 state 繫結到 Component
使用 react-redux 提供的 connect 方法 將元件和所需資料繫結。
需要注意的是,Store 建立時接收的是合併後的 reducers, 因此不同 reducer 上的處理資料繫結在了不同 reducer 物件上,而不是全部掛載在 Store 上。
mapStateToProps 將元件內部所需資料通過 props 傳入元件內部。更多繫結機制,具體可參考connect
import React, { Component } from `react`;
import { connect } from `react-redux`;
class ComponentA extends Component {
//...
}
let mapStateToProps = (state) => {
// attention !!!
let { reducerA, reducerB } = state;
return {
propA: reducerA.propA,
propB: reducerB.propB
}
};
export default connect(mapStateToProps)(ComponentA);
Component
1. 概念
React bindings for Redux embrace the idea of separating presentational and container components.
Redux 的 React 繫結庫包含了 容器元件和展示元件相分離 的開發思想。
- Presentational Components 展示型元件
- Container Components 容器型元件
展示型元件和容器型元件的區別在官方文件中已經給出很詳細的解釋了,但是中文文件的翻譯有誤,所以直接看英文比較更容易懂。
Presentational Components | Container Components | |
---|---|---|
Purpose | How things look (markup, styles) | How things work (data fetching, state updates) |
Aware of Redux | No | Yes |
To read data | Read data from props | Subscribe to Redux state |
To change data | Invoke callbacks from props | Dispatch Redux actions |
Are written | By hand | Usually generated by React Redux |
元件型別區分的模糊點在於怎麼界定元件的內部功能規劃。如果判定一個元件為展示型元件,那麼它所需資料和處理方法都應該從父級傳入,保持元件內部“純淨”。
在實際開發中,一個元件的邏輯跟業務緊密相關。如果需要將資料和方法從外部傳入,那麼父級元件所做的事情會很多,多重的子元件也會把父級邏輯弄亂,這就不是 redux 的初衷了。
中文文件翻譯的意思是:容器元件應該為路由層面的元件,但這樣既不符合實際開發需要,也違背了 redux 思想。真正界定兩種元件的因素是:
- 展示型元件: 類似純模板引擎,外加一層樣式渲染,只負責渲染從props傳進來的資料或者監聽事件和父元件做小聯動。它是“純淨”的,不需要使用到 Redux 的一套規則。
- 容器型元件: 需要非同步獲取資料,更新元件狀態等等。需要跟業務邏輯打交道的元件都可以認為是容器元件。這些邏輯的複雜性需要將資料整合到 Store 裡統一管理。
2. Component 基礎寫法
- 元件渲染完成後呼叫Action
當元件 connect 後,dispatch 方法已經注入到 props 中,所以觸發 Action 可以從 props 獲取 dispatch 方法。
import React, { Component } from `react`;
// actionCreator
import { actionA, actionB } from `actions/actionA`
class ComponentA extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
let { dispatch } = this.props;
dispatch(actionA())
}
}
export default connect()(ComponentA);
- 元件模板內呼叫Action
元件內部所需的渲染資料都已經繫結在了 props 上,直接獲取即可。
需要注意的是,在事件監聽中觸發 Action,需要用一個匿名函式封裝,否則 React 在渲染時就會執行事件繫結事件,而不是當事件發生再執行。
render() {
let { dispatch, propA, propB } = this.props;
return (
<section>
// Attention !!!
<input type="text" onClick={(ev) => dispatch(actionB(ev))} />
<p className={propA}>{propB}</p>
</section>
)
}
- 容器元件傳遞方法
容器型元件需要連線 Redux,使用 dispatch 觸發 actionCreator。
展示型元件需要用到的方法呼叫在容器型元件內定義好,通過 props 傳入到展示型元件中。
// get actionCreator
import { actionA } from `./actions/actionA`;
class Parent extends Component {
handleCallback(data) {
// use dispatch
let { dispatch } = this.props;
dispatch(actionA(data));
}
render() {
return (
<Child onSomethingChange={this.handleCallback} />
)
}
}
// connet Redux
export default connect()(Parent);
- 展示元件接收props
展示型元件不需要用到 Redux 的一切,它的 props 僅僅存在於父級傳入的資料和方法。
// don`t need action/dispatch/connect
class Child extends Component {
handleSomething(data) {
// handle anything with props
this.props.onSomethingChange(data);
}
render() {
return (
// just markup & style
<input onChange={handleSomething} />
)
}
}
Conclusion
圖示箭頭代表各概念之間的相互關係,不代表資料流。( 能理解下面這張圖,這篇文章就沒白看了 -。- )
參考文件
END.
相關文章
- UIStackView 入坑指南UIView
- vim 入坑指南
- uni-app 入坑指南APP
- Oracle函式入坑指南Oracle函式
- rust入坑指南之ownershipRust
- Omi 入坑指南 Third field 事件入門事件
- Go Web開發入坑指南GoWeb
- 2021年 Istio 大型“入坑”指南
- Omi 入坑指南 第四場 Router
- React Router v4 入坑指南React
- CTF萌新入坑指南(web篇)Web
- Flutter 入坑指南(dio +fish_redux)FlutterRedux
- 針不戳!GitHub Actions 入坑指南Github
- Flutter入坑指南:開發環境搭建Flutter開發環境
- 小米安全Dayeh:《Spring Security入坑指南1》Spring
- 小米安全Dayeh:《Spring Security入坑指南2》Spring
- Omi 入坑指南 The second floor 初步接觸
- 小程式入坑指南 | 鵝廠優文
- 踩坑指南:入門OpenTenBase之部署篇
- Rust入坑指南:海納百川Rust
- Angular 從入坑到挖坑 - 元件食用指南Angular元件
- webpack入門及踩坑應對指南Web
- 高效能非同步框架Celery入坑指南非同步框架
- 踩坑指南:入門OpenTenBase之監控篇
- Flink入坑指南第一章-簡介
- mpvue“踩坑”指南Vue
- phppresentation 踩坑指南PHP
- indexDB出坑指南Index
- Flutter入坑指南:編寫第一個Flutter應用Flutter
- 給 Web 開發人員的以太坊入坑指南Web
- 給Web開發人員的以太坊入坑指南Web
- Vue3.0新版API之composition-api入坑指南VueAPI
- 坑爹的Python陷阱(避坑指南)Python
- React Native踩坑指南:ios鍵盤遮擋輸入框React NativeiOS
- React+Redux之bindactioncreators的用法ReactRedux
- 基於 MVC 理解 React+ReduxMVCReactRedux
- Electron 打包爬坑指南
- ?踩坑指南——onnx系列
- Go 切片繞坑指南Go