Hooks 的 API 能夠參照 React 官網。本文主要是結合 Demo 詳細講解如何用 Hooks 來實現 React Class Component 寫法,讓你們更深的理解 Hooks 的機制而且更快的入門。 注意:Rax 的寫法和 React 是一致的,本文 Demo 基於 React 實現 ,查看 Demo 完整版node
本文內容包括以下:react
Hooks 的出現其實在弱化生命週期的概念,官網也講解了原先的生命週期在寫法上有哪些弊端,這裏不作優缺點的比較,只給你們作寫法轉換。git
Hooks 生命週期主要是藉助 useEffect
和 useState
來實現,請看以下 Demogithub
Class Component constructor 函數只會在組件實例化時調用一次,並且會在全部生命週期函數調用以前調用數組
useState 傳入初始化函數 fn 只會執行一次,而且執行時機在 render 以前dom
function useConstruct(fn) { useState(fn); }
依賴項給空數組,只會執行一次函數
// componentDidMount function useDidMount(fn) { useEffect(fn, []); }
依賴項不傳值,任何觸發的 render 都會執行this
// 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 不一樣的是,比較操做在組件外部。code
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 實例的。component
經過使用 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> ); }