React--状态管理

用State相应输入

        React控制UI的方式是声明式的。你不必直接控制UI的各个部分,只需要声明组建可以处于的不同状态,并根据用户的输入在它们之间切换。

声明式UI与命令式UI的比较

命令式UI:告诉计算机如何去更新UI的编程方式被称为命令式编程。

声明式UI:只需要声明你想要显示的内容,React就会通过计算得出该如何去更新UI。

声明式地考虑UI

步骤1:定位组件中不同的视图状态:

你需要去可视化UI界面中用户可能看到的所有不同的“状态”:

  • 无数据:表单有一个不可用状态的“提交”按钮。
  • 输入中:表单有一个可用状态的”提交“按钮。
  • 提交中:表单完全处于不可用状态,加载动画出现。
  • 成功时:显示”成功“的消息而非表单。
  • 错误时:与输入状态类似,但会多错误消息。

步骤2:确定是什么出发了这些状态的改变

你可以触发state的更新来响应两种输入: 

  • 人为输入。比如点击按钮、在表单中输入内容·····
  • 计算机输入。比如网络请求得到反馈、定时器被触发·····

计算机输入
人为输入

 以上两种情况中,你必须设置state变量去更新UI。

 注意:

注意,人为输入通常需要事件处理函数!!!

步骤3:通过useState表示内存中的state

state的每个部分都是”处于变化中的”,而且你需要让变化的部分尽可能的少。更复杂的程序会产生更多的bug! 

步骤4:删除任何不必要的state变量 

你的目的是防止出现在内存中的state不代表任何你希望用户看到的有效UI的情况。

这有一些问题你可以问自己,关于state变量的问题:

  • 这个state是否导致矛盾?
  • 相同的信息是否已经在另一个state变量中存在?
  • 你是否可以通过另一个state变量的相反值得到相同的信息? 

 步骤5:连接事件处理函数以设置state

选择State结构

构建state的原则

  1. 合并关联的state。
  2. 避免相互矛盾的state。
  3. 避免冗余的state。
  4. 避免重复的state。
  5. 避免深度嵌套的state。

这些原则的目标是使state易于更新而不引入错误。从state中删除冗余和重复数据有助于确保所有部分保持同步。 

合并相关联的state

如果某两个state变量总是一起变化,则将它们统一成一个state变量可能更好。这样你就不会忘记让它们始终保持同步。例子:

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

另一种情况是,你将数据整合到以单个对象或者一个数组中时,你不知道需要多少个state片段。例如,当你有一个用户可以添加自定义字段的表单时,这将会很有帮助。

 避免矛盾的state

当你的两个state有冲突时,最好使用一个state变量来代替它们。例,isSending和isSend不应该同时为true,最好使用一个status变量来代替它们,这个state变量可以采取三种有效状态其中之一:'typing'(初始)、‘sending’,‘sent'

避免容易与的state 

如何你能在渲染期间从组件的props或其现有的state变量中计算出一些信息,则不应该把这些信息放到改组件的state中。 

避免重复的state 

 当项目中有state产生了重复,只修改了一个state中的内容,另一个state未修改,会导致渲染出来的数据不相同。

避免深度嵌套的state

如果state嵌套太深,难以轻松更新,可以考虑将其扁平化。

在组件间共享状态

举例说明状态提升

这个例子中,父组件Accordion渲染了2个独立的Panel组件。

每个Panel组件都有一个布尔值isActive,用于控制其内容是否可见。

const [isActive, setIsActive] = useState(false);
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>
          显示
        </button>
      )}
    </section>
  );
}

export default function Accordion() {
  return (
    <>
      <h2>哈萨克斯坦,阿拉木图</h2>
      <Panel title="关于">
        阿拉木图人口约200万,是哈萨克斯坦最大的城市。它在 1929 年到 1997 年间都是首都。
      </Panel>
      <Panel title="词源">
        这个名字来自于 <span lang="kk-KZ">алма</span>,哈萨克语中“苹果”的意思,经常被翻译成“苹果之乡”。事实上,阿拉木图的周边地区被认为是苹果的发源地,<i lang="la">Malus sieversii</i> 被认为是现今苹果的祖先。
      </Panel>
    </>
  );
}

我们发现点击其中一个面板中的按钮并不会影响另外一个,他们是独立的。

由于一开始所有 Panel 内部的 isActive
都是 false,
因此它们的内容都不会显示。
此时,点击任意一个 Panel 组件
中的按钮都只会更新当前
Panel 组件内的 isActive 值


假设现在你想要改变这种行为,以便在任何时候只展开一个面板。应该如何做?

要协调好这两面板,我们需要分三步将状态”提升“到他们的父组件中。

  1. 从子组件中移除state。
  2. 从父组件传递硬编码数据。
  3. 为共同的父组件添加state,并将其与事件处理函数一起向下传递。

 第一步:从子组件中移除状态

你将把Panel组件对isActive的控制权交给他们的父组件。这意味着,父组件会将isActive作为prop传给子组件Panel。我们先从Panel组件中删除下面这一行:

const [isActive,setIsActive]=useState(false)

然后,把isActive加入Panel组件的props中:

function Panel({title, children, isActive}){

 现在Panel的父组件就可以通过向下传递prop来控制isActive。但相反地,Panel组件对isActive地值没有控制权--现在完全由父组件决定。

第二步:从公共父组件传递硬编码数据

这个例子中,公共父组件是Accordion。因为它位于两个面板之上,可以控制它们地props,所以它将成为当前激活面板的”控制之源”,通过Accordion组件将硬编码值isActive(例如true)传递给两个面板:

import { useState } from 'react';

export default function Accordion() {
  return (
    <>
      <h2>哈萨克斯坦,阿拉木图</h2>
      <Panel title="关于" isActive={true}>
        阿拉木图人口约200万,是哈萨克斯坦最大的城市。它在 1929 年到 1997 年间都是首都。
      </Panel>
      <Panel title="词源" isActive={true}>
        这个名字来自于 <span lang="kk-KZ">алма</span>,哈萨克语中“苹果”的意思,经常被翻译成“苹果之乡”。事实上,阿拉木图的周边地区被认为是苹果的发源地,<i lang="la">Malus sieversii</i> 被认为是现今苹果的祖先。
      </Panel>
    </>
  );
}

function Panel({ title, children, isActive }) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>
          显示
        </button>
      )}
    </section>
  );
}

第三步:为公共父组件添加状态

状态提升通常会改变原状态的数据存储类型。

这个例子中,一次只能激活一个面板。这意味着Accordion这个父组件需要记录那个面板是被激活的面板。我们可以用数字作为当前被激活Panel的索引,而不是boolean值:

const [activeIndex, setActiveIndex] = useState(0);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值