天天看點

面試官求你别再問我hook了

一 前言

先問大家幾個問題,這幾個問題都是我在面試中真實被問到的,屬實給我整不會了....

  • 寫​

    ​hooks​

    ​​跟寫類元件比,​

    ​hooks​

    ​有啥優勢?
  • 我們如何封裝一個​

    ​hook​

    ​?
  • ​hooks​

    ​原理是什麼?

面試雖然涼了,但是學習還得繼續?

二 深入了解hooks

useState

  • 使用

​useState​

​​的使用很簡單?,一句話帶過,傳回一個數組,數組的值為目前​

​state​

​​和更新​

​state​

​​的函數;​

​useState​

​的參數是變量、對象或者是函數,變量或者對象會作為​

​state​

​的初始值,如果是函數,函數的傳回值會作為初始值。

  • 批量更新

看下面這段代碼

function Count(){
    let [count,setCount] = useState(0)
    const handleAdd = function(){
        setCount(count+1)
        setCount(count+1)
    }
    return(
        <div>
            <p>{count}</p>    
            /*每次點選加1*/
            <button onClick={handleAdd}>加一</button>
        </div>
    )
}
複制代碼      

在同一個事件中并不會因為調用了兩次​

​setCount​

​​而讓​

​count​

​​增加兩次,試想如果在同一個事件中每次調用​

​setCount​

​​都生效,那麼每調用一次​

​setCount​

​​元件就會重新渲染一次,這無疑使非常影響性能的;實際上如果修改的​

​state​

​​是同一個,最後一個​

​setCount​

​​函數中的新​

​state​

​會覆寫前面的

useEffect && useLayoutEffect

這兩個​

​hook​

​​用法一緻,第一個參數是回調函數,第二個參數是數組,數組的内容是依賴項​

​deps​

​​,依賴項改變後執行回調函數;注意元件每次渲染會預設執行一次,如果不傳第二個參數隻要該元件有​

​state​

​​改變就會觸發回調函數,如果傳一個空數組,隻會在初始化執行一次。另外,如果用​

​return​

​傳回了一個函數,元件每次重新渲染的時候都會先執行該函數再調用回調函數。

  • 差別

表面上看,這兩個​

​hook​

​​的差別是執行時機不同,​

​useEffect​

​​的回調函數會在頁面渲染後執行;​

​useLayoutEffect​

​​會在頁面渲染前執行。實際上是​

​React​

​​對這兩個​

​hook​

​​的處理不同,​

​useEffect​

​​是異步調用,而​

​useLayoutEffect​

​是同步調用。

那什麼時候用​

​useEffect​

​​,什麼時候用​

​useLayoutEffect​

​呢?

我的了解是視情況而定 如果回調函數會修改​

​state​

​​導緻元件重新渲染,可以​

​useLayoutEffect​

​​,因為這時候用​

​useEffect​

​​可能會造成頁面閃爍; 如果回調函數中去請求資料或者js執行時間過長,建議使用​

​useEffect​

​​;因為這時候用​

​useLayoutEffect​

​堵塞浏覽器渲染。

useMemo && useCallback

​hook​

​​可用于性能優化,減少元件的重複渲染;現在就來看看這兩個神奇的​

​hook​

​怎麼用。

  • uesMemo
function MemoDemo() {
    let [count, setCount] = useState(0);
    let [render,setRender] = useState(false)
    const handleAdd = () => {
        setCount(count + 1);
    };
    const Childone = () => {
        console.log("子元件一被重新渲染");
        return <p>子元件一</p>;
    };
    const Childtwo = (props) => {
        return (
            <div>
                <p>子元件二</p>
                <p>count的值為:{props.count}</p>
            </div>
        );
    };
    const handleRender = ()=>{
        setRender(true)
    }
    return (
        <div style={{display:"flex",justifyContent:'center',alignItems:'center',height:'100vh',flexDirection:'column'}}>
            {
                useMemo(() => {
                    return <Childone />
                }, [render])
            }
            <Childtwo count={count} />
            <button onClick={handleAdd}>增加</button>
            <br/>
            <button onClick={handleRender} >子元件一渲染</button>
        </div>
    );
}
複制代碼      

​Childone​

​​元件隻有​

​render​

​改變才會重新渲染

這裡順帶講下,​

​React.memo​

​​,用​

​React.memo​

​​包裹的元件每次渲染時會和​

​props​

​​會和舊的​

​props​

​進行淺比較,如果沒有變化則元件不渲染;示例如下

const Childone = React.memo((props) => {
    console.log("子元件一被重新渲染",props);
    return <p>子元件一{props.num}</p>;
})
function MemoDemo() {
    let [count, setCount] = useState(0);
    let [render,setRender] = useState(false)
    let [num,setNum] = useState(2)
    const handleAdd = () => {
        setCount(count + 1);
    };
   
    const Childtwo = (props) => {
        return (
            <div>
                <p>子元件二</p>
                <p>count的值為:{props.count}</p>
            </div>
        );
    };
    const handleRender = ()=>{
        setRender(true)
    }
    return (
        <div style={{display:"flex",justifyContent:'center',alignItems:'center',height:'100vh',flexDirection:'column'}}>
            {/* {
                useMemo(() => {
                    return <Childone />
                }, [render])
            } */}
            <Childone num={num}/>
            <Childtwo count={count} />
            <button onClick={handleAdd}>增加</button>
            <br/>
            <button onClick={handleRender} >子元件一渲染</button>
        </div>
    );
}

