高阶函数
forwardRef
- 在 react 19 中,该API已经被废弃了;
- 19版本中,可以将 ref 作为 props 进行传递;
tsxfunction MyInput({placeholder, ref}) { return <input placeholder={placeholder} ref={ref} /> } //... <MyInput ref={ref} />
forwardRef
是 React 中的一个高阶函数,用于将ref
显式地传递给子组件。- 通常需要配合
useImperativeHandle
使用;
- 通常需要配合
- 在
TypeScript
中使用forwardRef
时,通常会配合泛型来指定 props 和 ref 的类型。
作用
- 实际上函数式组件是没有
ref
的; - 那我们想要拿到函数式组件内部的某个
DOM
实例,就可以通过forwardRef
将子组件的某个ref转发出去;
使用场景
- 父组件需要访问子组件的 DOM 节点或实例方法 :
- 通过
forwardRef
,父组件可以获取到子组件的 ref,并对其进行操作。
- 通过
- 自定义组件包装原生组件 :
- 当自定义组件内部使用了原生组件时,可以通过
forwardRef
将 ref 传递给原生组件。
- 当自定义组件内部使用了原生组件时,可以通过
示例代码
- 假设你有一个
SubCom
组件,需要将其 ref 传递给父组件:
tsx
import { useState, forwardRef, useRef, } from 'react';
const SubCom = forwardRef<HTMLInputElement, object>((props, ref) => {
const [val, setVal] = useState('嘻哈少将');
return (
<div>
<input ref={ref} type="text" value={val} onChange={(e) => setVal(e.target.value)} />
</div>
);
});
const ForwardRef: React.FC = () => {
const subComRef = useRef(null);
return (
<>
<SubCom ref={subComRef} />
<button onClick={() => console.log(subComRef.current)}>LOG</button>
</>
);
};
export default ForwardRef;
注意事项
- 类型安全 :
- 在 TypeScript 中,使用
forwardRef
时需要明确指定props
和ref
的类型,以确保类型安全; forwardRef<RefType, PropsType>
有两个两个泛型参数:- 第一个是
ref
的类型; - 第二个是
props
的类型;- 若子组件不需要props,但是项目中配置了eslint,传递一个
{}
就会有提示(若没有eslint,就可以传递一个空对象),很难受,这时,我们可以将props的类型设置为object
,就不会有eslint提示了;
- 若子组件不需要props,但是项目中配置了eslint,传递一个
- 第一个是
- 在 TypeScript 中,使用
- 性能考虑 :
forwardRef
会增加组件的复杂度,因此只在必要时使用;
memo
memo
允许你在 组件props
没有改变 的情况下 跳过重新渲染;memo
是 React 中的一个高阶组件,用于优化函数组件的性能。- 它通过记忆化(Memoization)技术,避免在 props 或 state 没有变化时不必要的重新渲染。这对于性能敏感的应用非常有用。
使用场景
- 性能优化 :
- 当组件的渲染开销较大,且其 props 很少发生变化时,使用
memo
可以显著提升应用性能。
- 当组件的渲染开销较大,且其 props 很少发生变化时,使用
- 避免不必要的渲染 :、
- 确保只有在 props 发生变化时才进行重新渲染。
示例代码
- 假设你有一个
Summary
组件,希望使用memo
来优化其性能
tsx
import React, { memo } from 'react';
interface SummaryProps {
title: string;
data: any[];
}
const Summary = (props: SummaryProps) => {
console.log('SummaryComponent rendered');
return (
<div>
<h1>{props.title}</h1>
{/* 渲染数据 */}
<ul>
{props.data.map((item, index) => ( <li key={index}>{item}</li> ))}
</ul>
</div>
);
};
export default memo(Summary);
解释
memo
:- 用于包裹
Summary
,使其成为一个记忆化的组件。
- 用于包裹
Summary
:- 这是你实际的函数组件,负责渲染内容。
console.log
:- 用于调试,观察组件是否在每次父组件更新时都重新渲染。
自定义比较逻辑
- 默认情况下,
memo
使用浅比较来判断 props 是否发生变化。如果需要更复杂的比较逻辑,可以传递一个自定义的比较函数作为第二个参数:
tsx
const areEqual = (prevProps: SummaryProps, nextProps: SummaryProps) => {
return prevProps.title === nextProps.title && prevProps.data === nextProps.data;
};
const Summary = memo(SummaryComponent, areEqual);
注意事项
- 浅比较 :
- 默认情况下,
memo
使用浅比较来判断 props 是否发生变化。 - 如果 props 是对象或数组,浅比较可能不够精确。
- 默认情况下,
- 性能影响 :
- 虽然
memo
可以提高性能,但如果过度使用或不正确使用,可能会导致性能下降。 - 因此,只在必要时使用
memo
。
- 虽然
‼️ 注意
- 使用
React.memo
缓存组件时,组件的 内部状态 不会被 重置 (即通过useState
或useReducer
创建的状态)。 React.memo
只是用于优化组件的渲染性能,它会比较组件的 props,如果 props 没有变化,则不会重新渲染组件,而是复用之前的渲染结果。- 然而,需要注意的是,
React.memo
只是比较 props 的浅层引用相等性。如果 props 中的对象或数组发生变化,即使内容相同,也会被视为不同的 props,从而导致组件重新渲染。
组件销毁
- 使用
React.memo
缓存的组件不会因为缓存机制而被销毁和重新创建。 React.memo
主要用于优化组件的渲染性能,通过比较组件的 props 来决定是否需要重新渲染。- 如果 props 没有变化,组件将不会重新渲染,而是复用之前的渲染结果。
关键点
- 组件的生命周期 :
React.memo
不会影响组件的生命周期。组件的挂载(mounting)和卸载(unmounting)仍然遵循 React 的标准生命周期规则。- 如果组件的父组件重新渲染,并且传递给
React.memo
组件的 props 没有变化,那么React.memo
组件不会重新渲染,但也不会被销毁和重新创建。
- 销毁和重新创建 :
- 组件的销毁和重新创建通常发生在以下情况:
- 父组件的逻辑导致组件被移除或添加到 DOM 中。
- 组件的
key
属性发生变化,React 会认为这是一个新的组件实例,从而销毁旧的实例并创建新的实例。
- 组件的销毁和重新创建通常发生在以下情况:
useImperativeHandle
useImperativeHandle
是 React 中的一个 Hook,用于自定义暴露给父组件的 ref。- 通过
useImperativeHandle
,你可以控制哪些方法或属性可以通过 ref 访问,从而实现更细粒度的控制。
使用场景
- 自定义 ref 行为 :
- 允许你在子组件中自定义通过 ref 暴露的方法或属性(自定义暴露给父组件的属性或方法
- 父组件调用子组件的方法 :
- 父组件可以通过
ref
调用子组件中的方法,而不仅仅是访问DOM
节点。
- 父组件可以通过
示例代码
-
假设你有一个子组件
SubCom
,希望父组件能够通过ref
调用子组件中的某个方法 或 访问某个属性:tsximport { useState, forwardRef, useRef, useImperativeHandle, } from 'react'; interface SubComType { val: string; onTest: () => void; }; const SubCom = forwardRef<SubComType, object>((props, ref) => { const [val, setVal] = useState('嘻哈少将'); const onTest = () => { console.log('onTest'); }; // 自定义需要暴露给父组件的属性或方法 useImperativeHandle(ref, () => ({ val, onTest, })); return ( <div> <input type="text" value={val} onChange={(e) => setVal(e.target.value)} /> </div> ); }); const ForwardRef: React.FC = () => { const subComRef = useRef<SubComType>(null); return ( <> <div>ForwardRef</div> <SubCom ref={subComRef} /> <button onClick={() => console.log(subComRef.current)}>LOG</button> <button onClick={() => subComRef.current?.onTest()}>调用子组件方法 - onTest</button> </> ); }; export default ForwardRef;
解释
forwardRef
:- 用于将 ref 传递给子组件。
useImperativeHandle
:- 用于自定义通过 ref 暴露的方法或属性。
SubComType
:- 定义了通过 ref 暴露的方法或属性的类型。
subComRef
:- 用于引用自组件的 ref;
onTest
方法 : 通过useImperativeHandle
暴露给父组件的方法。
注意事项
- 类型安全 :
- 在 TypeScript 中,使用
useImperativeHandle
时需要明确指定 ref 的类型,以确保类型安全。
- 在 TypeScript 中,使用
- 谨慎使用 :
- 过度使用
useImperativeHandle
可能会导致组件之间的耦合度增加,影响代码的可维护性。 希望这些信息对你有帮助!如果有更多问题,请随时提问。
- 过度使用
useLayoutEffect
useLayoutEffect
是 React 中的一个 Hook,类似于useEffect
,但它在 所有的 DOM 变更之后 同步 调用,但在 浏览器绘制之前。- 这意味着
useLayoutEffect
可以读取和修改 DOM 布局,而不会导致视觉上的闪烁或抖动。
主要用途
- 读取布局信息 :
- 在组件渲染后立即读取 DOM 节点的布局信息,如宽度、高度等。
- 同步更新 DOM :
- 在浏览器绘制之前同步更新 DOM,确保用户看到的是最新的布局。
与 useEffect
的区别
- 执行时机 :
useEffect
:- 在浏览器绘制之后异步执行。
useLayoutEffect
:- 在浏览器绘制之前同步执行。
- 适用场景 :
useEffect
:- 适用于大多数副作用操作,如数据获取、订阅事件等。
useLayoutEffect
:- 适用于需要在浏览器绘制之前读取和修改 DOM 布局的场景。
示例代码
- 在
Summary
组件中,useLayoutEffect
用于计算进度条的宽度,并根据数据动态调整进度条的样式。
tsx
useLayoutEffect(() => {
if (info[dateType].length === 0) return;
const boxWidth = 258;
const max = Math.max(...info[dateType]);
const maxIndex = info[dateType].findIndex(i => i === max);
const moneyWidth = document.querySelectorAll('.SummaryBoxItemMoney')
?.[maxIndex]
?.getBoundingClientRect()
?.width || 0;
const progressWidth = boxWidth - moneyWidth;
const perWidth = progressWidth / max;
setProgressWidth(info[dateType].map(i => i * perWidth));
}, [info, dateType]);
解释
- 条件检查 :
if (info[dateType].length === 0)
确保在没有数据时不会进行后续计算。
- 常量定义 :
boxWidth
: 进度条容器的宽度。max
: 当前日期类型下的最大值。maxIndex
: 最大值的索引。
- 计算
moneyWidth
:- 通过
getBoundingClientRect
获取最大值对应的 DOM 元素的宽度。
- 通过
- 计算
progressWidth
:- 进度条容器的总宽度减去最大值对应的 DOM 元素的宽度。
- 计算
perWidth
:- 每个单位值对应的进度条宽度。
- 更新
progressWidth
:- 根据每个值计算出对应的进度条宽度,并更新状态。
注意事项
- 性能影响 :
- 由于
useLayoutEffect
是同步执行的,频繁使用可能会导致性能问题。 - 因此,只在确实需要在浏览器绘制之前读取和修改 DOM 布局时使用。
- 由于
- 副作用 :
useLayoutEffect
中的副作用会阻塞浏览器的绘制,因此应尽量减少其内部的复杂计算和 DOM 操作。