解决横切关注点问题
什么是关注点?
关注点指的是,系统的某个功能模块。
什么是横切关注点?
横切关注点指的是,某个功能模块横跨系统中的大多业务模块,即在大多业务模块中都存在这个功能模块。
「日志模块」就是一个典型案例,因为它通常存在于各个业务模块,即横切所有需要日志模块的业务模块,因此日志模块就成为了横切这些业务模块的关注点,也就是横切关注点。
React 官方推荐解决横切关注点问题的方案有两个:
- HOC
- Higher Order Component,即高阶组件,以组件作为参数,返回一个新组件。
- Render Props
案例
我们通过一个案例来看这两个方案是如何解决横切关注点问题的。
我们要实现两个功能,分别是:
- 记录鼠标在容器中的位置
- 实现方块在容器中跟随鼠标移动
效果图如下:

可以看到,这两个功能,都涉及到了鼠标位置,那么获取鼠标位置功能就是横跨这两个模块的关注点。
下面是不使用 HOC 和 Render Props 的情况下,代码的实现逻辑:
jsx
// MouseTracker.js
import React, { Component } from "react";
import "../style.css";
export default class MouseTracker extends Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
divRef = React.createRef();
handleMouseMove = (event) => {
const { left, top } = this.divRef.current.getBoundingClientRect();
this.setState({
x: event.clientX - left,
y: event.clientY - top,
});
};
render() {
return (
<div
ref={this.divRef}
className="container"
onMouseMove={this.handleMouseMove}
>
<p>
鼠标相对容器的位置是 ({this.state.x}, {this.state.y})
</p>
</div>
);
}
}jsx
// MovableDiv.js
import React, { Component } from "react";
import "../style.css";
export default class MovableDiv extends Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
divRef = React.createRef();
handleMouseMove = (event) => {
const { left, top } = this.divRef.current.getBoundingClientRect();
this.setState({
x: event.clientX - left,
y: event.clientY - top,
});
};
getDivPosition = () => {
let divX = this.state.x - 50;
let divY = this.state.y - 50;
if (divX < 0) {
divX = 0;
} else if (divX > 300) {
divX = 300;
}
if (divY < 0) {
divY = 0;
} else if (divY > 400) {
divY = 400;
}
return { divX, divY };
};
render() {
const { divX, divY } = this.getDivPosition();
return (
<div
ref={this.divRef}
className="container"
onMouseMove={this.handleMouseMove}
>
<div
style={{
width: "100px",
height: "100px",
background: "red",
position: "absolute",
left: divX,
top: divY,
}}
></div>
</div>
);
}
}css
/* style.css */
.App {
display: flex;
}
.container {
position: relative;
width: 400px;
height: 500px;
margin: 10px;
border: 1px solid;
}可以看到两者在实现上,有许多代码相同的情况,为了能够复用代码逻辑,我们可以分别通过 HOC 和 Render Props 来实现。
HOC
jsx
// withMouseListener.js
import React, { PureComponent } from "react";
import '../style.css'
export default function withMouseListener(Comp) {
return class MouseListener extends PureComponent {
state = {
x: 0,
y: 0,
};
divRef = React.createRef();
handleMouseMove = (event) => {
const { left, top } = this.divRef.current.getBoundingClientRect();
this.setState({
x: event.clientX - left,
y: event.clientY - top,
});
};
render() {
return (
<div
ref={this.divRef}
className="container"
onMouseMove={this.handleMouseMove}
>
<Comp {...this.props} x={this.state.x} y={this.state.y} />
</div>
);
}
};
}jsx
// TestHoc.js
import React, { Component } from "react";
import withMouseListener from "./HOC/withMouseListener";
function MouseTracker(props) {
return <p>鼠标相对容器的位置是 ({props.x}, {props.y})</p>
}
const getDivPosition = (mouse) => {
let divX = mouse.x - 50;
let divY = mouse.y - 50;
if (divX < 0) {
divX = 0;
} else if (divX > 300) {
divX = 300;
}
if (divY < 0) {
divY = 0;
} else if (divY > 400) {
divY = 400;
}
return { divX, divY };
};
function MovableDiv(props) {
const { divX, divY } = getDivPosition(props)
return <div style={{
width: "100px",
height: "100px",
background: "red",
position: "absolute",
left: divX,
top: divY,
}}></div>
}
const MouseTrackerWithMouseListener = withMouseListener(MouseTracker)
const MovableDivWithMouseListener = withMouseListener(MovableDiv)
export default class TestHoc extends Component {
render() {
return (
<>
<MouseTrackerWithMouseListener />
<MovableDivWithMouseListener />
</>
);
}
}Render Props
jsx
// MouseListener.js
import React, { Component } from "react";
import "../style.css";
export default class MouseListener extends Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
divRef = React.createRef();
handleMouseMove = (event) => {
const { left, top } = this.divRef.current.getBoundingClientRect();
this.setState({
x: event.clientX - left,
y: event.clientY - top,
});
};
render() {
return (
<div
ref={this.divRef}
className="container"
onMouseMove={this.handleMouseMove}
>
{this.props.render ? this.props.render(this.state) : "默认值"}
</div>
);
}
}jsx
// TestRenderProps.js
import React, { Component } from "react";
import MouseListener from "./RenderProps/MouseListener";
const MouseTracker = (mouse) => (
<p>
鼠标相对容器的位置是 ({mouse.x}, {mouse.y})
</p>
);
const getDivPosition = (mouse) => {
let divX = mouse.x - 50;
let divY = mouse.y - 50;
if (divX < 0) {
divX = 0;
} else if (divX > 300) {
divX = 300;
}
if (divY < 0) {
divY = 0;
} else if (divY > 400) {
divY = 400;
}
return { divX, divY };
};
const MovableDiv = (mouse) => {
const { divX, divY } = getDivPosition(mouse);
return (
<div
style={{
width: "100px",
height: "100px",
background: "red",
position: "absolute",
left: divX,
top: divY,
}}
></div>
);
};
export default class TestRenderProps extends Component {
render() {
return (
<>
<MouseListener render={MouseTracker} />
<MouseListener render={MovableDiv} />
</>
);
}
}