一 前言
先问大家几个问题,这几个问题都是我在面试中实在被问到的,属实给我整不会了....
写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...不胜感激 !