「混合雙打」之如何在 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

相關文章
相關標籤/搜索