DOM组件使用forwardRef
通常情况下,我们想获取一个组件或则 DOM 元素通过 Ref 就可以实现,但是某些时候我们需要在子父级组建中传递使用实例,react在16.3版本之后React提供createRef
的同时,也提供了Forwarding Refs
来满足这个需求,特别是开发一些重复使用的组建库时尤其好用。比如下面的例子:
1 | function MyButton(props) { |
上面的代码中 MyButton 组件渲染了一个 DOM 元素。对于使用者而言,React 隐藏了将代码渲染成页面元素的过程,当其他组件使用MyButton时,并没有任何直接的方法来获取MyButton中的 button 元素,这样的设计方法有利于组件的分片管理,降低耦合。
但是像 MyButton 这样的组件,其实仅仅是对基本的 HTML 元素进行了简单的封装。有时候,实现某些效果需要直接操作DOM,比如focus、selection 和 animations 效果。
下面的例子将Forwarding Refs添加到MyButton组件中,以实现实例传递的效果。
1 | const MyButton = React.forwardRef((props, ref) => ( |
这个时候,ref可以直接操作 button 元素。其实执行过程非常简单,也就下面5步:
- 通过React.createRef()方法创建一个ref实例。
- 将其作为一个ref属性参数传递给MyButton组件。
- 使用React.forwardRef方法来创建一个组件,并将ref作为第二个参数传递。
- 将ref参数以ref属性的方式传递给 button 元素。
- 在渲染之后,可以使用
ref.current
来获取 button 元素的实例。
需要注意的是只有使用React.forwardRef来创建一个组件时,第二个ref参数才会存在。其他情况下组件并不会接收到ref
参数。Forwarding Refs
特性并不仅仅局限于用在HTML DOM元素上,这种方式也实用于组件之间传递Ref。
在高阶组件中使用Forwarding Refs
高阶组件(HOC)一般用来增强一般组件。一般组件被包装之后对于使用者来说并不清楚其是否是被包装过,此时使用 Ref 得到的是最外层高阶组件的实例。因此Forwarding Refs
特性对于高阶组件来说更有价值。
下面是一个高阶组件记录日志的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return LogProps;
}
logProps
组件用于在每次数据更新前后记录props
中的数据。我们用其包装前面的MyButton
组件。1
2
3
4
5
6
7
8
9
10
11class MyButton extends React.Component {
focus() {
// ...
}
render() {
//
}
}
export default logProps(MyButton);
此时通过import
并使用Refs实际上得到的是LogProps
的实例:1
2
3
4
5
6
7
8
9
import FancyButton from './FancyButton';
const ref = React.createRef();
<MyButton
label="Click Me"
handleClick={handleClick}
ref={ref}
/>;
我们使用Forwarding Refs
对高阶组件进行简单的改造即可解决这个问题:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// 通过forwardedRef参数传递ref的值
return <Component ref={forwardedRef} {...rest} />;
}
}
//然后使用 React.forwardRef 来包装创建 LogProps组件的实例
//注意这里使用 forwardedRef 来传递 父组件的 ref
//
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}
开发调试组件名称显示
如果我们不进行任何调整,下面的代码在调试工具中输出的组件名称为ForwardRef(MyComonent)
:1
2
3
4
5const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
);
可以通过displayName来设定想要现实的名字:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function logProps(Component) {
class LogProps extends React.Component {
// ...
}
//先定义返回的高阶组件方法
function forwardRef(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
//然后设定这个组件的名称
const name = Component.displayName || Component.name;
forwardRef.displayName = `logProps(${name})`;
//构建组件
return React.forwardRef(forwardRef);
}