对比分析组件逻辑复用的三种方案

1,240 阅读3分钟

组件逻辑复用的三种方案

一、复用组件逻辑的方案

  • 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来实现组件的逻辑复用

  • 不会产生组件嵌套

  • 变量作用域明确

  • 实现思路完全贴合工具函数封装的方式,容易理解,便于实现