react-redux

Connect: Extracting Data with mapStateToProps

  1. mapStateToProps 返回 state 中所需的最少數(shù)據(jù)
  2. mapStateToProps 的返回值變化,組件會重新渲染
  3. mapStateToProps可以重新組裝數(shù)據(jù)
  4. mapStateToProps 不應(yīng)做耗時(shí)操作。耗時(shí)操作可以嘗試在 action creator 、reduce 、render 中實(shí)現(xiàn),如果確實(shí)需要在 mapStateToProps 中實(shí)現(xiàn),可以考慮使用 可記憶的 selector, 例如 reselect
  5. 如果需要返回引用對象,一定返回新對象
  6. 也可以返回函數(shù) (state, [props]) => object ,此時(shí)每一個(gè) connect 的實(shí)例對象,都會調(diào)用一次 makeMapStateToProps 函數(shù)

Connect: Dispatching Actions with mapDispatchToProps

  1. 不傳這個(gè)參數(shù),默認(rèn)會將 dispatch 添加到 props 中。如果傳了,dispatch 默認(rèn)不會傳入 props 中。如果需要,需顯式返回。
  2. mapDispatchToProps 支持 Function 和 Object 兩種形式。推薦使用 plain object ,除非有特殊情況。
  3. 不建議在 component 中直接調(diào)用 store.dispatch 或者通過 context 直接獲取 dispatch

函數(shù)形式:

const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })

const mapDispatchToProps = dispatch => {
return {
    // dispatching actions returned by action creators
    increment: () => dispatch(increment()),
    decrement: () => dispatch(decrement()),
    reset: () => dispatch(reset())
  }
}

bindActionCreators 形式:

import { bindActionCreators } from 'redux'
// ...

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ increment, decrement, reset }, dispatch)
}

// component receives props.increment, props.decrement, props.reset
connect(
    null,
    mapDispatchToProps
)(Counter)

Plain Object 形式:
當(dāng)每一個(gè)字段都是對象的時(shí)候,redux 會假定這個(gè)對象就是一個(gè) action creator。但是此時(shí) props 中就沒有 dispatch 了。

import {increment, decrement, reset} from "./counterActions";

const actionCreators = {
    increment,
    decrement,
    reset
}

export default connect(mapState, actionCreators)(Counter);

// or
export default connect(
  mapState,
  { increment, decrement, reset }
)(Counter);

如何獲取 Store

  1. 使用 useStore Hook
  2. 理解 Context 的使用
    react-redux 是通過 react 的 context 特性實(shí)現(xiàn)向深層嵌套的組件傳遞 redux store 的。React redux version 6 中,context 是 ReactReduxContext ,由 React.createContext() 創(chuàng)建。
    <Provider> 實(shí)際是 <ReactReduxContext.Provider>
    connect 實(shí)際是用 <ReactReduxContext.Consumer> 獲取數(shù)據(jù)
  3. 自定義 context
  4. 多個(gè) store
  5. 直接使用 ReactReduxContext
import { ReactReduxContext } from 'react-redux'

// in your connected component
function MyConnectedComponent() {
    return (
    <ReactReduxContext.Consumer>
     {({ store }) => {
    // do something useful with the store, like passing it to a child
    // component where it can be used in lifecycle methods
    }}
    </ReactReduxContext.Consumer>
  )
}

-----------API

connect

可以自定義 compare 函數(shù),默認(rèn)都是淺比較
{
context?: Object,
pure?: boolean,
areStatesEqual?: Function,
areOwnPropsEqual?: Function,
areStatePropsEqual?: Function,
areMergedPropsEqual?: Function,
forwardRef?: boolean,
}

Provider

connectAdvanced

connect 的一個(gè)底層實(shí)現(xiàn),一般用不到。

batch()

組合多個(gè) store 的改變在一個(gè)事件循環(huán)中,所以 UI 只重繪一次

import { batch } from 'react-redux'

function myThunk() {
    return (dispatch, getState) => {
      // should only result in one combined re-render, not two
      batch(() => {
        dispatch(increment())
        dispatch(increment())
      })
    }
}

Hooks

  1. useSelector
  • 從 React Redux v7 開始,由于使用了 batch behavior ,在同一個(gè)組件中一個(gè) action 導(dǎo)致的多個(gè) useSelector 只會導(dǎo)致一次重繪。
  • useSelector 應(yīng)執(zhí)行快,避免耗時(shí)操作
  • 具有緩存機(jī)制,比較機(jī)制是 ===
  • 可以手動傳入比較機(jī)制
import { shallowEqual, useSelector } from 'react-redux'

