跳到主要内容

操作

2023年05月10日
柏拉文
越努力,越幸运

一、跨层级获取


需求: 有组件Parent,Parent组件中嵌套Child组件,Child嵌套组件GrandChild组件,这时,Parent组件想要获取GrandChild中的某个元素,实现如下

方案: 通过 React.forwardRef 实现跨层级获取

实现:

import React, { useRef, forwardRef, useEffect } from "react";
import ReactDOM from "react-dom";

function GrandChild(props) {
const { forwardRef } = props;
return (
<div>
<div ref={forwardRef}>GrandChild 组件中的关键元素,Parent 组件想要获取</div>
</div>
);
}

function Child(props) {
const { forwardRef } = props;
return (
<div>
<h3>孩子</h3>
<GrandChild forwardRef={refData} />
</div>
);
}
const NewChild = forwardRef((props, ref) => <Child forwardRef={ref} {...props} />);

function Parent() {
const parent = useRef(null);
useEffect(() => {
console.log(parent);
});
return (
<div>
<h3>父亲</h3>
<NewChild ref={parent} />
</div>
);
}

ReactDOM.render(<Parent />, document.getElementById("root"));

二、合并转发 Ref


需求: 有组件Parent,Parent组件中嵌套Child组件,Child组件中嵌套GrandChild组件,Parent组件想要获取Child组件中的多个元素,实现如下

方案: 通过 React.forwardRef 实现合并转发 Ref

实现

import React, { useRef, forwardRef, useEffect } from "react";
import ReactDOM from "react-dom";

function GrandChild(props) {
const { forwardRef } = props;
return (
<div ref={forwardRef}>
<div>孙子</div>
</div>
);
}

function Child(props) {
const { forwardRef } = props;
const btn = useRef(null);
const grandChild = useRef(null);
useEffect(() => {
forwardRef.current = {
btn,
grandChild,
};
});
return (
<div>
<h3>孩子</h3>
<button ref={btn}>按钮</button>
<GrandChild forwardRef={grandChild} />
</div>
);
}
const NewChild = forwardRef((props, ref) => <Child forwardRef={ref} {...props} />);

function Parent() {
const ref = useRef(null);
useEffect(() => {
console.log(ref);
});
return (
<div>
<h3>父亲</h3>
<NewChild ref={ref} />
</div>
);
}

ReactDOM.render(<Parent />, document.getElementById("root"));

三、高阶组件转发


背景: 如果通过高阶组件包裹一个原始类组件,就会产生一个问题,如果高阶组件 HOC 没有处理 ref ,那么由于高阶组件本身会返回一个新组件,所以当使用 HOC 包装后组件的时候,标记的 ref 会指向 HOC 返回的组件,而并不是 HOC 包裹的原始类组件

解决: 为了解决这个问题,forwardRef 可以对 HOC 做一层处理

实现:

import React, { useRef, forwardRef, useEffect } from "react";
import ReactDOM from "react-dom";

function HOC(Component) {
class Wrap extends React.Component {
render() {
const { forwardRef, ...otherProps } = this.props;
return <Component ref={forwardRef} {...otherProps} />;
}
}
return React.forwardRef((props, ref) => (
<Wrap forwardRef={ref} {...props}></Wrap>
));
}

class Index extends React.Component {
render() {
return <div>Index 组件</div>;
}
}

const NewIndex = HOC(Index);

function App() {
const ref = useRef(null);
useEffect(() => {
console.log(ref);
});
return (
<div>
<NewIndex ref={ref} />
</div>
);
}

ReactDOM.render(<App />, document.getElementById("root"));

四、类组件 Ref 通信


描述: 类组件有自己的实例,因此可以通过Ref直接获取组件中的上下文

实现

import React, { useRef } from "react";
import ReactDOM from "react-dom";

class Child extends React.Component {
say(content) {
console.log(content);
}
render() {
return (
<div>
<h3>Child 组件</h3>
</div>
);
}
}

function App() {
const childRef = useRef(null);
const handleSend = () => {
childRef && childRef.current.say("哈哈");
};
return (
<div>
<h3>App 组件</h3>
<button onClick={handleSend}>通信</button>
<Child ref={childRef}></Child>
</div>
);
}

ReactDOM.render(<App />, document.getElementById("root"));

五、函数组件 forwardRef + useImperativeHandle 通信


描述: 对于函数组件而言,自身没有实例。但是 React Hooks 提供了 useImperativeHandle , 可以完全让函数组件也能够流畅的使用 Ref 通信。

5.1 forwardRef(functionName)

import React, { useRef } from 'react';
import MyInput from './MyInput.js';

type RefType = {
a: string;
b: string;
}

export default function Form() {
const ref = useRef<RefType>(null);

function handleClick() {
ref.current.focus();
}

return (
<form>
<MyInput ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}

5.2 forwardRef(functionBody)

import React, { useRef } from 'react';
import MyInput from './MyInput.js';

type RefType = {
a: string;
b: string;
}

export default function Form() {
const ref = useRef<RefType>(null);

function handleClick() {
ref.current.focus();
}

return (
<form>
<MyInput ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}

5.3 forwardRef(()=>{ return <> })

import React, { useRef } from 'react';
import MyInput from './MyInput.js';

type RefType = {
a: string;
b: string;
}

export default function Form() {
const ref = useRef<RefType>(null);

function handleClick() {
ref.current.focus();
}

return (
<form>
<MyInput ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}

六、函数组件缓存数据


背景: 函数组件每一次 render,函数上下文会重新执行。那么有一种情况就是,在执行一些事件方法改变数据或者保存新数据的时候,有没有必要更新视图,有没有必要把数据放到 state 中。如果视图层更新不依赖想要改变的数据,那么 state 改变带来的更新效果就是多余的。这时候更新无疑是一种性能上的浪费。

描述: 基于以上背景,useRef 就派上用场了。useRef 可以创建出一个 ref 原始对象,只要组件没有销毁,ref 对象就一直存在,那么完全可以把一些不依赖于视图更新的数据储存到 ref 对象中。这样做的好处有两个:

  • 第一个能够直接修改数据,不会造成函数组件冗余的更新作用

  • 第二个 useRef 保存数据,如果有 useEffectuseMemo 引用 ref 对象中的数据,无须将 ref 对象添加成 dep 依赖项,因为 useRef 始终指向一个内存空间,所以这样一点好处是可以随时访问到变化后的值

实现

import React, { useRef } from "react";
import ReactDOM from "react-dom";

function App() {
const dom = useRef(null);
const scrollTop = useRef(0);

const handleScroll = () => {
scrollTop.current = dom.current.scrollTop;
console.log(scrollTop.current);
};


return (
<div
style={{ height: "200px", overflowY: "auto" }}
ref={dom}
onScroll={handleScroll}
>
<div style={{ height: "400px" }}></div>
</div>
);
}

ReactDOM.render(<App />, document.getElementById("root"));

如上所示, 通过useRef缓存记录 scrollTop 的结果,而且不会造成组件的更新,从而避免了无用的更新