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)