hello你們好,我是風不識途,最近一直在整理redux
系列文章,發現對於初學者不太友好,關係錯綜複雜,難卻是不太難,就是比較複雜(其實寫比較少),因此這篇帶你全面瞭解
redux、react-redux、redux-thunk
還有redux-sage,immutable
(多圖預警),因爲知識點比較多,建議先收藏(收藏等於學會了),對你有幫助的話就給個贊👍
JavaScript
符合函數式編程的範式, 因此也有純函數的概念React
中,純函數的概念很是重要,在接下來咱們學習的Redux
中也很是重要,因此咱們必須來回顧一下純函數純函數的定義簡單總結一下:react
純函數( Pure Function
)的注意事項:ios
爲何純函數在函數式編程中很是重要呢?git
React很是靈活,但它也有一個嚴格的規則:web
JavaScript
開發的應用程序, 已經變得很是複雜了:算法
JavaScript
須要管理的狀態愈來愈多, 愈來愈複雜了UI
的狀態管理不斷變化的state
是很是困難的:chrome
View
頁面也有可能會引發狀態的變化state
在何時, 由於什麼緣由發生了變化, 發生了怎樣的變化, 會變得很是難以控制和追蹤React
只是在視圖層幫助咱們解決了DOM
的渲染過程, 可是state
依然是留給咱們本身來管理:數據庫
state
,仍是組件之間的通訊經過props
進行傳遞Context
進行數據之間的共享React
主要負責幫助咱們管理視圖,state
如何維護最終仍是咱們本身來決定Redux
就是一個幫助咱們管理State
的容器:編程
Redux
是JavaScript
的狀態容器, 提供了可預測的狀態管理Redux
除了和React
一塊兒使用以外, 它也能夠和其餘界面庫一塊兒來使用(好比Vue
), 而且它很是小 (包括依賴在內,只有2kb)Redux
的核心理念很是簡單好比咱們有一個朋友列表須要管理:redux
products.push
的方式增長了一條數據products[0].age = 25
修改了一條數據bug
時,很難跟蹤到底哪裏發生的變化Redux
要求咱們經過action
來更新state
:axios
dispatch
來派發action
來更新action
是一個普通的JavaScript
對象,用來描述此次更新的type
和content
好比下面就是幾個更新friends
的action
:
action
的好處是能夠清晰的知道數據到底發生了什麼樣的變化,全部的數據變化都是可跟追蹤、可預測的action
是固定的對象,真實應用中,咱們會經過函數來定義,返回一個action
可是如何將state
和action
聯繫在一塊兒呢? 答案就是reducer
reducer
是一個純函數reducer
作的事情就是將傳入的state
和action
結合起來來生成一個新的state
單一數據源
state
被存儲在一顆object tree
中, 而且這個object tree
只存儲在一個store
Redux
並無強制讓咱們不能建立多個Store
,可是那樣作並不利於數據的維護state
變得方便維護、追蹤、修改State是隻讀的
state
的方法必定是觸發action
, 不要試圖在其它的地方經過任何的方式來修改state
View
或網絡請求都不能直接修改state
,它們只能經過action
來描述本身想要如何修改state
race condition
(竟態)的問題使用純函數來執行修改
reducer
將舊 state
和 action
聯繫在一塊兒, 而且返回一個新的state
reducer
拆分紅多個小的reducers
,分別操做不一樣state tree
的一部分reducer
都應該是純函數,不能產生任何的反作用redux
的安裝: yarn add redux
createStore
能夠用來建立 store
對象store.dispatch
用來派發 action
, action
會傳遞給 store
reducer
接收action
,reducer
計算出新的狀態並返回它 (store
負責調用reducer
)store.getState
這個方法能夠幫助獲取 store
裏邊全部的數據內容store.subscribe
方法可讓讓咱們訂閱 store
的改變,只要 store
發生改變, store.subscribe
這個函數接收的這個回調函數就會被執行sotore
, 決定 store 要保存什麼狀態action
, 用戶在程序中實現什麼操做reducer
, reducer 接收 action 並返回更新的狀態建立Store
來存儲這個state
store
時必須建立reducer
store.getState
來獲取當前的state
經過action
來修改state
dispatch
來派發action
action
中都會有type
屬性,也能夠攜帶其餘的數據修改reducer
中的處理代碼
reducer
是一個純函數,不能直接修改state
state
帶來的問題action
以前,監聽store
的變化import { createStore } from 'redux' // 1.初始化state const initState = { counter: 0 } // 2.reducer純函數 不能修改傳遞的state function reducer(state = initState, action) { switch (action.type) { case 'INCREMENT': return { ...state, counter: state.counter + 1 } case 'ADD_COUNTER': return { ...state, counter: state.counter + action.num } default: return state } } // 3.store 參數放一個reducer const store = createStore(reducer) // 4.action const action1 = { type: 'INCREMENT' } const action2 = { type: 'ADD_COUNTER', num: 2 } // 5.訂閱store的修改 store.subscribe(() => { console.log('state發生了改變: ', store.getState().counter) }) // 6.派發action store.dispatch(action1) store.dispatch(action2)
redux
變得複雜時代碼就難以維護store、reducer、action、constants
拆分紅一個個文件<details>
<summary>拆分目錄</summary>
</details>
redux
融入react
代碼案例:
Home
組件:其中會展現當前的counter
值,而且有一個+1和+5的按鈕Profile
組件:其中會展現當前的counter
值,而且有一個-1和-5的按鈕核心代碼主要是兩個:
componentDidMount
中訂閱數據的變化,當數據發生變化時從新設置 counter
store
的dispatch
來派發對應的action
當咱們多個組件使用
redux
時, 重複的代碼太多了, 好比: 訂閱state
取消訂閱state
或 派發action
獲取state
將重複的代碼進行封裝, 將不一樣的
state
和dispatch
做爲參數進行傳遞
// connect.js import React, { PureComponent } from 'react' import { StoreContext } from './context' /** * 1.調用該函數: 返回一個高階組件 * 傳遞須要依賴 state 和 dispatch 來使用state或經過dispatch來改變state * * 2.調用高階組件: * 傳遞該組件須要依賴 store 的組件 * * 3.主要做用: * 將重複的代碼抽取到高階組件中,並將該組件依賴的 state 和 dispatch * 經過調用mapStateToProps()或mapDispatchToProps()函數 * 並將該組件依賴的state和dispatch供該組件使用,其餘使用store的組件沒必要依賴store * * 4.connect.js: 優化依賴 * 目的:可是上面的connect函數有一個很大的缺陷:依賴導入的 store * 優化:正確的作法是咱們提供一個Provider,Provider來自於咱們 * Context,讓用戶將store傳入到value中便可; */ export function connect(mapStateToProps, mapDispatchToProps) { return function enhanceComponent(WrapperComponent) { class EnhanceComponent extends PureComponent { constructor(props, context) { super(props, context) // 組件依賴的state this.state = { storeState: mapStateToProps(context.getState()), } } // 訂閱數據發生變化,調用setState從新render componentDidMount() { this.unsubscribe = this.context.subscribe(() => { this.setState({ centerStore: mapStateToProps(this.context.getState()), }) }) } // 組件被卸載取消訂閱 componentWillUnmount() { this.unsubscribe() } render() { // 下面的WrapperComponent至關於 home 組件(就是你傳遞的組件) // 你須要將該組件須要依賴的state和dispatch做爲props進行傳遞 return ( <WrapperComponent {...this.props} {...mapStateToProps(this.context.getState())} {...mapDispatchToProps(this.context.dispatch)} /> ) } } // 取出Provider提供的value EnhanceComponent.contextType = StoreContext return EnhanceComponent } } // home.js // 定義組件依賴的state和dispatch const mapStateToProps = state => ({ counter: state.counter, }) const mapDispatchToProps = dispatch => ({ increment() { dispatch(increment()) }, addNumber(num) { dispatch(addAction(num)) }, }) export default connect(mapStateToProps,mapDispatchToProps)(依賴redux的組件)
redux
和react
沒有直接的關係,你徹底能夠在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Reduxconnect
、Provider
這些幫助咱們完成鏈接redux
、react的輔助工具,可是實際上redux
官方幫助咱們提供了 react-redux
的庫,能夠直接在項目中使用,而且實現的邏輯會更加的嚴謹和高效安裝react-redux
:
yarn add react-redux
// 1.index.js import { Provider } from 'react-redux' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ) // 2.home.js import { connect } from 'react-redux' // 定義須要依賴的state和dispatch (函數須要返回一個對象) export default connect(mapStateToProps, mapDispatchToProps)(About)
在以前簡單的案例中,redux
中保存的counter
是一個本地定義的數據
dispatch action
,state
就會被當即更新。redux
中保存的不少數據可能來自服務器,咱們須要進行異步的請求,再將數據保存到redux
中class
組件的componentDidMount
中發送,因此咱們能夠有這樣的結構:上面的代碼有一個缺陷:
爲何將網絡請求的異步代碼放在redux
中進行管理?
redux
來管理可是在redux
中如何能夠進行異步的操做呢?
Express
或Koa
框架的童鞋對中間件的概念必定不陌生Middleware
能夠幫助咱們在請求和響應之間嵌入一些操做的代碼,好比cookie解析、日誌記錄、文件壓縮等操做redux
也引入了中間件 (Middleware) 的概念:
dispatch
的action
和最終達到的reducer
之間,擴展一些本身的代碼</font>redux-thunk
是如何作到讓咱們能夠發送異步的請求呢?
dispatch(action)
,action
須要是一個JavaScript
的對象redux-thunk
可讓dispatch
(action
函數), action
<font color='red'>能夠是一個函數</font>該函數會被調用, 而且會傳給這個函數兩個參數: 一個dispatch
函數和getState
函數
dispatch
函數用於咱們以後再次派發action
getState
函數考慮到咱們以後的一些操做須要依賴原來的狀態,用於讓咱們能夠獲取以前的一些狀態安裝redux-thunk
yarn add redux-thunk
在建立store
時傳入應用了middleware
的enhance
函數
applyMiddleware
來結合多個Middleware
, 返回一個enhancer
將enhancer
做爲第二個參數傳入到createStore
中
定義返回一個函數的action
dispatch
以後會被執行<details>
<summary>查看代碼</summary>
<pre>import { createStore, applyMiddleware } from 'redux'
</pre></details>
import reducer from './reducer'
import thunk from 'redux-thunk'<br/>
const store = createStore(
reducer,
applyMiddleware(thunk) // applyMiddleware可使用中間件模塊
)
export default store
咱們以前講過,redux
能夠方便的讓咱們對狀態進行跟蹤和調試,那麼如何作到呢?
redux
官網爲咱們提供了redux-devtools
的工具使用步驟:
redux
中集成devtools
的中間件// store.js 開啓redux-devtools擴展 import { createStore, applyMiddleware, compose } from 'redux' // composeEnhancers函數 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose // 經過applyMiddleware來結合多個Middleware,返回一個enhancer const enhancer = applyMiddleware(thankMiddleware) // 經過enhancer做爲第二個參數傳遞createStore中 const store = createStore(reducer, composeEnhancers(enhancer)) export default store
Generator
函數是 ES6 提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣
Generator
函數有多種理解角度。語法上,首先能夠把它理解成,Generator
函數是一個狀態機,封裝了多個內部狀態。
// 生成器函數的定義 // 默認返回: Generator function* foo() { console.log('111') yield 'hello' console.log('222') yield 'world' console.log('333') yield 'jane' console.log('444') } // iterator: 迭代器 const result = foo() console.log(result) // 使用迭代器 // 調用next,就會消耗一次迭代器 const res1 = result.next() console.log(res1) // {value: "hello", done: false} const res2 = result.next() console.log(res2) // {value: "world", done: false} const res3 = result.next() console.log(res3) // {value: "jane", done: false} const res4 = result.next() console.log(res4) // {value: undefined, done: true}
redux-saga
是另外一個比較經常使用在redux
發送異步請求的中間件,它的使用更加的靈活Redux-saga
的使用步驟以下
redux-sage
: yarn add redux-saga
集成redux-saga
中間件
createSagaMiddleware
後, 須要建立一個 sagaMiddleware
applyMiddleware
使用這個中間件,接着建立 saga.js
這個文件saga
saga.js
文件的編寫
takeEvery
:能夠傳入多個監聽的actionType
,每個均可以被執行(對應有一個takeLatest
,會取消前面的)put
:在saga
中派發action
再也不是經過dispatch
, 而是經過put
all
:能夠在yield
的時候put
多個action
// store.js import createSageMiddleware from 'redux-saga' import saga from './saga' // 1.建立sageMiddleware中間件 const sagaMiddleware = createSageMiddleware() // 2.應用一些中間件 const enhancer = applyMiddleware(sagaMiddleware) const store = createStore(reducer,composeEnhancers(enhancer)) sagaMiddleware.run(saga) export default store // saga.js import { takeEvery, put, all } from 'redux-saga/effects' import { FETCH_HOME_DATA } from './constant' function* fetchHomeData(action) { const res = yield axios.get('http://123.207.32.32:8000/home/multidata') const banners = res.data.data.banner.list const recommends = res.data.data.recommend.list // dispatch action 提交action,redux-sage提供了put yield all([ yield put(changeBannersAction(banners)), yield put(changeRecommendAction(recommends)), ]) } function* mySaga() { // 參數一:要攔截的actionType // 參數二:生成器函數 yield all([ takeEvery(FETCH_HOME_DATA, fetchHomeData), ]) } export default mySaga
咱們來看一下目前咱們的reducer
:
reducer
既有處理counter
的代碼,又有處理home
頁面的數據counter
相關的狀態或home
相關的狀態會進一步變得更加複雜reducer
中進行管理,隨着項目的日趨龐大,必然會形成代碼臃腫、難以維護所以,咱們能夠對reducer
進行拆分:
counter
處理的reducer
home
處理的reducer
目前咱們已經將不一樣的狀態處理拆分到不一樣的reducer
中,咱們來思考:
reducer
中用到的constant
、action
等咱們也依然是在同一個文件中;reducer
函數本身來返回一個新的對象redux
給咱們提供了一個combineReducers
函數能夠方便的讓咱們對多個reducer
進行合併import { combineReducers } from 'redux' import { reducer as counterReducer } from './count' import { reducer as homeReducer } from './home' export const reducer = combineReducers({ counterInfo: counterReducer, homeInfo: homeReducer, })
那麼combineReducers
是如何實現的呢?
reducer
合併成一個對象, 最終返回一個combination
函數combination
函數過程當中, 會經過判斷先後返回的數據是否相同來決定返回以前的state
仍是新的state
在React
開發中,咱們老是會強調數據的不可變性:
state
,仍是reduex
中管理的state
JavaScript
編碼的過程當中,數據的不可變性都是很是重要的數據的可變性引起的問題(案例):
const obj1 = { name: 'jane', age: 18 } const obj2 = obj1 obj1.name = 'kobe' console.log(obj2.name) // kobe
有沒有辦法解決上面的問題呢?
Object.assign
或擴展運算符這種對象的淺拷貝有沒有問題呢?
有人會說,開發中不都是這樣作的嗎?
爲了解決上面的問題,出現了Immutable
對象的概念:
Immutable
對象的特色是隻要修改了對象,就會返回一個新的對象,舊的對象不會發生改變;可是這樣的方式就不會浪費內存了嗎?
Persistent Data Structure
(持久化數據結構或一致性數據結構)固然,咱們一聽到持久化第一反應應該是數據被保存到本地或者數據庫,可是這裏並非這個含義:
Immutable
: yarn add immutable
注意:我這裏只是演示了一些API,更多的方式能夠參考官網
做用:不會修改原有數據結構,返回一個修改後新的拷貝對象
JavaScrip
和ImutableJS
直接的轉換
Immutable
對象:Map
Immtable
數組:List
fromJS
const im = Immutable // 對象轉換成Immutable對象 const info = {name: 'kobe', age: 18} const infoIM = im.Map() // 數組轉換成Immtable數組 const names = ["abc", "cba", "nba"] const namesIM = im.List(names)
ImmutableJS
的基本操做:
修改數據:set(property, newVal)
get(property/index)
Immutable
對象數據(子屬性也是Immutable
對象): getIn(['recommend', 'topBanners'])
// set方法 不會修改infoIM原有數據結構,返回修改後新的數據結構 const newInfo2IM = infoIM.set('name', 'james') const newNamesIM = namesIM.set(0, 'why') // get方法 console.log(infoIM.get('name'))// -> kobe console.log(namesIM.get(0))// -> abc
ImmutableJS
重構redux
目前項目中採用的state管理方案(參考便可):
hello你們好,我是風不識途,最近一直在整理redux
系列文章,發現對於初學者不太友好,關係錯綜複雜,難卻是不太難,就是比較複雜(其實寫比較少),因此這篇帶你全面瞭解
redux、react-redux、redux-thunk
還有redux-sage,immutable
(多圖預警),因爲知識點比較多,建議先收藏(收藏等於學會了),對你有用的話就給個贊👍
函數式編程中有一個概念叫純函數, JavaScript
符合函數式編程的範式, 因此也有純函數的概念
在React
中,純函數的概念很是重要,在接下來咱們學習的Redux
中也很是重要,因此咱們必須來回顧一下純函數
純函數的維基百科定義(瞭解便可)
純函數的定義簡單總結一下:
* 純函數指的是, 每次給相同的參數, 必定返回相同的結果 * 函數在執行過程當中, 不能產生反作用
**純函數( `Pure Function` )的注意事項:**
爲何純函數在函數式編程中很是重要呢?
* 由於你能夠安心的寫和安心的用 * 你在寫的時候保證了函數的純度,實現本身的業務邏輯便可,不須要關心傳入的內容或者函數體依賴了外部的變量 * 你在用的時候,你肯定你的輸入內容不會被任意篡改,而且本身肯定的輸入,必定會有肯定的輸出
React很是靈活,但它也有一個嚴格的規則:
* 全部React組件都必須像"純函數"同樣保護它們的"props"不被更改
JavaScript
開發的應用程序, 已經變得很是複雜了:
* `JavaScript`**須要管理的狀態愈來愈多**, 愈來愈複雜了 * 這些狀態包括服務器返回的數據, 用戶操做的數據等等, 也包括一些`UI`的狀態
管理不斷變化的state
是很是困難的:
* **狀態之間相互存在依賴**, 一個狀態的變化會引發另外一個狀態的變化, `View`頁面也有可能會引發狀態的變化 * 當程序複雜時, `state`在何時, 由於什麼緣由發生了變化, 發生了怎樣的變化, 會變得很是難以控制和追蹤
React
只是在視圖層幫助咱們解決了DOM
的渲染過程, 可是state
依然是留給咱們本身來管理:
* 不管是組件定義本身的`state`,仍是組件之間的通訊經過`props`進行傳遞 * 也包括經過`Context`進行數據之間的共享 * `React`主要負責幫助咱們管理視圖,`state`如何維護最終仍是咱們本身來決定
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132319.png)
Redux
就是一個幫助咱們管理State
的容器:
* `Redux`是`JavaScript`的狀態容器, 提供了可預測的狀態管理
Redux
除了和React
一塊兒使用以外, 它也能夠和其餘界面庫一塊兒來使用(好比Vue
), 而且它很是小 (包括依賴在內,只有2kb)
Redux
的核心理念很是簡單
好比咱們有一個朋友列表須要管理:
* **若是咱們沒有定義統一的規範來操做這段數據,那麼整個數據的變化就是沒法跟蹤的** * 好比頁面的某處經過`products.push`的方式增長了一條數據 * 好比另外一個頁面經過`products[0].age = 25`修改了一條數據
整個應用程序錯綜複雜,當出現bug
時,很難跟蹤到底哪裏發生的變化
Redux
要求咱們經過action
來更新state
:
* **全部數據的變化, 必須經過**`dispatch`來派發`action`來更新 * `action`是一個普通的`JavaScript`對象,用來描述此次更新的`type`和`content`
好比下面就是幾個更新friends
的action
:
* 強制使用`action`的好處是能夠清晰的知道數據到底發生了什麼樣的變化,全部的數據變化都是可跟追蹤、可預測的 * 固然,目前咱們的`action`是固定的對象,真實應用中,咱們會經過函數來定義,返回一個`action`
可是如何將state
和action
聯繫在一塊兒呢? 答案就是reducer
* `reducer`是一個純函數 * `reducer`作的事情就是將傳入的`state`和`action`結合起來來生成一個新的`state`
單一數據源
* 整個應用程序的`state`被存儲在一顆`object tree`中, 而且這個`object tree`只存儲在一個`store` * `Redux`並無強制讓咱們不能建立多個`Store`,可是那樣作並不利於數據的維護 * 單一的數據源可讓整個應用程序的`state`變得方便維護、追蹤、修改
State是隻讀的
* 惟一修改`state`的方法必定是觸發`action`, 不要試圖在其它的地方經過任何的方式來修改`state` * 這樣就確保了`View`或網絡請求都不能直接修改`state`,它們只能經過`action`來描述本身想要如何修改`state` * 這樣能夠保證全部的修改都被集中化處理,而且按照嚴格的順序來執行,因此不須要擔憂`race condition`(竟態)的問題
使用純函數來執行修改
* 經過`reducer`將舊 `state` 和 `action` 聯繫在一塊兒, 而且返回一個新的`state` * 隨着應用程序的複雜度增長,咱們能夠將`reducer`拆分紅多個小的`reducers`,分別操做不一樣`state tree`的一部分 * 可是全部的`reducer`都應該是純函數,不能產生任何的反作用
redux
的安裝: yarn add redux
createStore
能夠用來建立 store
對象
store.dispatch
用來派發 action
, action
會傳遞給 store
reducer
接收action
,reducer
計算出新的狀態並返回它 (store
負責調用reducer
)
store.getState
這個方法能夠幫助獲取 store
裏邊全部的數據內容
store.subscribe
方法可讓讓咱們訂閱 store
的改變,只要 store
發生改變, store.subscribe
這個函數接收的這個回調函數就會被執行
建立sotore
, 決定 store 要保存什麼狀態
建立action
, 用戶在程序中實現什麼操做
建立reducer
, reducer 接收 action 並返回更新的狀態
建立一個對象, 做爲咱們要保存的狀態
建立Store
來存儲這個state
* 建立`store`時必須建立`reducer` * 咱們能夠經過 `store.getState` 來獲取當前的`state`
經過action
來修改state
* 經過`dispatch`來派發`action` * 一般`action`中都會有`type`屬性,也能夠攜帶其餘的數據
修改reducer
中的處理代碼
* 這裏必定要記住,`reducer`是一個**純函數**,不能直接修改`state` * 後面會講到直接修改`state`帶來的問題
能夠在派發action
以前,監聽store
的變化
import { createStore } from 'redux'
// 1.初始化state
const initState = { counter: 0 }
// 2.reducer純函數 不能修改傳遞的state
function reducer(state = initState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, counter: state.counter + 1 }
case 'ADD_COUNTER':
return { ...state, counter: state.counter + action.num }
default:
return state
}
}
// 3.store 參數放一個reducer
const store = createStore(reducer)
// 4.action
const action1 = { type: 'INCREMENT' }
const action2 = { type: 'ADD_COUNTER', num: 2 }
// 5.訂閱store的修改
store.subscribe(() => {
console.log('state發生了改變: ', store.getState().counter)
})
// 6.派發action
store.dispatch(action1)
store.dispatch(action2)
若是咱們將全部的邏輯代碼寫到一塊兒, 那麼當redux
變得複雜時代碼就難以維護
對代碼進行拆分, 將store、reducer、action、constants
拆分紅一個個文件
拆分目錄
redux
融入react
代碼案例:
* `Home`組件:其中會展現當前的`counter`值,而且有一個+1和+5的按鈕 * `Profile`組件:其中會展現當前的`counter`值,而且有一個-1和-5的按鈕
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132516.png)
核心代碼主要是兩個:
* 在 `componentDidMount`中訂閱數據的變化,當數據發生變化時從新設置 `counter` * 在發生點擊事件時,調用`store`的`dispatch`來派發對應的`action`
當咱們多個組件使用
redux
時, 重複的代碼太多了, 好比: 訂閱state
取消訂閱state
或 派發action
獲取state
將重複的代碼進行封裝, 將不一樣的
state
和dispatch
做爲參數進行傳遞
// connect.js
import React, { PureComponent } from 'react'
import { StoreContext } from './context'
/**
export function connect(mapStateToProps, mapDispatchToProps) {
return function enhanceComponent(WrapperComponent) {
class EnhanceComponent extends PureComponent {
constructor(props, context) {
super(props, context)
// 組件依賴的state
this.state = {
storeState: mapStateToProps(context.getState()),
}
}
// 訂閱數據發生變化,調用setState從新render
componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
this.setState({
centerStore: mapStateToProps(this.context.getState()),
})
})
}
// 組件被卸載取消訂閱
componentWillUnmount() {
this.unsubscribe()
}
render() {
// 下面的WrapperComponent至關於 home 組件(就是你傳遞的組件)
// 你須要將該組件須要依賴的state和dispatch做爲props進行傳遞
return (
<WrapperComponent
{...this.props}
{...mapStateToProps(this.context.getState())}
{...mapDispatchToProps(this.context.dispatch)}
/>
)
}
}
// 取出Provider提供的value
EnhanceComponent.contextType = StoreContext
return EnhanceComponent
}
}
// home.js
// 定義組件依賴的state和dispatch
const mapStateToProps = state => ({
counter: state.counter,
})
const mapDispatchToProps = dispatch => ({
increment() {
dispatch(increment())
},
addNumber(num) {
dispatch(addAction(num))
},
})
export default connect(mapStateToProps,mapDispatchToProps)(依賴redux的組件)
開始以前須要強調一下,redux
和react
沒有直接的關係,你徹底能夠在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux
儘管這樣說,redux依然是和React或者Deku的庫結合的更好,由於他們是經過state函數來描述界面的狀態,Redux能夠發射狀態的更新,讓他們做出相應。
雖然咱們以前已經實現了connect
、Provider
這些幫助咱們完成鏈接redux
、react的輔助工具,可是實際上redux
官方幫助咱們提供了react-redux
的庫,能夠直接在項目中使用,而且實現的邏輯會更加的嚴謹和高效
安裝react-redux
:
* `yarn add react-redux`
// 1.index.js
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
// 2.home.js
import { connect } from 'react-redux'
// 定義須要依賴的state和dispatch (函數須要返回一個對象)
export default connect(mapStateToProps, mapDispatchToProps)(About)
在以前簡單的案例中,redux
中保存的counter
是一個本地定義的數據
* 咱們能夠直接經過同步的操做來`dispatch action`,`state`就會被當即更新。 * 可是真實開發中,`redux`中保存的**不少數據可能來自服務器**,咱們須要進行**異步的請求**,再將數據保存到`redux`中
網絡請求能夠在class
組件的componentDidMount
中發送,因此咱們能夠有這樣的結構:
上面的代碼有一個缺陷:
* 咱們必須將**網絡請求**的異步代碼放到組件的生命週期中來完成
爲何將網絡請求的異步代碼放在redux
中進行管理?
* 後期代碼量的增長,若是把網絡請求異步函數放在組件的生命週期裏,這個生命週期函數會變得愈來愈複雜,組件就會變得愈來愈大 * 事實上,**網絡請求到的數據也屬於狀態管理的一部分**,更好的一種方式應該是將其也交給`redux`來管理
可是在redux
中如何能夠進行異步的操做呢?
* **使用中間件 (Middleware)** * 學習過`Express`或`Koa`框架的童鞋對中間件的概念必定不陌生 * 在這類框架中,`Middleware`能夠幫助咱們在**請求和響應之間嵌入一些操做的代碼**,好比cookie解析、日誌記錄、文件壓縮等操做
redux
也引入了中間件 (Middleware) 的概念:
* 這個中間件的目的是在`dispatch`的`action`和最終達到的`reducer`之間,擴展一些本身的代碼 * 好比日誌記錄、**調用異步接口**、添加代碼調試功能等等
redux-thunk
是如何作到讓咱們能夠發送異步的請求呢?
* 默認狀況下的`dispatch(action)`,`action`須要是一個`JavaScript`的對象 * `redux-thunk`可讓`dispatch`(`action`函數), `action`**能夠是一個函數** * 該函數會被調用, 而且會傳給這個函數兩個參數: 一個`dispatch`函數和`getState`函數 * `dispatch`函數用於咱們以後再次派發`action` * `getState`函數考慮到咱們以後的一些操做須要依賴原來的狀態,用於讓咱們能夠獲取以前的一些狀態
安裝redux-thunk
* `yarn add redux-thunk`
在建立store
時傳入應用了middleware
的enhance
函數
* 經過`applyMiddleware`來結合多個`Middleware`, 返回一個`enhancer` * 將`enhancer`做爲第二個參數傳入到`createStore`中 ![image-20200821182447344](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132723.png)
定義返回一個函數的action
* 注意:這裏不是返回一個對象了,而是一個**函數** * 該函數在`dispatch`以後會被執行
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132817.png)
查看代碼
咱們以前講過,redux
能夠方便的讓咱們對狀態進行跟蹤和調試,那麼如何作到呢?
* `redux`官網爲咱們提供了`redux-devtools`的工具 * 利用這個工具,咱們能夠知道每次狀態是如何被修改的,修改先後的狀態變化等等
使用步驟:
* 第一步:在瀏覽器上安裝[redux-devtools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd/related?utm_source=chrome-ntp-icon)擴展插件 * 第二步:在`redux`中集成`devtools`的中間件
// store.js 開啓redux-devtools擴展
import { createStore, applyMiddleware, compose } from 'redux'
// composeEnhancers函數
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose
// 經過applyMiddleware來結合多個Middleware,返回一個enhancer
const enhancer = applyMiddleware(thankMiddleware)
// 經過enhancer做爲第二個參數傳遞createStore中
const store = createStore(reducer, composeEnhancers(enhancer))
export default store
Generator
函數是 ES6 提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣
Generator
函數有多種理解角度。語法上,首先能夠把它理解成,Generator
函數是一個狀態機,封裝了多個內部狀態。
// 生成器函數的定義
// 默認返回: Generator
function* foo() {
console.log('111')
yield 'hello'
console.log('222')
yield 'world'
console.log('333')
yield 'jane'
console.log('444')
}
// iterator: 迭代器
const result = foo()
console.log(result)
// 使用迭代器
// 調用next,就會消耗一次迭代器
const res1 = result.next()
console.log(res1) // {value: "hello", done: false}
const res2 = result.next()
console.log(res2) // {value: "world", done: false}
const res3 = result.next()
console.log(res3) // {value: "jane", done: false}
const res4 = result.next()
console.log(res4) // {value: undefined, done: true}
redux-saga
是另外一個比較經常使用在redux
發送異步請求的中間件,它的使用更加的靈活
Redux-saga
的使用步驟以下
1. 安裝`redux-sage`: `yarn add redux-saga` 2. 集成`redux-saga`中間件 * 引入 `createSagaMiddleware` 後, 須要建立一個 `sagaMiddleware` * 而後經過 `applyMiddleware` 使用這個中間件,接着建立 `saga.js` 這個文件 * 啓動中間件的監聽過程, 而且傳入要監聽的`saga` 3. `saga.js`文件的編寫 * `takeEvery`:能夠傳入多個監聽的`actionType`,每個均可以被執行(對應有一個`takeLatest`,會取消前面的) * `put`:在`saga`中派發`action`再也不是經過`dispatch`, 而是經過`put` * `all`:能夠在`yield`的時候`put`多個`action`
// store.js
import createSageMiddleware from 'redux-saga'
import saga from './saga'
// 1.建立sageMiddleware中間件
const sagaMiddleware = createSageMiddleware()
// 2.應用一些中間件
const enhancer = applyMiddleware(sagaMiddleware)
const store = createStore(reducer,composeEnhancers(enhancer))
sagaMiddleware.run(saga)
export default store
// saga.js
import { takeEvery, put, all } from 'redux-saga/effects'
import { FETCH_HOME_DATA } from './constant'
function* fetchHomeData(action) {
const res = yield axios.get('http://123.207.32.32:8000/hom...
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
// dispatch action 提交action,redux-sage提供了put
yield all([
yield put(changeBannersAction(banners)),
yield put(changeRecommendAction(recommends)),
])
}
function* mySaga() {
// 參數一:要攔截的actionType
// 參數二:生成器函數
yield all([
takeEvery(FETCH_HOME_DATA, fetchHomeData),
])
}
export default mySaga
咱們來看一下目前咱們的reducer
:
* 當前這個`reducer`既有處理`counter`的代碼,又有處理`home`頁面的數據 * 後續`counter`相關的狀態或`home`相關的狀態會進一步變得更加複雜 * 咱們也會繼續添加其餘的相關狀態,好比購物車、分類、歌單等等 * 若是將全部的狀態都放到一個`reducer`中進行管理,隨着項目的日趨龐大,必然會形成代碼臃腫、難以維護
所以,咱們能夠對reducer
進行拆分:
* 咱們先抽取一個對`counter`處理的`reducer` * 再抽取一個對`home`處理的`reducer` * 將它們合併起來
目前咱們已經將不一樣的狀態處理拆分到不一樣的reducer
中,咱們來思考:
* 雖然已經放到不一樣的函數了,可是這些函數的處理依然是在同一個文件中,代碼很是的混亂 * 另外關於`reducer`中用到的`constant`、`action`等咱們也依然是在同一個文件中;
目前咱們合併的方式是經過每次調用reducer
函數本身來返回一個新的對象
事實上,redux
給咱們提供了一個combineReducers
函數能夠方便的讓咱們對多個reducer
進行合併
import { combineReducers } from 'redux'
import { reducer as counterReducer } from './count'
import { reducer as homeReducer } from './home'
export const reducer = combineReducers({
counterInfo: counterReducer,
homeInfo: homeReducer,
})
那麼combineReducers
是如何實現的呢?
* 它將咱們傳遞的`reducer`合併成一個對象, 最終返回一個`combination`函數 * 在執行`combination`函數過程當中, 會經過判斷先後返回的數據是否相同來決定返回以前的`state`仍是新的`state`
在React
開發中,咱們老是會強調數據的不可變性:
* 不管是類組件中的`state`,仍是`reduex`中管理的`state` * 事實上在整個`JavaScript`編碼的過程當中,數據的不可變性都是很是重要的
數據的可變性引起的問題(案例):
* 咱們明明沒有修改obj,只是修改了obj2,可是最終obj也被咱們修改掉了 * 緣由很是簡單,對象是引用類型,它們指向同一塊內存空間,兩個引用均可以任意修改
const obj1 = { name: 'jane', age: 18 }
const obj2 = obj1
obj1.name = 'kobe'
console.log(obj2.name) // kobe
有沒有辦法解決上面的問題呢?
* 進行對象的拷貝便可:`Object.assign`或擴展運算符
這種對象的淺拷貝有沒有問題呢?
* 從代碼的角度來講,沒有問題,也解決了咱們實際開發中一些潛在風險 * 從性能的角度來講,有問題,若是對象過於龐大,這種拷貝的方式會帶來性能問題以及內存浪費
有人會說,開發中不都是這樣作的嗎?
* 歷來如此,即是對的嗎?
爲了解決上面的問題,出現了Immutable
對象的概念:
* `Immutable`對象的特色是隻要修改了對象,就會返回一個新的對象,舊的對象不會發生改變;
可是這樣的方式就不會浪費內存了嗎?
* 爲了節約內存,又出現了一個新的算法:`Persistent Data Structure`(持久化數據結構或一致性數據結構)
固然,咱們一聽到持久化第一反應應該是數據被保存到本地或者數據庫,可是這裏並非這個含義:
* 用一種數據結構來保存數據 * 當數據被修改時,會返回一個對象,可是**新的對象會盡量的利用以前的數據結構而不會對內存形成浪費**,如何作到這一點呢?結構共享:
安裝Immutable
: yarn add immutable
注意:我這裏只是演示了一些API,更多的方式能夠參考官網
做用:不會修改原有數據結構,返回一個修改後新的拷貝對象
JavaScrip
和ImutableJS
直接的轉換
* 對象轉換成`Immutable`對象:`Map` * 數組轉換成`Immtable`數組:`List` * 深層轉換:`fromJS`
const im = Immutable
// 對象轉換成Immutable對象
const info = {name: 'kobe', age: 18}
const infoIM = im.Map()
// 數組轉換成Immtable數組
const names = ["abc", "cba", "nba"]
const namesIM = im.List(names)
ImmutableJS
的基本操做:
* 修改數據:`set(property, newVal)` * 返回值: 修改後新的數據結構 * 獲取數據:`get(property/index)` * 獲取深層`Immutable`對象數據(子屬性也是`Immutable`對象): `getIn(['recommend', 'topBanners'])`
// set方法 不會修改infoIM原有數據結構,返回修改後新的數據結構
const newInfo2IM = infoIM.set('name', 'james')
const newNamesIM = namesIM.set(0, 'why')
// get方法
console.log(infoIM.get('name'))// -> kobe
console.log(namesIM.get(0))// -> abc
ImmutableJS
重構redux
* yarn add Immutable * yarn add redux-immutable
使用redux-immutable中的combineReducers;
全部的reducer中的數據都轉換成Immutable類型的數據
目前項目中採用的state管理方案(參考便可):
* 相關的組件內部能夠維護的狀態,在組件內部本身來維護 * 只要是須要共享的狀態,都交給redux來管理和維護 * 從服務器請求的數據(包括請求的操做) ,交給redux來維護