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>
  }

本当に違うことがわかります~~コードをページに挿入してテストしてください~答えが正しい場合、次の後にページがこの部分を直接レンダリングしていることがわかります


状態を管理するにはどうすればよいですか?

状態には冗長または重複した情報が含まれていてはなりません

栗をあげる

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 は他の 2 つの変数の組み合わせですが、
次のように 3 つの 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: '[email protected]' },
  { name: 'Alice', email: '[email protected]' },
  { name: 'Bob', email: '[email protected]' }
];


受信者を選択し、電子メールの内容を書き込み、送信をクリックして、受信者を切り替えると、電子メールの内容が変更されていないことがわかります。これで情報が消去されることを期待します。現時点では、これは一部です。この要件を満たす必要がある場合は、特定の更新要件を満たしている必要があります。通常、onChange イベントを作成し、それを左側のスイッチ ボタンに配置して、右側のテキスト値をクリアします。ただし、この問題は、react を使用して解決できます。 React では

一意のキー (Chat key={email} など) をコンポーネントに渡してその状態を強制的にリセットすることで、デフォルトの動作をオーバーライドできます。上記のコード
:

<div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.email} contact={to} /> 重点就是这句话
    </div>

状態ロジックをリデューサーに抽出する

なぜそれを抽出するのでしょうか?

複数の状態を更新する必要があるコンポーネントの場合、数が多すぎると面倒になります。この場合、コンポーネントの外部にあるすべての状態更新ロジックを「リデューサー」と呼ばれる 1 つの関数に結合できます。ユーザーの「アクション」を指定します。
、リデューサー関数は、コードに栗を与えるために各アクションに応じて状態を更新する方法を指定します。

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 を使用する

コンテキストを使用すると、親コンポーネントがその下のコンポーネントに何らかの情報を提供できるようになります。コンポーネントの深さに関係なく、プロパティを介してレイヤーごとに透過的に送信する必要はありません。
使用方法は次のとおりです。

4 つのファイルを使用し、次のスタイルに結合します。


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>
  );
}


セクション.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>
  );
}

見出し.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);
  }
}

レベルコンテキスト.js

import { createContext } from 'react';

export const LevelContext = createContext(0);

状態拡張に Reducer と Context を使用する方法

リデューサーを使用して、複雑な状態の親コンポーネントを管理できます。他のコンポーネントはコンテキストを通じて状態を読み取ることができます。ディスパッチアクションの更新ステータス

栗をあげる


上の図を例に挙げると、上記の関数
App.jsを実現するには 4 つのファイルを使用できます。

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;

タスクリスト.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>
  );
}

友達、まずここに書きましょう、また明日~~

みんなも毎日幸せになろうね

記事の修正が必要な箇所はどなたでもご指摘ください~
学習には終わりがなく、協力は双方に利益をもたらします

ここに画像の説明を挿入

より良い意見を提案するために通り過ぎる小さな兄弟姉妹を歓迎します~~

おすすめ

転載: blog.csdn.net/tangdou369098655/article/details/131028193