首頁 > 軟體

在 React 中使用 Redux 解決的問題小結

2022-10-23 18:00:53

在 React 中使用 Redux 解決的問題

在 React 專案中未加入 Redux 時的問題

在 React 中元件通訊的資料流是單向的,頂層元件可以通過 props 屬性向下層元件傳遞資料,而下層元件不能直接向上層元件傳遞資料。要實現下層元件修改上層元件的資料,需要上層元件傳遞修改資料的方法到下層元件。當專案越來越大的時候,元件之間傳遞資料以及傳遞修改資料的方法變得越來越困難。

在 React 專案中加入 Redux 的好處

使用 Redux 管理資料,由於 Store 獨立於元件,使得資料管理獨立於元件,解決了元件與元件之間傳遞資料困難的問題。

React + Redux 安裝 Redux

在 react 專案中使用 redux 要下載兩個模組

npm install redux react-redux

React 中 Redux 的工作流程

在 React 中 Redux 的工作流程有些變化:

  1. 元件通過 dispatch 方法觸發 Action
  2. Store 接收 Action 並將 Action 分發給 Reducer
  3. Reducer 根據 Action 型別對狀態進行更改並將更改後的狀態返回給 Store
  4. 元件訂閱了 Store 中的狀態,Store 中的狀態更新會同步到元件

React 計數器案例

React 實現

建立專案安裝模組

# 建立專案(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

初步實現

// 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

開發時我們會把元件寫在單獨的檔案中,如果將 Counter 元件單獨提取,就無法存取 store 物件以及一些其它的問題,所以需要使用 redux。

安裝 react-redux

npm install react-redux

react-redux 用於讓 react 和 redux 完美結合,它僅僅提供兩個內容:

  • Provider 元件
    • 它可以將建立出來的 store 放到全域性,允許所有元件存取
    • 可以解決將元件儲存在單獨檔案中時,存取 store
  • connect 方法
  • connect 方法會幫助訂閱 store,當 store 中的狀態發生變化,會重新渲染元件
    • 解決了需要手動訂閱更新元件狀態
  • connect 方法可以獲取 store 中的狀態,對映到元件的 props 屬性中
  • connect 方法可以獲取 dispatch 方法,元件可以通過 props.dispatch存取

Provide 元件

  • Provider 元件要包裹專案中所有的元件,也就是應該被放在最外層元件上
  • Provider 通過 store 屬性接收建立的 store 物件

connect 方法

  • 首先呼叫 connect 方法
    • 第一個引數是一個函數
      • 函數接收的第一個引數,即元件中的狀態 state
      • 函數要返回一個物件,該物件定義的屬性都會對映到元件的 props 屬性中
    • 第二個引數也是一個函數
      • 函數接收的第一個引數,即 dispatch 方法
      • 函數要返回一個物件,可以定義觸發 Action 的方法,它們同樣會被對映到元件的 props 屬性中
    • 返回一個方法
  • 繼續呼叫返回的方法,並傳遞當前的元件

修改程式碼

// 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 第二個引數簡化元件

// 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 方法繼續簡化

觸發 Action 的方法 incrementdecrement 的內容基本是一樣的,redux 提供 bindActionCreators 方法生成一個函數,來簡化這種重複性程式碼。

  • 引數
    • actionCreators: 物件或返回物件的函數
      • key是生成函數的名稱
      • value是一個返回 Action 的函數
    • dispatch: 需傳遞Store 的 dispatch 方法
  • 返回一個物件,同 connect 接收的第二個引數
// 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 提取到單獨的檔案中。

// srcstoreactionscounter.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)

程式碼重構

為了繼續演示 Redux 的相關內容,將與 Redux 相關的程式碼從 src/index.js中抽離出去,讓專案結構更加合理。

  • 將 reducer 函數抽離到一個檔案中
  • 將建立 store 的程式碼手裡到一個檔案中
  • 將 Actions 的 type 抽象成模組中的成員,好處是:

    編輯器有提示,避免寫錯

    編輯器自動插入模組引入的程式碼

將 Actions 的 type 抽象成模組中的成員

