React——State

响应事件

  • 你可以通过将函数作为 prop 传递给元素如 <button> 来处理事件。
  • 必须传递事件处理函数,而非函数调用! onClick={handleClick} ,不是 onClick={handleClick()}
  • 你可以单独或者内联定义事件处理函数。
  • 事件处理函数在组件内部定义,所以它们可以访问组件的 props。
  • 你可以在父组件中定义一个事件处理函数,并将其作为 prop 传递给子组件。
  • 你可以根据特定于应用程序的名称定义事件处理函数的 prop。
  • 事件会向上传播。通过事件的第一个参数调用 e.stopPropagation() 来防止这种情况。
  • 事件可能具有不需要的浏览器默认行为。调用 e.preventDefault() 来阻止这种情况。
  • 从子组件显式调用事件处理函数 prop 是事件传播的另一种优秀替代方案。

State:组件的记忆

Hooks

在 React 中,useState 以及任何其他以“use”开头的函数都被称为 Hook。Hook 是特殊的函数,只在 React 渲染时有效。

useState Hook

useState Hook 提供了两个功能:

  1. State 变量 用于保存渲染间的数据。
  2. State setter 函数 更新变量并触发 React 再次渲染组件。
    //使用方法
    import { useState } from 'react';
    
    export default function StatePractice(){
        const [index, setIndex] = useState(0);
        function handleClick(){
            setIndex(index+1);
        }
        return(
    <div>
        <button onClick={handleClick}>增加</button>        
        <h1>{index}</h1>
    </div>
    );
    }

    State 是隔离且私有的 

  • State是隔离的,如果渲染相同的组件两次,每个副本都会有完全隔离的 state(即这两个组件不共享State)
  • state 完全私有于声明它的组件,父组件无法访问和修改子组件的State

渲染和提交

请求和提供 UI 的过程总共包括三个步骤:

  1. 触发 一次渲染
  2. 渲染 组件
  3. 提交 到 DOM

触发渲染

有两种原因会导致组件的渲染:

  1. 组件的 初次渲染。通过调用目标 DOM 节点的 createRoot,然后用你的组件调用 render 函数完成的。
    import Image from './Image.js';
    import { createRoot } from 'react-dom/client';
    
    const root = createRoot(document.getElementById('root'))
    root.render(<Image />);
    
  2. 组件(或者其祖先之一)的 状态发生了改变,此时会重新渲染。React 在更新时会递归渲染组件。如果一个组件返回了另一个组件,React 会继续渲染那个组件,直到所有嵌套组件都被渲染完成,最终确定屏幕上要显示的内容。

举例:

export default function Gallery() {
  return (
    <section>
      <h1>鼓舞人心的雕塑</h1>
      <Image />
      <Image />
      <Image />
    </section>
  );
}

function Image() {
  return (
    <img
      src="https://i.imgur.com/ZF6s192.jpg"
      alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
    />
  );
}

初次渲染:React 创建 <section><h1> 和三个 <img> 标签的 DOM 节点。

重新渲染:React 计算这些节点的属性自上次渲染以来的更改,但在提交阶段之前不会执行任何操作。

 React 把更改提交到 DOM 上 

  • 对于初次渲染, React 会使用 appendChild() DOM API 将其创建的所有 DOM 节点放在屏幕上。
  • 对于重渲染, React 将应用最少的必要操作(在渲染时计算!),以使得 DOM 与最新的渲染输出相互匹配。
  • React 仅在渲染之间存在差异时才会更改 DOM 节点。例如:
    export default function Clock({ time }) {
      return (
        <>
          <h1>{time}</h1>
          <input />
        </>
      );
    }

    这个组件每时每刻都在重新渲染,但<input />标签内的文本保持不变。

state 如同一张快照

“快照”

“快照”的比喻与上一节所讲渲染的过程相同:

阶段名称实际行为(React 内部)和“快照”的关系
触发渲染调用 setState(),React 标记该组件需要更新意味着准备拍一张新“照片”
执行渲染重新执行组件函数,生成新的 JSX(虚拟 DOM)实际“拍照”:组件函数执行一次,输出 JSX
提交渲染React 对比新旧虚拟 DOM,更新真实 DOM,执行副作用(useEffect)React 把这张“照片”展示到真实界面上

State是“组件的记忆”,它实际上存在于React本身,而不被函数限制;所有的“快照”(即当前的静态页面)是根据State的值来进行渲染的。

(个人理解:所谓“快照”,就如同一幅画布,首次渲染画出草稿,再画到画布上去;重新渲染则是画一份新的草稿,比对两份草稿的差异,然后用彩笔修改画布上一小部分。

  • 草稿是新的 JSX(React内部的虚拟DOM),它只是页面的描述;

  • 画布是实际的 DOM,它展示了当前的 UI。

  • React 每次渲染时都会画一份新的草稿,并根据差异修改画布中需要更新的部分

状态更新是异步的(设置 state 不会立即生效)

设置 state 只会影响下一次渲染的状态值,而不会立即改变当前代码执行过程中读取到的 state 值。第一个例子

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

在第一次渲染时,number 的值是 0。所以即使在点击事件处理函数中调用了三次 setNumber(number + 1),每次number 的值仍然是 0,

<button onClick={() => {
  setNumber(0 + 1);
  setNumber(0 + 1);
  setNumber(0 + 1);
}}>+3</button>

直到下一次渲染时,state 才会被更新。

一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。第二个例子

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setTimeout(() => {
          alert(number);
        }, 3000);
      }}>+5</button>
    </>
  )
}

这里虽然等待了3s,然而number仍然是0。

setNumber(0 + 5);
setTimeout(() => {
  alert(0);
}, 3000);

类比上一个例子,state在同一次渲染中并没有变化。

把一系列 state 更新加入队列

为什么要“排队”进行state 更新?

React 出于性能考虑会批量处理多个 state 更新(对于state的更新被放入队列,依次进行)。这意味着:

  • React 会等待事件处理函数执行完毕后再去更新 DOM。

  • 多次调用 setState 时,React 不会立刻渲染,而是将更新任务排进队列,等时机合适一起更新。

在下次渲染前多次更新同一个 state 

如果想在下次渲染之前多次更新同一个 state,可以使用更新函数。这种写法被称为“函数式更新”的写法:

setCount(c => c + 1);
setCount(c => c + 1);

传入 n => n + 1,不是立即更新,而是:

  1. 放进队列,等待当前函数内事件函数执行完;

  2. 下一次渲染前,React 统一处理队列;

  3. 每个更新函数用上一次的结果计算下一个,得出最终的 state。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值