一文彻底掌握ref,createRef,useRef,forwardRef,useImperativeHandle...
先有问题再有答案
react中的ref是做什么的
为什么需要它?
该如何正确的使用ref?
官方为什么不提倡过渡使用?
如何理解useRef?
React.createRef又是什么?
forwardRef起到什么作用?
useImperativeHandle与ref有什么关系?
你是如何理解ref,useref,forwardRef,useImperativeHandle之间的关系?
ref的设计目的
ref(reference)是React中的一个特性,一种特殊的属性,它允许我们访问和操作DOM元素或者Class组件实例。
在React中,我们通常倾向于通过数据流(状态和props)来控制组件的渲染和行为。
但在某些情况下,我们需要 ref来绕过React的虚拟DOM
,直接与DOM元素或组件实例进行交互。
在某些情况下,这是必要的。例如 需要直接测量DOM元素的尺寸或位置。需要调用底层DOM API,如scrollHeight
或scrollIntoView
。这些操作通常在React方式之外进行,这就是ref出现的原因
。
官方为什么不提倡过渡使用
React的设计理念是使用声明式编程和组件化,通过props和state来控制视图,保持单项数据流,这意味着当数据改变时,UI会自动改变,从而降低了理解代码的复杂性和维护代码的困难。
而使用ref则是命令式编程和双向数据流的一种形式,它使得我们可以直接操作DOM元素和组件实例,从而绕过了React的渲染机制,这可能会让整个应用的数据流变得难以理解和预测。尤其是在大型复杂的项目中,过度使用ref可能会导致浏览器性能下降,代码的维护性和可读性也会变差。
因此,尽管ref在某些场景中(例如处理焦点事件,媒体播放等)是不可或缺的,但是React官方并不倡导过度使用ref,而是应该尽可能地用props和state来组织和管理应用的数据流。
需要注意的是,这不是说ref是“坏”的或者“不应该”的使用,而是说在使用ref时需要更加小心和注意,只有在React模式无法解决的情况下,再考虑使用ref。
使用方式
类中使用
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myNode = React.createRef();
}
componentDidMount() {
// 组件挂载后,可以通过 this.myNode.current 访问 DOM 元素
console.log(this.myNode.current);
}
render() {
// 使用 createRef 创建的 ref
return <div ref={this.myNode}>Hello</div>;
}
}
在类组件中 通常建议使用React.createRef 来创建和管理 ref。
函数中使用
function MyComponent() {
const myRef = useRef();
useEffect(() => {
console.log(myRef.current);
}, []);
return <div ref={myRef}>Hello</div>;
}
上面代码中,我们用useRef创建了一个ref,并将其赋值到一个div元素上,在useEffect中获取到该元素。
知识点扫盲
-
关于ref? ref是React的一个特性,可以用于访问和操作DOM元素或Class组件的实例。
-
React.createRef React.createRef是一个函数,在类组件中使用 ,用于创建ref。这种方式创建的ref被赋给组件或DOM元素的ref属性后,可以在组件的生命周期方法(如componentDidMount)或其他方法中通过this.refName.current访问到实际的DOM元素或组件实例。
-
forwardRef React.forwardRef 是一个函数,用于对
ref进行转发和传递
,使得父组件可以获取子组件的引用,用于在高阶组件(HOC)中传递ref。因为默认情况下,ref不能被传递,但用此函数包裹后就能使得ref能被直接传到子组件上。 -
useRef useRef是React的一个Hook,用于在函数组件内创建ref。不仅可以用它来引用DOM元素或组件实例,也可以用来在整个组件生命周期内存储和操作可变值。使用useRef创建的ref在整个组件生命周期内保持不变。
-
useImperativeHandle useImperativeHandle是React的一个Hook,用于自定义暴露给父组件的实例值。也就是说,你可以确定父组件可以通过ref调用哪些子组件内部的方法或访问哪些属性。通常,父组件只能访问子组件暴露的DOM元素或定义的组件实例,但useImperativeHandle可以让你控制暴露什么给父组件。
父组件调用子组件的方法
react框架的世界中 操作dom可以有三种方式:
通过ref获取组件的实例
import React, { useRef, forwardRef, useImperativeHandle } from 'react';
const ChildComponent = forwardRef((props, ref) => {
// 这里通过 useImperativeHandle 关联 ref 和 someMethod 方法
useImperativeHandle(ref, () => ({
someMethod: () => {
console.log('Method from ChildComponent');
},
}));
return <div>Child Component</div>;
});
const ParentComponent = () => {
const childRef = useRef();
// 点击按钮时,调用子组件的 someMethod 方法
const handleClick = () => {
childRef.current.someMethod();
};
return (
<div>
{/* 传递 ref 给子组件 */}
<ChildComponent ref={childRef} />
<button onClick={handleClick}>Call Child Method</button>
</div>
);
};
export default ParentComponent;
在这个例子中,我们在子组件使用 forwardRef 和 useImperativeHandle 创建一个可供父组件调用的方法。然后在父组件中,我们创建一个 ref,并将其传递给子组件。在父组件的 handleClick 方法中,我们调用了子组件的方法。
通过props的回调
父组件可以通过props传递一个回调函数给子组件,子组件内部调用这个函数时,可以将自身的方法或属性作为参数传递给父组件。
import React from 'react';
function ChildComponent({ getMethod }) {
const childMethod = () => {
console.log("Child method called!");
}
getMethod(childMethod);
return <div>Child</div>;
}
function ParentComponent() {
function callChildMethod(method) {
method();
}
return (
<div>
<ChildComponent getMethod={callChildMethod} />
</div>
);
}
通过全局(没人用过)
import React, { useEffect } from 'react';
function ChildComponent() {
useEffect(() => {
window.myMethod = () => {
console.log("Child method called!");
}
return () => {
window.myMethod = null;
}
}, []);
return <div>Child</div>;
}
function ParentComponent() {
function callChildMethod() {
if (typeof window.myMethod === 'function') {
window.myMethod();
}
}
return (
<div>
<button onClick={callChildMethod}>Call Child Method</button>
<ChildComponent />
</div>
);
}
直接使用原生document对象 操作文档流
import React, { useEffect } from "react";
function ChildComponent() {
return <div id="child">Child</div>;
}
function ParentComponent() {
function changeChildText() {
const element = document.getElementById("child");
if (element) {
element.textContent = "Child Text Has Been Changed";
}
}
useEffect(() => {
changeChildText();
}, []);
return (
<div>
<ChildComponent />
</div>
);
}
在这个例子中,当ParentComponent渲染时,会调用 changeChildText 函数,查找id为"child"的元素并修改其文本。这样,即使此元素是在子组件中定义的,也可以被父组件修改。但是,这种方式只有当DOM已经渲染,并且id是唯一的时候才有效,而且不符合React的常规操作。
通常来说,向子组件传递状态和回调函数,然后让子组件自己更新是更好的方式。
转载自:https://juejin.cn/post/7418113695324536869