Redux使用指南
00-簡介
本文主要是用來記錄Redux結合React的使用程序,幫助大家在使用Redux的時候,能夠更好的理解Redux,從而更好地使用它
01-為什么需要Redux
-
JavaScript的應用程式越來越復雜了,需要管理的狀態也越來越多了
這些狀態包括服務器回傳的資料,快取資料,用戶操作產生的資料,也包括一些UI的狀態
-
我們要管理不斷變化的狀態是非常困難的
狀態之間會互相依賴,一個狀態的變化會引起另一個狀態的變化,view頁面的變化也可能會引起狀態的變化
如果我們想要去追蹤一個狀態的變化是非常難的,因為我們不知道狀態在什么時候發生了變化,因為什么原因發生了變化
-
React在視圖層幫助我們解決了DOM的渲染程序,但是State依然是交給我們自己來進行管理的
例如組件自己定義的state,父子組件通過props傳遞資訊,亦或是通過context進行全域共享狀態
React的核心思想
UI=render(state) -
說到底,Redux是一個幫助我們管理狀態的容器,我們可以將需要管理的狀態放進這個容器中,這個容器給我們提供了可預測的狀態管理功能
02-Redux的核心理念
- store
- action
- reducer
-
Store:它是Redux提供給我們存盤所有狀態的容器,這也是我們尋找狀態的地方
-
action:它是一個JS物件型別的資料,是用來定義狀態的變化行為,主要由type和資料組成
const changeStateAction={type:"CHANGESTATE",state:state} const IncrementAction={type:"INCREMENT"}可以看到 type是用來定義狀態發生改變的原因的,state就是你想要改變的狀態資料,這個state可加可不加,看自己的需求
這樣子使用action,我們可以很明確的知道,狀態改變的原因是什么,方便我們跟蹤狀態和預測狀態
在Redux中,所有的action都是需要通過dispatch進行更新資料的,這一定請大家牢記
-
reducer:這是Redux關鍵的一個地方,它是負責將action和state聯系起來的一個純函式
它可以將state和action結合起來生成一個新的state
03-Redux的三大原則
-
單一資料源
-
整個應用程式的state被存盤在一棵object tree中,并且這個Object Tree只存盤在一個Store中
-
單一資料源可以讓整個應用程式的state變得方便維護,追蹤和修改
-
-
State是只讀的
-
唯一修改state的途徑就是通過dispatch action,不要使用其他的方法去修改state
-
這樣子的好處就是 我們可以對狀態進行集中管理修改,并且會按照嚴格的順序執行,不需要擔心 race condition(競態)問題
-
這里解釋一下競態的概念
競態:對于同樣的輸入,程式的輸出有時候正確而有時候卻是錯誤的,這種一個計算結果的正確性與時間有關的現象就被稱為競態(RaceCondition)
-
-
使用純函式來執行修改
只執行修改應該修改的狀態,不會修改其他地方的狀態,非常安全,修改狀態不用擔心會影響其他的地方
我來解釋一下純函式的概念
先來看下維基百科的定義
- 在程式設計中,若一個函式符合一下條件,那么這個函式被稱為純函式:
- 此函式在相同的輸入值時,需產生相同的輸出,函式的輸出和輸入值以外的其他隱藏資訊或狀態無關,也和由I/O設備產生的 外部輸出無關.
- 該函式不能有語意上可觀察的函式副作用,諸如“觸發事件”,使輸出設備輸出,或更改輸出值以外物件的內容等, n 當然上面的定義會過于的晦澀,所以我簡單總結一下: p 確定的輸入,一定會產生確定的輸出; p 函式在執行程序中,不能產生副作用;
總結
- 確定的輸入有確定的輸出
- 在執行程序中,不會產生副作用.舉個例子,俗話說的好,是藥三分毒,吃藥雖然能治病,但是或多或少會影響我們身體的其他地方,這就是副作用.純函式就像吃藥不會對身體產生任何不好影響,100%有益,大家可以這么理解這句話??
04-在專案中使用Redux
需要安裝以下包
- redux
- react-redux
- redux-thunk
- immutable
- redux-immutable
前三個包是肯定要安裝的,4,5你們可以自己選擇,4,5是用來對狀態存盤的結構進行一個優化的,可以對專案起到一個優化的作用
這些包我現在就不再說了,后面用到了再和你們細講
05-redux的目錄結構

