项目中使用context的原因
传递 props 是将数据通过 UI 树显式传递到使用它的组件的好方法。
但是当你需要在组件树中深层传递参数以及需要在组件间复用相同的参数时,传递 props 就会变得很麻烦。最近的根节点父组件可能离需要数据的组件很远,状态提升 到太高的层级会导致 “逐层传递 props” 的情况。
如何组件过多的话(成百上千个组件的话)一层一层往下传递的话,就会变得很复杂而且不容易维护,所以通过Context 让父组件可以为它下面的整个组件树提供数据。
Context的实现
- 创建Context对象
首先,需要创建一个Context对象,可以通过React.createContext来完成。Context对象可以包含一个Provider组件和一个Consumer组件,但通常只使用Provider。 - 提供Context的值
使用<MyContext.Provider>组件来提供Context的值,它的value属性传递给后代组件。 - 在消费组件中使用Context
通过React.useContext()进行调用实现
import {createContext, useContext} from 'react'
interface contextType {
name: string,
value: string,
}
const ctx = createContext<contextType | null>(null)
const [name, setName] = useState<string>('张三');
const [value, setValue] = useState<string>('1234');
const contextValue:contextType = {name, value}
<ctx.provider value={contextValue}>
子组件
</ctx.provider>
//子组件的调用
import {useContext} from 'react'
const useGetData = () => {
const value = useContext(ctx)
if(!value) throw new Error('')
return value
}
const value = useGetData()
console.log(ctx.name)
console.log(ctx.value)
}
注意点
- 你在创建context对象的使用需要指明你需要传递数据的类型
- 创建对象的时候,你如果没有一个合理的默认值时, 可以设置为null ,不过你在获取数据的时候需要添加判断
- 传递的属性需要包裹在value里面,
Context结合useReducer扩展应用
Context进行数据传递, 而useReducer通过这个钩子实现数据状态的修改
下面展示所用的数据类型
export interface State {
id: number;
text: string;
done: boolean;
};
export type CounterAction =
| { type: "added" , id: State['id'], text: State['text'] }
| { type: "changed"; task: State }
| { type: "deleted"; id: State['id'] }
State你所声明的变量的类型, CounterAction表示可以 dispatch 至 reducer 的不同 action
如何使用Reducer
- 定义一个reducer函数(根据不同的action返回不同的新状态)
- 在组件中调用useReducer,并传入reducer函数和初始值
- 事件发生时,通过dispatch函数分配一个action对象(通过reducer要返回那个新状态并渲染UI)
//1.创建reducer
const reducer = (tasks: State[], action: CounterAction) =>{
switch (action.type) {
case 'added':
return [
...tasks,
{
id: action.id,
text: action.text,
done: false
} ]
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
}
}
//2.调用useReducer
const initialState: state[] = [
{ id: 0, text: 'Learn React', done: true },
{ id: 1, text: 'Learn Redux', done: false },
{ id: 2, text: 'Learn TypeScript', done: false },
]
const [tasks, dispatch] = useReducer(reducer, initialState);
//3.dispactch分配状态
//比如下面这种写法
dispatch({type:"added", id: "你设置的id", text: "你获得的text"})
使用ts的注意点如下
- interface State 描述了 reducer state 的类型。
- type CounterAction 描述了可以 dispatch 至 reducer 的不同 action。
- const initialState: State 为初始 state 提供类型,并且也将成为 useReducer 默认使用的类型。
- reducer(state: State[], action: CounterAction): State[] 设置了 reducer 函数参数和返回值的类型
如何和context结合使用 完整代码如下
const App: React.FC = () => {
// 创建上下文对象
const reducer = (tasks: State[], action: CounterAction) =>{
switch (action.type) {
case 'added':
return [
...tasks,
{
id: action.id,
text: action.text,
done: false
} ]
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
}
}
const [tasks, dispatch] = useReducer(reducer, [
{ id: 0, text: 'Learn React', done: true },
{ id: 1, text: 'Learn Redux', done: false },
{ id: 2, text: 'Learn TypeScript', done: false },
]);
//常见context上下文对象
//上面已经讲过创建可以赋值为空
const TaskContext = createContext<State[] | null>(null)
const TaskDispatchContext = createContext<Dispatch<CounterAction> | null>(null)
return (
<>
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
子组件
</TasksDispatchContext.Provider>
</TasksContext.Provider>
</>
);
}
//子组件的实现
import { useContext, useState } from "react"
import { State } from "@/store/type/useReducer"
import { TasksContext, TasksDispatchContext } from "@/store/tasksContext/tasksContext"
const StateManage:React.FC = () => {
const useGetTasks = () => {
const tasks = useContext(TasksContext)
if(!tasks) throw new Error('useGetTasks必须放在TasksContext.Provider中')
return tasks
}
const useGetDispatch = () => {
const dispatch = useContext(TasksDispatchContext)
if(!dispatch) throw new Error('useGetTasksDispatch必须放在TasksContext.Provider中')
return dispatch
}
const dispatch = useGetDispatch();
const tasks = useGetTasks()
// if(!tasks) return null
const [id, setId] = useState(tasks[tasks.length - 1].id + 1)
const [text, setText] = useState('')
const addTask = () => {
dispatch({type: 'added', id, text})
setId(id + 1)
setText('')
}
// 删除数据
const deleteTask = (id: number) => {
dispatch({type: 'deleted', id})
}
// 修改数据
const changeTask = (value: State) => {
dispatch({type: 'changed', task: {id: value.id, text: value.text, done: true}})
}
return (
<div>
stateManage
<ul>
{tasks.map(item => {
return <li key = {item.id} style={{color: item.done ? 'black' : 'red'}}>
<p style={{display: "inline-block"}} onClick={() => deleteTask(item.id)}>{item.text}</p>
<button onClick={() => changeTask(item)} style={{marginLeft: '30px'}}>修改task</button>
</li>
})}
</ul>
{/* 添加状态 */}
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={addTask}>添加数据到tasks中</button>
</div>
)
}
export default StateManage