redux

183 阅读4分钟

redux在hooks中的使用(react-redux.js.org/api/hooks)

hooks在Redux v7.1.0 中是被支持的,原有的connect API仍然是被支持的,但是hooks API更简单和对ts的支持更好

react-redux v7.2.3包已经包含了@types/react-redux会自动进行安装,你也可以手动自己安装

# Redux + Plain JS template
npx create-react-app my-app --template redux

# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript

yarn add react-redux

npm install @types/react-redux

结合ts类型推导

// 推断出自身的类型
export type RootState = ReturnType<typeof store.getState>

export type AppDispatch = typeof store.disptach
app/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

useSelector

import { createStore } from 'redux'
import { Provider } from 'react-redux'
const store = createStore(rootReducer)

<Provider store={store}>
  <App></App>
</Provider>

const result: any = useSelector(selector: Function, equalityFn?: Function )
  • useSelector的基本使用
import { useSelector } from 'react-redux'
const counter = useSelector(state => state.counter)
// 使用props进行提取
export const TodoListItem = (props) => {
  const todo = useSelector((state) => state.todos[props.id])
  return <div>{todo.text}</div>
}
  • reselect
// reselect redux的中间件
import { createSelector } from 'reselect'
import {useSelector, useDispatch  } from 'react-redux'
const makeSelectCompletedTodosCount = () => createSelector(
  // 第一个参数是state 第二个参数是props(options?)
  (state) => state.todos,
  (_, completed) => completed,
  // resultFun 参数是前面回调的返回值
  (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 (
    <div>
      <span>Number of done todos:</span>
      <CompletedTodosCount completed={true} />
      <span>Number of unfinished todos:</span>
      <CompletedTodosCount completed={false} />
    </div>
  )
}

useDispatch

从redux的store中返回一个dispatch函数

import { useDispatch } from 'react-redux';
const dispatch = useDispatch()

注意:父组件传递给子组件的方法,尽量使用useCallback包裹,子组件尽量使用memo包裹来对props进行浅层比较

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

  const incrementCounter = useCallback(() => {
    // Safe to add dispatch to the dependencies array
    dispatch({ type: 'increment-counter' })
  }, [dispatch])
  
  return (
    <div>
      <span>{ value }</span>
      <MyIncrementButton onIncrement={incrementCounter}></MyIncrementButton>
    </div>
  )
}
// memo包裹组件 根据props进行浅层比较,减少不必要的重新渲染
export const MyIncrementButton = React.memo(({ onIncrement }) => {
  <button onClick={onIncrement}>Increment counter</button>
})

react-hooks并不知道dispatch函数应该是stable稳定的,会警告说dispatch应该要被添加进useEffect和useCallback的依赖项数组中

useStore

会返回一个与传递给Provider组件相同的redux store引用

使用场景:这个hooks不经常使用,像useSelector才是经常使用的hook,然而在少数情形下,比如需要访问store, 取代reducers这会是非常有用的

import { useStore } from 'react-redux'
export const CounterComponent = ({ value }) => {
  const store = useStore()
  // 不要这样做,如果store状态改变 组件不会自动更新
  return <div>{ store.getState() }</div>
}

自定义context

Provider 允许通过 context 参数指定一个可选的 context。在构建复杂可复用的组件时,若不想让私人 store 与使用这个组件的用户的 Redux store 发生冲突,可以使用这种方法。

import { createStore } from 'redux'
import { Provider, createStoreHook, createSelectorHook, createDispatchHook } from 'react-redux'

const MyContext = createContext(null)

export const useStore = createStoreHook(MyContext)
export const useSelector = createSelectorHook(MyContext)
export const useDispatch = createDispatchHook(MyContext)

const myStore = createStore(rootReducer)

export function MyProvider({ children }){
  return (
    <Provider store={ myStore } context={ MyContext }>
      { children }
    </Provider>
  )
}

类组件中的connect

用法: connect(mapStateToProps, mapDispatchToProps)(Component)

mapStateToProps

将redux中的state映射给组件的props

const mapStateToProps = (state, ownProps) => {
  return {
    todo: state.todos[ownProps.id]
  }
}

mapDispatchToProps

将redux中的actions映射给组件的props


常规用法

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    toggleTodo: () => dispatch(toggleTodo(ownProps.todoId)),
    decrement: () => dispatch({ type: 'DECREMENT' })
  }
}

<button onClick={() => this.props.toggleTodo(this.props.todoId)}></button>
  • bindActionCreators
import { bindActionCreators } from 'redux'
// 通过store的dispatch绑定actionCreators
bindActionCreators(mapDispatchToProps, dispatch)
// actionCreators.js
export const addTodo = () => ({ type: 'ADD_TODO' })
export const delTodo = () => ({ type: 'DEL_TODO' })
export const toggleTodo = () => ({ type: 'TOGGLE_TODO' })

1、注入todos和所有的action creators

// 1、注入todos和所有的action creators
import * as actionCreators from './actionCreators'

const mapStateToProps = (state) => ({
  todos: state.todos
})

export default connect(mapStateToProps, actionCreators)(TodoApp)

2、注入todos和所有的action creators 作为actions

// 2、注入todos和所有的action creators 作为actions
import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'

const mapStateToProps = (state) => ({
  todos: state.todos
})

const mapDispatchToProps = (dispatch) => {
  return { actions: bindActionCreators(actionCreators, dispatch) }
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

3、注入todos和一个特定的action creator(addTodo)

// 3、注入todos和一个特定的action creator(addTodo)
import { addTodo } from './actionCreators'
import { bindActionCreators } from 'redux'

const mapStateToProps = (state) => ({
  todos: state.todos
})

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators({ addTodo }, dispatch)
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

4、注入todos和特定的creators简洁语法

// 4、注入todos和特定的creators简洁语法
import { addTodo, deleteTodo } from './actionCreators'
import { bindActionCreators } from 'redux'

const mapStateToProps = (state) => ({
  todos: state.todos
})

const mapDispatchToProps = {
  addTodo,
  deleteTodo
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

5、注入 todos, todoActionCreators as todoActions, and counterActionCreators as counterActions

// 5、注入 todos, todoActionCreators as todoActions, and counterActionCreators as counterActions
import * as todoActionCreators from './todoActionCreators'
import * as counterActionCreators from './counterActionCreators'
import { bindActionCreators } from 'redux'

const mapStateToProps = (state) => ({
  todos: state.todos
})

const mapDispatchToProps = (dispatch) => {
  return {
    todoActions: bindActionCreators(todoActionCreators, dispatch),
    counterActions: bindActionCreators(counterActionCreators, dispatch)
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

6、注入 todos, todoActionCreators and counterActionCreators together as actions

// 6、注入 todos, todoActionCreators and counterActionCreators together as actions
import * as todoActionCreators from './todoActionCreators'
import * as counterActionCreators from './counterActionCreators'
import { bindActionCreators } from 'redux'

const mapStateToProps = (state) => ({
  todos: state.todos
})

const mapDispatchToProps = (dispatch) => {
  return {
    actions: bindActionCreators({ ...todoActionCreators, ...counterActionCreators  }, dispatch),
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

7、注入 todos, and all todoActionCreators and counterActionCreators directly as props

// 7、注入 todos, and all todoActionCreators and counterActionCreators directly as props
import * as todoActionCreators from './todoActionCreators'
import * as counterActionCreators from './counterActionCreators'
import { bindActionCreators } from 'redux'

const mapStateToProps = (state) => ({
  todos: state.todos
})

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators({ ...todoActionCreators, ...counterActionCreators }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)