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
Hooks 的出現其實在弱化生命週期的概念,官網也講解了原先的生命週期在寫法上有哪些弊端,這裏不作優缺點的比較,只給你們作寫法轉換。git
Hooks 生命週期主要是藉助 useEffect
和 useState
來實現,請看以下 Demogithub
Class Component constructor 函數只會在組件實例化時調用一次,並且會在全部生命週期函數調用以前調用數組
useState 傳入初始化函數 fn 只會執行一次,而且執行時機在 render 以前bash
function useConstruct(fn) {
useState(fn);
}
複製代碼
依賴項給空數組,只會執行一次微信
// componentDidMount
function useDidMount(fn) {
useEffect(fn, []);
}
複製代碼
依賴項不傳值,任何觸發的 render 都會執行dom
// componentDidUpdate
function useDidUpdate(fn) {
useEffect(fn);
}
複製代碼
// componentWillUnmount
function useUnMount(fn) {
useEffect(() => fn, []);
}
複製代碼
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>
);
}
複製代碼
經過 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 實際上仍然是 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>
);
}
複製代碼
藉助 useEffect
和 useRef
的能力來保存上一次值
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 實際上仍然是 Function Component 類型,它自己是不能經過使用 ref 來獲取組件實例的,因此在 Hooks 中想要實現 父組件調用子組件的方法,須要兩個 API來配合使用,即forwardRef
和useImperativeHandle
。在子組件中使用 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>
);
}
複製代碼
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) 知識和資訊,歡迎掃碼關注交流。