REACT选择状态结构(1)

简介: REACT选择状态结构

构建状态的原则

当你编写一个包含某种状态的组件时,你必须选择使用多少个状态变量,以及它们的数据应该是什么形状。虽然即使状态结构欠佳,也可以编写正确的程序,但有一些原则可以指导您做出更好的选择:

  1. 组相关状态。如果始终同时更新两个或多个状态变量,请考虑将它们合并到单个状态变量中。
  2. 避免状态上的矛盾。当国家的结构方式使几个国家可能相互矛盾和“不同意”时,你就为错误留下了空间。尽量避免这种情况。
  3. 避免冗余状态。如果可以在渲染过程中从组件的 props 或其现有状态变量中计算出一些信息,则不应将该信息放入该组件的状态中。
  4. 避免状态重复。当相同的数据在多个状态变量之间或嵌套对象中复制时,很难使它们保持同步。尽可能减少重复。
  5. 避免深度嵌套状态。深度分层状态更新不是很方便。如果可能,更喜欢以扁平的方式构建状态。

这些原则背后的目标是使状态易于更新,而不会引入错误。从状态中删除冗余和重复的数据有助于确保其所有部分保持同步。这类似于数据库工程师可能希望“规范化”数据库结构以减少出现错误的可能性。套用阿尔伯特·爱因斯坦的话,“让你的状态尽可能简单——但不能更简单。

现在让我们看看这些原则是如何在行动中应用的。

有时您可能不确定是使用单个还是多个状态变量。

你应该这样做吗?

const [x, setX] = useState(0);
const [y, setY] = useState(0);


还是这个?

const [position, setPosition] = useState({ x: 0, y: 0 });


从技术上讲,您可以使用其中任何一种方法。但是,如果两个状态变量总是一起变化,那么将它们统一为一个状态变量可能是个好主意。然后,您不会忘记始终保持它们同步,就像在此示例中一样,移动光标会更新红点的两个坐标:

import { useState } from 'react';
 
export default function MovingDot() {
  const [position, setPosition] = useState({
    x: 0,
    y: 0
  });
  return (
    <div
      onPointerMove={e => {
        setPosition({
          x: e.clientX,
          y: e.clientY
        });
      }}
      style={{
        position: 'relative',
        width: '100vw',
        height: '100vh',
      }}>
      <div style={{
        position: 'absolute',
        backgroundColor: 'red',
        borderRadius: '50%',
        transform: `translate(${position.x}px, ${position.y}px)`,
        left: -10,
        top: -10,
        width: 20,
        height: 20,
      }} />
    </div>
  )
}

避免状态矛盾

这是一份带有和状态变量的酒店反馈表:isSendingisSent

import { useState } from 'react';
 
export default function FeedbackForm() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  const [isSent, setIsSent] = useState(false);
 
  async function handleSubmit(e) {
    e.preventDefault();
    setIsSending(true);
    await sendMessage(text);
    setIsSending(false);
    setIsSent(true);
  }
 
  if (isSent) {
    return <h1>Thanks for feedback!</h1>
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <p>How was your stay at The Prancing Pony?</p>
      <textarea
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <br />
      <button
        disabled={isSending}
        type="submit"
      >
        Send
      </button>
      {isSending && <p>Sending...</p>}
    </form>
  );
}
 
// Pretend to send a message.
function sendMessage(text) {
  return new Promise(resolve => {
    setTimeout(resolve, 2000);
  });
}

虽然此代码有效,但它为“不可能”状态敞开了大门。例如,如果您忘记打电话和在一起,您最终可能会陷入两者同时存在的情况。组件越复杂,就越难理解发生了什么。setIsSentsetIsSendingisSendingisSenttrue

由于 isSendingisSent 不应同时为 true,因此最好将它们替换为一个状态状态变量,该变量可能采用以下三种有效状态之一:(initial)、 和 :'typing''sending''sent'

import { useState } from 'react';
 
