抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

组件进阶

高阶组件

前面提到的组件,都是将组件变成 UI, 而这里将会提到令一种类型的组件:高阶组件(HOC)。

高阶组件就是传入组件,返回组件的函数:

1
const EnhancedComponent = higherOrderComponent(WrappedComponent);

高阶组件可以做很多事情:

  • 抽取重复代码
  • 条件渲染
  • 捕获拦截生命周期

例如,我们可以使用高阶组件来监控 props 的更新:[1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function withLogProps(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;
}

通常,高级组件的命名规范是 withXxxx

动态组件

有时候,开发者希望在运行时来确定组件的类型,例如:

1
2
3
const components = {'eat': someComponent1, 'drink': someComponent2};
const type = 'eat';
const el = <components[type] />;

但这种写法是不允许的。因为React 只支持

  • 大写开头的变量
  • 带点语法的组件: MyComponents.DatePicker

如果需要运行时确定,则可以再定义一个大写开头的变量:

1
2
3
4
const components = {'eat': someComponent1, 'drink': someComponent2};
const type = 'eat';
const Com = components[type]
const el = <Com />;

如果你渲染的不是自定义组件,而是 html 元素,则可以只传一个字符串;

1
2
const Com = 'a'
const el = <Com>123456</Com>; //渲染成 `a`

再强调一下,变量名一定要首字母大写,否则 React 会将它解析成 html 元素。

引用

介绍

有时候,React 提供的常用操作可能并不能完成所有事情。例如:

  • audiovideo 的操作
  • canvas 的绘图
  • 一些原生的第三方库

这时候你可能需要引用原始的 DOM了。

创建引用

通常使用 React.createRef 来创建一个引用:

1
2
3
4
5
6
7
8
9
class MyCanvas extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <canvas ref={this.myRef} />;
}
}

除了使用 React.createRef,还可以使用回调函数来创建引用:

1
2
3
4
5
6
7
8
9
10
11
class MyCanvas extends React.Component {
constructor(props) {
super(props);
}
render() {
let setRef = element => {
this.myRef = element;
};
return <canvas ref={setRef} />;
}
}

使用回调函数得到的引用就是组件实例或者 DOM 对象,无需通过 .current 来访问。

1
this.myRef.getContext("2d");

访问引用

ref 被传递给 render 中的元素时,对该节点的引用可以在 refcurrent 属性中被访问。[2]

1
const node = this.myRef.current;

ref 的值根据节点的类型而有所不同:[2:1]

  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
  • ref 属性用于自定义的类组件时,ref 对象接收组件的挂载实例作为其 current 属性。
  • 不能在函数组件上使用 ref 属性,因为他们没有实例。

下面的例子,我们创建了一个引用,来初始化一个 canvas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Draw extends React.Component {
constructor(props) {
super(props);
this.state = { value: 0 };
this.item = React.createRef();

// 防止 this 指向问题
this.draw = this.draw.bind(this);
}

draw() {
var canvas = this.item.current;
var context = canvas.getContext("2d");

// 绘制一个加载进度条
var sin = Math.sin(Math.PI / 6);
var cos = Math.cos(Math.PI / 6);
context.translate(100, 100);
var c = 0;
for (var i = 0; i <= 12; i++) {
c = Math.floor((255 / 12) * i);
context.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
context.fillRect(0, 0, 100, 10);
context.transform(cos, sin, -sin, cos, 0, 0);
}
}

render() {
return (
<div>
<button onClick={this.draw}>绘图</button>
<canvas height="200px" ref={this.item} />
</div>
);
}
}

转发引用

有时候,我希望通过 ref 属性获取子组件的某个 DOM 元素,例如上面 Draw 组件,我希望获取里面 canvasDOM

这时候,我们就需要使用 引用转发 了。

我们通常使用 React.forwardRef 来转发引用。

先来看看函数组件。

1
2
3
4
5
6
const Draw = React.forwardRef((props, ref) =>{
return <canvas height="200px" ref={ref} />
})

const ref = React.createRef();
<Draw ref={ref} />;

这样,通过 ref 便可以直接拿到子组件中 canvasDOM对象了。

但有人可能有疑问:不是不能在函数组件上使用 ref 属性嘛?

实际上,引用转发将组件的引用转发给了组件内的一个元素,这样访问函数组件的 ref 就成了访问组件内元素的 ref 了。

高阶组件的转发

对一个高阶组件使用 ref 有时候会出现问题,我们来看一个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

// 定义高阶组件
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;
}

// 使用高阶组件
let setRef = element => {
console.log(element)
};

const LoggedLink = logProps('a'); // 组件对象为字符串时,表示的是 html 元素

const el = (<LoggedLink ref="setRef" href="https://www.baidu.com">链接</LoggedLink>)

我们运行这个案例,发现浏览器输出的并不是 a 链接的引用,而是高级组件的引用:

这可能并不是我们想要的。我们希望高阶组件是透明的。

所以,我们可以使用转发技术来将高阶组件的 ref 进行转发。

需要注意一点: ref 不是 prop 属性。就像 key 一样,其被 React 进行了特殊处理。如果你对 HOC 添加 ref ,该 ref 将引用最外层的容器组件,而不是被包裹的组件。[1:1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function 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;

// 将自定义的 prop 属性 `forwardedRef` 定义为 ref
return <Component ref={forwardedRef} {...rest} />;
}
}

// 将 ref 转发到 `forwardedRef`
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}

  1. Refs 转发 ↩︎ ↩︎

  2. Refs & Dom ↩︎ ↩︎

评论