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

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

本文內容包括以下: 1、在 Hooks 中如何實現 Class Component 生命週期 2、在 Hooks 中如何實現 shouldComponentUpdate 3、在 Hooks 中如何實現 this 4、在 Hooks 中如何獲取上一次值 5、在 Hooks 中如何實現父組件調用子組件方法 6、在 Hooks 中如何獲取父組件獲取子組件的 dom 節點react

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

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

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

1.一、constructor

Class Component constructor 函數只會在組件實例化時調用一次,並且會在全部生命週期函數調用以前調用數組

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

function useConstruct(fn) {
  useState(fn);
}
複製代碼

1.二、componentDidMount

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

// componentDidMount
function useDidMount(fn) {
  useEffect(fn, []);
}
複製代碼

1.三、componentDidUpdate

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

// componentDidUpdate
function useDidUpdate(fn) {
  useEffect(fn);
}
複製代碼

1.四、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>
  );
}
複製代碼

2、在 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>
  );
}
複製代碼

3、在 Hooks 中如何實現 this

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

經過使用 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>
  );
}
複製代碼

4、在 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>
  );
}
複製代碼

5、在 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>
  );
}
複製代碼

6、在 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 的同時,也能夠取到組件實例方法,爲什麼這裏要拆分紅 3、四 兩個章節來說? 很遺憾,在 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>
  );
}
複製代碼

如下是個人公衆號,會時常更新 JS(Node.js) 知識和資訊,歡迎掃碼關注交流。

我的微信公衆號
相關文章
相關標籤/搜索