美文网首页
面试官求你别再问我hook了

面试官求你别再问我hook了

作者: 涅槃快乐是金 | 来源:发表于2022-03-22 21:57 被阅读0次

一 前言

先问大家几个问题,这几个问题都是我在面试中真实被问到的,属实给我整不会了....

  • 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

还是上面那个例子,我们把handleRenderuseCallback包裹,也就是说这里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>
    )
    }

这里为来减少代码量只展示了在同一个页面使用封装过的hook,事实上封装这段hook我们只改了几行代码,却实现了逻辑的重用是不是很神奇!😆需要注意的是hook的封装函数必须要以use开头,因为使用hook本身是有规则的,比如不能在条件语句中使用hook,不能在函数外使用hook;如果不适用use开头封装hook,则react无法自动检查该函数内使用的hook是否符合规则。

作者:食困症患者
链接:https://juejin.cn/post/7019493055396839431

相关文章

  • 面试官求你别再问我hook了

    一 前言 先问大家几个问题,这几个问题都是我在面试中真实被问到的,属实给我整不会了.... 写hooks跟写类组件...

  • 求求你别再问我为什么

    1. 那天我在洗被我弄脏了的塑料袋,被公司的HR看到了,她问:“你塑料袋子还要洗吗?” 我尴尬了下,回答说:“我饭...

  • 《求你别再熬夜了》

    高中时代最缺的就是睡觉的时间,睡觉的时间都是靠挤出来的,而大学的时间都是拿来熬夜煲电视剧,开黑的,一步入大学...

  • 求你了,别再啰嗦了

    我讨厌啰嗦的人,喋喋不休,反复咀嚼着一个意思重复说来说去。简直就是在碾轧听众的智商,摧残听者的耳膜。 有人说从心理...

  • 别再问我

    别再问我天为什么蓝了别再问我国际机场为啥没航班了别再问我五月的公园里为什么那么多人戴着口罩别再问我商店门口,车站门...

  • 求你了,别再瞧不起自己了

    如果以生病作为我生活的一个结点。 我想是因为生病带我进入了一种新的生活,因为生病让我接触了那些美好的未知世界。 生...

  • 老公,求你别再爱我了

    老公,求你别再爱我了。 我承认,当初和老公在一起的时候我很爱他,他也很爱我。一直到现在他还是很爱我。只不过,我对他...

  • 求求你别再熬夜了!

    “回家泡个热水澡,睡个好觉,明天早晨起来满街都是男人,个个都比豹哥好。” 电影《甜蜜蜜》里的这句台词,把睡个好觉当...

  • 求你,别再做慈善了

    一 我迷了路。 在村里边转了好半天,也没找到那所学校。 这边住户少,交通不便,跟外界接触也少。年纪大点的人又不太会...

  • 我求你,别再花痴了。

    1 就像你看到的一样,我刚结束了我的第二段暗恋。 我是一个南方女生,慢热又迟钝,还是一个资深外貌协会成员。从初中开...

网友评论

      本文标题:面试官求你别再问我hook了

      本文链接:https://www.haomeiwen.com/subject/xhsqjrtx.html