react-redux@7.1.0 useSelector: 別啦 connect

React-redux 7.1發版啦。react

API簡單的介紹

由於在新的項目中用到了hooks,可是用的時候react-redux還處於alpha.x版本的狀態。用不了最新的API,感受不是很美妙。好在,這兩天發佈了7.1版本。git

如今來看看怎麼用這個新的API。github

useSelector()

const result : any = useSelector(selector : Function, equalityFn? : Function)
複製代碼

這個是幹啥的呢?就是從redux的store對象中提取數據(state)。redux

注意: 由於這個可能在任什麼時候候執行屢次,因此你要保持這個selector是一個純函數。api

這個selector方法相似於以前的connect的mapStateToProps參數的概念。而且useSelector會訂閱store, 當action被dispatched的時候,會運行selector。數組

固然,僅僅是概念和mapStateToProps類似,可是確定有不一樣的地方,看看selector和mapStateToProps的一些差別:緩存

  • selector會返回任何值做爲結果,並不只僅是對象了。而後這個selector返回的結果,就會做爲useSelector的返回結果。
  • 當action被dispatched的時候,useSelector()將對前一個selector結果值和當前結果值進行淺比較。若是不一樣,那麼就會被re-render。 反之亦然。
  • selector不會接收ownProps參數,可是,能夠經過閉包(下面有示例)或使用柯里化selector來使用props。
  • 使用記憶(memoizing) selector時必須格外當心(下面有示例)。
  • useSelector()默認使用===(嚴格相等)進行相等性檢查,而不是淺相等(==)。

你可能在一個組件內調用useSelector屢次,可是對useSelector()的每一個調用都會建立redux store的單個訂閱。因爲react-reduxv7版本使用的react的批量(batching)更新行爲,形成同個組件中,屢次useSelector返回的值只會re-render一次。性能優化

相等比較和更新

當函數組件渲染時,會調用提供的selector函數,而且從useSelector返回其結果。(若是selector運行且沒有更改,則會返回緩存的結果)。閉包

上面有說到,只當對比結果不一樣的時候會被re-render。從v7.1.0-alpha.5開始,默認比較是嚴格比較(===)。這點於connect的時候不一樣,connect使用的是淺比較。這對如何使用useSelector()有幾個影響。jsp

使用mapState,全部單個屬性都在組合對象中返回。返回的對象是不是新的引用並不重要 - connect()只比較各個字段。使用useSelector就不行了,默認狀況下是,若是每次返回一個新對象將始終進行強制re-render。若是要從store中獲取多個值,那你能夠這樣作:

  • useSelector()調用屢次,每次返回一個字段值。

  • 使用Reselect或相似的庫建立一個記憶化(memoized) selector,它在一個對象中返回多個值,但只在其中一個值發生更改時才返回一個新對象。

  • 使用react-redux 提供的shallowEqual函數做爲useSelectorequalityFn參數。

就像下面這樣:

import { shallowEqual, useSelector } from 'react-redux'

// later
const selectedData = useSelector(selectorReturningObject, shallowEqual)
複製代碼

useSelector 例子

上面作了一些基本的闡述,下面該用一些例子來加深理解。

基本用法

import React from 'react'
import { useSelector } from 'react-redux'

export const CounterComponent = () => {
  const counter = useSelector(state => state.counter)
  return <div>{counter}</div>
}
複製代碼

經過閉包使用props來肯定要提取的內容:

import React from 'react'
import { useSelector } from 'react-redux'

export const TodoListItem = props => {
  const todo = useSelector(state => state.todos[props.id])
  return <div>{todo.text}</div>
}
複製代碼

使用記憶化(memoizing) selector

對於memoizing不是很瞭解的,能夠通往此處瞭解。

當使用如上所示的帶有內聯selector的useSelector時,若是渲染組件,則會建立selector的新實例。只要selector不維護任何狀態,這就能夠工做。可是,記憶化(memoizing) selectors 具備內部狀態,所以在使用它們時必須當心。

當selector僅依賴於狀態時,只需確保它在組件外部聲明,這樣一來,每一個渲染所使用的都是相同的選擇器實例:

import React from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect' //上面提到的reselect庫

const selectNumOfDoneTodos = createSelector(
  state => state.todos,
  todos => todos.filter(todo => todo.isDone).length
)

export const DoneTodosCounter = () => {
  const NumOfDoneTodos = useSelector(selectNumOfDoneTodos)
  return <div>{NumOfDoneTodos}</div>
}

export const App = () => {
  return (
    <>
      <span>Number of done todos:</span>
      <DoneTodosCounter />
    </>
  )
}
複製代碼