// srcstoreactionscounter.action.js
import { DECREMENT, INCREMENT } from "../const/counter.const"
export const increment = () => ({ type: INCREMENT })
export const decrement = () => ({ type: DECREMENT })

將 reducer 函數抽離到一個檔案中

// srcstorereducerscounter.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

將建立 store 的程式碼手裡到一個檔案中

// srcstoreindex.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 傳遞引數

當前計數器對數位進行遞增/減的數值為 1,現在通過給 Action 傳遞引數,允許自定義數值。

  • 傳遞引數:呼叫觸發 Action 函數的時候傳遞引數
  • 接收引數:返回 Action 的函數接收引數,新增到返回的 Action 物件中
  • 處理引數:reducer 函數處理的時候,可以從 action 物件獲取這個引數,進行處理

傳遞引數:

// src/components/Counter.js
function Counter({ count, increment, decrement }) {
	// 修改 Counter 元件中呼叫 increment decrement 函數的地方
  return (
    <div>
      <button onClick={() => increment(5)}>+</button>
      <span>{count}</span>
      <button onClick={() => decrement(5)}>-</button>
    </div>
  )
}

接收引數:

// srcstoreactionscounter.action.js
import { DECREMENT, INCREMENT } from "../const/counter.const"
export const increment = payload => ({ type: INCREMENT, payload })
export const decrement = payload => ({ type: DECREMENT, payload })

處理引數:

// srcstorereducerscounter.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 彈出框

在頁面中顯示兩個按鈕:

  • 顯示:顯示彈出框
  • 隱藏:隱藏彈出框

初始化靜態內容

Modal 元件

// srccomponentsModal.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

// srcApp.js
import Counter from './components/Counter'
import Modal from './components/Modal'

function App() {
  return (
    <div>
      <Counter />
      <Modal />
    </div>
  )
}

export default App

新增預設隱藏狀態

在 reducer 中新增顯示狀態的屬性

// srcstorereducerscounter.reducer.js
import { DECREMENT, INCREMENT } from "../const/counter.const"

const initialState = {
  count: 0,
  showStatus: false
}
function reducer(state = initialState, action) {...}

export default reducer

在元件中使用狀態

// srccomponentsModal.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

// srcstoreconstmodal.const.js
export const SHOWMODAL = 'showModal'
export const HIDEMODAL = 'hideModal'

定義生成 Action 的函數

// srcstoreactionsmodal.actions.js
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const"

export const show = () => ({ type: SHOWMODAL })
export const hide = () => ({ type: HIDEMODAL })

建立觸發 Action 的方法並使用

// srccomponentsModal.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 函數,處理變更:

// srcstorereducerscounter.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 中的狀態物件,所以要將其它狀態也包含進去
      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 中的狀態物件,所以要將其它狀態也包含進去

衍生的問題

在 reducer 函數中匹配了所有狀態的變更,當專案越來越大,狀態越來越多時,管理起來就很麻煩。

所以要將 rreducer 函數進行拆分。

拆分合並 reducer 拆分

將 modal 拆分出去

// srcstorereducersmodal.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
// srcstorereducerscounter.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 中的狀態物件,所以要將其它狀態也包含進去
      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 函數作為 value 的 object 物件,合併成一個最終的 reducer 函數,然後就可以對這個 reducer 呼叫 createStore 方法。

合併後的 reducer 可以呼叫各個子 reducer,並把它們返回的結果合併成一個 state 物件。

由 combineReducers() 返回的 state 物件,會將傳入的每個 reducer 返回的 state 按傳遞給 combineReducers() 時對應的 key 進行命名。

// srcstorereducersroot.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
})
// srcstoreindex.js
import { createStore } from 'redux'
import RootReducer from './reducers/root.reducer'

export const store = createStore(RootReducer)

調整元件

因為 store 中的資料結構發生變化,所以還需要調整下元件中獲取狀態的地方

// srcstoreindex.js
import { createStore } from 'redux'
import RootReducer from './reducers/root.reducer'

export const store = createStore(RootReducer)
// srccomponentsModal.js
const mapStateToProps = state => ({
  showStatus: state.modal.showStatus
})

到此這篇關於在 React 中使用 Redux 解決的問題的文章就介紹到這了,更多相關React Redux 案例內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com