图文并茂教你快速入门React系列04-状态管理
在React中,什么是状态?
响应式
使用 React,你不用直接从代码层面修改 UI。举个栗子哇,不用编写诸如“禁用按钮”、“启用按钮”、“显示成功消息”等命令。相反,你只需要描述组件在不同状态(“初始状态”、“输入状态”、“成功状态”)下希望展现的 UI,然后根据用户输入触发状态更改。
真实举例给你看
import { useState } from 'react';
export default function Form() {
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing');
if (status === 'success') {
return <h1>答对了!</h1>
}
async function handleSubmit(e) {
e.preventDefault();
setStatus('submitting');
try {
await submitForm(answer);
setStatus('success');
} catch (err) {
setStatus('typing');
setError(err);
}
}
function handleTextareaChange(e) {
setAnswer(e.target.value);
}
return (
<>
<h2>城市测验</h2>
<p>
哪个城市有把空气变成饮用水的广告牌?
</p>
<form onSubmit={handleSubmit}>
<textarea
value={answer}
onChange={handleTextareaChange}
disabled={status === 'submitting'}
/>
<br />
<button disabled={
answer.length === 0 ||
status === 'submitting'
}>
提交
</button>
{error !== null &&
<p className="Error">
{error.message}
</p>
}
</form>
</>
);
}
function submitForm(answer) {
// 模拟接口请求
return new Promise((resolve, reject) => {
setTimeout(() => {
let shouldError = answer.toLowerCase() !== 'lima'
if (shouldError) {
reject(new Error('猜的不错,但答案不对。再试试看吧!'));
} else {
resolve();
}
}, 1500);
});
}
仔细观察这段代码:
if (status === 'success') {
return <h1>答对了!</h1>
}
会发现真的不一样哦~~把代码放进页面里,测试一下看看~当你答案写对的时候,你会发现页面直接渲染了这部分
after:
如何管理状态?
状态不应包含冗余或重复的信息
举个栗子
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
function handleFirstNameChange(e) {
setFirstName(e.target.value);
setFullName(e.target.value + ' ' + lastName);
}
观察上面的代码,其实我们用到的fullName 是另外两个变量组合起来的,但是我们声明了三个useState
优化如下:
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = firstName + ' ' + lastName;
组件间共享状态
通常我们会如何处理组件状态共享呢?
方法:将状态从这两个组件移除,移到最近的父级组件,然后通过 props 将状态传递给这两个组件。这被称为“状态提升”。
保留和重置状态
什么场景会需要我们考虑保留和重置状态呢?
当重新渲染一个组件, 需要决定哪些部分保留和更新,以及丢弃或重新创建。默认情况下,React 会保留树中与先前渲染的组件树“匹配”的部分。
但是某些情况下,我们需要一些特定的处理,比如下面的这个例子
import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';
export default function Messenger() {
const [to, setTo] = useState(contacts[0]);
return (
<div>
<ContactList
contacts={contacts}
selectedContact={to}
onSelect={contact => setTo(contact)}
/>
<Chat contact={to} />
</div>
)
}
const contacts = [
{ name: 'Taylor', email: 'taylor@mail.com' },
{ name: 'Alice', email: 'alice@mail.com' },
{ name: 'Bob', email: 'bob@mail.com' }
];
我们选择了收件人,然后写邮件内容,点击发送以后,再切换收件人,发现邮件内容并么有改变,我们希望它可以清空信息,此时这就是我们的一些特定更新需求,那么如果需要实现这个需求,普通的情况下,我们的做法是会写一个onChange事件,放在左侧切换按钮上面,来清除右侧的文本值,但是使用react我们可以有不同的办法去解决这个问题:
机智的办法来了:React 允许你覆盖默认行为,可通过向组件传递一个唯一 key(如 Chat key={email} 来 强制 重置其状态。
上代码:
<div>
<ContactList
contacts={contacts}
selectedContact={to}
onSelect={contact => setTo(contact)}
/>
<Chat key={to.email} contact={to} /> 重点就是这句话
</div>
提取状态逻辑到 reducer
为什么要提取出去呢?
对于那些需要更新多个状态的组件来说,多了就乱了。对于这种情况,我们可以在组件外部将所有状态更新逻辑合并到一个称为 “reducer” 的函数中。指定用户的 “actions”。,reducer 函数指定状态应该如何更新以响应每个 action
举个代码栗子
import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
export default function TaskApp() {
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId
});
}
return (
<>
<h1>布拉格行程</h1>
<AddTask
onAddTask={handleAddTask}
/>
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
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;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('未知操作:' + action.type);
}
}
}
let nextId = 3;
const initialTasks = [
{ id: 0, text: '参观卡夫卡博物馆', done: true },
{ id: 1, text: '看木偶戏', done: false },
{ id: 2, text: '列侬墙图片', done: false }
];
如何进行深层 props 传递--使用 Context
Context 允许父组件将一些信息提供给它下层的任何组件,不管该组件多深层也无需通过 props 逐层透传。
使用方法如下:
我们使用四个文件,组合成下面的样式
App.js
import Heading from './Heading.js';
import Section from './Section.js';
export default function Page() {
return (
<Section>
<Heading>大标题</Heading>
<Section>
<Heading>一级标题</Heading>
<Heading>一级标题</Heading>
<Heading>一级标题</Heading>
<Section>
<Heading>二级标题</Heading>
<Heading>二级标题</Heading>
<Heading>二级标题</Heading>
<Section>
<Heading>三级标题</Heading>
<Heading>三级标题</Heading>
<Heading>三级标题</Heading>
</Section>
</Section>
</Section>
</Section>
);
}
Section.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Section({ children }) {
const level = useContext(LevelContext);
return (
<section className="section">
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}
Heading.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
const level = useContext(LevelContext);
switch (level) {
case 0:
throw Error('标题必须在 Section 内!');
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
default:
throw Error('未知级别:' + level);
}
}
LevelContext.js
import { createContext } from 'react';
export const LevelContext = createContext(0);
如何使用Reducer 和 Context 进行状态扩展
我们可以使用 reducer 管理复杂状态父组件。其他组件可以通过 context 读取状态。dispatch action 更新状态
举个栗子
以上图为例,我们可以使用四个文件来实现如上功能
App.js
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';
export default function TaskApp() {
return (
<TasksProvider>
<h1>HAPPY的一天</h1>
<AddTask />
<TaskList />
</TasksProvider>
);
}
TasksContext.js
import { createContext, useContext, useReducer } from 'react';
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider
value={dispatch}
>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}
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;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('未知操作:' + action.type);
}
}
}
const initialTasks = [
{ id: 0, text: '吃饭', done: true },
{ id: 1, text: '睡觉', done: false },
{ id: 2, text: '打豆豆', done: false }
];
AddTask.js
import { useState, useContext } from 'react';
import { useTasksDispatch } from './TasksContext.js';
export default function AddTask({ onAddTask }) {
const [text, setText] = useState('');
const dispatch = useTasksDispatch();
return (
<>
<input
placeholder="添加任务"
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={() => {
setText('');
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}}>添加</button>
</>
);
}
let nextId = 3;
TaskList.js
import { useState, useContext } from 'react';
import { useTasks, useTasksDispatch } from './TasksContext.js';
export default function TaskList() {
const tasks = useTasks();
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
<Task task={task} />
</li>
))}
</ul>
);
}
function Task({ task }) {
const [isEditing, setIsEditing] = useState(false);
const dispatch = useTasksDispatch();
let taskContent;
if (isEditing) {
taskContent = (
<>
<input
value={task.text}
onChange={e => {
dispatch({
type: 'changed',
task: {
...task,
text: e.target.value
}
});
}} />
<button onClick={() => setIsEditing(false)}>
保存
</button>
</>
);
} else {
taskContent = (
<>
{task.text}
<button onClick={() => setIsEditing(true)}>
编辑
</button>
</>
);
}
return (
<label>
<input
type="checkbox"
checked={task.done}
onChange={e => {
dispatch({
type: 'changed',
task: {
...task,
done: e.target.checked
}
});
}}
/>
{taskContent}
<button onClick={() => {
dispatch({
type: 'deleted',
id: task.id
});
}}>
删除
</button>
</label>
);
}
小伙伴们,先写到这里啦,我们明天再见啦~~
大家要天天开心哦
欢迎大家指出文章需要改正之处~
学无止境,合作共赢