如何用 Hooks 來實現 React Class Component 寫法?

如何用 Hooks 來實現 React Class Component 寫法?

Hooks 的 API 可以參照 React 官網。本文主要是結合 Demo 詳細講解如何用 Hooks 來實現 React Class Component 寫法,讓大家更深的理解 Hooks 的機制並且更快的入門。 注意:Rax 的寫法和 React 是一致的,本文 Demo 基於 React 實現 查看 Demo 完整版

本文內容包括如下:

  • 一、在 Hooks 中如何實現 Class Component 生命週期
  • 二、在 Hooks 中如何實現 shouldComponentUpdate
  • 三、在 Hooks 中如何實現 this
  • 四、在 Hooks 中如何獲取上一次值
  • 五、在 Hooks 中如何實現父組件調用子組件方法
  • 六、在 Hooks 中如何獲取父組件獲取子組件的 dom 節點

一、在 Hooks 中如何實現 Class Component 生命週期

Hooks 的出現其實在弱化生命週期的概念,官網也講解了原先的生命週期在寫法上有哪些弊端,這裏不做優缺點的比較,只給大家做寫法轉換。

Hooks 生命週期主要是藉助 useEffectuseState 來實現,請看如下 Demo

constructor

Class Component constructor 函數只會在組件實例化時調用一次,而且會在所有生命週期函數調用之前調用

useState 傳入初始化函數 fn 只會執行一次,並且執行時機在 render 之前

function useConstruct(fn) {
  useState(fn);
}

componentDidMount

依賴項給空數組,只會執行一次

// componentDidMount
function useDidMount(fn) {
  useEffect(fn, []);
}

componentDidUpdate

依賴項不傳值,任何觸發的 render 都會執行

// componentDidUpdate
function useDidUpdate(fn) {
  useEffect(fn);
}

componentWillUnmount

// componentWillUnmount
function useUnMount(fn) {
  useEffect(() => fn, []);
}

生命週期詳細 Demo 如下

import React, { useState, useEffect, useRef } from 'react';

// construct
function useConstruct(fn) {
  // useState 傳入初始化函數 fn 只會執行一次
  useState(fn);
}

// componentDidMount
function useDidMount(fn) {
  // 依賴項給空數組,只會執行一次
  useEffect(fn, []);
}

// componentDidUpdate
function useDidUpdate(fn) {
  // 依賴項不傳值,任何觸發的 render 都會執行
  useEffect(fn);
}

// componentWillUnmount
function useUnMount(fn) {
  useEffect(() => fn, []);
}

function Block() {
  const [count, setCount] = useState(0);
  const instance = useRef({});

  useConstruct(() => {
    instance.current.name = 1;
    console.log('Block Component----Construct');
  });

  useDidMount(() => {
    console.log('Block Component----componentDidMount');
  });

  useDidUpdate(() => {
    console.log('instance.current.name', instance.current.name);
    console.log('Block Component----componentDidUpdate');
  });

  useUnMount(() => {
    console.log('Block Component----componentWillUnmount');
  });

  console.log('Block render');
  return (
    <div style={{backgroundColor: '#eaeaea'}}>
      <p>Block組件</p>
      <p>count: {count}</p>
      <br />
      <button onClick={() => setCount(count + 1)}>點擊 count + 1</button>
    </div>
  );
}

export default function ParentComp() {
  const [unMountBlock, setUnMountBlock] = useState(false);
  return (
    <div>
      <p>unMountBlock: {unMountBlock?'true':'false'}</p>
      <br />
      {!unMountBlock ? <Block /> : null}
      <br />
      <button onClick={() => setUnMountBlock(true)}>點擊卸載 Block 組件</button>
    </div>
  );
}

二、在 Hooks 中如何實現 shouldComponentUpdate

通過 useMemo 來實現 shouldComponentUpdate,依賴項填寫當前組件依賴的 props,useMemo內部對依賴項進行淺比較,其中的任何一個依賴項變化時,重新 render 組件。 與 Class Component 不同的是,比較操作在組件外部。

import React, { useState, useMemo } from 'react';

function Counter(props) {
  console.log('Counter render');
  return (
    <div>
      <p>count: {props.count}</p>
    </div>
  );
}

function Time(props) {
  console.log('Time render');
  return (
    <div>
      <p>time: {props.time}</p>
    </div>
  );
}

export default function Demo() {
  const [count, setCount] = useState(0);
  const [time, setTime] = useState(0);
  const [count2, setCount2] = useState(10);

  // 用於實現 shouldComponentUpdate
  // 與 Class Component 不同點:當前是在組件外做比較
  const child1 = useMemo(() => <Counter count={count} />, [count]);
  const child2 = useMemo(() => <Time time={time} />, [time]);

  return (
    <div>
      <p>count: {count}</p>
      <p>time: {time}</p>
      <p>count2: {count2}</p>
      <br />
      <button onClick={() => setCount(count + 1)}>count + 1</button>
      <br />
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
      <br />
      <button onClick={() => setTime(time + 1)}>time + 1</button>
      <br />
      {child1}
      {child2}
    </div>
  );
}

