“混合双打”之如何在 Class Components 中使用 React Hooks

这是第 78 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注咱们吧~ 本文首发于政采云前端博客: “混合双打”之如何在 Class Components 中使用 React Hooks

前情提要

React 在 v16.8.0 版本中推出了 Hook,做为纯函数组件的加强,给函数组件带来了状态、上下文等等;以前一篇关于 React Hooks 的文章介绍了如何使用一些官方钩子和如何自建钩子,若是想要了解这些内容的同窗能够点击这里javascript

本文不会再介绍上文中已提到的部分钩子的基础使用,而是主要着眼解决一些实际开发中的场景。html

现状

Class Component 内部复杂的生命周期函数使得咱们组件内部的 componentDidMount 愈来愈复杂和臃肿,独立组件动辄上千行代码;组件嵌套层级愈来愈深,组件之间的状态复用也变得很是困难。前端

Hook 无疑是可选的,他不会对现有项目形成任何冲击和破坏,社区对于它的优点也有过不少讨论;不过目前官方也没有计划移除 Class,而是推荐渐进式的去使用 Hook,在一些新增的组件中优先选用 Hook。那么咱们想要在原有以 Class Component 为主的项目中开始使用 Hook,与原有的 Class Component 必然会产生交互,是否是须要将这些 Class Component 重写为 Hook 呢?java

将部分复杂的 Class Component 逐步重写为 Hook 应该排在项目迭代的中长期计划中,若是想要在一个迭代中进行大量改造,带来的巨大成本和反作用也是没法估量的。react

那么短时间内咱们就绕不开 Hook 与 Class 组件的混合使用。数组

解决方案

先简单介绍一下两种组件的基本写法:antd

Class Components:类组件的写法dom

export default class ShowHook extends Component {
    return (
    <h1>Hello Hook!</h1>
    );
}

Function Components:Hook 组件的写法函数

function ShowHook (props){
    return (
    <h1>Hello Hook!</h1>
    );
}

混合使用就难以免的须要进行通讯和参数传递,下面我用一个简单的处理模块显示隐藏的功能组件 ShowHook 做为一个例子,介绍三种是比较常见混合使用的方式,先来看一下效果:post

1.Render props

Render props 中来自父组件的 props children 是一个 Function,咱们能够将子组件的内部变量经过函数传递至父组件,达到通讯的目的。

// 子组件 SayHello.js
import React, { useState } from 'react';
function sayHello({ children }) {
  const [visible, changeVisible] = useState(false);
   const jsx = visible && (
    <h1 onClick={() => changeVisible(false)}> Hello Hook! </h1>
  );
  return children({ changeVisible, jsx });
}
export default sayHello;

父组件获取到 changeVisible 方法以后就能方便的控制 visible 的状态。

// 父组件 ShowHook.js
import React, { Component, Fragment } from 'react';
import SayHello from '../components/SayHello';
export default class ShowHook extends Component {
  render() {
    return (
      <SayHello>
        {({ changeVisible, jsx }) => {
          return (
            <React.Fragment>
              <button onClick={() => changeVisible(true)}>
                showChild
              </button>
              {jsx}
            </React.Fragment>
          );
        }}
      </SayHello>
    );
  }
}

props.children 经常使用的类型是字符串、对象甚至数组;但其实咱们也能够传入一个函数,只要最终能返回出DOM 树便可;Render props 是将 Render 部分抽离出来做为函数传入子组件;它主要的做用是将 state 部分抽成组件,实现 state 的复用。

// 封装子组件
function Mouse (props) {
  const [position, setPosition] = useState({x: 0,y: 0});
  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY })
  }
  return (
    <div onMouseMove={handleMouseMove}>
        {this.props.children(position)}
    </div>
  )
}
// 使用场景 1:图片位置跟随鼠标
class Cat1 extends React.Component {
  render() {
    return (
      <Mouse>
        {(position) => 
          <img src="/cat.jpg" 
            style={{ position: 'absolute', left: position.x, top: position.y }} 
          />
        }
      </Mouse>
    )
  }
}
// 使用场景 2:页面展现鼠标坐标
class Cat2 extends React.Component {
  render() {
    return (
      <Mouse>
        {(position) => 
          <h1>x: {position.x} y: {position.y}</h1>
        }
      </Mouse>
    )
  }
}

