阅读 2946

React组件封装技巧(HOC、Render Props、Hook)

引言:在React项目开发的过程中,怎么减少代码冗余,提供代码质量,加强代码的可维护性,都是我们经常要考虑的问题。接下来,我会用HOC、Render Props、Hook这三种方式,示范一些常用的组件封装的技巧

一、HOC(高阶组件)

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

可能之前没有详细了解过HOC的朋友,看到这个名词会比较陌生,以为自己没用过,其实,HOC早已在你的项目中广泛被用到,只不过是你没有留心注意而已。大家肯定写过以下代码

import { connect } from 'react-redux'


const HocDemoComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(DemoComponent)


// react-redux connect函数就返回一个高阶组复制代码

从上面的代码可以看出,高阶组件其实就是参数为组件,返回值为新组件的函数。

举一个简单的例子,比如说,我们有很多组件都会用到鼠标当前的位置,常规操作是,我们在每一个组件中去注册获取鼠标位置的方法。这样的话,代码比较冗余,而且维护的时候也不方便。

我们用HOC的方式实现下。

MousePoint.js //一个简单的位置信息的展示

import React from 'react'
import mousePositionHoc from '../hoc/MousePosition'
class MousePoint extends React.Component{
  constructor(props){
    super(props)
  }
  render(){
    return(
      <div>
        <span>鼠标的横坐标{this.props.positionX}</span>
        <span>鼠标的纵坐标{this.props.positionY}</span>
      </div>
    )
  }
}
export default mousePositionHoc(MousePoint)复制代码

这是一个简单的组件,展示鼠标的横纵坐标的信息,props参数就是从我们的HOC封装函数中来。

MousePosition.js // HOC封装函数

import React from 'react'
export default (Component) => {
 return class WrappedComponent extends React.Component {
    constructor(props){
      super(props)
      this.state = {
        positionX: 0,
        positionY: 0
      }
    }
    componentDidMount() {
      document.addEventListener('mousemove', (e) => {
        this.setState({
          positionX: e.clientX,
          positionY: e.clientY
        })
      }) // 在这里我们更新鼠标的位置,并存储在state中去,然后通过props传递给被传入的组件
    }
    render(){
      return(
        <Component {...this.props} {...this.state}/>
        //props:这里返回的是WrappedComponent这个组件,所以本应该传递给Component组件的props,我们应该通过WrappedComponent传递下去
        //state: WrappedComponent可以操作自己的状态,我们可以将这些状态通过props的方式传递给Component组件
      )
    }
    
  }
}

复制代码

如果我们其他组件也想用到鼠标坐标的值的话,就不需要在componentDidMount中多次写相关的事件绑定了。只需要mousePosition(Component)就好。

以上就是一个高阶组件的实现方式。紧紧抓住一个概念”高阶组件其实就是参数为组件,返回值为新组件的函数“。

高阶组件可以用到很多的场景中去,打印日志、提取公共函数、调用公共api、渲染公共UI等等。

二、Render Props

React官方给出的定义是:Render Props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术

React Router中就用到了Render Props

<Route path="/home" render={() => <div>Home</div>} />复制代码

同样是鼠标位置信息这段逻辑,我们用Render Props实现方式如下:

import React from 'react'
import MousePoint from './MousePoint'
export default class MouseTracker extends React.Component{
  constructor(props){
    super(props)
  }
  render(){
    return(
      <div>
        <MousePoint
          render={(state) => {
            return(
              <div>
                <span>鼠标横坐标是{state.positionX}</span>
                <span>鼠标纵坐标是{state.positionY}</span>
              </div>
            )
          }}
        />
      </div>
    )
  }
}

复制代码

我们这里渲染一个MousePoint组件,这个组件接受一个render props,这个props不是简单的属性或者对象,而是一个函数。

import React from 'react'
export default class MousePoint extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      positionX: 0,
      positionY: 0
    };
  }
  componentDidMount() {
    document.addEventListener('mousemove', (e) => {
      this.setState({
        positionX: e.clientX,
        positionY: e.clientY
      })
    })
  }
  
  render() {
    return ( 
      <div>
         {
          this.props.render(this.state)
         } 
      </div>
    );
  }
}复制代码

我们在MousePoint组件中去执行这个render props,this.state为参数,函数拿到这个值后,成功渲染出鼠标的位置信息。

在React中,props可以传递任何对象,包括组件以及函数。通过这种自由组合的方式,我们能够实现非常功能。

说到这里,我们不得不提到this.props.children

import React from 'react'
export default class ChildComponent extends React.Component{
  constructor(props){
    super(props)
  }
  render(){
    return(
      <div>
        {this.props.children}
        {this.props.render}
      </div>
    )
  }
}


function App() {
  return (
    <div className="App">
        <ChildComponent render={<p>This is a message</p>}>
          <p>Hello World</p>
        </ChildComponent>
    </div>
  );
}复制代码

这也是一种能够让我们封装代码的方式之一,不过一般用在样式的封装上。

this.props.children 指的就是组件start tag 和 end tag中间包括的部分。我们也可以通过props的方式去传递组件,像上面代码中render这样的方式。这就比较类似Vue中slot。

三、Hook

最后我们使用Hook的方式再实现一次获取鼠标位置逻辑

官方定义:

Hook
是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

我们知道以往的函数组件只能当做展示组件,因为不能够使用React的生命周期和state,所以只能接受props,来渲染页面,react 16.8中引入了Hook这个概念,使函数式组件也具备class组件同样的特性。

我们先来自定义一个Hook

import React, { useState, useEffect } from 'react'
export default () => {
  const [positionX, setPositionX] = useState(0)
  const [positionY, setPositionY] = useState(0)
  const getMousePosition = (e) => {
    setPositionX(e.clientX)
    setPositionY(e.clientY)
  }
  useEffect(() => {
    document.addEventListener('mousemove', getMousePosition)
    return () => {
      document.removeEventListener('mousemove', getMousePosition)
    };
  });
  return {
    positionX: positionX,
    positionY: positionY
  }
}复制代码

import React, {
  useState,
  useEffect
} from 'react'
import useMousePosition from '../hooks/useMouse'




export default () => {
  const mousePosition = useMousePosition()
  return(
    <div>
      <span>鼠标的横坐标{mousePosition.positionX}</span>
      <span>鼠标的纵坐标{mousePosition.positionY}</span>
    </div>
  )
复制代码

从发展的角度来看,Hook无疑是将来业界通用的方式。书写逻辑清晰,复用方便,函数式组件,摒弃了class这种麻烦的实现方式。不过毕竟刚出来不久,实现大范围的普及,还需要一定的时间。所以,还不太了解Hook的朋友,也不要过于慌张,感兴趣可以去官网了解相关知识,我这里只是做一个简单的实例,之后还会出一些Hook相关的文章,供大家详细探讨。

以上就是React中常用的组件封装技巧。掌握了这些,不知道有没有激起你重构代码的冲动呢?


文章分类
阅读
文章标签