图文并茂教你快速入门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>
  );
}

小伙伴们,先写到这里啦,我们明天再见啦~~

大家要天天开心哦

欢迎大家指出文章需要改正之处~
学无止境,合作共赢

在这里插入图片描述

欢迎路过的小哥哥小姐姐们提出更好的意见哇~~

posted @ 2023-06-04 03:00  糖~豆豆  阅读(68)  评论(0编辑  收藏  举报
Live2D