// later
const selectedData = useSelector(selectorReturningObject, shallowEqual)
  • 配合 reselect 使用時(shí),當(dāng)一個(gè) selector 只在一個(gè)組件中使用時(shí),確保 selector 是同一個(gè)實(shí)例;當(dāng) selector 要在多個(gè)組件或者同個(gè)組件的多個(gè)實(shí)例中使用時(shí),確保 selector 是多個(gè)實(shí)例。因?yàn)?reselect 的 selector 是根據(jù)參數(shù)的變化來緩存計(jì)算結(jié)果的。
import React, { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const makeSelectCompletedTodosCount = () =>
    createSelector(
      state => state.todos,
      (_, completed) => completed,
      (todos, completed) =>
      todos.filter(todo => todo.completed === completed).length
    )

export const CompletedTodosCount = ({ completed }) => {
const selectCompletedTodosCount =         useMemo(makeSelectCompletedTodosCount, [])

  const matchingCount = useSelector(state =>
      selectCompletedTodosCount(state, completed)
  )

  return <div>{matchingCount}</div>
}

export const App = () => {
    return (
      <>
      <span>Number of done todos:</span>
      <CompletedTodosCount completed={true} />
      <span>Number of unfinished todos:</span>
      <CompletedTodosCount completed={false} />
    </>
  )
}
  1. useDispatch
  • 只要 store 對象不變,useDispatch 的返回值 dispatch 就不會變。一般來說,在應(yīng)用中,是不變的。但 React hooks lint rules 不知道,所以 dependency 里面可以加上 dispatch
  • 一個(gè)利用 useMemo 的優(yōu)化示例
import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
    const dispatch = useDispatch()
    const incrementCounter = useCallback(
      () => dispatch({ type: 'increment-counter' }),
      [dispatch]
    )

    return (
      <div>
        <span>{value}</span>
        <MyIncrementButton onIncrement={incrementCounter} />
      </div>
    )
  }

  export const MyIncrementButton = React.memo(({ onIncrement }) => (
      <button onClick={onIncrement}>Increment counter</button>
  ))

這個(gè)例子中,React.useMemo 的使用可以避免 onIncrement 的變化導(dǎo)致 MyIncrementButton 的重繪。

  1. useStore
    這應(yīng)該盡量少用,優(yōu)先考慮 useSelector
  2. 自定義 context 的相關(guān) hooks 的使用
    createStoreHook(MyContext)
    createDispatchHook(MyContext)
    createDispatchHook(MyContext)
警告

useSelector 可能會存在 Stale Props and "Zombie Children" 的問題。(舊的 Props 和 僵尸子節(jié)點(diǎn))

性能

使用 connect() 的組件,如果 state 和 props 都沒有改變,即使父組件重新渲染,子組件也不會重新渲染。但是 useSelector 的函數(shù)組件,子組件會隨父組件一起重新渲染,即使 state 和 props 都沒有改變。
為了更好的性能優(yōu)化,我們可以使用 React.memo 來解決這個(gè)問題:

const CounterComponent = ({ name }) => {
  const counter = useSelector(state => state.counter)
  return (
    <div>
      {name}: {counter}
    </div>
  )
}

export const MemoizedCounterComponent = React.memo(CounterComponent)

Hooks Recipes

  • useActions:bindActionCreators 在函數(shù)組件中的替代,需要自己復(fù)制黏貼定義。
import { bindActionCreators } from 'redux'
import { useDispatch } from 'react-redux'
import { useMemo } from 'react'

export function useActions(actions, deps) {
  const dispatch = useDispatch()
  return useMemo(
    () => {
      if (Array.isArray(actions)) {
        return actions.map(a => bindActionCreators(a, dispatch))
      }
      return bindActionCreators(actions, dispatch)
    },
    deps ? [dispatch, ...deps] : [dispatch]
  )
}
  • useShallowEqualSelector
import { useSelector, shallowEqual } from 'react-redux'

export function useShallowEqualSelector(selector) {
  return useSelector(selector, shallowEqual)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 我們已經(jīng)詳細(xì)介紹了Action,Reducer,Store和它們之間的流轉(zhuǎn)關(guān)系。Redux的基礎(chǔ)知識差不多也介紹完...
    張歆琳閱讀 3,878評論 1 17
  • 英文文檔鏈接 API <Provider store> 使得層級之下的組件可以通過connect()函數(shù)訪問到Re...
    txwslyf閱讀 1,545評論 0 0
  • 注意:文章很長,只想了解邏輯而不深入的,可以直接跳到最后的總結(jié)部分。 初識 首先,從它暴露對外的API開始 現(xiàn)在對...
    stonehank閱讀 702評論 0 0
  • 前言 本文 有配套視頻,可以酌情觀看。 文中內(nèi)容因各人理解不同,可能會有所偏差,歡迎朋友們聯(lián)系我討論。 文中所有內(nèi)...
    珍此良辰閱讀 12,192評論 23 111
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    余生動聽閱讀 10,851評論 0 11

友情鏈接更多精彩內(nèi)容