使用状态响应输入&&正确选择状态结构
使用 React,不用直接从代码层面修改 UI,只需要描述组件在不同状态下希望展现的 UI,然后根据用户输入触发状态更改。(此概念详情可以参考卡颂老师的《React设计原理》)
比如
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
// 以上写法会产生多余的状态
const fullName=firstName+lastName
// 在组件渲染时通过计算fullName 可以简化代码,并方便管理
在组件间共享状态(父==>子)
有时候需求需要两个组件的状态始终同步更改。要完成状态共享,可以将相关状态从这两个组件上移除,并把这些状态移到最近的父级组件,然后通过 props 将状态传递给这两个组件。这被称为“状态提升”
使用方法
将多个组件的共同的state转移到共同的父组件中,然后在父组件中通过 props 把参数传递下去,并向下传递事件处理程序,以便子组件可以改变父组件的 state
受控组件&非受控组件
- “受控”(由 prop 驱动):组件中的信息是由
props而不是其自身状态驱动 - “不受控”(由 state 驱动):即父组件无法控制在子组件中的状态
保留和重置状态
只要在相同位置渲染的是相同组件, React 就会保留状态。
重置使数据更新
- 可通过向组件传递一个唯一
key来 强制重置其状态,会使用新数据和UI来重新渲染组件。 - 不要嵌套组件的定义,否则你会意外地导致 state 被重置。
为被切换的组件保留 state
- 渲染出所有的组件,使用css的方式来控制显隐。不过涉及到大量的DOM节点会影响性能。
- 进行状态提升在父组件中保存信息。
- 保存在内存里
提取状态逻辑到 reducer 中
对于那些需要更新多个状态的组件来说,在组件外部将所有状态更新逻辑合并到一个称为 “reducer” 的函数中。使用useReducer来创建对应修改动作的指令集,调用时只需要dispatch对应的方法和参数
- reducers 必须是纯粹的。 这一点和状态更新函数是相似的,
reducers在是在渲染时运行的(actions 会排队直到下一次渲染)。 这就意味着reducers必须纯净,它们不应该包含异步请求、定时器或者任何副作用(对组件外部有影响的操作)。它们应该以不可变值的方式去更新对象和数组(使用useImmerReducer简化 reducers) - 每个 action 都描述了一个单一的用户交互
简单的使用
//声明
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
//定义
function tasksReducer(tasks, action) {
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;
}
});
}}
// 调用
function handleAddTask(text) {
dispatch({
type: 'added',
id: id,
text: text,
});
}
reducer实现
export function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(n=>reducer(n));
}
return [state, dispatch];
}
使用 Context 进行深层数据传递
一般我们通过 props 将参数从父组件传递给子组件,如果是深层次的组件,props就不再方便了,这时候我们可以使用Context来进行透传。
使用方法
- 通过
export const MyContext = createContext(defaultValue)创建并导出 context。 - 在子组件中,把 context 传递给
useContext(MyContext)Hook 来读取它。 - 在父组件中把 children 包在
<MyContext.Provider value={...}>中来提供 context
Context 的使用场景
- 修改主题模式。
- 登录的用户信息的透传
- 使用 context 来保存当前路由。这
- 状态管理
Reducer 结合 Context使用
- 创建两个 context (一个用于 state,一个用于 dispatch 函数), 让组件的 context 使用 reducer, 使用组件中需要读取的 context。 将reducer和context相关代码组织到同一个文件中,导出一个像
TasksProvider可以提供 context 的组件,以及像useTasks和useTasksDispatch这样的自定义 Hook。