[TOC]
# 官網
[Redux](https://redux.js.org)
# 需要狀態管理?


這種需要在每個組件都設置和管理`props`的情況,會隨著應用的發展而變得繁瑣,那么我們就會考慮每個組件都需要層次傳遞`props`嗎?
其實 `祖父 --> 父 --> 子` 這種情況,React 不使用`props`層層傳遞也是能拿到數據的,使用[Context](https://reactjs.org/docs/context.html)即可。后面要講到的 react-redux 就是通過 `Context` 讓各個子組件拿到`store` 中的數據的。
# [Flux](https://facebook.github.io/flux/)

由于原始的 Flux 架構在實現上有些部分可以精簡和改善,在實際項目上我們通常會使用開發者社群開發的 Flux-like 相關的架構實現(例如:Redux、Alt、Reflux 等)。
Flux 架構不是一個庫或者框架,它是一種解決問題的思路,它認為 MVC 框架存在很大的問題,它推翻了MVC框架,并用一個新的思維來管理數據流轉。
每種軟件架構都有它特定的適用場景,Flux也是。如果你的項目足夠簡單,全都是靜態組件,組件之間沒有共享數據,那么使用Flux 只會徒增煩惱。
## Flux 架構示例
在 Flux中,我們把應用關注點分為4種邏輯模塊:
* DIspatcher:處理動作分發,維持Store直接的依賴關系;(相當于MVC的 Controller)
```js
import {Dispatcher} from 'flux';
//創建flux中的dispatcher,由flux框架控制為單例模式的;
export default new Dispatcher();
```
* Store:負責存儲數據和處理數據相關邏輯; (相當于MVC的Model)
```js
// CounterStore 相當于一類組件的數據中心,它是單例的共享的;
// Store中包含了組件是無狀態model,其中包含了組件的初始狀態值,以普通對象的形式存在;
// 一般在store中應該提供一個get方法,用于組件獲取初始狀態的值;
// store中應該為Dispatcher注冊狀態計算函數,用于更新計算出新的數據,當組件同步這些數據后,組件的狀態會更新,渲染被驅動進行;
import AppDispatcher from '../dispatchers/Appdispatcher.js';
import * as ActionTypes from '../actions/ActionTypes';
import {EventEmitter} from 'events';
// 定義監聽事件;
const CHANGE_EVENT = 'changed';
// 定義組件的初始值,當組件加載的時候會在聲明周期函數中同步這些值到組件的狀態或者屬性上去;
const counterValues = {
'First': 0,
'Second': 10,
'Third': 30
};
// 定義組件的store對象;
const CounterStore = Object.assign({},EventEmitter.prototype,{
//定義getter函數,方便數據的同步;
getCounterValues:function(){
return counterValues;
},
//定義事件發射函數,store中的數據發生改變的時候通知對應的組件更新數據[是否會造成所有監聽到該事件的組件都去調用getter函數同步數據呢?
//會不會造成應用運行效率的降低呢];
emitChange:function(){
this.emit(CHANGE_EVENT);
},
//定義事件監聽器添加函數,當組件掛載成功的時候添加監聽函數;
//callbacek函數由調用者傳入,用于指明監聽到事件后的行為,
//通常callback函數的作用是更新組件的內部狀態;
addChangeListener:function(callback){
this.on(CHANGE_EVENT,callback);
},
//監聽函數移除函數;
removeChangeListener:function(callback){
this.removeListener(CHANGE_EVENT,callback);
}
});
//用來登記各種Action的回調函數
CounterStore.dispatchToken = AppDispatcher.register(
(action)=>{
if(action.type===ActionTypes.INCREMENT){
counterValues[action.counterCaption]++;
CounterStore.emitChange();
}else if(action.type===ActionTypes.DECREMENT){
counterValues[action.counterCaption]--;
CounterStore.emitChange();
}
}
);
//定義導出接口;
export default CounterStore;
```
* Action:驅動Dispatcher的 JavaScript 對象;(多出來的,可以理解為對應的MVC框架的用戶請求)
```js
//action描述了組件發出的請求,包含了請求的類型和請求傳遞的數據;
//action是一個純函數,其產出只依賴與輸入;
//在實際的操作中,counterCasption相當于組件的ID,當組件產生動作的同時如果攜帶了數據,那么還可以添加對應的數據項;
import * as ActionTypes from './ActionTypes.js';
import AppDispatcher from '../dispatchers/Appdispatcher.js';
//定義action構造器;
export const increment = (counterCaption)=>{
AppDispatcher.dispatch(
{
type:ActionTypes.INCREMENT,
counterCaption:counterCaption
}
);
};
export const decrement = (counterCaption)=>{
AppDispatcher.dispatch(
{
type:ActionTypes.DECREMENT,
counterCaption:counterCaption
}
);
}
```
* View:視圖部分,負責顯示用戶界面。(相當于MVC的 View)
components:
```js
//定義組件;
import React, {Component, PropTypes} from 'react';
import * as Actions from '../actions/Actions';
import CounterStore from '../stores/CounterStore';
const buttonStyle = {
margin: '10px'
};
//定義組件的主體部分;
class Counter extends Component {
constructor(props) {
super(props);
//綁定this到成員函數;
this.onChange = this.onChange.bind(this);
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
//獲取組件的初始狀態;
this.state = {
count: CounterStore.getCounterValues()[props.caption]
}
}
// 使用 shouldComponentUpdate 函數增加組件的渲染速度;
// 因為當事件發射器發射事件后,所有監聽該事件的組件中的主句或者狀態將被更新,this.setState函數將被調用,采用 shouldComponentUpdate
// 函數進行判斷可以提高組件的渲染效率;
shouldComponentUpdate(nextProps, nextState) {
return (nextProps.caption !== this.props.caption) || (nextState.count !== this.state.count);
}
//組件成功掛載到dom上之后添加事件的監聽;
componentDidMount() {
CounterStore.addChangeListener(this.onChange);
}
//組件被移除后對應的監聽也應該被移除;
componentWillUnmount(callback) {
CounterStore.removeChangeListener(this.onChange);
}
//更新組件的狀態;
onChange() {
//同步store中的數據到組件中;
const newCount = CounterStore.getCounterValues()[this.props.caption];
this.setState({count: newCount});
}
onClickIncrementButton() {
Actions.increment(this.props.caption);
}
onClickDecrementButton() {
Actions.decrement(this.props.caption);
}
render() {
const {caption} = this.props;
return (<div>
<span>JunJun的計算器</span>
<button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
<button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button>
<span>{caption}count:{this.state.count}</span>
</div>);
}
}
//約束組件的屬性類型;
Counter.propTypes = {
caption: PropTypes.string.isRequired
};
//聲明組件的引用接口;
export default Counter;
```
## 示例
[react-flux-demo](https://github.com/jiji262/react-flux-demo)
[自己對react中flux框架部分底層原理的理解](https://blog.csdn.net/zj20142213/article/details/78885928)
# [Redux](https://redux.js.org/)
眾多 Flux-like 相關的架構中,Redux有很多其他框架無法比擬的優勢。在客戶端,服務器端,原生應用都可以使用 Redux。
Redux 并不是和React 有特別關系,其他JS 框架也可以使用Redux 作為狀態管理器。
Flux 可以有多個 state 來處理不同類型的數據,而**Redux 整個應用的 state 都在一個 單獨的 Object中**,通過 `reducer`來定義整個應用state 的該如何響應。
原生的 Flux 會有許多分散的 store 儲存各個不同的狀態,但在 redux 中,只會有唯一一個 store 將所有的state 用 對象樹 (object tree)的方式包起來。
```js
//原生 Flux 的 store
const userStore = {
name: ''
}
const todoStore = {
text: ''
}
// Redux 的單一 store
const state = {
userState: {
name: ''
},
todoState: {
text: ''
}
}
```
Redux 擁有許多方便好用的輔助測試工具(例如:[redux-devtools](https://github.com/gaearon/redux-devtools)、[react-transform-boilerplate](https://github.com/gaearon/react-transform-boilerplate)),方便測試和使用 `Hot Module Reload`。
## Redux核心概念

Redux 數據流的模型大致上可以簡化成: `View -> Action -> (Middleware) -> Reducer`
reducer 示例:`(state, action) => newState`
## Redux App 的數據流程圖
(使用者與 `View` 互動 => `dispatch` 出 `Action` => `Reducers` 依據 `action type` 分配到對應處理方式,回傳新的 state => 透過 `React-Redux` 傳送給 React,React 重新繪制 View):

## Redux 示例
```
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { createStore } from 'redux'
const tiger = 10000;
// 定義 action
const increase = {
type:'漲工資'
}
const decrease = {
type:'扣工資'
}
// 定義 reducer
const reducer = (state = tiger, action) => {
switch (action.type){
case '漲工資':
return state += 100;
case '扣工資':
return state -= 100;
default:
return state;
}
}
// 創建 store 并綁定 reducer
,此處可以添加中間件
const store = createStore(reducer);
// 訂閱事件
const unsubscribe = store.subscribe(() => {
console.log(store.getState())
});
// 派發事件
console.log(`dispatch action: INCREMENT`);
store.dispatch(increase)
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
```
## [React-Redux](https://github.com/reactjs/react-redux)
React-Redux 將所有組件分成兩大類:
展示組件(presentational component)和容器組件(container component)。
* 展示組件只負責 UI 的呈現,通過 `props` 層層傳遞所需數據;
* 容器組件只負責維護內部狀態,進行數據分發和處理派發 action 。
## React-Redux 示例:
```
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';
class App extends Component {
render() {
// 來自 mapDispatchToProps
const { PayIncrease, PayDecrease } = this.props;
return (
<div className="App">
<div className="App">
<h2>當月工資為{this.props.tiger}</h2>
<button onClick={PayIncrease}>升職加薪</button>
<button onClick={PayDecrease}>遲到罰款</button>
</div>
</div>
);
}
}
const tiger = 10000;
// 這是 action
const increase = {
type: '漲工資'
}
const decrease = {
type: '扣工資'
}
// 這是 reducer
const reducer = (state = tiger, action) => {
switch (action.type) {
case '漲工資':
return state += 100;
case '扣工資':
return state -= 100;
default:
return state;
}
}
// 創建 store
const store = createStore(reducer);
// 需要渲染什么數據
function mapStateToProps(state) {
return {
tiger: state
}
}
// 需要觸發什么行為
function mapDispatchToProps(dispatch) {
return {
PayIncrease: () => dispatch({ type: '漲工資' }),
PayDecrease: () => dispatch({ type: '扣工資' })
}
}
// 連接組件
App = connect(mapStateToProps, mapDispatchToProps)(App)
ReactDOM.render(
<Provider store={store}> // 融合
<App />
</Provider>,
document.getElementById('root')
)
```
## 生成 容器組件
```js
connect (mapStateToProps, rnapDispatchToProps) (presentationalCornponent) ;
```
`connect` 函數的返回值是一個 WrappedComponent 組件。
`connect` 是典型的柯里化函數,它執行兩次,第一次是設置參數;第二次是接收一個正常的 presentationalComponent 組件,并在該組件的基礎上返回一個容器組件 WrappedComponent。
**這其實是一種高階組件(HOC)的用法。**
## `connect` 是如何獲取到 Redux store 中的內容的?
這就要借助于 `Provider` 組件來實現了。react-redux 提供了`Provider` 組件,一般用法是需要將 `Provider` 作為整個應用的根組件,并獲取 `store` 為其 `prop`,以便后續進行下發處理。
在開發中,借助于react-redux的基本模式便是:
```js
const WrappedComponent = connect (mapStateToProps, mapDispatchToProps) (presentationalCornponent);
ReactDOM.render(
<Provider store={store}>
<WrappedComponent />
</Provider>,
rootEl
)
```
[示例](https://codesandbox.io/s/oj874qk28y)
## [中間件(middleware)](http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html)
在 Express/Koa 這樣的服務器框架中,中間件扮演著對 request/response 統一進行特定處理行為的角色,它們可以接觸到 request、response 以及觸發下一個 middleware 繼續處理的 next 方法。
Redux 的 Middleware 是對`store.dispatch()`進行了封裝之后的方法,可以使 `dispatch` 傳遞 action 以外的函數或者 promise ;通過`applyMiddleware`方法應用中間件。(**middleware 鏈中的最后一個 middleware 開始 dispatch action 時,這個 action 必須是一個普通對象**)
常用的中間件:[redux-logger](https://github.com/LogRocket/redux-logger) redux-actions
```
const store = createStore(
reducer,
// 依次執行
applyMiddleware(thunk, promise, logger)
)
```
### 異步操作怎么辦?
Action 發出以后,Reducer 立即算出 State,這叫做同步;
Action 發出以后,過一段時間再執行 Reducer,這就是異步。
如果你使用了 asynchronous(異步)的行為的話,需要使用其中一種 middleware: [redux-thunk](https://github.com/gaearon/redux-thunk)、[redux-promise](https://github.com/acdlite/redux-promise) 或 [redux-promise-middleware](https://github.com/pburtchaell/redux-promise-middleware) ,這樣可以讓你在 actions 中 dispatch Promises 而非 function。
asynchronous(異步)運作方式:

# redux-saga VS redux-thunk
[redux-thunk](https://github.com/gaearon/redux-thunk) 、[redux-saga](https://redux-saga-in-chinese.js.org/) 是 redux 應用中最常用的兩種異步流處理方式。
## redux-saga
[redux-saga](https://redux-saga-in-chinese.js.org/docs/ExternalResources.html) 是一個用于管理應用程序 Side Effect(副作用,例如異步獲取數據,訪問瀏覽器緩存等)的 library,它的目標是讓副作用管理更容易,執行更高效,測試更簡單,在處理故障時更容易。
redux-saga 啟動的任務可以**在任何時候通過手動來取消**,也可以把任務和其他的 Effects **放到 race 方法里以自動取消**
### 執行流程
ui組件觸發 action 創建函數 --->
action 創建函數返回一個 action ------>
action 被傳入 redux 中間件(被 saga 等中間件處理) ,產生新的 action,傳入 reducer------->
reducer 把數據傳給ui組件顯示 ----->
mapStateToProps ------>
ui組件自渲染顯示
### 常見 effect 的用法
1. call 異步阻塞調用
2. fork 異步非阻塞調用,無阻塞的執行fn,執行fn時,不會暫停Generator
3. put 相當于dispatch,分發一個action
4. select 相當于getState,用于獲取 store 中相應部分的state
5. take 監聽 action,暫停 Generator,匹配的 action 被發起時,恢復執行。take 結合 fork,可以實現 takeEvery 和 takeLatest 的效果
6. takeEvery 監聽 action,每監聽到一個 action,就執行一次操作
7. takeLatest 監聽 action,監聽到多個 action,只執行最近的一次
8. cancel 指示 middleware 取消之前的 fork 任務,cancel 是一個無阻塞 Effect。也就是說,Generator 將在取消異常被拋出后立即恢復
9. race 競速執行多個任務
10. throttle 節流
### 參考
[Redux-saga](https://www.jianshu.com/p/6f96bdaaea22)
## redux-thunk
redux-thunk 的主要思想是擴展 action,使得 action 從一個對象變成一個函數。
從 UI 組件直接觸發任務。
```js
// redux-thunk 示例
import {applyMiddleware, createStore} from 'redux';
import axios from 'axios';
import thunk from 'redux-thunk';
const initialState = { fetching: false, fetched: false, users: [], error: null }
const reducer = (state = initialState, action) => {
switch(action.type) {
case 'FETCH_USERS_START': {
return {...state, fetching: true}
break;
}
case 'FETCH_USERS_ERROR': {
return {...state, fetching: false, error: action.payload}
break;
}
case 'RECEIVE_USERS': {
return {...state, fetching: false, fetched: true, users: action.payload}
break;
}
}
return state;
}
const middleware = applyMiddleware(thunk); //應用中間件
// 默認action對象形式:store.dispatch({type: 'FOO'});
// redux-thunk 的作用即是將 action 從一個對象變成一個函數
store.dispatch((dispatch) => {
dispatch({type: 'FETCH_USERS_START'});
// 下面是一些異步操作
axios.get('http://rest.learncode.academy/api/wstern/users')
.then((response) => {
dispatch({type: 'RECEIVE_USERS', payload: response.data})
})
.catch((err) => {
dispatch({type: 'FECTH_USERS_ERROR', payload: err})
})
});
```
# 其他狀態管理庫
我們平時在開發 React 項目中,深深的感受到了 Redux 的“長得丑,用得煩”,有的人去改造它,如 `dva`、`rematch`,對 Redux 包裝語法糖,也有如 `smox` ,直接重熔再生,完全擺脫 Redux 的局限的同時,還能擁抱“新特性”。其他狀態管理庫:[alt](https://github.com/goatslacker/alt)、[dob](https://github.com/dobjs/dob)
## [nofux](https://github.com/jayphelps/nofux)
# 參考
[Mobx使用詳解](https://www.jianshu.com/p/505d9d9fe36a)
[詳解react、redux、react-redux之間的關系](https://www.jb51.net/article/138084.htm)
[Redux 和 Mobx的選擇問題:讓你不再困惑!](https://www.jb51.net/article/123964.htm)
https://www.bookstack.cn/read/reactjs101/Ch07-react-flux-introduction.md
[React 技術棧系列教程](http://www.ruanyifeng.com/blog/2016/09/react-technology-stack.html)
# 相關知識
## getInitialState
`object getInitialState()
`
```
getInitialState: function () {
return {
items: ListStore.getAll()
};
},
```
在組件掛載之前調用一次。返回值將會作為 this.state 的初始值。
## 為什么頁面刷新后 store 被重置了?
所謂單頁應用,就是在不刷新瀏覽器的情況下可以在整個網頁應用實時共享數據。
store 是內存機制,不是緩存機制,頁面刷新和關閉都會導致 store 初始化,store 里面一般保存什么數據呢?
1. 組件的初始狀態;
2. 后端數據的初始狀態;
如果你需要存儲是數據是要實時存儲并且讀取顯示出來,那么存在本地緩存(但是在PC端,是由存在刷新操作的,可以判斷 `sessionStorage` 中是否有值,有值的話將`sessionStorage` 中的數據直接付給defaultState。)或者服務端,這樣每次刷新頁面都需要讀取本地緩存或者服務端的API,然后保存到 store,再從 store 去讀到組件上。
## state本地持久化
1. H5 本地存儲方式
2. redux-persist
redux-persist 會將 redux 的 store 中的數據緩存到瀏覽器的 localStorage 中。
# 參考
[Tips to learn React + Redux in 2018](https://www.robinwieruch.de/tips-to-learn-react-redux/)