react中hooks之useRef 用法总结
1. 基本概念
useRef 是 React 的一个 Hook,返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数。这个对象在组件的整个生命周期内保持不变。
2. 主要用途和特性
2.1 获取 DOM 元素实例
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// 直接访问 DOM 元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>聚焦输入框</button>
</>
);
}
2.2 存储组件渲染周期之间的共享数据
- useRef 只会在组件初始化时执行一次
- state 改变引起的重新渲染不会导致 useRef 重新执行
- 适合存储不需要触发视图更新的数据
function Counter() {
const [count, setCount] = useState(0);
const renderCount = useRef(0); // 用于记录渲染次数
useEffect(() => {
renderCount.current += 1;
console.log(`组件已渲染 ${renderCount.current} 次`);
});
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
2.3 useRef 的重要特性
- current 值的修改不会触发重新渲染
function Example() {
const countRef = useRef(0);
const handleClick = () => {
// 修改 ref 不会导致组件重新渲染
countRef.current += 1;
console.log('当前值:', countRef.current);
};
return <button onClick={handleClick}>点击</button>;
}
- 不应作为其他 Hooks 的依赖项
function BadExample() {
const valueRef = useRef(0);
// ❌ 错误示例
useEffect(() => {
console.log(valueRef.current);
}, [valueRef.current]); // 不要这样做
}
function GoodExample() {
const valueRef = useRef(0);
// ✅ 正确示例
useEffect(() => {
console.log(valueRef.current);
}); // 不将 ref 作为依赖项
}
3. forwardRef 和 useImperativeHandle
3.1 基本用法示例
// CustomInput.jsx
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
// 只暴露需要的方法
focus: () => {
inputRef.current.focus();
},
getValue: () => {
return inputRef.current.value;
}
}));
return <input ref={inputRef} {...props} />;
});
// Parent.jsx
function Parent() {
const inputRef = useRef();
const handleClick = () => {
inputRef.current.focus();
console.log(inputRef.current.getValue());
};
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={handleClick}>操作输入框</button>
</div>
);
}
3.2 复杂组件示例(不同粒度的暴露)
const ComplexComponent = forwardRef((props, ref) => {
const inputRef = useRef();
const checkboxRef = useRef();
const formRef = useRef();
useImperativeHandle(ref, () => ({
// 粒度级别 1:表单级操作
form: {
reset: () => {
inputRef.current.value = '';
checkboxRef.current.checked = false;
},
validate: () => {
return inputRef.current.value.length > 0;
}
},
// 粒度级别 2:具体输入框操作
input: {
focus: () => inputRef.current.focus(),
getValue: () => inputRef.current.value,
setValue: (value) => {
inputRef.current.value = value;
}
},
// 粒度级别 3:简单方法
clear: () => {
inputRef.current.value = '';
}
}));
return (
<form ref={formRef}>
<input ref={inputRef} type="text" />
<input ref={checkboxRef} type="checkbox" />
</form>
);
});
// 使用示例
function ComplexParent() {
const componentRef = useRef();
const handleOperations = () => {
// 使用不同粒度的操作
componentRef.current.form.reset();
componentRef.current.input.focus();
componentRef.current.input.setValue('新值');
componentRef.current.clear();
if (componentRef.current.form.validate()) {
console.log('表单验证通过');
}
};
return (
<div>
<ComplexComponent ref={componentRef} />
<button onClick={handleOperations}>执行操作</button>
</div>
);
}
4. 注意事项
- useRef 不能直接引用函数式组件,必须配合 forwardRef 使用
- useRef 的值改变不会触发重新渲染,如果需要在值改变时重新渲染,应使用 useState
- 使用 useImperativeHandle 时,应该只暴露必要的方法,保持良好的封装性
- 避免在 render 过程中读取或写入 ref.current
5. 最佳实践
- 使用 TypeScript 定义暴露的接口类型
- 合理划分暴露方法的粒度
- 文档化暴露的方法
- 遵循最小暴露原则
- 在清理阶段(cleanup)正确处理 ref,特别是涉及定时器等资源时
6. 使用场景建议
- 访问 DOM 元素或组件实例
- 存储定时器 ID
- 存储上一次的值
- 存储不需要触发重新渲染的数据
- 跨组件方法调用(通过 forwardRef)
通过合理使用 useRef,可以优化组件性能,实现更复杂的组件交互,同时保持代码的可维护性和可读性。
原文地址:https://blog.csdn.net/qq_34645412/article/details/145162945
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!