一、useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init?)
- useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
- 数据结构简单用useState,复杂时用useReducer
- 简化版的redux
- 结合context解决跨组件的问题
- state dispatch默认没有模块化,数据混在一起
- state或store:数据
- action:命令
- reducer: 规定,根据action的type做相应处理返回新的state
- dispatch: 派发action
二、基本使用
CounterReducer.tsx
// import React, { FC, useState } from 'react' //注释的是使用useState的情况
import React, { FC, useReducer } from 'react'
type StateType = { count: number }
type ActionType = { type: string }
const initialState: StateType = { count: 100 } // 初始值100
// 根据传入的action 返回新的state(不可变数据)
function reducer(state: StateType, action: ActionType) {
switch(action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
default:
throw new Error()
}
}
const CountReducer:FC = () => {
// const [count, setCount] = useState(100)
const [state, dispatch] = useReducer(reducer, initialState)
return (
// <>
// <p>coint: {count}</p>
// <button onClick={() => setCount(count + 1)}>+</button>
// <button onClick={() => setCount(count - 1)}>-</button>
// </>
<>
<p>coint: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
)
}
export default CountReducer
三、Todo List By useReducer项目
使用Context和useReducer实现跨组件通讯
代码地址:点击跳转
store.ts
reducer.tsx
List.tsx
import React, { FC, useContext } from 'react'
// import reducer from './reducer'
// import initialState from './store'
import { TodoContext } from '.'
const List:FC = () => {
// const [state, dispatch] = useReducer(reducer, initialState)
// const [state, dispatch] = useReducer(reducer, initialState)
const context = useContext(TodoContext)
const { state, dispatch } = context
const del = (id: string) => {
dispatch({
type: 'delete',
payload: id
})
}
return (
<>
<p>list</p>
<ul>
{
state.map(item => {
return <li key={item.id}>
<span>{item.title}</span>
<button onClick={() => del(item.id)}>删除</button>
</li>
})
}
</ul>
</>
)
}
export default List
InputForm.tsx
import React, { FC, ChangeEvent, useState, useContext } from 'react'
// import reducer from './reducer'
// import initialState from './store'
import { nanoid } from 'nanoid'
import { TodoContext } from '.'
const InputForm:FC = () => {
// const [state, dispatch] = useReducer(reducer, initialState)
const context = useContext(TodoContext)
const { state, dispatch } = context
// 输入框文字
const [text, setText] = useState('')
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setText(event.target.value)
}
// 新增todo
const handleSubmit = (event: ChangeEvent<HTMLFormElement>) => {
event.preventDefault()
if (!text.trim()) return
const newTodo = {
id: nanoid(5),
title: text
}
dispatch({
type: 'add',
payload: newTodo
})
setText('')
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="new-todo">what needs to be done?</label>
<br />
<input id='new-todo' onChange={handleChange} value={text} />
<button type='submit'>Add #{state.length + 1}</button>
</form>
)
}
export default InputForm
index.tsx
import React, { FC, createContext, useReducer } from 'react'
import List from './List'
import InputForm from './InputForm'
import reducer, { ActionType } from './reducer'
import initialState from './store'
export const TodoContext = createContext({
state: initialState,
// eslint-disable-next-line
dispatch: ( action: ActionType ) => {/* 空 */}
})
const Demo:FC = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<TodoContext.Provider value={{ state, dispatch }}>
<p>Todo list by reducer</p>
<List />
<InputForm />
</TodoContext.Provider>
)
}
export default Demo