在 React 中使用 Redux 解決的問題小結
在 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 完美結合,它僅僅提供兩個內(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 組件要包裹項目中所有的組件,也就是應該被放在最外層組件上
- 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ù),來簡化這種重復性代碼。
- 參數(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)
代碼重構
為了繼續(xù)演示 Redux 的相關內(nèi)容,將與 Redux 相關的代碼從 src/index.js中抽離出去,讓項目結構更加合理。
- 將 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,并把它們返回的結果合并成一個 state 對象。
由 combineReducers() 返回的 state 對象,會將傳入的每個 reducer 返回的 state 按傳遞給 combineReducers() 時對應的 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ù)結構發(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
})
到此這篇關于在 React 中使用 Redux 解決的問題的文章就介紹到這了,更多相關React Redux 案例內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
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

