在react中使用redux並實現計數器案例

開水泡飯的部落格發表於2021-06-01

React + Redux

在recat中不使用redux 時遇到的問題

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

img

在react中加入redux 的好處

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

img

使用redux

下載redux

npm install redux react-redux

redux 工作流程

  1. 元件通過 dispatch 觸發action
  2. store 接受 action 並將 action 分發給 reducer
  3. reducer 根據 action 型別對狀態進行更改並將更改後的資料返回給store
  4. 元件訂閱了store中的狀態,store中的狀態更新會同步到元件

img

使用react+redux實現計數器

  1. 建立專案,並安裝 redux
# 如果沒有安裝react腳手架則執行這條命令安裝reate腳手架
npm install -g create-react-app
# 建立reate專案
create-react-app 專案名
# 進入專案
cd 專案名
# 安裝 redux
npm install redux reate-redux
  1. 引入redux,並根據開始實現的程式碼在react中實現計數器
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
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 Count() {
  return <div>
    <button onClick={() => store.dispatch(increment)}>+</button>
    <span>{store.getState().count}</span>
    <button onClick={() => store.dispatch(decrement)}>-</button>
  </div>
}

store.subscribe( () => {
  console.log(store.getState())
  ReactDOM.render(
    <React.StrictMode>
      <Count />
    </React.StrictMode>,
    document.getElementById('root')
  );
})

ReactDOM.render(
  <React.StrictMode>
    <Count />
  </React.StrictMode>,
  document.getElementById('root')
);

明顯以上方式雖然可以實現計數器的功能,但在實際專案中肯定不能這樣使用,因為元件一般都在單獨的檔案中的,這種方式明顯在其他元件中並不能獲取到Store。

計數器案例程式碼優化-讓store全域性可訪問

為了解決Store獲取問題需要使用react-redux來解決這個問題,react-redux給我們提供了Provider元件和connect方法

  • Provide 元件

是一個元件 可以吧建立出來的store 放在一個全域性的地方,讓元件可以拿到store,通過provider元件,將 store 放在了全域性的元件可以夠的到的地方 ,provider要求我們放在最外層元件

  • connect

connect 幫助我們訂閱store中的狀態,狀態發生改變後幫助我們重新渲染元件

通過 connect 方法我們可以拿到 store 中的狀態 把 store 中的狀態對映到props中

通過 connect 方法可以拿到 dispatch 方法

connect 的引數為一個函式 這個函式可以拿到store中的狀態,要求我們這個函式必須返回一個物件,在這個物件中寫的內容都會對映給元件的props屬性

connect 呼叫後返回一個函式 返回的這個函式繼續呼叫需要傳入元件告訴connect需要對映到那個元件的props

  1. 新建 Component 資料夾、建立 Count.js 檔案
import React from 'react'

function Count() {
    return <div>
        <button onClick={() => store.dispatch(increment)}>+</button>
        <span>{store.getState().count}</span>
        <button onClick={() => store.dispatch(decrement)}>-</button>
    </div>
}

export default Count
  1. 引入 Provider 元件放置在最外層,並制定store
ReactDOM.render(
  // 通過provider元件 將 store 放在了全域性的元件可以夠的到的地方  provider要求我們放在最外層元件
  <Provider store={store}><Count /></Provider>,
  document.getElementById('root')
);
  1. 引入 connect 方法 根據 connect 的使用來包裹元件
const mapStateProps = state => ({
    count: state.count,
    a: '1'
})
// connect 的引數為一個函式 這個函式可以拿到store中的狀態,要求我們這個函式必須返回一個物件,在這個物件中寫的內容都會對映給元件的props屬性
// connect 呼叫後返回一個函式 返回的這個函式繼續呼叫需要傳入元件告訴connect需要對映到那個元件的props
export default connect(mapStateProps)(Count)
  1. 改造 Count 元件把 action 複製到該檔案中
const increment = {
    type: 'increment'
}

const decrement = {
    type: 'decrement'
}
function Count({count,dispatch}) {
    return <div>
        <button onClick={() => {dispatch(increment)}}>+</button>
        <span>{count}</span>
        <button onClick={() => {dispatch(decrement)}}>-</button>
    </div>
}

現在專案已經可以執行了但是Count元件中的 提交Action的那一長串程式碼影響檢視的可讀性,所以程式碼還是需要優化

計數器案例程式碼優化-讓檢視中的程式碼可讀性更高

我們希望檢視中直接呼叫一個函式這樣檢視程式碼可讀性強,這個需要利用connect的第二個引數,第二個引數是一個函式,這個函式的形參就是dispatch方法,要求這個函式返回一個物件,返回的這個物件中的內容都會對映到元件的props屬性上

  1. 申明一個變數為connect中的第二個引數,在這個變數中返回執行不同action操作的物件
// connect 的第二個引數 這個引數是個函式 這個函式的形參就是dispatch方法 要求返回一個物件 這個物件中的屬性會被對映到元件的props上
const mapDispatchToProps = dispatch => ({
    increment (){
        dispatch({
            type: 'increment'
        })
    },
    decrement (){
        dispatch({
            type: 'decrement'
        })
    }
})

