react 组件公共逻辑抽离方式,主要为:
- mixin,早期实现方式,现已被
react
废弃,这里不再介绍 - HOC 高阶组件
- Render Props
HOC 高阶组件
HOC(Higher-order component)
,它不是react
的API,也不是一种功能,而是一种模式(也可以说是类工厂模式的应用),它是react
组件进阶的一种写法,本质上是一个方法,接收一个组件,然后对这个组件进行加工,再输出加工后的组件。
- 使用场景
当多个组件都要使用某部分公共逻辑时,可以将这某分公共逻辑抽离出来,作为HOC
加工的部分,比如对props
属性的修改或新增等操作,然后再传递给各个组件
HOC组件可以有两种实现方式:正向代理的HOC
和 反向继承的Render props HOC
正向代理的HOC组件
- 创建HOC步骤:
- 创建一个方法,参数是要加工的组件
- 在方法内部创建一个类组件,写公共逻辑内容
- 在类组件的
render
方法中,渲染被加工的组件,并将组件本身的props
,和加工后的状态,以属性方式传给被加工的组件 - 最后,返回这个类组件
- 使用方式:将原来使用
MyComponent
的组件替换成HOCComponent(MyComponent)
组件,使用即可
// widthMouseHOC.js
// 定义一个HOC组件:公共逻辑部分,是获取当前鼠标坐标信息
import React, {Component} from 'react'
const withMouse= (Comp) => {
class withMouseHOC extends Component {
constructor(props) {
super(props)
this.state = {
x: 0,
y: 0
}
}
getMousePosition = (e) => {
this.setState({
x: e.clientX,
y: e.clientY,
})
}
render() {
return <div style={{height: '500px',background: 'yellow'}} onMouseMove={this.getMousePosition}>
<p>这是经过HOC的组件</p>
{/* this.props是透传props, this.state是将公共逻辑部分的状态传进去 */}
<Comp {...this.props} mouse={this.state}/>
</div>
}
}
return withMouseHOC
}
export default withMouse
// UseMouseHOC.js
// 使用了HOC组件
import widthMouseHOC from './widthMouseHOC'
function UseMouseHOC(props) {
const {x, y} = props.mouse
return (
<div style={{background: 'green'}}>
<p>这是一个需要使用鼠标位置的组件</p>
<p>从HOC中获取鼠标位置为:(x: {x}, y: {y})</p>
<p>从容器组件中获取的其它信息:{props.name}</p>
</div>
)
}
// 返回一个高阶组件
export default widthMouseHOC(UseMouseHOC)
// 使用套用了HOC组件的业务组件
import React, {Component} from 'react'
import UseMouseHOC1 from './UseMouseHOC1'
export default class HOCDemo extends Component {
constructor(props) {
super(props)
this.state = {
msg: '这是一个最外层的容器组件',
}
}
render() {
return <div>
<p>{this.state.msg}</p>
<UseMouseHOC1 name={'hahah'}/>
</div>
}
}
效果如下:
反向继承的Render Props
HOC组件,本质上是一个方法,对组件MyComponent
进行加工后输出加工后的组件HOCComponent(MyComponent)
,这个加工过程会把加工的属性传给要用到的组件;
Render Props呢,就跟HOC组件相比,它依然是把加工过程抽离出来,但不是在HOC组件内部渲染被加工的组件,而在将加工的状态通过this.props.render
方式来反向返回给被加工的组件继承使用
下面还是以例子来说明:
- 定义一个反向继承的组件,这个组件是用来抽离公共逻辑部分,最后通过
this.props.render
来将状态返回出去,给其他组件继承:
// Mouse.js
// 反向继承的HOC组件:公共逻辑部分,是获取当前鼠标坐标信息
import React, { Component } from 'react'
class Mouse extends Component {
constructor(props) {
super(props)
this.state = {
x: 0,
y: 0
}
}
getMousePosition = (e) => {
this.setState({
x: e.clientX,
y: e.clientY,
})
}
render() {
return <div style={{ height: '500px', background: 'yellow' }} onMouseMove={this.getMousePosition}>
<p>这是经过反向继承的HOC的组件</p>
{/* 这里改成,由this.props.render将状态反向传递出去 */}
{this.props.render(this.state)}
</div>
}
}
export default Mouse
- 其他组件是在
render
方法中渲染公共组件,并在这个公共组件上定义render
方法来继承公共组件的状态
// UseMouseHOC.js
// 使用方式,是要加工的组件,包裹公共抽离部分组件
import Mouse from './Mouse'
function UseMouseHOC(props) {
return (
<div style={{background: 'green'}}>
<p>这里是从容器组件中拿到的属性{props.name}</p>
<Mouse render={
(({x, y}) => <p>从HOC组件中,继承的状态-鼠标位置为:({x}, {y})</p>)
}/>
</div>
)
}
export default UseMouseHOC
- 在容器组件中使用,混合公共逻辑的组件
// 使用套用了HOC组件的容器组件
import React, {Component} from 'react'
import UseMouseHOC from './UseMouseHOC'
export default class HOCDemo extends Component {
constructor(props) {
super(props)
this.state = {
msg: '这是一个最外层的容器组件',
}
}
render() {
return <div>
<p>{this.state.msg}</p>
<UseMouseHOC name={'hahah'}/>
</div>
}
}
效果如下:
两种实现方式的对比
- 正向HOC:模式简单,容易理解,但会增加组件层级,也就是我们上面说的类组件,所以会增加属性透传成本
- Render Props:代码会相对简单,但比较难理解
两个方式都是可以实现HOC的,在使用的时候选择自己熟悉的就可以了