上面使用了 React 官方文档中的例子进行改写,具体效果以下:
场景 1:

场景 2:

2.使用 HOC

HOC (Higher-Order Components) 是另外一种提升代码复用率的常见技巧,它接收一个组件做为参数,最终返回出一个新的组件。

下面的方法使得 button 控制任意组件显示隐藏的功能被封装为高阶组件,得以复用,而且 setVisible 方法也能被传递到 Class Component 中。

// 高阶组件 SayHello.js
import React, { useState, Fragment } from 'react';
const sayHello = (Component) => {
  return (props) => {
    const [visible, setVisible] = useState(false);
    return (
      <React.Fragment>
        <button onClick={() => setVisible(true)}>
          showChild
        </button>
        {visible && <Component changeVisible={setVisible} visible={visible} />}
      </React.Fragment>
    );
  };
};
export default sayHello;

在外部 Class Component 中咱们能够定制受内部显示/隐藏控制的组件,而且使用高阶组件中向外传递的 props 。

// ShowHook.js
import React, { Component } from 'react';
import SayHello from '../components/SayHello';
class ShowHook extends Component {
render() {
  const { changeVisible } = this.props;
  return (
    <h1 onClick={() => changeVisible(false)}> Hello Hook! </h1>
  );
}
}
export default SayHello(ShowHook);

HOC 在实际使用中是将一些反作用函数、公用方法做为组件抽取出来,从而提高复用率;咱们能够把父组件的
render 部分改成一个弹窗,或任意内容使得子组件获得复用,例如:

// 复用高阶组件 SayHello
import React, { Component } from 'react';
import SayHello from '../components/SayHello';
import { Modal } from 'antd';
class ShowModal extends Component {
  render() {
    const { changeVisible, visible } = this.props;
    return (
      <Modal
          title="Basic Modal"
          visible={visible}
        onOk={() => changeVisible(false)}
          onCancel={() => changeVisible(false)}
      >
          <p>Some contents...</p>
          <p>Some contents...</p>
          <p>Some contents...</p>
      </Modal>
    );
    }
}
export default SayHello(ShowHook);

这样就能够轻松的控制弹窗的显示隐藏;实际效果以下:

3.useImperativeHandle & Refs 转发 (React.forwardRef)

Ref 转发是一项将 Ref 自动地经过组件传递到其一子组件的技巧。对于大多数应用中的组件来讲,这一般不是必需的,但其对某些组件,尤为是可重用的组件库是颇有用的。

它能够将子组件的方法暴露给父组件使用。

// 父组件 ShowHook.js
import React, { Component } from 'react';
import SayHello from './SayHello';
export default class ShowHook extends Component {
  showChild = () => {
    console.log(this.child);
    //能够看到 changeVisible 方法被挂载到了 this.child 下
    // {changeVisible: f()}
    this.child.changeVisible(true);
  }
  // 将子组件暴露出来的对象挂载到 child
  onRef = (ref) => {
    this.child = ref;
  }
  render()  {
    return (
      <React.Fragment>
          <button onClick={this.showChild}>showChild</butotn>
        <SayHello
          ref={this.onRef}
        />
      </React.Fragment>
    );
  }
}
// 子组件 SayHello.js
import React, { useState, useImperativeHandle, forwardRef } from 'react';
function SayHello(props, ref) {
  const [visible, changeVisible] = useState(false);
   // 暴露的子组件方法,给父组件调用
  useImperativeHandle(ref, () => {
    return {
      changeVisible,
    };
  });
  return visible && (
    <h1 onClick={() => changeVisible(false)}> Hello Hook! </h1>
  );
}
export default forwardRef(SayHello);

上面例子中封装了一个子组件,任意一个使用了该子组件的地方均可以控制它的状态。

结束语

目前 Hooks 尚不具有完整的 Class Component 的功能,一些不经常使用的生命周期函数尚不支持,例如:getSnapshotBeforeUpdate, getDerivedStateFromError 以及 componentDidCatch,但官方已将他们 排入计划内,相信不久以后就会获得支持;将来 Hooks 可能将成为 React Components 的首选,在现阶段就让咱们愉快的混合使用吧。

参考文章

How to Use React Hooks in Class Components

React拾遗:Render Props及其使用场景

Hooks FAQ

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com

相关文章
相关标签/搜索