export default function FeedbackForm() {
  const [text, setText] = useState('');
  const [status, setStatus] = useState('typing');
 
  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('sending');
    await sendMessage(text);
    setStatus('sent');
  }
 
  const isSending = status === 'sending';
  const isSent = status === 'sent';
 
  if (isSent) {
    return <h1>Thanks for feedback!</h1>
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <p>How was your stay at The Prancing Pony?</p>
      <textarea
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <br />
      <button
        disabled={isSending}
        type="submit"
      >
        Send
      </button>
      {isSending && <p>Sending...</p>}
    </form>
  );
}
 
// Pretend to send a message.
function sendMessage(text) {
  return new Promise(resolve => {
    setTimeout(resolve, 2000);
  });
}

为了提高可读性,您仍然可以声明一些常量:

const isSending = status === 'sending';
const isSent = status === 'sent';


但它们不是状态变量,因此您无需担心它们彼此不同步。

避免冗余状态

如果可以在渲染过程中从组件的 props 或其现有状态变量中计算出一些信息,则不应将该信息放入该组件的状态中。

例如,采用此表单。它有效,但你能在其中找到任何冗余状态吗?

import { useState } from 'react';
 
export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');
 
  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
    setFullName(e.target.value + ' ' + lastName);
  }
 
  function handleLastNameChange(e) {
    setLastName(e.target.value);
    setFullName(firstName + ' ' + e.target.value);
  }
 
  return (
    <>
      <h2>Let’s check you in</h2>
      <label>
        First name:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        Your ticket will be issued to: <b>{fullName}</b>
      </p>
    </>
  );
}

此窗体有三个状态变量:、 和 。但是,是多余的。在渲染过程中,您始终可以从 firstNamelastName 计算 fullName,因此请将其从状态中删除。firstNamelastNamefullNamefullName

这是你如何做到的:

import { useState } from 'react';
 
export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
 
  const fullName = firstName + ' ' + lastName;
 
  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
  }
 
  function handleLastNameChange(e) {
    setLastName(e.target.value);
  }
 
  return (
    <>
      <h2>Let’s check you in</h2>
      <label>
        First name:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        Your ticket will be issued to: <b>{fullName}</b>
      </p>
    </>
  );
}

这里,不是状态变量。相反,它是在渲染期间计算的:fullName

const fullName = firstName + ' ' + lastName;


因此,更改处理程序不需要执行任何特殊操作来更新它。当您调用 or 时,会触发重新渲染,然后根据新数据计算下一个。setFirstNamesetLastNamefullName

避免状态重复

此菜单列表组件可让您从以下几种中选择一种旅行小吃:

import { useState } from 'react';
 
const initialItems = [
  { title: 'pretzels', id: 0 },
  { title: 'crispy seaweed', id: 1 },
  { title: 'granola bar', id: 2 },
];
 
export default function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedItem, setSelectedItem] = useState(
    items[0]
  );
 
  return (
    <>
      <h2>What's your travel snack?</h2>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.title}
            {' '}
            <button onClick={() => {
              setSelectedItem(item);
            }}>Choose</button>
          </li>
        ))}
      </ul>
      <p>You picked {selectedItem.title}.</p>
    </>
  );
}

 

 

目前,它将所选项目存储为状态变量中的对象。但是,这并不是很好:selectedItem 的内容与项目列表中的某个项目是同一个对象。这意味着有关项目本身的信息在两个位置重复。selectedItem

为什么这是一个问题?让我们使每个项目都可编辑:

import { useState } from 'react';
 
const initialItems = [
  { title: 'pretzels', id: 0 },
  { title: 'crispy seaweed', id: 1 },
  { title: 'granola bar', id: 2 },
];
 
export default function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedItem, setSelectedItem] = useState(
    items[0]
  );
 
  function handleItemChange(id, e) {
    setItems(items.map(item => {
      if (item.id === id) {
        return {
          ...item,
          title: e.target.value,
        };
      } else {
        return item;
      }
    }));
  }
 
  return (
    <>
      <h2>What's your travel snack?</h2> 
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            <input
              value={item.title}
              onChange={e => {
                handleItemChange(item.id, e)
              }}
            />
            {' '}
            <button onClick={() => {
              setSelectedItem(item);
            }}>Choose</button>
          </li>
        ))}
      </ul>
      <p>You picked {selectedItem.title}.</p>
    </>
  );
}