- actionCreators:用來創建action的地方的
- constants:用來記錄action中的type常量的,統一管理
- reducer:負責將action和state聯系起來
- index.js 容器創建的地方
06-redux的最基本使用
針對比較簡單的程式,你們可以只使用一個reducer,這里也是講一個reducer的情況,后面會講到reducer拆分的情況
-
??創建一個store容器,定義存盤狀態的地方
import { createStore} from "redux" import reducer from './reducer.js'; const store = createStore(reducer); export default store; -
??創建
constants,定義好狀態改變的型別const ADDNUM="ADDNUM"; const SUBNUM="SUBNUM"; export { ADDNUM, SUBNUM, } -
??創建actionCreators,定義好需要改變狀態的行為
import {ADDNUM,SUBNUM} from "./constants" //這里寫成一個函式,是為了方便dispatch的時候,方便傳值的 //最后回傳的是一個action物件 const addNumAction=(num)=>{ return { type:ADDNUM, //這里其實可以直接寫num,而不用寫 num:num //這是js的一個特點,物件名和傳遞的引數相同,可以直接省略掉后面的引數的 num:num, } }; const subNumAction=(num)=>{ return { type:SUBNUM, num:num, } } export { addNumAction, subNumAction, } -
??定義reducer,將action和state聯系起來
import {ADDNUM,SUBNUM} from "./constants" //初始化state的值 const defaultValue=https://www.cnblogs.com/codespirit-zx/archive/2021/10/05/{ num:0, } //reduce接收state和action兩個值 //state=defaultValue,是把默認值給state const reducer=(state=defaultValue,action)=>{ //根據action的type型別來判斷該執行什么樣的操作 //沒有找打對應的型別就會回傳state,不執行任何操作 switch(action.type){ case ADDSUM: return {...state,num:state.num+action.num} case SUBNUM: return {...state,num:state.num-action.num} default: return state } } export default reducer -
在組件中使用
test.js
import React, { memo } from 'react' import { connect } from "react-redux" import { addNumAction, subNumAction, } from "../store2/actionCreators" //下面這步大家可能不理解 為啥可以拿到現在組件沒有的值 //實際上 我們是先讓這個組件拿到加強版組件的屬性 //因為加強后,這個原來組件的一切會被加強版組件繼承過去 到那個時候 就有這些屬性 //請結合我下面 connect函式詳解 這一節 來好好理解下 const Test = memo(function Test(props) { return ( <div> <h1>當前計數{props.num}</h1> <button onClick={e => props.addNum()}>加5</button> <button onClick={e => props.subNum()}>減5</button> </div> ) }) const mapStateToprops = state => { return { num: state.num } } const mapDispatchToprops = dispacth => { return { addNum: function () { dispacth(addNumAction(5)); }, subNum: function () { dispacth(subNumAction(5)); } } } export default connect(mapStateToprops, mapDispatchToprops)(Test);index.js
import React from 'react'; import ReactDOM from 'react-dom'; import store from './store2'; import { Provider } from 'react-redux'; import Test from './pages/test'; ReactDOM.render( <Provider store={store}> <Test /> </Provider>, document.getElementById('root') );在組件中使用有幾個注意事項
- 在入口檔案index.js中,首先從react-redux引入Provider,這是負責將我們創建的store容器,通過context共享出去,這里注意一下,這個Provider是react-redux內部實作的,但本質上是context,最后會轉換為context
- 在test.js中有三個關鍵的地方
- mapStateToProps:負責將狀態映射到組件的屬性上
- mapDispatchToprops:負責將dispatch的action映射到屬性上,進行資料的更改
- connect:負責將1,2傳入到test中,形成一個新的組件,這里用到了高階組件的方法,待會會細講
- 這幾個的核心思想是 將disptch的行為,和我們需要的狀態映射到組件上,這樣子組件能通過props拿到這些值和行為,從而自己進行相對應的操作和資料展示
07-connect函式詳解
我們這次來自己實作一下connect函式,看看react-redux究竟做了怎樣的一個操作
01-創建一個context
創建一個context檔案,里面放著我們的context
這一步的操作是模仿react-redux所提供的Provider,因為它的核心機制是context
import React from "react";
const StoreContext = React.createContext();
export { StoreContext };
02-在connect函式中進行一個參考
import React, { Component } from 'react'
import { StoreContext } from './context';
// 首先我們使用高階組件 來給我們的組件來傳遞函式
export default function connect(MapStateToProps, MapDispathToprop) {
// 回傳值是一個函式 這個函式接收一個組件
// 這個函式的回傳值是一個類組件 這個類組件是對之前傳進來的組件的一個加強版
return function Enhanced(WrappedComponent) {
return class EnhancedHOC extends Component {
static contextType = StoreContext;
constructor(props, context) {
// 因為context在構造器中,一開始是沒有值的,需要我們手動去給context賦值,從父類那繼承下context
//這樣就相當于把從contextProvider中傳遞過來的值拿到并給到構造器中的context
super(props, context);
//這一步是將容器中的state放入到 當前組件中的state中
this.state = {
storeState: MapStateToProps(context.getState()),
}
}
//需要更改的值 在生命周期函式componentDidMount()中完成
componentDidMount() {
// 訂閱store容器中的狀態變化 監聽到變化后,使用setState進行一個修改
// store.subscribe的回傳值就是取消訂閱狀態變化
this.unSubscribe = this.context.subscribe(() => {
this.setState({
storeState: MapStateToProps(this.context.getState()),
})
})
}
componentWillUnmount() {
//在卸載組件的時候 將取消訂閱
this.unSubscribe();
}
render() {
return (
<WrappedComponent
{...this.props}
{...MapStateToProps(this.context.getState())}
{...MapDispathToprop(this.context.dispatch)} />
)
}
}
}
}
03-在入口檔案中引入context即可
在這里將store作為引數傳給context
import React from 'react';
import ReactDOM from 'react-dom';
import store from './store';
// import { StoreContext } from './connect/context';
// 使用react-redux所需要引入的報
import { Provider } from 'react-redux';
import App from './App3';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
// <StoreContext.Provider value=https://www.cnblogs.com/codespirit-zx/archive/2021/10/05/{store}>
//
//
04-圖解
防止大家不明白,我畫了圖供大家理解

