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(
<span>鼠标的横坐标{this.props.positionX}</span>
<span>鼠标的纵坐标{this.props.positionY}</span>
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(
<MousePoint
render={(state) => {
return(
<span>鼠标横坐标是{state.positionX}</span>
<span>鼠标纵坐标是{state.positionY}</span>
我们这里渲染一个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 (
this.props.render(this.state)
}
我们在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(
{this.props.children}
{this.props.render}
function App() {
return (
<div className="App">
<ChildComponent render={<p>This is a message</p>}>
<p>Hello World</p>
</ChildComponent>
}
这也是一种能够让我们封装代码的方式之一,不过一般用在样式的封装上。
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'