?? 什么是 Redux 以及狀態(tài)的定義
Redux 是一個用于跨組件或應(yīng)用程序范圍狀態(tài)的狀態(tài)管理系統(tǒng)。我們可以將狀態(tài)的定義分為三種主要類型:
Local State ??是屬于單個組件的狀態(tài),例如切換狀態(tài)。它使用useState()或useReducer()從組件內(nèi)部進(jìn)行管理。
跨組件狀態(tài)??它是一種影響多個組件的狀態(tài),例如觸發(fā)模態(tài)覆蓋的按鈕,可以從某處的按鈕打開并由模態(tài)內(nèi)的另一個按鈕關(guān)閉毯辅。useState()或useReducer()可以通過使用props來管理此類行為。
App-wide state ??它是一種影響應(yīng)用程序所有組件的狀態(tài)煞额,例如用戶身份驗證思恐,登錄后導(dǎo)航欄顯示更多選項,其他組件更新膊毁。
?? Redux 與 React 上下文
React Context是一個集成功能胀莹,它還允許我們通過創(chuàng)建管理狀態(tài)的上下文提供程序組件來避免 prop 鏈。但是媚媒,它有一些潛在的缺點嗜逻,可能不會影響應(yīng)用程序的開發(fā)。需要注意的是缭召,應(yīng)用程序不限于一種狀態(tài)管理栈顷,兩者都可以在同一個應(yīng)用程序中使用。通常嵌巷,只有一個用于應(yīng)用程序的寬狀態(tài)萄凤,React Context仍然可以用于選定的多組件狀態(tài),這在應(yīng)用程序的某些部分很重要搪哪。
React 上下文缺點
它在復(fù)雜的設(shè)置中效果不佳 ?? 這取決于應(yīng)用程序的大小靡努,但對于中小型應(yīng)用程序可能不是問題。但是,在具有影響多個組件和許多上下文提供者的各種狀態(tài)的大型應(yīng)用程序中惑朦,開發(fā)人員最終可能會得到深度嵌套的 JSX 代碼兽泄。
性能?? 據(jù) React 團(tuán)隊的一位成員介紹,useContext的使用對于低頻率更新(更改主題漾月、身份驗證等)非常有用病梢,但在數(shù)據(jù)變化很大時效果不佳,因此不是替代類似通量的狀態(tài)傳播梁肿。另一方面蜓陌,Redux是一個類似 Flux 的狀態(tài)管理庫,而useContext并不是它的好替代品吩蔑。
(如果您想了解更多關(guān)于Flux 架構(gòu)模式的信息钮热,這里是一個鏈接)
?? 理論上的 Redux
Redux的主要目標(biāo)是創(chuàng)建一個中央數(shù)據(jù)存儲,負(fù)責(zé)管理整個應(yīng)用程序的狀態(tài)烛芬。確實隧期,在這個 store 中,可以處理所有的跨組件狀態(tài)蛀骇,如認(rèn)證厌秒、主題化读拆、用戶輸入等。 store 中包含的數(shù)據(jù)可以在組件中使用,如果某些數(shù)據(jù)發(fā)生變化主儡,組件可以做出相應(yīng)的反應(yīng)并更新 UI逗威。為此,組件訂閱中央存儲辟灰,當(dāng)數(shù)據(jù)發(fā)生變化時个榕,存儲會通知組件。
組件如何更改存儲中的數(shù)據(jù)
組件從不直接操作存儲數(shù)據(jù)芥喇∥鞑桑基本上,組件通過訂閱可以直接訪問存儲數(shù)據(jù)继控,但沒有直接數(shù)據(jù)流向另一個方向械馆。相反,需要設(shè)置的reducer函數(shù)負(fù)責(zé)改變存儲數(shù)據(jù)(術(shù)語reducer已經(jīng)表示一種接受一些輸入武通、轉(zhuǎn)換輸入并返回新結(jié)果的函數(shù))霹崎。
Actions用于觸發(fā)reducer函數(shù),而不是直接訪問 store冶忱,組件可以調(diào)度這些actions尾菇。然后該操作被轉(zhuǎn)發(fā)到reducer,它讀取并執(zhí)行所需操作的描述。reducer返回一個新狀態(tài)派诬,它替換中央數(shù)據(jù)存儲中的現(xiàn)有狀態(tài)劳淆。最后,當(dāng)狀態(tài)更新時默赂,所有訂閱組件都會收到通知憔儿,以便它們可以更新 UI。
?? 安裝 Redux
以下說明可以在Redux 官方網(wǎng)站上找到放可。
"推薦使用 React 和 Redux 啟動新應(yīng)用的方法是使用官方 Redux+JS 模板或 Redux+TS 模板來創(chuàng)建 React App谒臼,它利用了 Redux Toolkit 和 React Redux 與 React 組件的集成。 "
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
" Redux 核心庫作為 NPM 上的一個包提供耀里,可與模塊捆綁器或 Node 應(yīng)用程序一起使用: "
# NPM
npm install redux
# Yarn
yarn add redux
?? 實戰(zhàn)前的 Redux 基礎(chǔ)
現(xiàn)在我們玩Redux來了解它的機制蜈缤。
在應(yīng)用程序中創(chuàng)建一個新的 JS 文件(我稱之為 redux-demo.js)后,可以從Redux中導(dǎo)入createStore函數(shù)并使用它來創(chuàng)建我們的商店冯挎。
import {createStore} from 'redux';
const store = createStore();
createStore函數(shù)需要一個參數(shù)底哥,該參數(shù)是reducer ,用于生成新的狀態(tài)快照房官。reducer還使用默認(rèn)操作執(zhí)行趾徽,該操作應(yīng)返回初始狀態(tài)。
// Giving state a default value of {counter: 0}
const counterReducer = (state = {counter: 0}, action) => {
return {
counter: state.counter + 1
}
};
const store = createStore(counterReducer);
創(chuàng)建reducer函數(shù)時翰守,必須包含兩個參數(shù)孵奶,舊狀態(tài)和分派動作。此外蜡峰,它必須始終返回一個新的狀態(tài)對象了袁。因此,reducer函數(shù)是一個“純函數(shù)”湿颅,這意味著相同的輸入值應(yīng)該始終產(chǎn)生完全相同的輸出载绿,并且內(nèi)部不應(yīng)該有 HTTP 或本地存儲請求等副作用。
const counterSubscriber = () => {
const latestState = store.getState();
console.log(latestState)
};
store.subscribe(counterSubscriber);
Redux通過訂閱store并將訂閱者函數(shù)本身作為參數(shù)傳遞來了解訂閱者函數(shù)油航。
.getState()返回更新后的最新狀態(tài)快照崭庸,因此它在狀態(tài)更改后運行。
store.dispatch({type: "increment"});
分派是一種分派動作的方法谊囚。action是一個帶有type屬性的JS 對象怕享,它充當(dāng)標(biāo)識符,通常是唯一的字符串秒啦。
嘗試使用命令node [file name].js(在我的例子中 為 node redux-demo.js )在終端上運行以下代碼:
import {createStore} from 'redux'
const counterReducer = (state = {counter: 0}, action) => {
return {
counter: state.counter + 1
}
};
const store = createStore(counterReducer);
const counterSubscriber = () => {
const latestState = store.getState();
console.log(latestState)
};
store.subscribe(counterSubscriber);
store.dispatch({type: "increment"});
(如果您收到“ SyntaxError: Cannot use import statement outside a module ”熬粗,只需在 package.json 中添加 “type”:“module” )
在終端上,您現(xiàn)在應(yīng)該能夠看到:
{ counter: 2 }
計數(shù)器在初始化時增加余境,并在分派操作時再次增加驻呐。
盡管一切正常灌诅,但這并不是Redux所期望的行為,因為主要目標(biāo)是在 reducer 中為不同的操作做不同的事情含末。因此猜拾,是時候使用 reducer 函數(shù)的第二個參數(shù)action了。
import {createStore} from 'redux'
const counterReducer = (state = {counter: 0}, action) => {
if (action.type === "increment") {
return {counter: state.counter + 1}
}
if (action.type === "decrement") {
return {counter: state.counter - 1}
}
return state;
};
const store = createStore(counterReducer);
const counterSubscriber = () => {
const latestState = store.getState();
console.log(latestState)
};
store.subscribe(counterSubscriber);
store.dispatch({type: "increment"});
store.dispatch({type: "decrement"});
如果我們再次運行該文件佣盒,結(jié)果應(yīng)該是:
{ counter: 1 }
{ counter: 0 }
初始化時挎袜,reducer返回原始狀態(tài),之后狀態(tài)由調(diào)度函數(shù)遞增和遞減肥惭,計數(shù)器為 0盯仪。
?? React + Redux 實戰(zhàn)
在現(xiàn)有應(yīng)用程序中實現(xiàn) Redux。
由于Redux不是特定于 react 的蜜葱,但可以在任何 JavaScript 項目中使用全景,我們還將使用第二個名為react-redux的包。
這個包包括一個組件以使商店對整個應(yīng)用程序可用牵囤,以及一對自定義鉤子useSelector和useDispatch允許組件與商店交互爸黄。
// In the terminal
npm i redux react-redux
更多信息可以在官方網(wǎng)站上找到。
按照慣例揭鳞,通常會創(chuàng)建一個名為store的文件夾并將所有與Redux相關(guān)的文件放在其中炕贵。然后我創(chuàng)建一個index.js文件,但是你可以隨意調(diào)用它野崇。
讓我們像之前一樣創(chuàng)建一個簡單的計數(shù)器称开,并導(dǎo)出store變量以提供應(yīng)用程序的組件:
// src/store/index.js
import {createStore} from "redux"
function counterReducer(state = {counter: 0}, action) {
switch (action.type) {
case 'increment':
return {counter: state.counter + 1}
case 'decrement':
return {counter: state.counter - 1}
default:
return state
}
}
const store = createStore(counterReducer)
export default store
在src/index.js文件中,它位于我們的應(yīng)用程序樹的頂部舞骆,也是我們渲染整個應(yīng)用程序的地方钥弯,我們導(dǎo)入:
import {Provider} from "react-redux";
隨著我們可以包裝 , 并通過我們的store來存儲Provider 的 store prop。
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import {Provider} from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store}><App/></Provider>);
在 React 組件中使用 Redux 數(shù)據(jù)
要從組件中訪問store數(shù)據(jù)督禽,我們可以從 react redux 庫中導(dǎo)入useSelector或useStore鉤子。第一個非常方便总处,因為它允許我們從商店中提取某些變量狈惫。但是,react-redux 文檔建議您通常更喜歡useSelector:
useSelector:當(dāng)一個動作被調(diào)度時鹦马, useSelector() 將對先前的選擇器結(jié)果值和當(dāng)前結(jié)果值進(jìn)行參考比較胧谈。如果它們不同,組件將被強制重新渲染荸频。如果它們相同菱肖,則組件不會重新渲染。
useStore:這個鉤子可能不應(yīng)該經(jīng)常使用旭从。首選 useSelector() 作為您的主要選擇稳强。但是场仲,這對于需要訪問存儲的不太常見的場景(例如更換減速器)可能很有用。
useSelector鉤子接受一個函數(shù)退疫,該函數(shù)確定我們要從存儲中提取哪條數(shù)據(jù)渠缕,因此計數(shù)器變量,因為它是唯一存在的褒繁。通過使用這個鉤子亦鳞,react-redux 為組件創(chuàng)建了對Redux Store的訂閱,以便在每次數(shù)據(jù)更改時自動更新棒坏。
import classes from './Counter.module.css';
import {useSelector} from "react-redux";
const Counter = () => {
const counter = useSelector(state => state.counter)
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{counter}</div>
</main>
);
};
export default Counter;
從組件調(diào)度操作
為了從組件內(nèi)調(diào)度動作燕差,react-redux 提供了另一個名為useDispatch的鉤子。調(diào)用鉤子時坝冕,不需要任何參數(shù)谁不,因為它只返回一個可以在執(zhí)行時發(fā)送動作的函數(shù)。在函數(shù)的執(zhí)行中徽诲,現(xiàn)在可以提供一個對象刹帕,該對象包含與更新計數(shù)器的操作相關(guān)聯(lián)的類型。
import classes from './Counter.module.css';
import {useDispatch, useSelector} from "react-redux";
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter);
const incrementHandler = () => {
dispatch({type: "increment"});
};
const decrementHandler = () => {
dispatch({type: "decrement"});
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{counter}</div>
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
</main>
);
};
export default Counter;
將有效負(fù)載附加到操作
到目前為止谎替,我們已經(jīng)了解了如何發(fā)送簡單的操作偷溺,但有時操作也可以有一個稱為有效負(fù)載的附加值。在 dispatch 函數(shù)中钱贯,除了類型挫掏,我們可以隨意添加和命名其他值。讓我們?yōu)?reducer 函數(shù)創(chuàng)建一個新的案例場景秩命,它需要一個額外的值尉共,稱為 amount。
// src/store/index.js
function counterReducer(state = {counter: 0}, action) {
switch (action.type) {
...
case 'increase':
return {counter: state.counter + action.amount}
...
}
}
在組件中弃锐,我們可以創(chuàng)建一個新的處理程序來調(diào)度這個帶有額外“數(shù)量”值的新動作袄友,并將它傳遞給一個新按鈕。
// Component
import classes from './Counter.module.css';
import {useDispatch, useSelector} from "react-redux";
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter);
[...]
const decrementHandler = () => {
dispatch({type: "decrement"});
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{counter}</div>
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={decrementHandler}>Decrement</button>
<button onClick={increaseHandler}>Increase by 3</button>
</div>
</main>
);
};
export default Counter;
與多個全局狀態(tài)一起工作
計數(shù)器狀態(tài)可能不是我們想要處理的唯一全局狀態(tài)霹菊。因此剧蚣,讓我們添加一個新的切換狀態(tài)來使用幾個全局狀態(tài)來練習(xí)我們的計數(shù)器。添加新狀態(tài)時旋廷,我們還必須在其他情況下處理它鸠按,否則我們可能會覆蓋其他狀態(tài)變量并破壞應(yīng)用程序。
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'increment':
return {...state, counter: state.counter + 1}
case 'decrement':
return {...state, counter: state.counter - 1}
case 'increase':
return {...state, counter: state.counter + action.amount}
case 'toggle':
return {...state, showCounter: !state.showCounter}
default:
return state
}
}
在組件內(nèi)部饶碘,選擇新的ShowCounter 狀態(tài)并將其添加到 JSX 邏輯中:
import classes from './Counter.module.css';
import {useDispatch, useSelector} from "react-redux";
const Counter = () => {
const dispatch = useDispatch();
const {counter, showCounter} = useSelector(state => state);
const toggleCounterHandler = () => {
dispatch({type: "toggle"})
};
const incrementHandler = () => {
dispatch({type: "increment"});
};
const increaseHandler = () => {
dispatch({type: "increase", amount: 3});
};
const decrementHandler = () => {
dispatch({type: "decrement"});
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{showCounter && <div className={classes.value}>{counter}</div>}
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={decrementHandler}>Decrement</button>
<button onClick={increaseHandler}>Increase by 3</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
?? Redux 工具包
Redux Toolkit 是一個幫助開發(fā)人員使用 Redux 并防止錯誤的庫目尖。該包旨在成為編寫 Redux 邏輯的標(biāo)準(zhǔn)方式,因此值得擁有自己的部分扎运。您可以在Redux Tollkit 網(wǎng)站上找到更多信息瑟曲。
安裝:
# NPM
npm install @reduxjs/toolkit
# Yarn
yarn add @reduxjs/toolkit
Redux 庫可以從 package.json 中刪除饮戳,因為它已經(jīng)包含在 reduxjs/toolkit 中。
創(chuàng)建切片
此功能允許開發(fā)人員創(chuàng)建不同的全局狀態(tài)切片测蹲。如果我們有不直接相關(guān)的不同狀態(tài)莹捡,例如身份驗證和計數(shù)器狀態(tài),這將很有用扣甲。此外篮赢,我們可以在不同的文件中創(chuàng)建切片,以獲得更易于管理的代碼琉挖。 createSlice接受一個對象作為參數(shù)启泣,我們可以在其中添加相關(guān)的counter和showcounter邏輯。
每個切片都需要一個name示辈、一個initialState和一個reducers對象寥茫。后者包含方法,每個方法都有一個名稱并接收最新狀態(tài)的副本矾麻。這樣的結(jié)構(gòu)可以讓開發(fā)者停止編寫 if/case 檢查纱耻,減少代碼的長度,使其更具可讀性险耀。
與之前的 reducer 方法不同弄喘,我們現(xiàn)在可以改變state,或者至少看起來如此甩牺!事實上蘑志,Redux Toolkit和createSlice函數(shù)不會意外操作現(xiàn)有state,因為Redux Toolkit在內(nèi)部使用另一個名為Immer的包贬派,它克隆現(xiàn)有state急但,創(chuàng)建一個新的state對象,保存所有未編輯的狀態(tài)并覆蓋我們在 an不可變的方式(查看更多)搞乏。此功能使開發(fā)人員的工作更加輕松和安全波桩,因為我們不再需要擔(dān)心重寫狀態(tài)一遍又一遍地為每種方法。當(dāng)需要有效負(fù)載時查描,我們也可以接受action參數(shù)并在方法中使用它。
這是我們新創(chuàng)建的切片的外觀:
import {createStore} from "redux";
import {createSlice} from "@reduxjs/toolkit";
const initialState = {counter: 0, showCounter: true}
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
},
increase(state, action) {
state.counter += action.amount
},
toggleCounter(state) {
state.showCounter = !state.showCounter
}
}
});
連接 Redux 工具包狀態(tài)
在我們的例子中,我們可以簡單地將counterSlice.reducer作為 store 的參數(shù)傳遞缘缚,它會起作用:
const store = createStore(counterSlice.reducer)
但是,如果我們有多個切片桥滨,我們可以使用標(biāo)準(zhǔn)的 Redux 函數(shù)combineReducers甚至更好的是 Redux Toolkit 提供 的configureStore函數(shù)弛车。configureStore是createStore的替代品蒲每,但它使合并 reducer 更容易。它接受一個配置對象作為參數(shù)邀杏,它需要一個 reducer 屬性。reducer 屬性可以接受單個 reducer望蜡,或者,如果應(yīng)用程序有多個切片脖律,則可以接受包含要合并的所有 reducer 的對象。
const store = configureStore({
reducer: counterSlice.reducer
})
#OR
const store = configureStore({
reducer: {counter: counterSlice.reducer}
})
調(diào)度
createSlice自動為所有reducer創(chuàng)建標(biāo)識符芦疏,要訪問它們,我們可以簡單地從sliceName.actions中選擇它們酸茴。我們現(xiàn)在可以使用Redux Toolkit創(chuàng)建的方法,當(dāng)調(diào)用該方法時弊决,會創(chuàng)建已經(jīng)具有type屬性的action 對象魁淳,每個action都有一個唯一標(biāo)識符飘诗。因此,作為開發(fā)人員界逛,我們不再需要擔(dān)心創(chuàng)建操作對象和唯一標(biāo)識符或避免拼寫錯誤±ジ澹現(xiàn)在我們可以導(dǎo)出文件底部的所有動作:
import {configureStore, createSlice} from "@reduxjs/toolkit";
[...]
const store = configureStore({
reducer: {counter: counterSlice.reducer}
})
export const counterActions = counterSlice.actions
export default store
回到 Counter 組件,我們可以導(dǎo)入這些操作并相應(yīng)地重構(gòu)我們的代碼:
import {useDispatch, useSelector} from "react-redux";
import {counterActions} from "../store";
const Counter = () => {
const dispatch = useDispatch();
/* Change useSelector(state => state)
to useSelector(state => state.counter) */
const {counter, showCounter} = useSelector(state => state.counter);
const toggleCounterHandler = () => {
dispatch(counterActions.toggleCounter())
};
const incrementHandler = () => {
dispatch(counterActions.increment());
};
const increaseHandler = () => {
dispatch(counterActions.increase(3));
};
const decrementHandler = () => {
dispatch(counterActions.decrement());
};
[...]
對增加減速器的另一個小改動息拜,現(xiàn)在默認(rèn)情況下溉潭,數(shù)量變?yōu)?/em>有效負(fù)載:
increase(state, action) {
state.counter += action.payload
},
多個切片的示例
向 React 應(yīng)用程序添加更多切片時,存在創(chuàng)建過長文件的風(fēng)險少欺≡辏可能值得將其拆分為更小的部分并為創(chuàng)建的每個切片創(chuàng)建一個文件。
// store/index.js
import {configureStore} from "@reduxjs/toolkit";
import counterSliceReducer from './counter'
import authSliceReducer from './auth'
const store = configureStore({
reducer: {counter: counterSliceReducer, auth: authSliceReducer}
});
export default store;
// store/counter.js
import {createSlice} from "@reduxjs/toolkit";
const initialCounterState = {counter: 0, showCounter: true};
const counterSlice = createSlice({
name: "counter",
initialState: initialCounterState,
reducers: {
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
},
increase(state, action) {
state.counter += action.payload
},
toggleCounter(state) {
state.showCounter = !state.showCounter
}
}
});
export const counterActions = counterSlice.actions;
export default counterSlice.reducer
import {createSlice} from "@reduxjs/toolkit";
const authInitialState = {isAuthenticated: false};
const authSlice = createSlice({
name: "auth",
initialState: authInitialState,
reducers: {
login(state) {
state.isAuthenticated = true
},
logout(state) {
state.isAuthenticated = false
}
}
});
export const authActions = authSlice.actions
export default authSlice.reducer
重構(gòu)代碼后不要忘記修復(fù)小的導(dǎo)入錯誤赞别!
文章來源:https://pietropiraino.hashnode.dev/react-redux-redux-toolkit