React Docs 2023 进阶版
新年快乐!
学习
React 思维方法
- 将 UI 分解成一个组件构成的层次结构
- 先用 React 建立一个静态版本
- 找到 UI 状态的最小且完整的表示方法
- 确定你的状态数据应该保存在哪里
- 添加数据流
描述用户界面
不要把数字放在 && 的左边
- bad:
messageCount && <p>新消息</p>
- good:
messageCount > 0 && <p>新消息</p>
- 为了测试这个条件,JavaScript 将 && 左侧自动转换为布尔值。然而,如果左边是 0,那么整个表达式就会得到这个值(0),React 会呈现 0 而不是什么都不呈现
用 StrictMode 检测不纯的计算结果
- 通过两次调用组件函数,Strict Mode 有助于发现违反这些规则的组件
- 你可以使用 Strict Mode 来发现你的组件中的错误
- 组件、初始化函数和更新器函数需要是纯的
添加交互性
State + Event handler:向任何组件添加状态,并在需要时更新它
- Hook 在同一组件的每次渲染中都依赖于稳定的调用顺序。 在内部,React 为每个组件持有一个状态对数组 React Hooks: Not Magic, Just Arrays
当你调用 useState 时,React 会给你一个关于该渲染的 state 快照。与照片或电影帧不同,你返回的 UI 快照是可以互动的
- 变量和事件处理程序不会在重新渲染中 "存活"。每个渲染器都有自己的事件处理程序。 每一次渲染(和它里面的函数)都将永远 "看到" React 给本次渲染的 state 快照。 在过去创建的事件处理程序,其状态值来自于它们被创建时的数据
管理 state
是什么触发了 state 变化
- 人类的输入,比如点击一个按钮,在一个字段中输入,浏览一个链接。(事件处理程序!)
- 计算机输入,如网络请求的响应,超时的回调,图片的加载
设计良好的 state 结构是非常重要的
- 把相关的状态组合到一起。如果你总是同时更新两个或更多的状态变量,可以考虑将它们合并为一个状态变量
- 避免状态中的矛盾。当状态的结构中,几块状态可能相互矛盾和 "不一致" 时,你就为错误留下了空间。尽量避免这种情况
- 避免多余的状态。如果你能在渲染过程中从组件的道具或其现有的状态变量中计算出一些信息,你就不应该把这些信息放到该组件的状态中
- 避免状态的重复。当相同的数据在多个状态变量之间或在嵌套对象中重复时,就很难保持它们的同步。尽可能地减少重复
- 避免深度嵌套的状态。深度分层的状态在更新时不是很方便。在可能的情况下,最好以扁平的方式来构造状态
- 除非你特别想阻止更新,否则不要把 props 放到状态中
- 对于选择形式的 UI 交互,在状态中保留 ID 或索引而不是对象本身
保留和重设 state
- 只要组件在 UI 树中的位置被渲染后,React 就会保留它的状态。如果它被移除,或者在同一位置有不同的组件被渲染,React 会丢弃它的状态
- 同一位置上的同一组件保留状态
- 同一位置上的不同组件重置状态
- 在同一位置上重置状态。 a. 在不同的位置上渲染组件 b. 给每个组件一个带有 key 的显式身份。你可以通过给子树一个不同的 key 来强迫它重置其状态。
useState 和 useReducer
- useState 声明了一个你可以直接更新的状态变量
- useReducer 声明了一个状态变量,它的更新逻辑在一个 Reducer 函数中
使用 Reducer 和 Context 进行扩展
- 你可以将 Reducer 和 Context 结合起来,让任何组件可以读取和更新它上面的状态
逃生舱
Refs
- Refs 是一个逃生舱口,用来保存那些不用于渲染的值。你不会经常需要它们
- 和 state 一样,Refs 可以让你在组件的重新渲染之间保留信息
- 与 state 不同的是,设置 ref 的当前值并不会触发重新渲染
- 通常情况下,你会在非破坏性的操作中使用 ref ,比如聚焦、滚动或测量 DOM 元素
Effects
- 与事件不同,effect 是由渲染本身引起的,而不是由特定的交互引起的
- effect 可以让一个组件与一些外部系统(第三方 API,网络等)同步
- 你不能 "选择" effect 的依赖项,它们是由 effect 里面的代码决定的
如何删除不必要的 Effect
- 如果你能在渲染过程中计算出一些东西,你就不需要 Effect
- 为了缓存昂贵的计算结果,可以使用 useMemo 而不是 useEffect
- 如果要重置整个组件树的状态,请传递一个不同的 key 给他
- 如果要重置特定的 state 来响应 props 变化,请在 render 期间设置
- 因组件完成渲染而需要运行的代码应该在 effect 中,其余的应该在 event 中 — 当你需要在不同组件中设置 state 变量时,请考虑提升 state
- 你可以用 effect 获取数据,但你需要实现清理方法并且避免竞态问题(ignore 变量)
Reactive Effects 的生命周期
- 组件可以挂载、更新和卸载
- 每个 effect 都有一个独立的生命周期
- 每个 effect 描述一个独立的同步进程,可以启动和停止
- 当使用 effect 时,应该从每个单独的 effect 的角度(如何开始和停止同步)考虑,而不是从组件的角度(挂载、更新或卸载)考虑
- 在组件体中声明的值是 reactive 的
- 当 reactive 的值随时间改变后,effect 应该重新同步这种改变
- Linter 会验证 effect 中使用的所有 reactive 值都被指定为依赖项
- 所有由 linter 标记的错误都是有用的。总有一种方法可以在不破坏规则的情况下修复他
区分 Event 和 Effect
- 事件处理在响应特定交互时运行
- Effect 在需要同步改变时运行
- 事件处理程序内部的逻辑是非反应性的
- Effect 中的逻辑是反应性的
- 可以将非反应性逻辑从 Effects 移动到 Effect Events
- 只从 Effects 内部调用 Effect Events
- 不要将 Effect Events 传递给其他组件或 hook
移除 Effect 依赖项
- 如果 Effect 中的代码是为了响应特定的交互而运行,请将该代码移动到事件处理程序
- 如果 Effect 不同部分因为不同的原因需要重新运行,将其拆分为几个单独的 Effect
- 如果你想在之前的状态的基础上更新一些状态,传递一个 updater 函数
- 如果你想读取最新的值而不“反应”它,从你的 Effect 中提取一个 Effect Event
- 在 JavaScript 中,对象和函数如果是在不同的时间创建的,那他们是不相等的
- 尽量避免对象和函数依赖,将它们移到组件外或在 Effect 内
使用自定义 Hook 复用逻辑
- 自定义 Hook 只共享状态逻辑,不共享状态本身
- 你可以将 reactive 值从一个 Hook 传递到另一个 Hook,并且它们保持最新
- 当你的组件重新渲染时,所有 Hook 都会重新运行
- 自定义 hook 的代码应该是纯的,就像组件的代码一样
- 将自定义 Hook 接收到的事件处理程序包装为 Effect Event
- 不要创建像 useMount 这样的自定义 Hook
- 如何以及在哪里选择代码的边界取决于你
参考
组件
内置组件
<Fragment>
,也可以写成<>…</>,允许将多个 JSX 节点组合在一起<Profiler>
让你以编程方式测量 React 树的渲染性能<Suspense>
让你在加载子组件时,显示加载中状态<StrictMode>
允许额外的开发检查,帮助你尽早发现 bug
<StrictMode>
-
在开发早期发现组件中的常见错误
-
“严格模式”启用以下仅限开发时的行为:
- 你的组件会重新渲染,额外的一次用来寻找由不纯渲染引起的 bug
- 你的组件将额外重新运行 Effect ,以查找由于丢失 Effect 清理而导致的错误
- 你的组件将被检查是否使用了废弃的 api
-
严格模式在开发过程中调用你的一些函数(只有那些应该是纯的)两次。这包括:
- 你的组件函数体(只有顶级逻辑,所以不包括事件处理程序中的代码)
- 传递给 useState、set 函数、useMemo 或 useReducer 的函数
- 一些类组件方法,如 constructor, render, shouldComponentUpdate(参见整个列表)
Hooks
- Hook 只在组件或另一个 Hook 的顶层有效
State Hooks
- State 让组件“记住”信息
- useState声明了一个可以直接更新的状态变量
- useReducer声明了一个状态变量,其中更新逻辑在 reducer 函数中
useState
- set 函数、updater 函数、initializer 函数
- 通过设置 key 来复原 state
- 太多的重渲染:通常意味着你在渲染期间无条件地设置状态,所以你的组件进入一个无限循环
useReducer
Context Hooks
- Context 让一个组件从远方的父组件那里接收信息,而不需要将其作为 props 传递
- useContext 读取并订阅一个上下文
Ref Hooks
- Refs 让一个组件持有一些不用于渲染的信息
- useRef声明一个 Ref。你可以在其中保存任何值,但最常见的是用来保存一个 DOM 节点
- useImperativeHandle让你自定义你的组件所暴露的引用。这一点很少使用
Effect Hooks
- Effect 让一个组件连接到外部系统并与之同步
性能 Hooks
- useMemo 让你缓存一个昂贵的计算结果。
- useCallback 让你在把一个函数定义传递给一个优化的组件之前缓存它
- useTransition 让你把一个状态转换标记为非阻塞,并允许其他更新打断它
- useDeferredValue 让你推迟更新用户界面的一个非关键部分,让其他部分先更新
useCallback
- 你把它作为一个 props 传递给一个用 memo 包装的组件。如果值没有变化,你想跳过重新渲染的过程。Memoization 让你的组件只在依赖关系不一样的时候才重新渲染
- 你所传递的函数后来被用作某个 Hook 的依赖项。例如,另一个用 useCallback 包装的函数依赖于它,或者你从 useEffect 中依赖于这个函数
useMemo
- 你放在 useMemo 中的计算明显很慢,而且它的依赖关系很少改变
- 你把它作为一个 props 传递给一个用 memo 包装的组件。如果值没有变化,你想跳过重新渲染的过程。Memoization 让你的组件只在依赖关系不一样的时候才重新渲染
- 你所传递的值后来被用作某个 Hook 的依赖项。例如,也许另一个 useMemo 计算值依赖于它。或者,也许你是依赖于 useEffect 的这个值
useDeferredValue
- 显示陈旧的内容,而新鲜的内容正在加载中
- 表明内容是陈旧的
- 推迟 UI 的一部分的重新渲染
- 延迟值与防抖和节流有什么不同?
- 与 debouncing 或 throttling 不同,它不需要选择任何固定的延迟
- 与 debouncing 或 throttling 不同,由 useDeferredValue 完成的延迟再渲染默认是可中断的
- 如果你要优化的工作不发生在渲染过程中,debouncing 和 throttling 仍然是有用的
其他钩子
- useDebugValue 让你在 React DevTools 自定义 Hook 显示的标签
- useId 可以让组件将一个独特的 ID 与自己联系起来。通常与可访问性 API 一起使用
- useSyncExternalStore 让一个组件订阅一个外部存储
useDebugValue
- 为一个自定义的 Hook 添加标签
- 推迟调试值的格式化
- 我们不建议给每个自定义 Hook 添加调试值。它对那些属于共享库的自定义 Hooks 最有价值,因为它们有一个复杂的内部数据结构,很难检查
转载自:https://juejin.cn/post/7183571094199074875