08-react-redux中Hook的使用和reducer的拆分
不知道大家在看完以上的介紹會不會覺得這樣用太麻煩了,用起來比較繁瑣
接下來我要給大家介紹下React在16.8版本新推出的Hook,有了Hook這個跨世紀的發明,React撰寫起來也更加方便了
而React-redux也是大量用到了Hook這個新特性,結合著使用,比之前快了不知道多少倍
接下來 我舉得例子都是我寫的網易云專案中 我是怎么組織redux的,大家主要看思想即可,里面一些我定義的變數不要過于計較了
主要是學習這個用法和思想
01-在根目錄下創建store檔案夾
實際在寫專案的時候,各個頁面的狀態和行為是比較多的,這時候就需要我們去拆分reducer,讓每個頁面去管理自己的reducer,最后在這個主目錄下進行一個合并,一起放到store容器中
index.js
import { createStore, applyMiddleware} from "redux"
//為了在redux中進行異步請求,使用redux-thunk
import reduxThunkMiddleWare from "redux-thunk";
import zReducer from "./reducer";
//為了使用redux-thunk 需要從redux中引入applyMiddleware(中間件)
const storeEnhancer = applyMiddleware(reduxThunkMiddleWare);
// 在參考中間件后,在狀態容器中,你得使用composeEnhancers將應用的中間件進行一個包裹,再往里面傳遞,在這個函式的定義內,第二個引數傳遞就是composeEnhancers
const store = createStore(zReducer, composeEnhancers(storeEnhancer));
export default store;
- 創建一個redux容器,容器是唯一的,雖然redux沒有規定必須一個,但是唯一的容器,方便我們追蹤狀態和查找
- 為了進行異步請求,使用redux-thunk,應用中間件
- 我怕還有小伙伴不知道該怎么使用redux-thunk,我還是簡單的介紹下redux-thunk的使用步驟
- 下載redux-thunk這個包
- 引入redux-thunk,并從redux中引入applyMiddlware
- 將redux-thunk放入applyMiddleware
- 在創建容器的時候,用composeEnhancers對applyMiddleWare進行包裹傳入,其實這也是高階組件的用法,大家感興趣可以翻讀下原始碼
reducer.js
// import { combineReducers } from 'redux';
import { combineReducers } from 'redux-immutable';
import { reducer as recommendReducer } from "../pages/discover/children_Pages/recommend/store";
import { reducer as songPlayer } from "../pages/player/store";
import { reducer as albumReducer } from "../pages/discover/children_Pages/album/store"
const zReducer = combineReducers({
recommend: recommendReducer,
player: songPlayer,
album: albumReducer,
})
export default zReducer
-
因為我在專案中使用的是immutable.js這個包,改變了我的資料存盤結構,所以是在redux-immutable引入的combineReducer中
-
這個combineReducer是用來幫助我們合并之前拆分的reducer的
-
combineReducer是傳入一個物件的
-
物件中的每個值代表著我們每個拆分的reducer
可能有小伙伴一頭霧水,請接著往下看,到后面所有東西就都會串聯起來
02-每個頁面有自己的Store
網頁應用有很多個頁面,我們拆分reducer的準則是每個頁面配備一個store
具體我們看實體 接下來的實體是音樂播放欄 我們看下目錄結構

