useReducer
在学习 redux 之前,我们先学习下useReducer的使用。
前面用的 setState 都是直接修改值,那如果在修改值之前需要执行一些固定的逻辑呢?
这时候就要用 useReducer 了:
js
import { Reducer, useReducer } from "react";
interface Data {
result: number;
}
interface Action {
type: 'add' | 'minus',
num: number
}
function reducer(state: Data, action: Action) {
switch(action.type) {
case 'add':
return {
result: state.result + action.num
}
case 'minus':
return {
result: state.result - action.num
}
}
return state;
}
function App() {
const [res, dispatch] = useReducer<Reducer<Data, Action>>(reducer, { result: 0});
return (
<div>
<div onClick={() => dispatch({ type: 'add', num: 2 })}>加</div>
<div onClick={() => dispatch({ type: 'minus', num: 1 })}>减</div>
<div>{res.result}</div>
</div>
);
}
export default App;
useReducer 的类型参数传入 Reducer<数据的类型,action 的类型>
然后第一个参数是 reducer,第二个参数是初始数据。
点击加的时候,触发 add 的 action,点击减的时候,触发 minus 的 action。
当然,你直接 setState 也可以:
js
import { useState } from "react";
function App() {
const [res, setRes] = useState({ result: 0});
return (
<div>
<div onClick={() => setRes({ result: res.result + 2 })}>加</div>
<div onClick={() => setRes({ result: res.result - 1 })}>减</div>
<div>{res.result}</div>
</div>
);
}
export default App;
有同学可能会说,用 useState 比 useReducer 简洁多了。
确实,这个例子不复杂,没必要用 useReducer。
但如果要执行比较复杂的逻辑呢?
用 useState 需要在每个地方都写一遍这个逻辑,而用 useReducer 则是把它封装到 reducer 里,通过 action 触发就好了。
当修改 state 的逻辑比较复杂,用 useReducer。
这就是useReducer的用法。
它有如下概念:
- state 或 store
- action
- reducer
- dispatch
了解了这些,学习redux就会轻松一些,因为useReducer就是抄了redux的逻辑写成的。
context + useReducer
我们使用context 和 useReducer 来完成一个全局共享的状态库。
创建store.ts,定义全局对象
js
import { nanoid } from 'nanoid'
export type TodoType = {
id: string
title: string
}
const initialState: TodoType[] = [
{
id: nanoid(5),
title: '学习react',
},
{
id: nanoid(5),
title: '学习vue',
},
]
export default initialState
创建reducer.ts,定义如何修改全局数据
js
import { type TodoType } from './store'
export type ActionType = {
type: string
payload?: any // 附加的内容,要新增的todo等
}
function reducer(state: TodoType[], action: ActionType) {
switch (action.type) {
case 'add':
return [...state, action.payload]
case 'delete':
return state.filter(todo => todo.id !== action.payload)
default:
throw new Error()
}
}
export default reducer
创建index.tsx,通过执行useReducer函数,并传入reducer, initialState,返回数据state,及修改state的函数dispatch。然后把 state, dispatch 赋值给 context,这样所有的组件都能获取到state, dispatch。
js
import { type FC, createContext, useReducer } from 'react'
import List from './List'
import InputForm from './InputForm'
import initialState from './store'
import reducer, { type ActionType } from './reducer'
export const TodoContext = createContext({
state: initialState,
dispatch: (action: ActionType) => {},
})
const Demo: FC = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<TodoContext.Provider value={{ state, dispatch }}>
<List />
<InputForm />
</TodoContext.Provider>
)
}
export default Demo
创建List.tsx:
js
import { type FC, useContext } from 'react'
import { TodoContext } from '.'
const List: FC = () => {
const { state, dispatch } = useContext(TodoContext)
return (
<ul>
{state.map(item => (
<li key={item.id}>
<span>{item.title}</span>
<button onClick={() => dispatch({ type: 'delete', payload: item.id })}>删除</button>
</li>
))}
</ul>
)
}
export default List
创建InputForm.tsx:
js
import { type FC, type ChangeEvent, useState, useContext, memo } from 'react'
import { nanoid } from 'nanoid'
import { TodoContext } from '.'
const InputForm: FC = () => {
const [text, setText] = useState('')
const { dispatch } = useContext(TodoContext)
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setText(e.target.value)
}
const handleSubmit = (e: ChangeEvent<HTMLFormElement>) => {
e.preventDefault()
dispatch({ type: 'add', payload: { id: nanoid(5), title: text } })
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="new-todo">what do you want to do?</label>
<br />
<input
type="text"
id="new-todo"
style={{ border: '1px solid #ccc' }}
onChange={handleChange}
value={text}
/>
<button type="submit">添加</button>
</form>
)
}
export default InputForm
这样就完成了一个简化版本的redux。
redux
下面我们看一个真实项目如何使用 redux:
首先安装 @reduxjs/toolkit 和 react-redux:
js
npm i @reduxjs/toolkit react-redux -S
新建 index.ts:
js
import { configureStore } from '@reduxjs/toolkit'
import userReducer, { type UserStateType } from './userReducer'
import pageInfoReducer, { type PageInfoStateType } from './pageInfoReducer'
export type StateType = {
user: UserStateType
pageInfo: PageInfoStateType
}
export default configureStore({
// reducer的值就是全局的state数据
reducer: {
user: userReducer,
pageInfo: pageInfoReducer,
},
})
新建userReducer.ts:
js
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'
export interface UserStateType {
username: string
nickname: string
}
const initialState: UserStateType = {
username: '',
nickname: '',
}
export const userSlice = createSlice({
name: 'user',
initialState: initialState,
reducers: {
// Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
// 并不是真正的改变状态值,因为它使用了 Immer 库
// 可以检测到"草稿状态" 的变化并且基于这些变化生产全新的
// 不可变的状态
logoutReducer: state => {
state.username = ''
state.nickname = ''
},
setUserInfoReducer: (state: UserStateType, action: PayloadAction<UserStateType>) => {
state.username = action.payload.username
state.nickname = action.payload.nickname
},
},
})
export const { logoutReducer, setUserInfoReducer } = userSlice.actions
export default userSlice.reducer
新建pageInfoReducer.ts:
js
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'
export interface PageInfoStateType {
title: string
desc?: string
js?: string
css?: string
isPublish?: boolean
}
const initialState: PageInfoStateType = {
title: '',
desc: '',
js: '',
css: '',
isPublish: false,
}
export const pageInfoSlice = createSlice({
name: 'pageInfo',
initialState: initialState,
reducers: {
resetPageInfo: (state: PageInfoStateType, action: PayloadAction<PageInfoStateType>) => {
Object.assign(state, action.payload)
},
changePageTitle: (state: PageInfoStateType, action: PayloadAction<string>) => {
state.title = action.payload
},
},
})
export const { resetPageInfo, changePageTitle } = pageInfoSlice.actions
export default pageInfoSlice.reducer
上面的步骤定义了数据源,以及定义了修改数据源的方法。
那怎么在页面获取数据呢?
新建一个文件useGetUserInfo.ts:
js
import { useSelector } from 'react-redux'
import { type StateType } from '../store'
import { type UserStateType } from '../store/userReducer'
function useGetUserInfo() {
const { username, nickname } = useSelector<StateType, UserStateType>(state => state.user)
return { username, nickname }
}
export default useGetUserInfo
StateType类型为整个数据源的类型
UserStateType类型为整个数据源里面属性user的类型
那如何更新数据呢?
js
import { resetPageInfo } from '@/store/pageInfoReducer'
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const handleValuesChange = () => {
const values = form.getFieldsValue()
dispatch(resetPageInfo(values))
}
redux 包括以下核心概念:
- state 或 store:数据源
- reducers:定义了如何修改数据源
- dispatch:触发reducer,并传入新的值