关于hook:面试官求你别再问我hook了

44次阅读

共计 6019 个字符,预计需要花费 16 分钟才能阅读完成。

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

写 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>
)
}

复制代码
这里为来缩小代码量只展现了在同一个页面应用封装过的 hook,事实上封装这段 hook 咱们只改了几行代码,却实现了逻辑的重用是不是很神奇!😆须要留神的是 hook 的封装函数必须要以 use 结尾,因为应用 hook 自身是有规定的,比方不能在条件语句中应用 hook, 不能在函数外应用 hook; 如果不实用 use 结尾封装 hook,则 react 无奈主动查看该函数内应用的 hook 是否合乎规定。

最初
如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163 互相学习,咱们会有业余的技术答疑解惑

如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点 star: https://gitee.com/ZhongBangKe… 不胜感激!

正文完
 0