请注意,如果您先单击某个项目的“选择”,然后对其进行编辑,则输入会更新,但底部的标签不会反映编辑内容。这是因为您有重复的状态,并且忘记更新。selectedItem

虽然您也可以更新,但更简单的解决方法是删除重复项。在此示例中,您不是对象(创建与内部对象的重复项),而是保持 in 状态,然后通过在数组中搜索具有该 ID 的项来获取:selectedItemselectedItemitemsselectedIdselectedItemitems

import { useState } from 'react';
 
const initialItems = [
  { title: 'pretzels', id: 0 },
  { title: 'crispy seaweed', id: 1 },
  { title: 'granola bar', id: 2 },
];
 
export default function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedId, setSelectedId] = useState(0);
 
  const selectedItem = items.find(item =>
    item.id === selectedId
  );
 
  function handleItemChange(id, e) {
    setItems(items.map(item => {
      if (item.id === id) {
        return {
          ...item,
          title: e.target.value,
        };
      } else {
        return item;
      }
    }));
  }
 
  return (
    <>
      <h2>What's your travel snack?</h2>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            <input
              value={item.title}
              onChange={e => {
                handleItemChange(item.id, e)
              }}
            />
            {' '}
            <button onClick={() => {
              setSelectedId(item.id);
            }}>Choose</button>
          </li>
        ))}
      </ul>
      <p>You picked {selectedItem.title}.</p>
    </>
  );
}

状态曾经是这样复制的:

  • items = [{ id: 0, title: 'pretzels'}, ...]
  • selectedItem = {id: 0, title: 'pretzels'}

但是在更改之后,它是这样的:

  • items = [{ id: 0, title: 'pretzels'}, ...]
  • selectedId = 0

重复消失了,你只保留了本质状态!

现在,如果您编辑所选项目,下面的消息将立即更新。这是因为会触发重新渲染,并会找到标题更新的项目。您不需要将所选项目保持在状态,因为只有所选 ID 是必需的。其余的可以在渲染期间计算。setItemsitems.find(...)

REACT选择状态结构(2)https://developer.aliyun.com/article/1529474

相关文章
|
5月前
|
前端开发
|
5月前
|
JavaScript 前端开发 算法
虚拟DOM是React的关键技术,它是个轻量的JS对象树,模拟实际DOM结构。
【6月更文挑战第27天】虚拟DOM是React的关键技术,它是个轻量的JS对象树,模拟实际DOM结构。当状态改变,React不直接修改DOM,而是先构建新的虚拟DOM树。通过 diff 算法比较新旧树,找到最小变更,仅更新必要部分,提高性能,避免频繁DOM操作。虚拟DOM还支持跨平台应用,如React Native。它优化了更新流程,简化开发,并提升了用户体验。
42 1
|
5月前
|
存储 前端开发 数据库
|
6月前
|
前端开发 JavaScript
React中渲染html结构---dangerouslySetInnerHTML
React中渲染html结构---dangerouslySetInnerHTML
54 0
|
6月前
|
前端开发 开发工具 git
React项目包结构的作用
React项目包结构的作用
114 0
|
前端开发
前端项目实战叁拾玖-​react-admin+material ui-多表测试基本结构-第叁层
前端项目实战叁拾玖-​react-admin+material ui-多表测试基本结构-第叁层
53 0
|
前端开发
前端项目实战肆拾-​react-admin+material ui-多表测试基本结构-外链关联表
前端项目实战肆拾-​react-admin+material ui-多表测试基本结构-外链关联表
66 0
|
前端开发
前端项目实战叁拾柒-​react-admin+material ui-多表测试基本结构-第一层
前端项目实战叁拾柒-​react-admin+material ui-多表测试基本结构-第一层
82 0
|
前端开发
React框架课时二认识项目的结构目录一
React框架课时二认识项目的结构目录一
172 0
|
前端开发
「React实践」不同内容相似结构?按个开关试试
前端开发中,有些相似性高的不同模块,可以通过合理的设计,减少开发量 ,提升代码可扩展性
137 1