一 前言
先問大家幾個問題,這幾個問題都是我在面試中真實被問到的,屬實給我整不會了....
- 寫
跟寫類元件比,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>
)
}
複制代碼