複制代碼      

這個例子是把上個例子中的​

​Childone​

​​拆出來套上​

​React.memo​

​​的結果,點選增加後元件不會該元件不會重複渲染,因為​

​num​

​沒有變化

  • useCallback

還是上面那個例子,我們把​

​handleRender​

​​用​

​useCallback​

​​包裹,也就是說這裡​

​num​

​​不變化每次都會傳同一個函數,若是這裡不用​

​useCallback​

​​包裹,每次都會生成新的​

​handleRender​

​​,導緻​

​React.memo​

​​函數中的​

​props​

​淺比較後發現生成了新的函數,觸發渲染

const Childone = React.memo((props) => {
    console.log("子元件一被重新渲染",props);
    return <p>子元件一{props.num}</p>;
})
export default function MemoDemo() {
    let [count, setCount] = useState(0);
    let [render,setRender] = useState(false)
    let [num,setNum] = useState(2)
    const handleAdd = () => {
        setCount(count + 1);
    };
   
    const Childtwo = (props) => {
        return (
            <div>
                <p>子元件二</p>
                <p>count的值為:{props.count}</p>
            </div>
        );
    };
    const handleRender = useCallback(()=>{
        setRender(true)
    },[num])
    return (
        <div style={{display:"flex",justifyContent:'center',alignItems:'center',height:'100vh',flexDirection:'column'}}>
            {/* {
                useMemo(() => {
                    return <Childone />
                }, [render])
            } */}
            <Childone num={num} onClick={handleRender}/>
            <Childtwo count={count} />
            <button onClick={handleAdd}>增加</button>
            <br/>
            <button onClick={handleRender} >子元件一渲染</button>
        </div>
    );
}
複制代碼      

useRef

這個​

​hook​

​通常用來擷取元件執行個體,還可以用來緩存資料❗ 擷取執行個體就不過多解釋了,需要注意的是隻有類元件才有執行個體;

重點看下​

​useRef​

​如何緩存資料的:

function UseRef() {
    let [data, setData] = useState(0)
    let initData = {
        name: 'lisa',
        age: '20'
    }
    let refData = useRef(initData)   //refData聲明後元件再次渲染不會再重新賦初始值
    console.log(refData.current);
    refData.current = {       //修改refData後頁面不會重新渲染
        name: 'liyang ',
        age: '18'
    }
    const reRender = () => {
        setData(data + 1)
    }
    return (
        <div>
            <button onClick={reRender}>點選重新渲染元件</button>
        </div>
    )
}
複制代碼      

元件重新渲染後,變量會被重新指派,可以用​

​useRef​

​​緩存資料,這個資料改變後是不會觸發元件重新渲染的,如果用​

​useState​

​​儲存資料,資料改變後會導緻元件重新渲染,是以我們想悄悄儲存資料,​

​useRef​

​是不二選擇?

三 自定義hook

自定義​

​hook​

​​,也就是​

​hook​

​​的封裝,至于為什麼要封裝​

​hook​

​​呢?​

​react​

​官網給出了答案

使用 Hook 其中一個​​目的​​就是要解決 class 中生命周期函數經常包含不相關的邏輯,但又把相關邏輯分離到了幾個不同方法中的問題。 通過自定義 Hook,可以将元件邏輯提取到可重用的函數中。

先來看下這個例子:

export default function CustomHook() {
    let refone = useRef(null)
    let X, Y, isMove = false,left,top
    //基于滑鼠事件實作拖拽
    useEffect(() => {
        const dom = refone.current
        dom.onmousedown = function (e) {
            isMove = true
            X = e.clientX - dom.offsetLeft;
            Y = e.clientY - dom.offsetTop;
        }
        dom.onmousemove = function (e) {
            if (isMove) {
                left = e.clientX - X
                top = e.clientY - Y
                dom.style.top = top + "px"
                dom.style.left = left + "px"
            }

        }
        dom.onmouseup = function (e) {
            isMove = false
        }
    }, [])
    return (
        <div style={{ display: "flex", justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
            <div ref={refone} style={{ width: '70px', height: '70px', backgroundColor: '#2C6CF9',position:'absolute' }}></div>
        </div>
    )
}
複制代碼      
import {useEffect, useRef } from "react";
function useDrop() {
    let refone = useRef(null)
    let X, Y, isMove = false,left,top
    //基于滑鼠事件實作拖拽
    useEffect(() => {
        const dom = refone.current
        dom.onmousedown = function (e) {
            isMove = true
            X = e.clientX - dom.offsetLeft;
            Y = e.clientY - dom.offsetTop;
        }
        dom.onmousemove = function (e) {
            if (isMove) {
                left = e.clientX - X
                top = e.clientY - Y
                dom.style.top = top + "px"
                dom.style.left = left + "px"
            }

        }
        dom.onmouseup = function (e) {
            isMove = false
        }
    }, [])
    return refone
}
export default function CustomHook() {
    let refone = useDrop()
    let reftwo = useDrop()
    return (
        <div style={{ display: "flex", justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
            <div ref={refone} style={{ width: '70px', height: '70px', backgroundColor: '#2C6CF9',position:'absolute' }}></div>
            <div ref={reftwo} style={{ width: '70px', height: '70px', backgroundColor: 'red',position:'absolute' }}></div>
        </div>
    )
    }
複制代碼      

最後