React?中使用?Redux?的?4?種寫法小結(jié)
Redux 是一種狀態(tài)容器 JS 庫(kù),提供可預(yù)測(cè)的狀態(tài)管理,經(jīng)常和 React 配合來(lái)管理應(yīng)用的全局狀態(tài),進(jìn)行響應(yīng)式組件更新。
Redux 一般來(lái)說(shuō)并不是必須的,只有在項(xiàng)目比較復(fù)雜的時(shí)候,比如多個(gè)分散在不同地方的組件使用同一個(gè)狀態(tài)。對(duì)于這種情況,如果通過(guò) props 層層傳遞,代碼會(huì)變得不可維護(hù),這時(shí)候我們可以考慮使用 Redux 這類狀態(tài)管理庫(kù)。
不使用 Redux 的寫法
我們創(chuàng)建一個(gè) User 組件,顯示用戶名,并支持設(shè)置用戶名。先看看不使用 Redux 的寫法。
import { Component, createRef } from 'react'; class User extends Component { state = { username: '前端西瓜哥' }; inputRef = createRef(); setUsername = () => { this.setState({ username: this.inputRef.current.value }); }; render() { return ( <div> <div>用戶名: {this.state.username}</div> <input ref={this.inputRef} type="text" /> <button onClick={this.setUsername}>設(shè)置用戶名</button> </div> ); } } export default User;
下面我們改造一下這個(gè)組件,將狀態(tài)遷移到 Redux 里。
最底層的寫法
Redux 是和框架無(wú)關(guān)的,我們先看看只用 Redux 庫(kù)的寫法。
demo:codesandbox.io/s/redux-pla…
首先我們創(chuàng)建一個(gè) reducer。
// user_reducer.js import { SET_USERNAME } from './constants'; // 初始值 const defaultState = { name: '前端西瓜哥', age: 88 }; // 用于修改 user 狀態(tài)的 reducer export const userReducer = (preState = defaultState, action) => { switch (action.type) { case SET_USERNAME: // type 值都統(tǒng)一放到 constants return { ...preState, name: action.payload }; // 這里還可以根據(jù)需要,添加類似 setUserAga 等邏輯 default: return preState; } };
// constants.js export const SET_USERNAME = 'SET_USERNAME';
reducer 是一個(gè)用于更新?tīng)顟B(tài)的函數(shù),接收原來(lái)的狀態(tài) preState 和一個(gè)更新動(dòng)作對(duì)象 action。
action 對(duì)象有一個(gè) 表示此次操作的描述 type
和 其他數(shù)據(jù)屬性(通常為 payload)
。payload 會(huì)以某種方式去計(jì)算出一個(gè)新的狀態(tài),替換掉 redux 中原來(lái)的 state。
{ type: 'SET_USERNAME', payload: '新用戶名' }
type 通常是一個(gè)字符串,比如我們會(huì)用 'COUNT_INCREMENT'
來(lái)給一個(gè)計(jì)數(shù)器加一,或用 'SET_USERNAME'
來(lái)更新用戶名。reducer 會(huì)根據(jù)不同的 type 來(lái)執(zhí)行不同的更新 state 行為。
action 的構(gòu)造我們通常會(huì)用一個(gè)函數(shù)幫忙構(gòu)建,這種函數(shù)稱為 Action Creator:
// user_action.js import { SET_USERNAME } from './constants'; export const setUsernameAction = (data) => { return { type: SET_USERNAME, payload: data }; };
有了 reducer,我們可以用它們來(lái)構(gòu)建我們的 store。store 可以訪問(wèn)所有的保存在 redux 狀態(tài):
import { combineReducers, createStore } from 'redux'; import { userReducer } from './user_reducer'; const store = createStore( combineReducers({ user: userReducer }) ); export default store;
combineReducers 可以將多個(gè) reducer 組合在一起,有各自對(duì)應(yīng)的屬性名。比如上面的代碼,我們可以通過(guò) store.getState().user
來(lái)拿到用戶對(duì)象。
如果你又新增了 counter 狀態(tài)對(duì)象,只需再加上 counter: counterReducer
,就可以用 store.getState().counter
來(lái)拿到這個(gè)對(duì)象。
createStore 用于創(chuàng)建應(yīng)用中所有的 state,然后這些 state 都會(huì)存放到這個(gè)被返回的 store 里。
現(xiàn)在我們的 User 組件就變成這樣了:
import { Component, createRef } from 'react'; import store from '../store/store'; import { setUsernameAction } from '../store/user_action'; class User extends Component { inputRef = createRef(); componentDidMount() { store.subscribe(() => { this.setState({}); }); } setUsername = () => { store.dispatch(setUsernameAction(this.inputRef.current.value)); }; render() { return ( <div> <div>用戶名: {store.getState().user.name}</div> <input ref={this.inputRef} type="text" /> <button onClick={this.setUsername}>設(shè)置用戶名</button> </div> ); } } export default User;
store.getState()
可以拿到 state 對(duì)象,通過(guò)它,我們獲取到其下我們需要的對(duì)象,比如 user 對(duì)象。store.dispatch(action)
派發(fā) action 對(duì)象,觸發(fā)狀態(tài)的更新。store.subscribe(fn)
訂閱狀態(tài)的變化,執(zhí)行回調(diào)函數(shù)。這里我們一發(fā)現(xiàn)狀態(tài)發(fā)生了變化,就立刻重新渲染組件。
Redux 本質(zhì)是發(fā)布訂閱模式,狀態(tài)集中在一起,狀態(tài)可以通過(guò) store.getState()
訪問(wèn),通過(guò) store.dispatch(action)
改變狀態(tài),通過(guò) store.subscribe(fn)
訂閱狀態(tài)變化(React 組件監(jiān)聽(tīng)到變化后,重新渲染組件)。
這種寫法是最原始的寫法,可以用在任何框架中。
缺點(diǎn)很明顯:用到 redux 的組件要訂閱 state 變化,一變化就重新渲染組件。有時(shí)候其他組件的 state 變化了,當(dāng)前組件也會(huì)進(jìn)行不必要的重新渲染。
自己去判斷吧,又太繁瑣,容易寫錯(cuò),也容易忘記訂閱。對(duì)于忘記訂閱的問(wèn)題,我們也可以直接把讓根組件來(lái)監(jiān)聽(tīng)和重新渲染,但這樣性能很差。
接下來(lái)西瓜哥要講的 React-Redux 庫(kù)可以解決這個(gè)問(wèn)題。它能夠在當(dāng)前組件用到的特定 state 發(fā)生改變時(shí),才重新渲染組件。
React-Redux
發(fā)現(xiàn)大家都很喜歡在 React 里用 Redux,于是 Facebook 出了一個(gè) React-Redux 庫(kù),讓大家能夠更好更正確地在 React 中使用 Redux。
React-Redux 配合 connect 高階組件
我們先看看使用 connect 的寫法。
demo:codesandbox.io/s/react-red…
React-Redux 引入了一個(gè)容器組件的概念,這個(gè)組件專門負(fù)責(zé)和 redux 打交道 。容器組件其實(shí)是一個(gè)高階組件,將真正的 UI 組件做一個(gè)封裝,在上面做了以下工作:
將 state 和 dispatch 映射到 props,注入到 UI 組件中
監(jiān)聽(tīng) state 變化,必要時(shí)重新渲染 UI 組件。
高階組件:一個(gè)函數(shù),它會(huì)接收組件參數(shù),然后返回一個(gè)新的組件。高階組件的作用是對(duì)真正的 UI 組件做一些復(fù)用的邏輯的封裝,通常用于做功能增強(qiáng)。
隨著 React Hooks 愈發(fā)流行,大家現(xiàn)在更喜歡用 React Hooks 來(lái)取代高階函數(shù),寫法更優(yōu)雅。
const ContainerComponent = connect( mapStateToProps, mapDispatchToProps, )(UIComponent);
現(xiàn)在開(kāi)始改造項(xiàng)目。
我們創(chuàng)建一個(gè) container 文件夾,里面放上 User.jsx 文件,里面寫上如下內(nèi)容:
// containers/User.jsx import { connect } from 'react-redux'; import UserUI from '../components/User'; import { setUsernameAction } from '../store/user_action'; export default connect( // mapStateToProps (state) => ({ user: state.user }), // mapDispatchToProps (dispatch) => ({ setUsername: (newName) => dispatch(setUsernameAction(newName)) }) )(UserUI);
然后記得在使用該容器的地方,傳入我們的 store 對(duì)象,如下:
import UserContainer from './containers/User'; import store from './store/store'; import './styles.css'; export default function App() { return <UserContainer store={store} />; }
當(dāng)然每個(gè)容器組件都要傳入 store 未免太麻煩,我們通常會(huì)使用另一種做法:使用 redux-react 提供的 Context Provider,包裹住根組件,如下:
import { Provider } from 'react-redux'; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
然后是 UI 組件的改造:
import { Component, createRef } from 'react'; class User extends Component { inputRef = createRef(); render() { return ( <div> <div>用戶名: {this.props.user.name}</div> <input ref={this.inputRef} type="text" /> <button onClick={() => this.props.setUsername(this.inputRef.current.value)} > 設(shè)置用戶名 </button> </div> ); } } export default User;
UI 組件的 props 會(huì)拿到 user 對(duì)象、setUsername 方法以及我們注入的 store 對(duì)象(如果用 Context 的方式則取不到)。
使用了 connect 后,只有組件用到的 state 改變了,才會(huì)觸發(fā)組件的更新。
這里有個(gè)需要特別注意的地方,就是你要 保證新的狀態(tài)對(duì)象和舊狀態(tài)不相等,這樣才能觸發(fā)組件重新渲染,這在處理對(duì)象方法時(shí)容易出錯(cuò)。你需要拷貝一個(gè)新的對(duì)象作為新的狀態(tài),推薦使用擴(kuò)展運(yùn)算符的寫法。
// user_reducer.js // 錯(cuò)誤的寫法,新的 state 依舊指向原來(lái)的對(duì)象 preState.name = action.payload; return preState; // 正確的寫法 return { ...preState, name: action.payload };
或者可以考慮使用 immer 這種不可變數(shù)據(jù)結(jié)構(gòu)庫(kù)。
React-Rudex 配合 React Hooks
前面我們用了 connect 這么一個(gè)高階組件,是為了給 UI 組件增強(qiáng)功能。
說(shuō)到增強(qiáng)功能,react-redux 也提供了現(xiàn)在非常流行的 React Hooks 寫法,寫起來(lái)更優(yōu)雅,也是目前西瓜哥我所在公司的做法。
這里我們就不需要 connect 高階組件了,也就是說(shuō)不需要容器組件。
demo:codesandbox.io/s/react-red…
// User.js import { useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { setUsernameAction } from '../store/user_action'; const User = () => { // 獲取狀態(tài) const user = useSelector((state) => state.user); // 獲取 dipatch 方法 const dipatch = useDispatch(); const inputRef = useRef(null); return ( <div> <div>用戶名: {user.name}</div> <input ref={inputRef} type="text" /> <button onClick={() => { const newName = inputRef.current.value; dipatch(setUsernameAction(newName)); }} > 設(shè)置用戶名 </button> </div> ); }; export default User;
通過(guò) useSelector 我們可以拿到通過(guò)上下文綁定的 state,然后從中獲取我們需要用到的狀態(tài)。
const user = useSelector((state) => state.user);
如果有多個(gè),我們可以寫成對(duì)象的形式:
const { user, counter } = useSelector((state) => ({ user: state.user, counter: state.counter }));
是不是有點(diǎn)像 connect 的 mapStateToProps。
然后是獲取 dispatch 方法:
const dipatch = useDispatch();
hook 非常優(yōu)雅,但我也發(fā)現(xiàn),相比 connect 寫法,我們的 redux 狀態(tài)邏輯和組件耦合在一起了。不過(guò)一般我們的組件都是業(yè)務(wù)組件,還是可以接受的。
Redux Toolkit
我們可以看到,我們要維護(hù)一個(gè)狀態(tài),我們要寫 reducer 方法、action creator 方法,還要用一個(gè) contants.js 文件集中式管理所有的 actionType 字符串。
你發(fā)現(xiàn)你寫了非常多的 模板代碼,每加一個(gè) state 就要?jiǎng)?chuàng)建上面這些東西,各個(gè)文件里跑來(lái)跑去,人都麻了。
于是 redux 又出了一個(gè)工具集庫(kù) Redux Toolkit,來(lái)解決這個(gè)問(wèn)題。
demo:codesandbox.io/s/redux-too…
Redux Toolkit 提供了 createSlice 方法,可以幫你用更少的代碼生成配套的 reducer 和 action,而且有很好的可維護(hù)性。
// userSlice.js import { createSlice } from '@reduxjs/toolkit'; const userSlice = createSlice({ name: 'user', initialState: { name: '前端西瓜哥', age: 88 }, reducers: { setUsername: (state, action) => { // 因?yàn)?Redux Toolkit 內(nèi)置使用了 immer,所以可以直接改。 state.name = action.payload; } } }); // actions export const { setUsername } = userSlice.actions; // 獲取自己需要的 state,用在組件的 userSeletor hook 上。 export const selectUser = (state) => state.user; // reducer export default userSlice.reducer;
createSlice 傳入 name(標(biāo)識(shí)符,生成 actions 要用到)、initialState(初始值)、reducers(變成了對(duì)象形式)參數(shù),然后返回一個(gè)對(duì)象。
這個(gè)返回的 slice 對(duì)象有 actions 對(duì)象屬性,比如上面的代碼,actions 下有一個(gè) setUsername 的方法,執(zhí)行后會(huì)返回 {type: "user/setUsername", payload: "新名字"}
。
可以看到 action 的 type 是根據(jù) name 和 reducers 的屬性生產(chǎn)的,確保唯一性。
slice 還有一個(gè) reducer 對(duì)象,其實(shí)就是將前面?zhèn)魅氲?reducers 配合自動(dòng)生成的 action 轉(zhuǎn)換為了函數(shù)的形式。
createSlice 干了什么事?createSlice 將原來(lái)管理一個(gè)狀態(tài)但代碼卻是分離的 action 和 reducer 集中在了一起,不用自己去起 actionType 的名字。
然后是生成 store 也要改成 configureStore 的寫法:
// store.js import { configureStore } from '@reduxjs/toolkit'; import userReducer from './userSlice'; const store = configureStore({ reducer: { user: userReducer } }); export default store;
總結(jié)
簡(jiǎn)單總結(jié)一下:
原始寫法,過(guò)于簡(jiǎn)陋,需要自己通過(guò)
store.subscribe(fn)
來(lái)判斷一個(gè)組件是否要重新渲染,寫起來(lái)麻煩、性能堪憂。配合 Redux React 庫(kù),通過(guò) connect 來(lái)注入 redux 狀態(tài),要多寫一個(gè) connect 高階組件生成的容器組件,但降低了耦合度。Redux React 會(huì)只在組件需要的狀態(tài)改變時(shí),重新渲染組件。這里要注意改變時(shí),新舊狀態(tài)不能相同,尤其是對(duì)象的情況,否則重新渲染不會(huì)工作。
如果你的項(xiàng)目主要使用函數(shù)組件,可以不用 connect,直接用 useSelector 來(lái)獲取狀態(tài),以及用 userDispatch 來(lái)改變狀態(tài)。非常優(yōu)雅。
Redux 又推出了 Redux Toolkit,解決了配置復(fù)雜、需要寫太多模板、需要手動(dòng)安裝大量相關(guān)包的問(wèn)題。
到此這篇關(guān)于在 React 中使用 Redux 的 4 種寫法的文章就介紹到這了,更多相關(guān)React使用 Redux內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React DOM-diff 節(jié)點(diǎn)源碼解析
這篇文章主要為大家介紹了React DOM-diff節(jié)點(diǎn)源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02React項(xiàng)目中使用Redux的?react-redux
這篇文章主要介紹了React項(xiàng)目中使用Redux的?react-redux,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09React踩坑之a(chǎn)ntd輸入框rules中的required=true問(wèn)題
這篇文章主要介紹了React踩坑之a(chǎn)ntd輸入框rules中的required=true問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06基于React Native 0.52實(shí)現(xiàn)輪播圖效果
這篇文章主要為大家詳細(xì)介紹了基于React Native 0.52實(shí)現(xiàn)輪播圖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11React中style的使用及注意事項(xiàng)(推薦)
React中style的使用和直接在HTML中使用有些不同,第一,React中必須是style="opacity:{this.state.opacity};"這種寫法,第二如果設(shè)置多個(gè)style格式如下,多個(gè)style中間使用逗號(hào)分割,這篇文章主要介紹了React中style的使用注意事項(xiàng),需要的朋友可以參考下2023-02-0240行代碼把Vue3的響應(yīng)式集成進(jìn)React做狀態(tài)管理
這篇文章主要介紹了40行代碼把Vue3的響應(yīng)式集成進(jìn)React做狀態(tài)管理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05react-native聊天室|RN版聊天App仿微信實(shí)例|RN仿微信界面
這篇文章主要介紹了react-native聊天室|RN版聊天App仿微信實(shí)例|RN仿微信界面,需要的朋友可以參考下2019-11-11