ref
- ref 是 React 提供的一种机制,用于访问和操作 DOM 元素或 React 组件的实例;
- 它可以用于获取某个 DOM 元素的引用,从而执行一些需要直接操作 DOM 的任务,例如手动设置焦点、选择文本或触发动画;
使用 ref
-
步骤:
创建一个 ref
:使用 React.createRef 或 useRef Hook 创建一个 ref;将 ref 传递给 React 元素
:将 ref 赋值给 React 元素的 ref 属性;访问 ref
:通过 ref.current 访问元素或组件实例;
-
示例:
import { createRoot } from "react-dom/client"; import { useRef, useEffect } from 'react'; function TextInput() { const inputRef = useRef(null); useEffect(() => { // 组件挂载后,input 元素获取焦点 inputRef.current.focus(); }, []); return <input ref={inputRef} type="text" />; } createRoot(document.getElementById("root")).render(<TextInput />);
ref 的函数形式
-
ref 可以直接赋值或通过函数进行赋值,在早期 ref 还支持传递字符串,但是新版本已经不再推荐这种写法;
-
示例:
import { createRoot } from "react-dom/client"; import { useRef, useEffect } from 'react'; function TextInput() { const inputRef = useRef(null); useEffect(() => { // 组件挂载后,input 元素获取焦点 inputRef.current.focus(); }, []); return <input ref={node => inputRef.current = node} type="text" />; } createRoot(document.getElementById("root")).render(<TextInput />);
forwardRef
forwardRef 是一个用于转发 ref 到子组件的方式,它允许父组件访问子组件的 DOM 节点或组件实例;
它常用于封装第三方 UI 库组件或实现高阶组件;
使用 forwardRef
-
步骤:
定义一个函数组件
:使用 React.forwardRef 包裹函数组件,使其能够接收 ref;转发 ref
:在组件内部,将 ref 传递给某个子组件或 DOM 元素;
-
示例:
import { createRoot } from "react-dom/client"; import { useRef, useEffect, forwardRef } from 'react'; const FancyButton = forwardRef((props, ref) => ( <button ref={ref} className="fancy-button"> { props.children } </button> )); function App() { const buttonRef = useRef(null); useEffect(() => { console.log(buttonRef.current); // 输出 <button> 元素 }, []); return <FancyButton ref={buttonRef}>Click me</FancyButton>; } createRoot(document.getElementById("root")).render(<App />);
Suspense
Suspense 是 React 提供的一个组件,用于处理异步操作 (如数据获取或组件懒加载);
在异步操作完成之前显示一个备用的 UI (如加载指示器);
基础使用
-
包裹异步组件:使用 Suspense 组件包裹需要进行异步操作的组件,并提供一个 fallback 属性,指定加载时的备用 UI;
-
异步加载组件:使用 React.lazy 实现组件的懒加载;
import { createRoot } from "react-dom/client"; import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); function App() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); } createRoot(document.getElementById("root")).render(<App />);
进阶用法
-
新版本 Suspense 支持异步操作;
-
如果其子组件中存在异步处理,那么通过返回一个 promise 状态值来标识当前子组件加载状态,在子组件为 pending 时,Suspense 会渲染 fallback ui,直至异步完成;
import { createRoot } from "react-dom/client"; import { Suspense } from 'react'; // 创建一个支持 Suspense 的数据获取资源 function createResource(promise) { let status = 'pending'; let result; const suspender = promise.then( (data) => { status = 'success'; result = data; }, (error) => { status = 'error'; result = error; } ); return { read() { if (status === 'pending') { throw suspender; } else if (status === 'error') { throw result; } else { return result; } } }; } // 获取用户数据 const userResource = createResource(new Promise((resolve) => { setTimeout(() => { resolve({ name: 'zhangsan', email: '1105389168@qq.com' }) }, 1000); })); function UserProfile() { // 读取资源,如果未准备好会抛出Promise const user = userResource.read(); return ( <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> </div> ); } function App() { return ( <Suspense fallback={<div>Loading user profile...</div>}> <UserProfile /> </Suspense> ); } createRoot(document.getElementById("root")).render(<App />);
原理
-
React Suspense 的工作原理基于一种新的渲染模式,称为 “可中断的渲染”(Interruptible Rendering);这一模式允许 React 暂停在某些组件上的渲染,并在这些组件准备好之后继续渲染;
-
以下是 Suspense 的核心机制:
异步边界
:当 React 渲染一个组件时,如果它遇到一个 Suspense 组件,并且 fallback 触发了,则会暂停该组件树的渲染;资源的异步加载
:React.lazy 用于异步加载 React 组件;这些组件会在它们的模块加载完成之前暂停渲染;数据的异步获取
:Suspense 可以与数据获取库结合使用;在这种情况下,React 会等待数据加载完成后再继续渲染;缓存机制
:React 的新架构使用缓存来存储加载的数据或组件;这确保了相同的数据或组件在不同的渲染周期中可以被复用,减少了不必要的加载;协调(Reconciliation)和优先级调度
:React 的调度器会根据任务的优先级来协调更新;高优先级的任务(如用户交互)会优先处理,而低优先级的任务(如数据获取)会被延迟处理;这种机制使得 React 可以在任务之间进行切换,提高应用的响应速度和性能;
lazy
-
使用 React.lazy:
定义一个懒加载组件
:使用 React.lazy 动态导入组件;使用 Suspense 包裹懒加载组件
:将懒加载组件放在 Suspense 中,并提供 fallback 以显示加载指示器;
-
示例:
import { createRoot } from "react-dom/client"; import React, { Suspense } from 'react'; const LazyComponent = React.lazy(() => import('./LazyComponent')); function App() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </Suspense> </div> ); } createRoot(document.getElementById("root")).render(<App />);
memo
memo 允许你的组件在 props 没有改变的情况下跳过重新渲染;
语法
-
使用 memo 将组件包装起来,以获得该组件的一个 记忆化 版本;通常情况下,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染;但 React 仍可能会重新渲染它:记忆化是一种性能优化,而非保证;
memo(Component, arePropsEqual?)
-
参数:
Component
:要进行记忆化的组件;memo 不会修改该组件,而是返回一个新的、记忆化的组件;它接受任何有效的 React 组件,包括函数组件和 forwardRef 组件;可选参数 arePropsEqual
:一个函数,接受两个参数:组件的前一个 props 和新的 props;如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回 true;否则返回 false;通常情况下,不需要指定此函数;默认情况下,React 将使用 Object.is 比较每个 prop;
-
返回值:
- memo 返回一个新的 React 组件;
- 它的行为与提供给 memo 的组件相同,只是当它的父组件重新渲染时 React 不会总是重新渲染它,除非它的 props 发生了变化;
用法
-
当 props 没有改变时跳过重新渲染
; -
使用 state 更新记忆化组件
:- 即使一个组件被记忆化了,当它自身的状态发生变化时,它仍然会重新渲染;
- 记忆化只与从父组件传递给组件的 props 有关;
-
使用 context 更新记忆化组件
:- 即使组件已被记忆化,当其使用的 context 发生变化时,它仍将重新渲染;
- 记忆化只与从父组件传递给组件的 props 有关;
-
最小化 props 的变化
:- 当使用 memo 时,只要任何一个 prop 与先前的值不是 浅层相等 的话,组件就会重新渲染;这意味着 React 会使用 Object.is 比较组件中的每个 prop 与其先前的值;注意,Object.is(3, 3) 为 true,但 Object.is({}, {}) 为 false;
- 为了最大化使用 memo 的效果,应该尽量减少 props 的变化次数;例如,如果 props 是一个对象,可以使用 useMemo 避免父组件每次都重新创建该对象:
function Page() { const [name, setName] = useState('Taylor'); const [age, setAge] = useState(42); const person = useMemo( () => ({ name, age }), [name, age] ); return <Profile person={person} />; } const Profile = memo(function Profile({ person }) { // ... });
- 最小化 props 的改变的更好的方法是确保组件在其 props 中接受必要的最小信息;例如,它可以接受单独的值而不是整个对象:
function Page() { const [name, setName] = useState('Taylor'); const [age, setAge] = useState(42); return <Profile name={name} age={age} />; } const Profile = memo(function Profile({ name, age }) { // ... });
- 即使是单个值有时也可以投射为不经常变更的值;例如,这里的组件接受一个布尔值,表示是否存在某个值,而不是值本身:
function GroupsLanding({ person }) { const hasGroups = person.groups !== null; return <CallToAction hasGroups={hasGroups} />; } const CallToAction = memo(function CallToAction({ hasGroups }) { // ... });
-
指定自定义比较函数
- 在极少数情况下,最小化 memoized 组件的 props 更改可能是不可行的;
- 在这种情况下,可以提供一个自定义比较函数,React 将使用它来比较旧的和新的 props,而不是使用浅比较;这个函数作为 memo 的第二个参数传递;它应该仅在新的 props 与旧的 props 具有相同的输出时返回 true;否则应该返回 false;
const Chart = memo(function Chart({ dataPoints }) { // ... }, arePropsEqual); function arePropsEqual(oldProps, newProps) { return ( oldProps.dataPoints.length === newProps.dataPoints.length && oldProps.dataPoints.every((oldPoint, index) => { const newPoint = newProps.dataPoints[index]; return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y; }) ); }
React✍️ React Hooks 深入理解
上一篇