组件逻辑复用的三种方案
一、复用组件逻辑的方案
- HOC:高阶组件
- Render Props
- Hooks
1、通过高阶组件HOC复用组件逻辑
import React from 'react'
// 高阶组件
const withMouse = (Component) => {
class withMouseComponent extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 1. 透传所有 props 2. 增加 mouse 属性 */}
<Component {...this.props} mouse={this.state}/>
</div>
)
}
}
return withMouseComponent
}
const App = (props) => {
const a = props.a
const { x, y } = props.mouse // 接收 mouse 属性
return (
<div style={{ height: '500px' }}>
<h1>The mouse position is ({x}, {y})</h1>
<p>{a}</p>
</div>
)
}
export default withMouse(App) // 返回高阶函数
我的理解:
通过高阶组件来复用组件公共逻辑的做法,实际上就是一种装饰模式,即将所要使用的组件放到公共函数中装饰一番,返回一个具有公共逻辑的高阶组件,其实还是那个组件,只不过“趟了一趟浑水”(函数封装),粘带了一些“泥潭”(公共逻辑)中“泥”(具体公共逻辑)而已。
2、通过Render Props实现组件逻辑复用
import React from 'react'
import PropTypes from 'prop-types'
class Mouse extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
{this.props.render(this.state)}
</div>
)
}
}
Mouse.propTypes = {
render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
}
const App = (props) => (
<div style={{ height: '500px' }}>
<p>{props.a}</p>
<Mouse render={
/* render 是一个函数组件 */
({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>
}/>
</div>
)
/**
* 即,定义了 Mouse 组件,只有获取 x y 的能力。
* 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
*/
export default App
顾名思义Render Props方案,就是将render 作为了porps属性,来处理公共逻辑部分如何渲染。 将公共逻辑部分抽离成了一个单独功能的组件,然后在App组件中直接使用这个组件,但也只是使用该组件的逻辑部分,至于渲染的控制,肯定还需要有使用者App组件来控制,这个控制便是通过传入一个函数组件作为render属性来处理。
3、Hook来实现组件的逻辑复用
import { useState, useEffect } from 'react'
function useMousePosition() {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
useEffect(() => {
function mouseMoveHandler(event) {
setX(event.clientX)
setY(event.clientY)
}
// 绑定事件
document.body.addEventListener('mousemove', mouseMoveHandler)
// 解绑事件
return () => document.body.removeEventListener('mousemove', mouseMoveHandler)
}, [])
return [x, y]
}
export default useMousePosition
import React from 'react'
import useMousePosition from '../customHooks/useMousePosition'
function App() {
// 自定义组件的使用
const [x, y] = useMousePosition()
return <div style={{ height: '500px', backgroundColor: '#ccc' }}>
<p>The mouse position is {x} {y}</p>
</div>
}
export default App
react hook的这种处理,实际上就是把公共逻辑部分完全提取出去,通过传入参数,返回结果的形式,处理了公共逻辑。这其实也正是函数的思想,在不使用hook处理的情况下,开发中有些公共逻辑的处理,也会封装成工具函数。
在我看来,这种复用公共逻辑的方式,就是将工具函数Hook化。
二、3种复用组件逻辑方案的优劣对比
1、通过高阶组件HOC复用组件逻辑
-
组件进行了嵌套,增加了组件层级,不易渲染,开发过程中不易调试
-
HOC会劫持props,容易出现疏漏 2、通过Render Props实现组件逻辑复用
-
将render作为props来控制公共逻辑的渲染,这种实现思路与日常开发流程有点相悖,需要掰扯掰扯才能靠到这种处理思路上来,有一定学习成本。
-
render只能是纯函数形式,默认情况下纯函数的能力有限。 3、Hook来实现组件的逻辑复用
-
不会产生组件嵌套
-
变量作用域明确
-
实现思路完全贴合工具函数封装的方式,容易理解,便于实现