Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。
安装
npm install --save redux
多数情况下,还需要使用 React 绑定库和开发者工具。
Redux 默认并不包含 React 绑定库,需要单独安装。
npm install --save @reduxjs/toolkit react-redux
npm install --save-dev redux-devtools
重点
应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。
1,创建 Redux Store
// src/redux/stor
import { configureStore } from '@reduxjs/toolkit'
import navSlice from './navSlice'
const store = configureStore({
reducer: {
nav: navSlice
},
})
// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>
// 推断出类型: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
export default store
2,为 React 提供 Redux Store
React Redux 包含一个 组件,这使得 Redux store 能够在应用的其他地方使用:
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import APP from './APP'
const container = document.getElementById('root') as HTMLElement
const root = ReactDOM.createRoot(container)
root.render(
<Provider store={store}>
<BrowserRouter>
<APP />
</BrowserRouter>
</Provider>
)
3,创建 Redux State Slice
// src/redux/navSlice
import { createSlice } from '@reduxjs/toolkit'
import type { RootState } from './store'
// 定义初始化状态的类型
interface navState {
value: number
}
// 初始化状态
const initialState: navState = {
value: 33,
}
export const navSlice = createSlice({
name: 'nav',
initialState,
reducers: {
increment: (state) => {
// Redux Toolkit 允许我们在 reducers 中编写 mutating 逻辑。
// 它实际上并没有 mutate state 因为它使用了 Immer 库,
// 它检测到草稿 state 的变化并产生一个全新的基于这些更改的不可变 state
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
},
})
// 为每个 case reducer 函数生成 Action creators
export const { increment, decrement, incrementByAmount } = navSlice.actions
export const selectCount = (state: RootState) => state
export default navSlice.reducer
4,将 Slice Reducers 添加到 Store 中
// src/redux/store
import { configureStore } from '@reduxjs/toolkit'
import navSlice from './navSlice'
export default configureStore({
reducer: {
nav: navSlice
},
})
5,组件中使用 Redux 状态和操作
import React, { useEffect, useState } from "react"
import { NavLink } from "react-router-dom"
import styles from './Nav.scss'
import { useDispatch, useSelector } from "react-redux"
import { RootState } from "../../redux/store"
import { increment } from "../../redux/navSlice"
interface Props {
id: number;
name: string;
bool: boolean;
}
const Nav: React.FC<Props> = (props) => {
const count = useSelector((state: RootState) => state.nav.value)
const dispatch = useDispatch()
const [perm, setPerm] = useState<boolean>(props.bool)
const changeNav = () => {
setPerm(!perm)
dispatch(increment())
}
return (
<div className={styles.nav}>
<button onClick={() => { changeNav() }}>导航切换</button>{count}
<span>{props.name}{props.id}</span>
{
perm ?
<nav>
<NavLink to="">首页</NavLink>
<NavLink to="product/123">产品</NavLink>
<NavLink to="contact">联系我们</NavLink>
<NavLink to="messages">messages</NavLink>
</nav> :
<nav>
<NavLink to="">首页</NavLink>
<NavLink to="product/123">产品</NavLink>
</nav>
}
</div>
)
}
export default Nav
虽然你可以将 RootState 和 AppDispatch 类型导入每个组件, 更好的方式是创建 useDispatch 和 useSelector 钩子的类型定义,以便在你的应用程序中使用 有几个重要的原因:
- 对于 useSelector ,不需要你每次输入(state: RootState)
- 对于 useDispatch ,默认的 Dispatch 类型不知道 thunk 。为了正确调度 thunk ,你需要使用 store
中包含 thunk 中间件类型的特定自定义 AppDispatch 类型,并将其与 useDispatch 一起使用。添加一个预先输入的useDispatch 钩子可以防止你忘记在需要的地方导入 AppDispatch。
// src/redux/hooks
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
// 在整个应用程序中使用,而不是简单的 `useDispatch` 和 `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
由于这些是实际变量,而不是类型,因此将它们定义在单独的文件中很重要(hooks.tsx),而不是 store 设置文件。这允许你将它们导入到需要使用挂钩的任何组件文件中,并避免潜在的循环导入依赖问题。
最后修改组件中的使用方法
import React, { useEffect, useState } from "react"
import { NavLink } from "react-router-dom"
import styles from './Nav.scss'
// import { useDispatch, useSelector } from "react-redux"
import { useAppDispatch, useAppSelector } from "src/redux/hooks"
import { RootState } from "../../redux/store"
import { increment } from "../../redux/navSlice"
interface Props {
id: number;
name: string;
bool: boolean;
}
const Nav: React.FC<Props> = (props) => {
// const count = useSelector((state: RootState) => state.nav.value)
// `state` 参数已正确推断为 `RootState` 类型
const count = useAppSelector(state => state.nav.value)
// const dispatch = useDispatch()
const dispatch = useAppDispatch()
const [perm, setPerm] = useState<boolean>(props.bool)
const changeNav = () => {
setPerm(!perm)
dispatch(increment())
}
return (
<div className={styles.nav}>
<button onClick={() => { changeNav() }}>导航切换</button>{count}
<span>{props.name}{props.id}</span>
{
perm ?
<nav>
<NavLink to="">首页</NavLink>
<NavLink to="product/123">产品</NavLink>
<NavLink to="contact">联系我们</NavLink>
<NavLink to="messages">messages</NavLink>
</nav> :
<nav>
<NavLink to="">首页</NavLink>
<NavLink to="product/123">产品</NavLink>
</nav>
}
</div>
)
}
export default Nav