若是selector依賴於組件的props,可是隻會在單個組件的單個實例中使用,則狀況也是如此:

import React from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const selectNumOfTodosWithIsDoneValue = createSelector(
  state => state.todos,
  (_, isDone) => isDone,
  (todos, isDone) => todos.filter(todo => todo.isDone === isDone).length
)

export const TodoCounterForIsDoneValue = ({ isDone }) => {
  const NumOfTodosWithIsDoneValue = useSelector(state =>
    selectNumOfTodosWithIsDoneValue(state, isDone)
  )

  return <div>{NumOfTodosWithIsDoneValue}</div>
}

export const App = () => {
  return (
    <>
      <span>Number of done todos:</span>
      <TodoCounterForIsDoneValue isDone={true} />
    </>
  )
}
複製代碼

可是,若是selector被用於多個組件實例而且依賴組件的props,那麼你須要確保每一個組件實例都有本身的selector實例(爲何要這樣?看這裏):

import React, { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const makeNumOfTodosWithIsDoneSelector = () =>
  createSelector(
    state => state.todos,
    (_, isDone) => isDone,
    (todos, isDone) => todos.filter(todo => todo.isDone === isDone).length
  )

export const TodoCounterForIsDoneValue = ({ isDone }) => {
  const selectNumOfTodosWithIsDone = useMemo(
    makeNumOfTodosWithIsDoneSelector,
    []
  )

  const numOfTodosWithIsDoneValue = useSelector(state =>
    selectNumOfTodosWithIsDoneValue(state, isDone)
  )

  return <div>{numOfTodosWithIsDoneValue}</div>
}

export const App = () => {
  return (
    <>
      <span>Number of done todos:</span>
      <TodoCounterForIsDoneValue isDone={true} />
      <span>Number of unfinished todos:</span>
      <TodoCounterForIsDoneValue isDone={false} />
    </>
  )
}
複製代碼

useDispatch()

const dispatch = useDispatch()
複製代碼

這個Hook返回Redux store中對dispatch函數的引用。你能夠根據須要使用它。

用法和以前的同樣,來看個例子:

import React from 'react'
import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch()

  return (
    <div>
      <span>{value}</span>
      <button onClick={() => dispatch({ type: 'increment-counter' })}>
        Increment counter
      </button>
    </div>
  )
}
複製代碼

當使用dispatch將回調傳遞給子組件時,建議使用useCallback對其進行記憶,不然子組件可能因爲引用的更改進行沒必要要地呈現。

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>
))
複製代碼

useStore()

const store = useStore()
複製代碼

這個Hook返回redux <Provider>組件的store對象的引用。

這個鉤子應該不長被使用。useSelector應該做爲你的首選。可是,有時候也頗有用。來看個例子:

import React from 'react'
import { useStore } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const store = useStore()

  // 僅僅是個例子! 不要在你的應用中這樣作.
  // 若是store中的state改變,這個將不會自動更新
  return <div>{store.getState()}</div>
}
複製代碼

性能

前面說了,selector的值改變會形成re-render。可是這個與connect有些不一樣,useSelector()不會阻止組件因爲其父級re-render而re-render,即便組件的props沒有更改。

若是須要進一步的性能優化,能夠在React.memo()中包裝函數組件:

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

export const MemoizedCounterComponent = React.memo(CounterComponent)
複製代碼

Hooks 配方

配方: useActions()

這個是alpha的一個hook,可是在alpha.4中聽取Dan的建議被移除了。這個建議是基於「binding actions creator」在基於鉤子的用例中沒啥特別的用處,而且致使了太多的概念開銷和語法複雜性。

你可能更喜歡直接使用useDispatch。你可能也會使用Redux的bindActionCreators函數或者手動綁定他們,就像這樣: const boundAddTodo = (text) => dispatch(addTodo(text))

可是,若是你仍然想本身使用這個鉤子,這裏有一個現成的版本,它支持將action creator做爲單個函數、數組或對象傳遞進來。

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] : deps)
}
複製代碼

配方: useShallowEqualSelector()

import { shallowEqual } from 'react-redux'

export function useShallowEqualSelector(selector) {
  return useSelector(selector, shallowEqual)
}
複製代碼

使用

如今在hooks組件裏,咱們不須要寫connect, 也不須要寫mapStateToProps, 也不要寫mapDispatchToProps了,只須要一個useSelector

問題

你們對於這個版本有沒有感受不滿意的地方?

原文:簡書: react-redux@7.1的api,用於hooks

代碼註釋:v7.1 code

相關文章
相關標籤/搜索