三、在 Hooks 中如何實現 this

首先你要明白 Hooks 實際上仍然是 Function Component 類型,它是沒有類似於 Class Component 的 this 實例的。

通過使用 useRef 來模擬實現,internalRef.current 可以認爲是當前的 this 變量,用來綁定相關變量

import React, { useEffect, useRef } from 'react';

export default function useThis() {
  // internalRef.current 默認值爲 {}
  const internalRef = useRef({});
  // internalRef.current 類似於 this 變量
  const self = internalRef.current;

  if (self.didMount) {
    console.log('componentDidMount', self.didMount);
  }

  useEffect(() => {
    self.didMount = true;
  }, []);

  return (
    <div>
      <p>如何使用this 變量</p>
    </div>
  );
}

四、在 Hooks 中如何獲取上一次值

藉助 useEffectuseRef 的能力來保存上一次值

import React, { useState, useRef, useEffect } from 'react';

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

export default function Counter() {
  const [count, setCount] = useState(0);
  const previousCount = usePrevious(count);

  return (
    <div>
      <p>count: {count}</p>
      <p>previousCount: {previousCount}</p>
      <button onClick={() => setCount(count + 1)}>點擊 count + 1</button>
    </div>
  );
}

五、在 Hooks 中如何實現父組件調用子組件方法

上節已經說到,Hooks 實際上仍然是 Function Component 類型,它本身是不能通過使用 ref 來獲取組件實例的,所以在 Hooks 中想要實現 父組件調用子組件的方法,需要兩個 API來配合使用,即forwardRefuseImperativeHandle。在子組件中使用 useImperativeHandle 來導出方法,並使用 forwardRef 包裹組件, 在父組件中使用 useRef傳遞 ref 給子組件。

import React, { useRef, useImperativeHandle, forwardRef } from 'react';

const TextInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);

  const handleFocus = () => {
    inputRef.current.focus();
  };

  // 暴露方法給外部組件調用
  // useImperativeHandle 應當與 forwardRef 一起使用
  useImperativeHandle(ref, () => ({
    focusInput: handleFocus,
    hello: ()=>{}
  }));

  return (
    <div>
      <input ref={inputRef} type="text" />
      <br />
      <button onClick={handleFocus}>內部調用 Focus the input</button>
    </div>
  );
});

export default function Parent() {
  const inputRef = useRef(null);

  const handleFocus = () => {
    console.log(typeof findDOMNode)
    console.log(inputRef.current)
    // 調用子組件方法
    inputRef.current.focusInput();
  };

  return (
    <div>
      <TextInput ref={inputRef} />
      <br />
      <button onClick={handleFocus}>父組件調用子組件focusInput</button>
    </div>
  );
}

六、在 Hooks 中如何獲取父組件獲取子組件的 dom 節點

findDOMNode 用於找到組件的dom節點,用於相關的 dom 處理操作。

import React, { useRef, useImperativeHandle, forwardRef } from 'react';
import {findDOMNode} from 'react-dom';

const TextInput = forwardRef((props, ref) => {
  return (
    <div ref={ref}>
      <input ref={inputRef} type="text" />
      <br />
      <button onClick={handleFocus}>內部調用 Focus the input</button>
    </div>
  );
});

export default function Parent() {
  const inputRef = useRef(null);

  const handleFindDom = () => {
    const node = findDOMNode(inputRef.current);
  };

  return (
    <div>
      <TextInput ref={inputRef} />
      <br />
      <button onClick={handleFocus}>父組件調用子組件focusInput</button>
    </div>
  );
}

這裏可能有人會提出疑問,在 Class Component 裏面 ref 可以取到組件 dom 的同時,也可以取到組件實例方法,爲何這裏要拆分成 三、四 兩個章節來講?
很遺憾,在 Hooks 裏面無法通過一個 ref 同時實現兩個功能,只能通過規範的方式來使用,比如:

import React, { useRef, useImperativeHandle, forwardRef } from 'react';
import {findDOMNode} from 'react-dom';

const TextInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  const compRef = useRef(null);

  const handleFocus = () => {
    inputRef.current.focus();
  };

  useImperativeHandle(ref, () => ({
    // 導出方法
    focusInput: handleFocus,
    // 導出當前 dom 節點
    compRef: compRef
  }));
  
  return (
    <div ref={compRef}>
      <input ref={inputRef} type="text" />
      <br />
      <button onClick={handleFocus}>內部調用 Focus the input</button>
    </div>
  );
});

export default function Parent() {
  const inputRef = useRef(null);

  const handleFocus = () => {
    // 獲取 TextInput 組件的 dom 節點
    const node = findDOMNode(inputRef.current.compRef.current);
    console.log(node);
    // 調用 TextInput 組件方法
    inputRef.current.focusInput();
  };

  return (
    <div>
      <TextInput ref={inputRef} />
      <br />
      <button onClick={handleFocus}>父組件調用子組件focusInput</button>
    </div>
  );
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章