先看个栗子吧
比如,有这么个功能,页面中有一块区域,鼠标在区域中移动时,里面有一个圆需要跟随鼠标移动;在下面另一块区域,鼠标在区域中移动时要显示鼠标相对于这个区域的坐标点。那么我们在之前的实现方案应该是这样的:
import React, { createRef, PureComponent } from 'react'
// 可移动球
export default class MoveBall extends PureComponent {
state = {
x: 0,
y: 0
}
areaRef = createRef()
handleMove = e => {
const {left, top} = this.areaRef.current.getBoundingClientRect()
const x = ~~(e.clientX - left)
const y = ~~(e.clientY - top)
this.setState({ x, y })
}
render() {
return (
<div
ref={this.areaRef}
className="area"
onMouseMove={this.handleMove}
>
<div className="circle" style={{
left: this.state.x - 40,
top: this.state.y - 40
}} />
</div>
)
}
}
// 显示鼠标位置
export default class ShowPoint extends PureComponent {
state = {
x: 0,
y: 0
}
areaRef = createRef()
handleMove = e => {
const {left, top} = this.areaRef.current.getBoundingClientRect()
const x = ~~(e.clientX - left)
const y = ~~(e.clientY - top)
this.setState({ x, y })
}
render() {
return (
<div
ref={this.areaRef}
className="area"
onMouseMove={this.handleMove}
>
<div>
mouse left: {this.state.x},
mouse top: {this.state.y}
</div>
</div>
)
}
}
如上代码所示,某些组件的各种功能及其处理逻辑完全相同,只是显示的页面内容不一样。所以这种情况一般使用以下两种处理方案 (来解决重复代码的问题):
1. 使用 HOC
将功能逻辑部分进行抽离为一个高阶组件,大致实现如下:
import React, {createRef, PureComponent} from 'react'
export default function withMove(Comp) {
return class extends PureComponent {
state = {
x: 0,
y: 0
}
areaRef = createRef()
handleMove = e => {
// 同上面代码...
}
render() {
return (
<div
ref={this.areaRef}
className="area"
onMouseMove={this.handleMove}
>
<Comp {...this.props} {...this.state} />
</div>
)
}
}
}
// 外面使用
import withMove from './hoc/withMove'
function RenderPoint(props) {
const {x, y} = props
return <p>Left: {x}, Top: {y}</p>
}
const ShowPoint = withMove(RenderPoint)
export default ShowPoint
2. 使用 render props
就像上一篇 React 学习之 Context (旧与新) 所写的 ctx.Consumer 的用法一样,我可以将鼠标移动的组件抽离出去,将传递的子组件(props.children)或者任意一个属性约定为一个函数,调用函数并把参数传递过去即可。这样,这个鼠标移动的部分就可以复用了,至于具体渲染的内容,只需要在使用这个组件时用函数返回即可,示例如下:
import React, { createRef, PureComponent } from 'react'
export default class MouseMove extends PureComponent {
state = {
x: 0,
y: 0
}
areaRef = createRef()
handleMove = e => {
// 同上面代码...
}
render() {
return (
<div
ref={this.areaRef}
className="area"
onMouseMove={this.handleMove}
>
{this.props.render(this.state)}
</div>
)
}
}
// 外面使用时:
import React, { memo } from 'react'
import MouseMove from './MouseMove'
const renderPoint = pt => <p>Left: {pt.x}, Top: {pt.y}</p>
function ShowPoint() {
return <MouseMove render={renderPoint} />
}
export default memo(ShowPoint)
注意点
- 某个组件需要某个属性,该属性是一个函数,函数的返回值用于界面渲染
- 函数的参数作为界面渲染需要的数据传递出去
- 使用纯组件,避免每次都传递一个新的函数,导致不必要的重新渲染
- 通常使用属性
render用来传递 (但实现方案不过就是通过属性调用函数,所以属性名随你定)