ref / useRef / forwardRef / Callback ref
ref是什么
ref是一个引用,用于获取组件实例或DOM元素。(也可以用来保存一个可变对象在多次渲染的一致性数据)。 可以使用多种方式使用和创建ref,包括ref属性,useRef hook, forwardRef函数和callback ref.
ref属性
ref属性:ref属性可以用于获取组件实例或DOM元素。它可以直接在组件上使用,例如:
function MyComponent() {
const myRef = useRef(null);
function handleClick() {
myRef.current.focus();
}
return (
<div>
<input type="text" ref={myRef} />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
在这个例子中,MyComponent组件使用useRef钩子创建了一个ref,并将其传递给input元素的ref属性。在handleClick函数中,可以通过myRef.current来获取input元素,并调用其focus方法。
useRef Hook
useRef钩子可以创建一个ref对象,并在组件的生命周期中保持引用不变。它可以用于保存组件的状态或其他信息。例如:
function MyComponent() {
const inputRef = useRef(null);
const [text, setText] = useState("");
function handleChange(event) {
setText(event.target.value);
}
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<div>
<input type="text" ref={inputRef} value={text} onChange={handleChange} />
</div>
);
}
在这个例子中,MyComponent组件使用useRef钩子创建了一个inputRef对象,并将其传递给input元素的ref属性。在useEffect钩子中,可以通过inputRef.current来获取input元素,并调用其focus方法。同时,MyComponent组件使用useState钩子来保存input元素的值,并将其传递给input元素的value属性。
forwardRef函数
forwardRef函数可以用于将ref属性向下传递给子组件。它可以用于包装其他组件,并将ref属性传递给包装组件的子组件。例如:
const WrappedComponent = forwardRef((props, ref) => {
return <ChildComponent {...props} forwardedRef={ref} />;
});
function ParentComponent() {
const childRef = useRef(null);
function handleClick() {
childRef.current.doSomething();
}
return (
<div>
<WrappedComponent ref={childRef} />
<button onClick={handleClick}>Call Child Component Method</button>
</div>
);
}
在这个例子中,WrappedComponent是一个高阶组件,用于包装ChildComponent,并将ref属性传递给ChildComponent的forwardedRef属性。在ParentComponent中,可以使用useRef钩子创建一个childRef对象,并将其传递给WrappedComponent的ref属性。在handleClick函数中,可以通过childRef.current来获取ChildComponent实例,并调用其doSomething方法。
callback ref
callback ref是一种回调函数,用于在ref被挂载或卸载时执行一些自定义逻辑。它可以用于获取组件实例或DOM元素,并执行一些自定义操作。例如:
function MyComponent() {
const myRef = useRef(null);
useEffect(() => {
if (myRef.current) {
console.log("Ref has been mounted!");
}
}, [myRef]);
function handleRef(ref) {
myRef.current = ref;
}
return <div ref={handleRef}>My Component</div>;
}
在这个例子中,MyComponent组件使用useRef钩子创建了一个myRef对象,并定义了一个handleRef回调函数,用于将组件的ref赋值给myRef.current。在useEffect钩子中,可以判断myRef.current是否存在,并输出相应的信息。同时,MyComponent组件将handleRef函数作为回调函数传递给div元素的ref属性,用于在ref被挂载时执行自定义逻辑。
ref特点
- 多次渲染保存的是同一个对象的引用值,也就是说如果不手动更新其内部的属性值,ref的值一直不会变。
- ref对象只有一个属性是current,改变ref引用对象的current属性,不会引发组件的重新渲染,只有组件卸载(销毁)后才会没。
- 函数式组件是不能直接添加ref属性的,因为它本身没有实例。需要forwardRef去包装一下
使用场景
- 可以获取上一次的值
const usePrevious = (val) => {
const ref = useRef();
useEffect(() => {
ref.current = val;
});
return res.current;
}
- 可以保存在组件整个生命周期都不会变化的值
// 保存定时器ID
const App = () => {
let timer = useRef(null);
useEffect(() => {
timer.current = setInterval(() => {}, 1000);
}, []);
return <button onClick={() => {clearInterval(timer.current)}}>stop</button>
}
- 可以用useRef实现一个DeepCompare版本的useEffect
import { isEqual } from 'lodash';
const useDeepCompareEffect = (cb, deps) => {
const emitEffect = useRef(0);
const prevDeps = useRef(deps);
if (!isEqual(prevDeps,current, deps) {
emitEffect.current++;
}
prevDeps.current = deps;
return useEffect(cb, [emitEffect.current])
}