React 7/n - 交互
响应事件
添加事件处理函数
如需添加一个事件处理函数,你需要先定义一个函数,然后 将其作为 prop 传入 合适的 JSX 标签。
事件处理函数有如下特点:
- 通常在你的组件 内部 定义。
- 名称以 handle 开头,后跟事件名称。
在事件处理函数中读取 props
由于事件处理函数声明于组件内部,因此它们可以直接访问组件的 props
将事件处理函数作为 props 传递
通常,我们会在父组件中定义子组件的事件处理函数。比如:置于不同位置的 Button 组件,可能最终执行的功能也不同
命名事件处理函数 prop
内置组件(<button> 和 <div>
)仅支持 浏览器事件名称,例如 onClick。但是,当你构建自己的组件时,你可以按你个人喜好命名事件处理函数的 prop。这种处理非常优雅,因为是使程序非常可读
按照惯例,事件处理函数 props 应该以 on 开头,后跟一个大写字母。例如,Button 组件的 onClick prop 本来也可以被命名为 onSmash
事件传播
事件处理函数还将捕获任何来自子组件的事件。通常,我们会说事件会沿着树向上“冒泡”或“传播”:它从事件发生的地方开始,然后沿着树向上传播。
阻止传播
https://zh-hans.react.dev/learn/responding-to-events#stopping-propagation
事件处理函数接收一个 事件对象 作为唯一的参数。按照惯例,它通常被称为 e ,代表 “event”(事件)。你可以使用此对象来读取有关事件的信息
这个事件对象还允许你阻止传播。如果你想阻止一个事件到达父组件,可以调用 e.stopPropagation():
捕获阶段事件 极少数情况下,你可能需要捕获子元素上的所有事件,即便它们阻止了传播。例如,你可能想对每次点击进行埋点记录,传播逻辑暂且不论。那么你可以通过在事件名称末尾添加 Capture 来实现这一点:
阻止默认行为
某些浏览器事件具有与事件相关联的默认行为。例如,点击 <form>
表单内部的按钮会触发表单提交事件,默认情况下将重新加载整个页面。可以调用事件对象中的 e.preventDefault()
来阻止这种情况发生
e.stopPropagation()
阻止触发绑定在外层标签上的事件处理函数。e.preventDefault()
阻止少数事件的默认浏览器行为。
state: 组件的记忆
https://zh-hans.react.dev/learn/state-a-components-memory
要使用新数据更新组件,需要做两件事:
- 保留 渲染之间的数据。
- 触发 React 使用新数据渲染组件(重新渲染)。
useState Hook 提供了这两个功能:
- State 变量 用于保存渲染间的数据。
- State setter 函数 更新变量并触发 React 再次渲染组件。
在 React 中,useState 以及任何其他以“use”开头的函数都被称为 Hook。 Hook 是特殊的函数,只在 React 渲染时有效。它们能让你 “hook” 到不同的 React 特性中去。
State 是隔离且私有的
State 是屏幕上组件实例内部的状态。换句话说,如果你渲染同一个组件两次,每个副本都会有完全隔离的 state!改变其中一个不会影响另一个。
state的作用域“只限于”屏幕上的某块特定区域
渲染和提交
不要过早进行优化!
一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。在 那次渲染的 onClick 内部,number 的值即使在调用 setNumber(number + 5) 之后也还是 0。它的值在 React 通过调用你的组件“获取 UI 的快照”时就被“固定”了。
React 会对 state 更新进行批处理
setNumber(n => n + 1) 使用更新函数
总而言之,以下是你可以考虑传递给 setNumber state 设置函数的内容:
- 一个更新函数(例如:n => n + 1)会被添加到队列中。
- 任何其他的值(例如:数字 5)会导致“替换为 5”被添加到队列中,已经在队列中的内容会被忽略。
事件处理函数执行完成后,React 将触发重新渲染。在重新渲染期间,React 将处理队列。更新函数会在渲染期间执行,因此 更新函数必须是 纯函数 并且只 返回 结果。不要尝试从它们内部设置 state 或者执行其他副作用。在严格模式下,React 会执行每个更新函数两次(但是丢弃第二个结果)以便帮助你发现错误。
命名惯例: 通常可以通过相应 state 变量的第一个字母来命名更新函数的参数
更新 state 中的对象
https://zh-hans.react.dev/learn/updating-objects-in-state
state 中可以保存任意类型的 JavaScript 值,包括对象。但是,你不应该直接修改存放在 React state 中的对象。相反,当你想要更新一个对象时,你需要创建一个新的对象(或者将其拷贝一份),然后将 state 更新为此对象。
你应该像处理数字、布尔值、字符串一样将对象视为不可变的。因此你应该替换对象的值,而不是对对象进行修改。=> 将 state 视为只读的
把所有存放在 state 中的 JavaScript 对象都视为只读的
使用展开语法复制对象
重点: https://zh-hans.react.dev/learn/updating-objects-in-state#copying-objects-with-the-spread-syntax
使用展开语法,方便
请注意 … 展开语法本质是是“浅拷贝”——它只会复制一层。这使得它的执行速度很快,但是也意味着当你想要更新一个嵌套属性时,你必须得多次使用展开语法。
如果想简洁可以可以使用https://github.com/immerjs/use-immer
Immer原理: 由 Immer 提供的 draft 是一种特殊类型的对象,被称为 Proxy,它会记录你用它所进行的操作。这就是你能够随心所欲地直接修改对象的原因所在!从原理上说,Immer 会弄清楚 draft 对象的哪些部分被改变了,并会依照你的修改创建出一个全新的对象。
可以看使用案例: https://zh-hans.react.dev/learn/updating-objects-in-state#write-concise-update-logic-with-immer
更新 state 中的数组
学习文档: https://zh-hans.react.dev/learn/updating-arrays-in-state
数组是另外一种可以存储在 state 中的 JavaScript 对象,它虽然是可变的,但是却应该被视为不可变。同对象一样,当你想要更新存储于 state 中的数组时,你需要创建一个新的数组(或者创建一份已有数组的拷贝值),并使用新数组设置 state。
在 JavaScript 中,数组只是另一种对象。同对象一样,你需要将 React state 中的数组视为只读的。这意味着你不应该使用类似于 arr[0] = ‘bird’ 这样的方式来重新分配数组中的元素,也不应该使用会直接修改原始数组的方法,例如 push() 和 pop()。
相反,每次要更新一个数组时,你需要把一个新的数组传入 state 的 setting 方法中。为此,你可以通过使用像 filter() 和 map() 这样不会直接修改原始值的方法,从原始数组生成一个新的数组。然后你就可以将 state 设置为这个新生成的数组。
使用 map 在没有 mutation 的前提下将一个旧的元素替换成更新的版本。
结论: 请使用 Immer 来保持代码简洁
TODO NEXT
网站当前构建日期: 2025.02.01