React 文档笔记
前段时间阅读 React 官方文档时, 顺手记录了一些个人认为比较重要的知识点作为笔记, 都是比较基础的知识
(如果发现有误, 请联系我)
style 写法:
{{ }}
tailwind 与 组件库容易冲突, 支持 tailwind 的组件库: heroui(最好使用 next.js 在搭建项目时直接一起搭建)
\export default 默认导出, export 具名导出, 有很多区别, 可以自己思考一下
建议将 props 解构使用并提供默认值(默认值只会对 undefined 生效)
大量组件渲染, 涉及变动时建议添加 key 进行绑定
列表渲染组件 map 返回 tsx(同样记得加 key) 或者 fliter 返回 过滤后数组 (=> 直接少量数据返回 或者 => {return …})
<>
及</>
不可添加 key, 此时替换为<Fragment>
或</ Fragment>
组件不应当更改任何值, 只负责进行渲染
命名惯例: 按照惯例,通常将事件处理程序命名为
handle
,后接事件名某些浏览器事件具有与事件相关联的默认行为, 例:点击
<form>
表单内部的按钮会触发表单提交事件,默认情况下将重新加载整个页面, 应当使用 e.preventDefault() 进行阻止使用
e.stopPropagation()
阻止冒泡{ }
内部应当传递事件而非函数调用, 实际上, 传递函数调用的话将在每次渲染时被运行useState
↗ 最终提供功能: State 变量 用于保存渲染间的数据, State setter 函数 更新变量并触发 React 再次渲染组件hooks 依托于一个稳定的调用顺序
react 渲染机制: 初次渲染根组件, 之后渲染 被更新的组件 及 其子组件( 递归过程 ), 在开发环境中,React 会在组件首次挂载后立即重新挂载一次, 以便于发现各种问题
state setter 处理机制: react 将会收集 该事件处理函数 中的所有更新(需要重新渲染组件的操作), 并在该函数执行完毕后 一次性 合并更新并触发重新渲染, 在 react 18 前, 异步函数中的所有更新将被 立刻执行 ,但在 react 18 后, 异步函数中的更新同样将被收集并合并更新
使用函数式更新可以在末尾集中运行时, 每次运行后及时更新 state 的值(但是还是可以被后面的赋值给覆盖)
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> \<h1>{number}\</h1> <button onClick={() => { setNumber(number + 5); setNumber(n => n + 1); setNumber(42); }}>增加数字\</button> </> ) }
结果:
更新步骤 | 队列中的值变化 | 说明 |
---|---|---|
初始值 | 0 | 渲染时的初始状态 |
setNumber(0 + 5) | 计划更新为 5 | 基于当前值 0 |
setNumber(n => n + 1) | 计划更新为 6 | 基于前一次队列值 5 |
setNumber(42) | 覆盖为 42 | 直接赋值,忽略前序计算结果 |
最终结果 | 42 | 最后一次更新覆盖所有前序值 |
使用展开语法
[...a, b]
或{ ...a, b:'b'}
可以进行数组或对象的快速合并, 在对象中使用 [ ] 可以做到动态命名使用
useImmer
代替useState
可以更快和更直接地更新对象react 中推荐与不推荐使用的数组方法
避免使用 (会改变原始数组) | 推荐使用 (会返回一个新数组) ) | |
---|---|---|
添加元素 | push ,unshift | concat ,[...arr] 展开语法(例子↗) |
删除元素 | pop ,shift ,splice | filter ,slice (例子↗) |
替换元素 | splice ,arr[i] = ... 赋值 | map (例子↗) |
排序 | reverse ,sort | 先将数组复制一份(例子↗) |
对象并不是 真的 位于数组“内部”, 可能他们在代码中看起来像是在数组“内部”. 同样 对象并不是真的嵌套, 只是看起来像嵌套
state 设置原则: 使 state 易于更新而不引入错误 , 例: 少使用 Boolean 类型的 state, 而将其直接命名为各种状态
初始 state 命名惯例: 以
initial
或default
开头state 不建议多层嵌套, 建议扁平化处理, 使用 递归 对子元素进行渲染和使用 递归 对子元素进行处理
可以在组件中间添加 JSX 等内容, 将作为 children 特殊属性传给组件
在 react 中, **UI 树中 **相同位置的相同组件会被视为同一个组件, state 会被保留下来, 而在一般情况下, 组件被销毁后, state 会被重置
使用 key 可以很方便地独立组件并重置 state, 想要在这种情况下保留 state 的话, 请使用状态提升
复杂情况下 useState 的代替: useReducer, reducer 可以整合状态逻辑, 增加代码的可理解性和易维护性
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
给予初始值: initailTasks, 集中处理函数: tasksReducer, 初始值及后续更改后的值将被封装在 tasks 中, 使用 dispatch 将触发 tasksReducer 函数
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return ...;
}
case 'changed': {
return ...;
case 'deleted': {
return ...;
}
default: {
throw Error('未知 action: ' + action.type);
}
}
}
tasksReducer 函数中集中处理 tasks 变化, 通过 switch 来对应 action 中传递的各种情况并 return 处理后的 tasks(使用 action 中的参数对 tasks 进行修改), 还可以使用 default 进行意外的错误处理
在对应情况的函数中, 使用 dispatch(action)
分发 action 触发 tasksReducer 函数, 便会根据 dispatch 中 type 的值使用对应的 switch 进行 tasks 的对应处理
使用 context 代替 props 层层透传
首先, 创建一个 context (一般在新的文件中),
例:
export const LevelContext = createContext(1);
其中
creatContext()
中的为初始值, LevelContext 为创建后的 context 名然后, 在需要应用的组件(注意: 指最终接收和应用 context 的子组件)中导入 LevelContext ,
使用 useContext 将它赋值给一个变量以便投入使用,
例:
const level = useContext(LevelContext);
此后, 可以在该组件中使用 level 作为正常变量进行对应的操作
最后, 在需要进行透传的组件中(上文子组件的父组件)中使用 level 作为 props, 并在定义组件时, 引入 LevelContext 并用其包裹子组件
例:
import { LevelContext } from './LevelContext.js';
export default function Section({ level, children }) {
return (
<section className="section">
<LevelContext value={level}> //传给子组件的值
{children}
</LevelContext>
</section>
);
}
此后, context 便会在组件间进行透传(可以传到上文的两种组件中), 并会穿过中间层级的组件
ts 中, 可以使用
:React.FC<{}>
快捷地为传入的 props 中的各项数据进行类型声明Object.keys(obj)
方法可以快速地得到 obj 的键名组成的数组context 及 reducer 函数在多数时候可以使用 store 库进行代替, store 库中的状态更改时也会触发重新渲染
每次组件触发重新渲染时, 常规变量的值都会被重置, 想要保存变量的值, 应当使用 useState 或者 useRef
函数: useRef
使用
const ref = useRef(0);
创建一个 ref, 括号中为提供的初始值然而, ref 返回的是一个对象
{ current: 0 // 你向 useRef 传入的值 }
在组件中应当使用 ref.current 访问储存的值ref 与 state 异同
ref state useRef(initialValue)
返回{ current: initialValue }
useState(initialValue)
返回 state 变量的当前值和一个 state 设置函数 ([value, setValue]
)更改时不会触发重新渲染 更改时触发重新渲染。 可变 —— 你可以在渲染过程之外修改和更新 current
的值。“不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。 你不应在渲染期间读取(或写入) current
值。你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照↗。 ref 可以被便利地更改: 不需要使用 set 函数
ref 不是快照, state 则是, 例如在异步操作中, state 被提交后则固定, 而 ref 在操作执行前仍可以更改并将变化反应到结果上, 所以 ref 也常常被用于各种异步操作中
ref 不会触发浏览器的重新渲染, 所以它不应当被使用在与 DOM 相关的变化中, 相反, 在一些频繁触发的操作中, 使用 ref 可以极大地优化性能: 例如: 制作秒表, 制作防抖函数中
使用
element.scrollIntoView()
方法将元素滚动到视野中, 可传入参数对象:
{
behavior: '...',
block: '...',
inline: '...'
}
ref 在很多时候被用来控制 DOM , 处理一些 react 无法处理的 DOM 事件, 常见示例包括管理焦点、滚动位置或调用 React 未暴露的浏览器 API, 但是,如果尝试手动 修改 DOM,则可能会与 React 所做的更改发生冲突, 尽量不要这么做
要实现功能, 首先:
const myRef = useRef(null);
将 ref 设置为null之后, 将 DOM 绑定在 JSX 上:
<div ref={myRef}>
在 DOM 节点被创建时, React 会把对该节点的引用放入
myRef.current
, 然后便可以访问这个 DOM 元素可以使用 ref 回调更精确地控制 ref: 即将函数传递给
ref
属性以控制 ref 在不同情况下的各种状态如同其他 props , ref 也可以被传给子元素, 便可以透过父元素控制子元素状态
可以使用
useImperativeHandle
控制父元素可以访问的子元素特性的范围配置:
useImperativeHandle(ref, createHandle, [deps])
ref
:传递给组件的ref
。createHandle
:返回一个对象,该对象就是暴露给父组件的实例值。[deps]
:可选参数,是一个依赖数组,当依赖项发生变化时,createHandle
会重新执行。
使用 flushSync 可以使 set state 函数不等待更新队列立刻更新 DOM
配置:
flushSync(callback)
callback
:一个回调函数,在该函数中进行状态更新操作。Effect 是 React 范式中的一种脱围机制, 它允许你指定由渲染自身,而不是特定事件引起的副作用。
Effect 在 提交↗ 结束后、页面更新后运行: 此时是将 React 组件与外部系统(如网络或第三方库)同步的最佳时机。
Effect 的使用:
导入 Effect 后
( import { useEffect } from 'react')
, 在组件顶部进行调用function MyComponent() { useEffect(() => { // 每次渲染后都会执行此处的代码 }); return <div />;
简单来讲,
useEffect
会“延迟”一段代码的运行,直到渲染结果反映在页面上useEffect 还可以接收第二个参数: 依赖数组,
当无依赖数组时: 在每次渲染后调用
当存在依赖数组时, 需要将 Effect 中使用的参数放入其中, 否则会报错
当依赖数组为空时: 在组件挂载后调用(注意)
使用正常的依赖数组时, 依赖数组中的 任意元素变化 将调用 Effect (注意: Effect 为浅比较)
按需添加清理(cleanup)函数: 因为在每次渲染后都会调用 Effect , 所以 Effect 产生的副作用应当被及时进行清理
可以在 Effect 中返回一个 清理(cleanup)函数, React 会在每次 Effect 重新运行之前调用清理函数,并在组件卸载(被移除)时最后一次调用清理函数