// connect 的引數為一個函式 這個函式可以拿到store中的狀態,要求我們這個函式必須返回一個物件,在這個物件中寫的內容都會對映給元件的props屬性
// connect 呼叫後返回一個函式 返回的這個函式繼續呼叫需要傳入元件告訴connect需要對映到那個元件的props
export default connect(mapStateProps, mapDispatchToProps)(Count)
  1. 在元件中結構props在檢視中直接繫結事件
function Count({count,increment,decrement}) {
    return <div>
        <button onClick={increment}>+</button>
        <span>{count}</span>
        <button onClick={decrement}>-</button>
    </div>
}

通過這次優化我們發現 呼叫 dispatch 觸發action 的方法的程式碼都是重複的,所以還需要繼續優化

優化呼叫 dispatch 觸發action 的方法的重複程式碼簡化

利用 bindActionCreators 來簡化 dispatch 觸發 action的操作,bindActionCreators來幫助我們生成執行action動作的函式

bindActionCreators 有兩個引數,第一個引數為 執行action的物件,第二個引數為 dispatch方法

  1. 分離action操作,新建store/actions/counter.actions.js檔案把執行action操作單獨放在這個檔案並匯出
export const increment = () => ({type: 'increment'})
export const decrement = () => ({type: 'decrement'})
  1. 在Count.js中匯入關於計數器的action,用bindActionCreators方法來生成dispatch執行action函式
import { bindActionCreators } from 'redux'
import * as counterActions from './../store/actions/counter.actions'


const mapDispatchToProps = dispatch => (bindActionCreators(counterActions, dispatch))
// connect 的引數為一個函式 這個函式可以拿到store中的狀態,要求我們這個函式必須返回一個物件,在這個物件中寫的內容都會對映給元件的props屬性
// connect 呼叫後返回一個函式 返回的這個函式繼續呼叫需要傳入元件告訴connect需要對映到那個元件的props
export default connect(mapStateProps, mapDispatchToProps)(Count)

程式碼優化到這裡我們發現,redux的程式碼與元件融合在一起,所以我需要拆分成獨立的,為什麼要抽離redux呢?因為我們要讓我們的程式碼結構更加合理

重構計數器,把redux相關程式碼抽離

把reducer函式抽離為單獨的檔案、把建立store抽離到單獨的檔案中

  1. 因為在reducer 和 actions中我們都寫了字串,但是字串沒有提示所以我們把字串定義成常量防止我們出現單詞錯誤這種低階錯誤,新建 src/store/const/counter.const.js 檔案
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
  1. 新建 src/store/reducers/counter.reducers.js 檔案把 reducer 函式抽離到此檔案中
import { INCREMENT, DECREMENT} from './../const/counter.const'
const initialState = {
    count: 0
}
// eslint-disable-next-line import/no-anonymous-default-export
export default (state = initialState, action) => {
    switch (action.type) {
        case INCREMENT:
            return {
                count: state.count + 1
            }
        case DECREMENT:
            return {
                count: state.count - 1
            }

        default:
            return state
    }
}
  1. 更改actions中的字串為引入變數
import { INCREMENT, DECREMENT} from './../const/counter.const'

export const increment = () => ({type: INCREMENT})
export const decrement = () => ({type: DECREMENT})
  1. 建立src/store/index.js檔案 ,在這個檔案中建立store 並匯出
import { createStore } from 'redux';
import reducer from './reducers/counter.reducers'

export const store = createStore(reducer)
  1. 在引入store的檔案中改變為衝專案中store檔案中引入store
import React from 'react';
import ReactDOM from 'react-dom';
import Count from './components/Count';
import { store } from './store'
import { Provider } from 'react-redux'
/**
 * react-redux 讓react 和 redux 完美結合
*    Provider 是一個元件 可以吧建立出來的store 放在一個全域性的地方 讓元件可以拿到store
*    connect  是一個方法
 */


ReactDOM.render(
  // 通過provider元件 將 store 放在了全域性的元件可以夠的到的地方  provider要求我們放在最外層元件
  <Provider store={store}><Count /></Provider>,
  document.getElementById('root')
);

為action 傳遞引數,對計數器案例做擴充套件

這個計數器案例已經實現了點選按鈕加一減一操作了,現在有個新需求我們需要加減一個數值例如加五減五

這就需要對action傳遞引數了

  1. 在檢視中按鈕繫結函式傳入引數
function Count({count,increment,decrement}) {
    return <div>
        <button onClick={() => increment(5)}>+</button>
        <span>{count}</span>
        <button onClick={() => decrement(5)}>-</button>
    </div>
}
  1. 在dispacth執行action動作時接受引數並傳入到action中
export const increment = payload => ({type: INCREMENT, payload})
export const decrement = payload => ({type: DECREMENT, payload})
  1. 在reducers中接收引數並作相應處理
export default (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
    }
}

原文地址:https://kspf.xyz/archives/10/

相關文章