大家看到這個結構是不是很熟悉呢,與我們之前講的部分的結構劃分是一摸一樣的
與之前不同的是 index.js檔案中的內容變了,不再是創建容器了,而是負責將reducer匯出去,然后在主目錄下的容器中引入合并reducer
reducer.js
//引入immutable 改變資料結構
import { Map } from "immutable"
import {
GETSONG, CURRENTSONGINDEX
} from "./constatnts"
//使用map對這個物件進行包裹
const defaultState = Map({
playList: [],
currentSongIndex: 0,
})
function reducer(state = defaultState, action) {
switch (action.type) {
case GETSONG:
return state.set("playList", action.playList);
case CURRENTSONGINDEX:
return state.set("currentSongIndex", action.currentSongIndex);
default:
return state;
}
}
export default reducer;
index.js(大家可以看到 index沒有創建容器了,而是匯出reducer了)
import reducer from "./reducer";
export {
reducer
};
actionCreators.js
//這是我們定義的網路請求
import {
requestLyric,
} from "../../../service/player"
import * as actionType from "./constatnts"
const changeLyricAction = (lyric) => ({
type: actionType.GET_LYRIC,
lyric,
})
//因為引入了redux-thunk這個中間件,所以可以在redux中進行網路請求
//我們需要請求資料的時候,使用這個行為進行派發即可
const getSongLyric = (id) => {
return (dispatch, getState) => {
const lyrics = getState().getIn(["player", "lyrics"]);
const songInfo = getState().getIn(["player", "songInfo"]);
const newLyrics = [...lyrics];
const songIndex = songInfo.findIndex(item => item.id === id);
requestLyric(id).then(res => {
if (songIndex === -1) {
newLyrics.push(res.data.lrc.lyric);
//拿到資料后 可以對我們定義的行為進行派發
dispatch(changeLyricAction(newLyrics));
} else {
dispatch(changeLyricAction(newLyrics));
}
})
}
}
export {getSongLyric}
03-組件中使用
import React, { memo, useEffect } from 'react'
import { shallowEqual, useSelector,useDispatch } from 'react-redux'
import {getSongLyric} from "./store/actionsCreators"
export default memo(function ZXPlayer() {
const dispatch=useDispatch();
const { lyrics, currentSongIndex } = useSelector(state => ({
//因為我們使用了immutable 所以我們取資料的時候得使用 getIn()
//shallowEqual是提升性能的一個方案,防止redux一直對我們的物件繼進行深層比較,消耗性能
lyrics: state.getIn(["player", "lyrics"]),
currentSongIndex: state.getIn(["player", "currentSongIndex"]),
}), shallowEqual);
useEffect(()=>{
dispatch(getSongLyric(id))
},[])
return <div>測驗</div>
})
- 在組件中使用 我們引入了兩個Hook,這個是react-redux中所添加的 分別是useSelector和useDispatch
- useSelector:負責拿到我們存盤在容器中的狀態 對應的是mapStateToProps
- useDispatch:負責從容器中拿到dispatch這個行為 對應的是 mapDispatchToProps 不同的是這次沒有把行為添加進去,行為是由我們自己來控制派發的,hook只是幫助我們拿到了dispatch這個函式而已
- 我們在看了這個后,可以很明白的知道相比之前簡單了不是一點半點
- 以前我們需要自己定義state和需要派發的dispatch
- 現在我們只需要useSelector和useDispatch,就可以非常輕松的拿到state和dispatch,省去了一大筆麻煩
09-收尾
講的這,其實redux的使用就這么多了,redux的使用難度我個人覺得并沒有很難,重點是去理解redux的作業原理是什么,
connect函式是如何將state和dispatch組合到原來的組件上到,這個我覺得是重點,大家要好好理解connect函式的執行邏輯是什么
文章后面講的Hook的使用,也只是幫助我們更簡單的使用redux而已,但是其核心是沒變的
好,這次我們就講的這吧,大家如果還有什么不明白的歡迎留言,我看到就會回復大家的
本文來自博客園,作者:CodeSpirit,轉載請注明原文鏈接:https://www.cnblogs.com/codespirit-zx/p/15369159.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/308410.html
標籤:其他
上一篇:節日禮物待簽收,開箱國慶頭像
下一篇:vue腳手架安裝使用less
