在 React 中使用 Redux 解決的問題小結(jié)
在 React 中使用 Redux 解決的問題
在 React 項目中未加入 Redux 時的問題
在 React 中組件通信的數(shù)據(jù)流是單向的,頂層組件可以通過 props 屬性向下層組件傳遞數(shù)據(jù),而下層組件不能直接向上層組件傳遞數(shù)據(jù)。要實現(xiàn)下層組件修改上層組件的數(shù)據(jù),需要上層組件傳遞修改數(shù)據(jù)的方法到下層組件。當項目越來越大的時候,組件之間傳遞數(shù)據(jù)以及傳遞修改數(shù)據(jù)的方法變得越來越困難。
在 React 項目中加入 Redux 的好處
使用 Redux 管理數(shù)據(jù),由于 Store 獨立于組件,使得數(shù)據(jù)管理獨立于組件,解決了組件與組件之間傳遞數(shù)據(jù)困難的問題。
React + Redux 安裝 Redux
在 react 項目中使用 redux 要下載兩個模塊
npm install redux react-redux
React 中 Redux 的工作流程
在 React 中 Redux 的工作流程有些變化:
- 組件通過 dispatch 方法觸發(fā) Action
- Store 接收 Action 并將 Action 分發(fā)給 Reducer
- Reducer 根據(jù) Action 類型對狀態(tài)進行更改并將更改后的狀態(tài)返回給 Store
- 組件訂閱了 Store 中的狀態(tài),Store 中的狀態(tài)更新會同步到組件
React 計數(shù)器案例
React 實現(xiàn)
創(chuàng)建項目安裝模塊
# 創(chuàng)建項目(React 17 版本) npx create-react-app myapp # 安裝 redux cd myapp npm install redux
刪掉無用的文件
├─ src │ ├─ App.css │ ├─ App.test.js │ ├─ index.css │ ├─ logo.svg │ ├─ reportWebVitals.js │ └─ setupTests.js
初步實現(xiàn)
// src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux' const initialState = { count: 0 } function reducer (state = initialState, action) { switch (action.type) { case 'increment': return { count: state.count + 1 } case 'decrement': return { count: state.count - 1 } default: return state } } const store = createStore(reducer) const increment = { type: 'increment' } const decrement = { type: 'decrement' } function Counter() { return <div> <button onClick={() => store.dispatch(increment)}>+</button> <span>{store.getState().count}</span> <button onClick={() => store.dispatch(decrement)}>-</button> </div> } store.subscribe(() => { ReactDOM.render( <React.StrictMode> <Counter /> </React.StrictMode>, document.getElementById('root') ); }) console.log(store.getState()) ReactDOM.render( <React.StrictMode> <Counter /> </React.StrictMode>, document.getElementById('root') );
使用 Redux
開發(fā)時我們會把組件寫在單獨的文件中,如果將 Counter 組件單獨提取,就無法訪問 store 對象以及一些其它的問題,所以需要使用 redux。
安裝 react-redux
npm install react-redux
react-redux 用于讓 react 和 redux 完美結(jié)合,它僅僅提供兩個內(nèi)容:
- Provider 組件
- 它可以將創(chuàng)建出來的 store 放到全局,允許所有組件訪問
- 可以解決將組件存儲在單獨文件中時,訪問 store
- connect 方法
- connect 方法會幫助訂閱 store,當 store 中的狀態(tài)發(fā)生變化,會重新渲染組件
- 解決了需要手動訂閱更新組件狀態(tài)
- connect 方法可以獲取 store 中的狀態(tài),映射到組件的 props 屬性中
- connect 方法可以獲取 dispatch 方法,組件可以通過 props.dispatch訪問
Provide 組件
- Provider 組件要包裹項目中所有的組件,也就是應(yīng)該被放在最外層組件上
- Provider 通過 store 屬性接收創(chuàng)建的 store 對象
connect 方法
- 首先調(diào)用 connect 方法
- 第一個參數(shù)是一個函數(shù)
- 函數(shù)接收的第一個參數(shù),即組件中的狀態(tài) state
- 函數(shù)要返回一個對象,該對象定義的屬性都會映射到組件的 props 屬性中
- 第二個參數(shù)也是一個函數(shù)
- 函數(shù)接收的第一個參數(shù),即 dispatch 方法
- 函數(shù)要返回一個對象,可以定義觸發(fā) Action 的方法,它們同樣會被映射到組件的 props 屬性中
- 返回一個方法
- 第一個參數(shù)是一個函數(shù)
- 繼續(xù)調(diào)用返回的方法,并傳遞當前的組件
修改代碼
// src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux' import Counter from './components/Counter' import { Provider } from 'react-redux' const initialState = { count: 0 } function reducer (state = initialState, action) { switch (action.type) { case 'increment': return { count: state.count + 1 } case 'decrement': return { count: state.count - 1 } default: return state } } const store = createStore(reducer) ReactDOM.render( // 通過 provider 組件將 store 放在了全局,供所有組件可以訪問 <React.StrictMode> <Provider store={store}> <Counter /> </Provider> </React.StrictMode>, document.getElementById('root') );
提取的 Counter 組件
// src/components/Counter.js import { connect } from 'react-redux' function Counter(props) { return <div> <button onClick={() => props.dispatch({ type: 'increment' })}>+</button> <span>{props.count}</span> <button onClick={() => props.dispatch({ type: 'decrement' })}>-</button> </div> } const mapStateToProps = state => ({ count: state.count }) export default connect(mapStateToProps)(Counter)
使用 connect 第二個參數(shù)簡化組件
// src/components/Counter.js import { connect } from 'react-redux' function Counter({count, increment, decrement}) { return <div> <button onClick={increment}>+</button> <span>{count}</span> <button onClick={decrement}>-</button> </div> } const mapStateToProps = state => ({ count: state.count }) const mapDispatchToProps = dispatch => ({ increment () { dispatch({ type: 'increment' }) }, decrement () { dispatch({ type: 'decrement' }) } }) export default connect(mapStateToProps, mapDispatchToProps)(Counter)
使用 bindActionCreators 方法繼續(xù)簡化
觸發(fā) Action 的方法 increment
和 decrement
的內(nèi)容基本是一樣的,redux 提供 bindActionCreators 方法生成一個函數(shù),來簡化這種重復(fù)性代碼。
- 參數(shù)
- actionCreators: 對象或返回對象的函數(shù)
- key是生成函數(shù)的名稱
- value是一個返回 Action 的函數(shù)
- dispatch: 需傳遞Store 的 dispatch 方法
- actionCreators: 對象或返回對象的函數(shù)
- 返回一個對象,同 connect 接收的第二個參數(shù)
// src/components/Counter.js import { connect } from 'react-redux' import { bindActionCreators } from 'redux' function Counter({count, increment, decrement}) { return <div> <button onClick={increment}>+</button> <span>{count}</span> <button onClick={decrement}>-</button> </div> } const mapStateToProps = state => ({ count: state.count }) const mapDispatchToProps = dispatch => ({ ...bindActionCreators({ increment() { return { type: 'increment' } }, decrement() { return { type: 'decrement' } } }, dispatch) }) export default connect(mapStateToProps, mapDispatchToProps)(Counter)
此時還沒有達到簡化的效果,可以將 actionCreators 提取到單獨的文件中。
// src\store\actions\counter.action.js export const increment = () => ({ type: 'increment' }) export const decrement = () => ({ type: 'decrement' })
// src/components/Counter.js import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import * as counterActions from '../store/actions/counter.action' function Counter({count, increment, decrement}) { return <div> <button onClick={increment}>+</button> <span>{count}</span> <button onClick={decrement}>-</button> </div> } const mapStateToProps = state => ({ count: state.count }) const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch) export default connect(mapStateToProps, mapDispatchToProps)(Counter)
代碼重構(gòu)
為了繼續(xù)演示 Redux 的相關(guān)內(nèi)容,將與 Redux 相關(guān)的代碼從 src/index.js
中抽離出去,讓項目結(jié)構(gòu)更加合理。
- 將 reducer 函數(shù)抽離到一個文件中
- 將創(chuàng)建 store 的代碼手里到一個文件中
- 將 Actions 的 type 抽象成模塊中的成員,好處是:
編輯器有提示,避免寫錯
編輯器自動插入模塊引入的代碼
將 Actions 的 type 抽象成模塊中的成員
// src\store\actions\counter.action.js import { DECREMENT, INCREMENT } from "../const/counter.const" export const increment = () => ({ type: INCREMENT }) export const decrement = () => ({ type: DECREMENT })
將 reducer 函數(shù)抽離到一個文件中
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from "../const/counter.const" const initialState = { count: 0 } function reducer(state = initialState, action) { switch (action.type) { case INCREMENT: return { count: state.count + 1 } case DECREMENT: return { count: state.count - 1 } default: return state } } export default reducer
將創(chuàng)建 store 的代碼手里到一個文件中
// src\store\index.js import { createStore } from 'redux' import reducer from './reducers/counter.reducer' export const store = createStore(reducer)
修改后的 src/index.js
// src/index.js import React from 'react' import ReactDOM from 'react-dom' import Counter from './components/Counter' import { Provider } from 'react-redux' import { store } from './store' ReactDOM.render( // 通過 provider 組件將 store 放在了全局,供所有組件可以訪問 <React.StrictMode> <Provider store={store}> <Counter /> </Provider> </React.StrictMode>, document.getElementById('root') )
為 Action 傳遞參數(shù)
當前計數(shù)器對數(shù)字進行遞增/減的數(shù)值為 1,現(xiàn)在通過給 Action 傳遞參數(shù),允許自定義數(shù)值。
- 傳遞參數(shù):調(diào)用觸發(fā) Action 函數(shù)的時候傳遞參數(shù)
- 接收參數(shù):返回 Action 的函數(shù)接收參數(shù),添加到返回的 Action 對象中
- 處理參數(shù):reducer 函數(shù)處理的時候,可以從 action 對象獲取這個參數(shù),進行處理
傳遞參數(shù):
// src/components/Counter.js function Counter({ count, increment, decrement }) { // 修改 Counter 組件中調(diào)用 increment decrement 函數(shù)的地方 return ( <div> <button onClick={() => increment(5)}>+</button> <span>{count}</span> <button onClick={() => decrement(5)}>-</button> </div> ) }
接收參數(shù):
// src\store\actions\counter.action.js import { DECREMENT, INCREMENT } from "../const/counter.const" export const increment = payload => ({ type: INCREMENT, payload }) export const decrement = payload => ({ type: DECREMENT, payload })
處理參數(shù):
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from "../const/counter.const" const initialState = { count: 0 } function reducer(state = initialState, action) { switch (action.type) { case INCREMENT: return { count: state.count + action.payload } case DECREMENT: return { count: state.count - action.payload } default: return state } } export default reducer
Redux 彈出框
在頁面中顯示兩個按鈕:
- 顯示:顯示彈出框
- 隱藏:隱藏彈出框
初始化靜態(tài)內(nèi)容
Modal 組件
// src\components\Modal.js function Modal() { const styles = { width: 200, height: 200, position: 'absolute', left: '50%', top: '50%', marginLeft: -100, marginTop: -100, backgroundColor: 'skyblue' } return ( <div> <button>顯示</button> <button>隱藏</button> <div style={styles}></div> </div> ) } export default Modal
修改 src/index.js
// src/index.js import React from 'react' import ReactDOM from 'react-dom' import App from './App' import { Provider } from 'react-redux' import { store } from './store' ReactDOM.render( // 通過 provider 組件將 store 放在了全局,供所有組件可以訪問 <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root') )
修改 src/App.js
// src\App.js import Counter from './components/Counter' import Modal from './components/Modal' function App() { return ( <div> <Counter /> <Modal /> </div> ) } export default App
添加默認隱藏狀態(tài)
在 reducer 中添加顯示狀態(tài)的屬性
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from "../const/counter.const" const initialState = { count: 0, showStatus: false } function reducer(state = initialState, action) {...} export default reducer
在組件中使用狀態(tài)
// src\components\Modal.js import { connect } from 'react-redux' function Modal({ showStatus }) { const styles = { width: 200, height: 200, position: 'absolute', left: '50%', top: '50%', marginLeft: -100, marginTop: -100, backgroundColor: 'skyblue', display: showStatus ? 'block' : 'none' } return ( <div> <button>顯示</button> <button>隱藏</button> <div style={styles}></div> </div> ) } const mapStateToProps = state => ({ showStatus: state.showStatus }) export default connect(mapStateToProps)(Modal)
定義操作按鈕
定義 Action 的 type
// src\store\const\modal.const.js export const SHOWMODAL = 'showModal' export const HIDEMODAL = 'hideModal'
定義生成 Action 的函數(shù)
// src\store\actions\modal.actions.js import { HIDEMODAL, SHOWMODAL } from "../const/modal.const" export const show = () => ({ type: SHOWMODAL }) export const hide = () => ({ type: HIDEMODAL })
創(chuàng)建觸發(fā) Action 的方法并使用
// src\components\Modal.js import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import * as modalActions from '../store/actions/modal.actions' function Modal({ showStatus, show, hide }) { const styles = { width: 200, height: 200, position: 'absolute', left: '50%', top: '50%', marginLeft: -100, marginTop: -100, backgroundColor: 'skyblue', display: showStatus ? 'block' : 'none' } return ( <div> <button onClick={show}>顯示</button> <button onClick={hide}>隱藏</button> <div style={styles}></div> </div> ) } const mapStateToProps = state => ({ showStatus: state.showStatus }) const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch) export default connect(mapStateToProps, mapDispatchToProps)(Modal)
修改 reducer 函數(shù),處理變更:
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from '../const/counter.const' import { HIDEMODAL, SHOWMODAL } from '../const/modal.const' const initialState = { count: 0, showStatus: false } function reducer(state = initialState, action) { switch (action.type) { case INCREMENT: // reducer 返回的對象會替換 store 中的狀態(tài)對象,所以要將其它狀態(tài)也包含進去 return { ...state, count: state.count + action.payload } case DECREMENT: return { ...state, count: state.count - action.payload } case SHOWMODAL: return { ...state, showStatus: true } case HIDEMODAL: return { ...state, showStatus: false } default: return state } } export default reducer
注意:reducer 返回的對象會替換 store 中的狀態(tài)對象,所以要將其它狀態(tài)也包含進去
衍生的問題
在 reducer 函數(shù)中匹配了所有狀態(tài)的變更,當項目越來越大,狀態(tài)越來越多時,管理起來就很麻煩。
所以要將 rreducer 函數(shù)進行拆分。
拆分合并 reducer 拆分
將 modal 拆分出去
// src\store\reducers\modal.reducer.js import { HIDEMODAL, SHOWMODAL } from '../const/modal.const' const initialState = { show: false } const reducer = (state = initialState, action) => { switch (action.type) { case SHOWMODAL: return { ...state, showStatus: true } case HIDEMODAL: return { ...state, showStatus: false } default: return state } } export default reducer
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from '../const/counter.const' const initialState = { count: 0, } function reducer(state = initialState, action) { switch (action.type) { case INCREMENT: // reducer 返回的對象會替換 store 中的狀態(tài)對象,所以要將其它狀態(tài)也包含進去 return { ...state, count: state.count + action.payload } case DECREMENT: return { ...state, count: state.count - action.payload } default: return state } } export default reducer
合并
合并 reducer 需要借助 redux 提供的 combineReducers
方法。
combineReducers 把一個由多個不同 reducer 函數(shù)作為 value 的 object 對象,合并成一個最終的 reducer 函數(shù),然后就可以對這個 reducer 調(diào)用 createStore 方法。
合并后的 reducer 可以調(diào)用各個子 reducer,并把它們返回的結(jié)果合并成一個 state 對象。
由 combineReducers() 返回的 state 對象,會將傳入的每個 reducer 返回的 state 按傳遞給 combineReducers() 時對應(yīng)的 key 進行命名。
// src\store\reducers\root.reducer.js import { combineReducers } from 'redux' import CounterReducer from './counter.reducer' import ModalReducer from './modal.reducer' // 合并后的 store 為 { counter: { count: 0 }, modal: { showStatus: false } } export default combineReducers({ counter: CounterReducer, modal: ModalReducer })
// src\store\index.js import { createStore } from 'redux' import RootReducer from './reducers/root.reducer' export const store = createStore(RootReducer)
調(diào)整組件
因為 store 中的數(shù)據(jù)結(jié)構(gòu)發(fā)生變化,所以還需要調(diào)整下組件中獲取狀態(tài)的地方
// src\store\index.js import { createStore } from 'redux' import RootReducer from './reducers/root.reducer' export const store = createStore(RootReducer)
// src\components\Modal.js const mapStateToProps = state => ({ showStatus: state.modal.showStatus })
到此這篇關(guān)于在 React 中使用 Redux 解決的問題的文章就介紹到這了,更多相關(guān)React Redux 案例內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React模擬實現(xiàn)Vue的keepAlive功能
Vue中,keep-alive組件可以緩存組件狀態(tài),在路由切換時重新掛載,實現(xiàn)這一功能在React中并不簡單,但我們可以借助一個第三方庫——react-activation 來模擬Vue的keep-alive功能,需要的朋友可以參考下2024-10-10解決React報錯Rendered more hooks than during
這篇文章主要為大家介紹了React報錯Rendered more hooks than during the previous render解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12