操作
一、跨层级获取
需求: 有组件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>
);
}
import { forwardRef,useImperativeHandle } from 'react';
type RefType = {
a: string;
b: string;
}
type PropsType = {
}
const MyInput = function MyInput(props: PropsType, ref: React.ForwardedRef<RefType>) {
useImperativeHandle(
ref,
() => {
const handleRefs = {
a: '',
b: ''
};
return handleRefs;
},
[]
);
return (
<label>
{label}
</label>
);
}
const forWardRefMyInput = forwardRef<RefType,PropsType>(MyInput);
export default forWardRefMyInput;
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>
);
}
import { forwardRef,useImperativeHandle } from 'react';
type RefType = {
a: string;
b: string;
}
type PropsType = {
}
const forWardRefMyInput = forwardRef<RefType,PropsType>(function MyInput(props: PropsType, ref: React.ForwardedRef<RefType>) {
useImperativeHandle(
ref,
() => {
const handleRefs = {
a: '',
b: ''
};
return handleRefs;
},
[]
);
return (
<label>
{label}
</label>
);
});
export default forWardRefMyInput;
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>
);
}
import { forwardRef,useImperativeHandle } from 'react';
type RefType = {
a: string;
b: string;
}
type PropsType = {
}
function MyInput(props: PropsType & React.ForwardedRef<RefType>) {
const { ref } = props;
useImperativeHandle(
ref,
() => {
const handleRefs = {
a: '',
b: ''
};
return handleRefs;
},
[]
);
return (
<label>
{label}
</label>
);
}
const forWardRefMyInput = forwardRef<RefType,PropsType>((props: PropsType,ref: React.ForwardedRef<RefType>)=>{
return <MyInput {...props} ref={MyInput} />
});
export default forWardRefMyInput;
六、函数组件缓存数据
背景: 函数组件每一次 render
,函数上下文会重新执行。那么有一种情况就是,在执行一些事件方法改变数据或者保存新数据的时候,有没有必要更新视图,有没有必要把数据放到 state
中。如果视图层更新不依赖想要改变的数据,那么 state
改变带来的更新效果就是多余的。这时候更新无疑是一种性能上的浪费。
描述: 基于以上背景,useRef
就派上用场了。useRef
可以创建出一个 ref
原始对象,只要组件没有销毁,ref
对象就一直存在,那么完全可以把一些不依赖于视图更新的数据储存到 ref
对象中。这样做的好处有两个:
-
第一个能够直接修改数据,不会造成函数组件冗余的更新作用
-
第二个
useRef
保存数据,如果有useEffect
,useMemo
引用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
的结果,而且不会造成组件的更新,从而避免了无用的更新