React hooks文档笔记(二) 添加交互性

文章介绍了React中的事件传播机制,包括事件捕获和冒泡,以及如何通过`stopPropagation`和`preventDefault`来控制事件行为。同时,详细讲解了`useState`钩子的使用,包括状态的设置和更新,并解析了React渲染和提交的过程,包括触发渲染、组件渲染、提交DOM的更改和浏览器的绘制。此外,文章还讨论了状态队列和如何有效地更新对象状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 事件传播

js的事件流:

  • 事件捕获:从外到内
  • 事件冒泡:从内到外(默认模式)
export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <button onClick={() => alert('Playing!')}>
        Play Movie
      </button>
      <button onClick={() => alert('Uploading!')}>
        Upload Image
      </button>
    </div>
  );
}

点击任一按钮,它的 onClick 将首先运行,然后是父节点

的 onClick—— 所以会出现两条消息。
在这里插入图片描述
在这里插入图片描述 在这里插入图片描述如果你点击灰色区域本身,只有父
的 onClick 会运行。

1.1 停止传播

为阻止传播,可以在onClick中加一行引用事件参数e并调用e.stopPropagation()方法,此时当用户点击<button>时触发器该条语句,停止事件向外扩散,父节点<div>的onClick将不再触发。

1.2 阻止默认事件

e.preventDefault() 阻止默认行为
<form>:当内部按钮点击后,将默认重载整个页面

export default function Signup() {
  return (
    <form onSubmit={e => {
      e.preventDefault();    //点击按钮后将不再重载页面
      alert('Submitting!');
    }}>
      <input />
      <button>Send</button>
    </form>
  );
}

2. [Hook] useState 状态

形式:const [index, setIndex] = useState(默认值)

“[]”从useState解构出来两部分,

  1. 用于保留渲染之间数据的状态变量index
  2. 一个状态设置函数,setIndex用于更新变量触发 React 再次渲染组件

只能使用在组件,不能在条件、循环或其他嵌套函数中调用 Hooks,hook调用原理:
React报错#310复盘小结+hooks使用的场景+调用原理

只有 Hook 的调用顺序在多次渲染之间保持一致,React 才能正确地将内部 state 和对应的 Hook 进行关联。

useState内部:

React 为每个组件保存一组状态对。 它还维护当前对索引,该索引在渲染前设置为 0。 每次调用 useState 时,React 都会为您提供下一个状态对并递增索引。

let componentHooks = [];
let currentHookIndex = 0;
// How useState works inside React (simplified).
function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
    // This is not the first render,
    // so the state pair already exists.
    // Return it and prepare for next Hook call.
    currentHookIndex++;
    return pair;
  }

  // This is the first time we're rendering,
  // so create a state pair and store it.
  pair = [initialState, setState];

  function setState(nextState) {
    // When the user requests a state change,
    // put the new value into the pair.
    pair[0] = nextState;
    updateDOM();
  }

  // Store the pair for future renders
  // and prepare for the next Hook call.
  componentHooks[currentHookIndex] = pair;
  currentHookIndex++;
  return pair;
}

3. 渲染和提交

举个例子:

组件是厨房里的厨师,用食材烹制美味佳肴。React 是一个服务员,负责接收客户的订单并为他们带来菜肴。这个请求和提供 UI 的过程分为三个步骤:

  1. 触发渲染(将客人的订单送到厨房)
  2. 渲染组件(在厨房准备订单)
  3. 提交给 DOM(将制作好的菜肴放在客户的桌上)
    在这里插入图片描述

3.1 触发渲染

组件渲染有两个原因:

  1. 初始渲染
    root.render(<App />);

  2. 组件(或其祖先之一)的state已更新

3.2 React渲染组件

export default function Gallery() {
  return (
    <section>
      <h1>Inspiring Sculptures</h1>
      <Image />
      <Image />
    </section>
  );
}
function Image() {
  return (
    <img src="xxx" alt="xxx" />
  );
}
  • 在初始渲染时,React 将调用根组件。
    <section><h1> 和两个<img>标签创建DOM节点

  • 对于后续的渲染,React 将调用状态更新触发渲染的函数组件
    React 将计算它们的哪些属性(如果有)自上次渲染以来发生了变化。 在下一步,即提交阶段之前,它不会对这些信息做任何事情。

3.3 提交对 DOM 的更改

  • 初始渲染时,React 将使用appendChild() DOM API 将它创建的所有 DOM 节点放在屏幕上。
  • 对于重新渲染,React 将应用最少的必要操作(在渲染时计算!)以使 DOM 匹配最新的渲染输出。

如果渲染之间存在差异,React 只会更改 DOM 节点。

举例:
Clock组件会随着父节点传入不同的props 参数 time 而重新渲染,但在input中输入‘1’,重新渲染时‘1’没有消失。

export default function Clock({ time }) {
  return (
    <>
      <h1>{time}</h1>
      <input />
    </>
  );
}

在这里插入图片描述在这里插入图片描述

这是因为随着time的改变, React 只更新了<h1>的内容,而没有触及<input>

3.4 浏览器绘制

渲染完成并且 React 更新 DOM 后,浏览器将重新绘制屏幕

4. 渲染快照

当 React 重新渲染一个组件时:

  1. React 再次调用你的函数。
  2. 您的函数返回一个新的 JSX 快照(使用该渲染中的state计算的,在快照中保持状态值“固定”)
  3. React 然后更新屏幕以匹配您返回的快照。

在这里插入图片描述
setState 只会为下一次渲染更改它

export default function Counter() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+2</button>
    </>
  )
}

点击第一次后:
在这里插入图片描述
点击第二次后:
在这里插入图片描述
发现两次点击都只加了1

在第一次渲染期间,number是0,调用onClick:
第一个setNumber(number + 1)等价于 setNumber(0 + 1)
第二个setNumber(number + 1)等价于 setNumber(0 + 1)
React 准备在下一次渲染时更改number为1,也就是说设置状态为1了两次
在这里插入图片描述

在处理状态更新之前,React 会等到事件处理程序中的所有代码都已运行。这使您可以更新多个状态变量——甚至来自多个组件——而不会触发太多重新渲染

q: 在下一次渲染之前需要多次更新相同的state?


答: 可以传递一个函数,该函数根据前一个状态计算下一个状态队列
例如将上述例子替换为setNumber(n => n + 1);

n => n + 1更新函数(必须是纯函数并且只返回结果)

当您将它传递给状态设置器时:

  1. 在事件处理程序中的所有其他代码运行之后,React 将此函数排队等待处理。
  2. 在下一次渲染期间,React 遍历队列并为您提供最终的更新状态。
队列更新nreturns
n => n + 100+1=1
n => n + 111+1=2

状态队列例子

实现状态队列:https://codesandbox.io/s/0xym3z?file=/processQueue.js&utm_medium=sandpack

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
  setNumber(42);
}}>
队列更新nreturns
“replace with 5”0(unused)5
n => n + 155+1=6
“replace with 42”6(unused) 42

在下一次渲染期间,React 遍历状态队列:

  1. setNumber(number + 5): number是0,所以setNumber(0 + 5)。React 将“替换为5”添加到它的队列中。
  2. setNumber(n => n + 1): n => n + 1是一个更新函数。React 将该函数添加到它的队列中。
  3. setNumber(42): React 将“替换为42”添加到它的队列中。

5. 更新state中的对象

用 Immer 编写简洁的更新对象state逻辑:Immer编写简洁的更新state逻辑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你脸上有BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值