react hooks可以使用在函數(shù)dom中,由于函數(shù)是沒有辦法像class那樣保存state假颇,所以可以用hooks的特性來保存state的狀態(tài)胚鸯。
1.useState和useEffect
useState接受一個(gè)初始值,并返回一個(gè)數(shù)值笨鸡,第一個(gè)是你要命名的該值姜钳,第二個(gè)是需要進(jìn)行更改該值得時(shí)候用的,類似于setState
useEffect相當(dāng)于componentDidMount而return的部分相當(dāng)于componentWillUnmount形耗,但也有點(diǎn)不同傲须,該方法接受第一個(gè)參數(shù)是函數(shù),第二個(gè)參數(shù)為一個(gè)數(shù)組趟脂,填寫當(dāng)哪個(gè)useState的值發(fā)生改變的時(shí)候泰讽,會執(zhí)行這useEffect里面的內(nèi)容。填寫一個(gè)[]則每次之后會加載頁面和離開頁面的時(shí)候執(zhí)行。
假如這里傳入了count已卸,則每次count發(fā)生變化都會執(zhí)行一次useEffect里面的代碼佛玄。
需要注意的是這里的setCount(c => c + 1) 最好不要變成setCount(count+1),因?yàn)樘钊肟諗?shù)組的時(shí)候,只會執(zhí)行一次useEffect累澡,而這時(shí)的count會根據(jù)當(dāng)時(shí)函數(shù)里面的count來決定是什么梦抢。這會引起閉包陷阱。所以最好的辦法是使用函數(shù)傳入count愧哟,然后返回一個(gè)新的count奥吩。
import React, { useState, useEffect } from 'react'
function myTimer() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log(count)
const intenval = setInterval(() => {
setCount(c => c + 1)
}, 1000)
return () => clearInterval(intenval)
}, [])
return <span>{count}</span>
}
export default myTimer
2.useReducer
useReducer類似于useState,當(dāng)改變的是一個(gè)對象之類的蕊梧,useReducer能使問題變得簡單霞赫。
useReducer接受一個(gè)方法,用來處理需要改變時(shí)候返回的state的函數(shù)肥矢,第二個(gè)是初始值端衰。返回的是一個(gè)初始值和一個(gè)dispacth方法。
然后只需要在改變的時(shí)候調(diào)用dispatch()傳入需要改變的狀態(tài)和數(shù)值即可甘改。由countReducer來進(jìn)行處理并返回一個(gè)新的count即可旅东。
import React, { useState, useReducer, useEffect } from 'react'
// 先聲明一個(gè)處理的方法
function countReducer(state, action) {
switch (action.type) {
case 'add':
return state + 1
case 'minus':
return state - 1
default:
return state
}
}
function myTimer() {
// const [count, setCount] = useState(0)
const [count, dispatchCount] = useReducer(countReducer, 0)
useEffect(() => {
const intenval = setInterval(() => {
// setCount(c=>c+1)
dispatchCount({ type: 'add' })
}, 1000)
return () => {
console.log('didmount')
return clearInterval(intenval)
}
}, [])
return <span>{count}</span>
}
export default myTimer
3.uselayoutEffect
這個(gè)和useEffect有點(diǎn)相似,但這個(gè)是在Dom還沒渲染完成的時(shí)候執(zhí)行十艾,而useEffect是在渲染之后執(zhí)行的抵代。
一般進(jìn)行更改某些變量或者動畫都應(yīng)該在渲染之后再進(jìn)行執(zhí)行,所以這個(gè)使用得不是很多忘嫉。
4.useContext
類似于在全局當(dāng)中定義一個(gè)變量荤牍。方便祖先組件與后代組件(中間隔了好多層組件)傳值。
首先需要使用React.CreateContext()榄融,將其封裝成一個(gè)組件導(dǎo)出。
import React from 'react'
export default React.createContext('')
然后在父組件中引入該組件救湖。使用其提供的Provider組件將子組件包裹在一起愧杯。而value值就是需要傳給子組件的值了。這里的value可以用this.state.xxx來代替鞋既,當(dāng)發(fā)生改變的時(shí)候力九,子組件獲取到該值也會發(fā)生改變。
<MyContext.Provider value="text" >
<Component {...pageProps} />
</MyContext.Provider>
在子組件中邑闺,就可以通過useContext(context component)來獲取了跌前,需要注意的是,必須是functional component陡舅,不然會報(bào)錯(cuò)抵乓。
通過useContext獲取到的值就是上面包裹的value的值
import React, { useState, useReducer, useEffect, useContext } from 'react'
import MyContext from '../lib/my-context'
function myTimer() {
const context = useContext(MyContext)
const [count, dispatchCount] = useReducer(countReducer, 0)
useEffect(() => {
const intenval = setInterval(() => {
// setCount(c=>c+1)
dispatchCount({ type: 'add' })
}, 1000)
return () => {
console.log('didmount')
return clearInterval(intenval)
}
}, [])
return (
<span>
{count}
{context}
</span>
)
}
5.useRef
由于在函數(shù)組件中是不存在ref的。所以假如需要給函數(shù)組件里面擁有ref,就需要使用useRef了灾炭。
使用方法也很簡單茎芋,useRef()返回的就是一個(gè)ref,只需要將其放在想要獲取的dom上即可蜈出。
import React, {useState, useReducer, useEffect, useContext, useRef} from 'react'
function myTimer() {
const [count, dispatchCount] = useReducer(countReducer, 0)
const context = useContext(MyContext)
const spanRef = useRef()
console.log(spanRef)
return (
<span ref={spanRef}>
{count}
{context}
</span>
)
}
6.useMeme,useCallback
主要是用于優(yōu)化性能的田弥。某些組件沒有更新的時(shí)候,不需要進(jìn)行更新操作铡原,就可以用到這兩個(gè)了。
首先我們將代碼更改一下,加入了一個(gè)Child的函數(shù)組件,Child中接受兩個(gè)參數(shù)割去,一個(gè)是button事件狞玛,用來更改count的值,一個(gè)是顯示count的值酌儒,input用來更改name的值辜妓。然后在Child中console一下,記錄每次Child更新的時(shí)間點(diǎn)忌怎。
function myCount() {
const [name, setName] = useState('yiki')
const [count, dispatchCount] = useReducer(countReducer, 0)
const config = {
text: `count is ${count}`,
color: count > 3 ? 'red' : 'blue'
}
return (
<div>
<input value={name} onChange={e => setName(e.target.value)}></input>
<Child
onButtonClick={() => dispatchCount({ type: 'add' })}
config={config}
></Child>
</div>
)
}
function Child({ onButtonClick, config }) {
console.log('child render')
return (
<button onClick={onButtonClick} style={{ color: config.color }}>
{config.text}
</button>
)
}
export default myTimer
可以發(fā)現(xiàn)籍滴,當(dāng)每次input輸入改變的時(shí)候,都會打印出child render榴啸。而這不是我們想要的孽惰。因?yàn)閏ount并沒有發(fā)生改變。Child組件是不需要發(fā)生改變的鸥印。這時(shí)候我們就需要用到useMemo和memo了勋功。
memo類似于class里面的shouldComponentUpdate,可以包裹在函數(shù)組件本身库说。由于該組件完全取決于onButtonClick和config狂鞋,所以只要這兩個(gè)數(shù)值不發(fā)生改變,那么Child組件就不會發(fā)生改變潜的。
import { memo } from 'react'
const Child = memo(function Child({ onButtonClick, config }) {
console.log('child render')
return (
<button onClick={onButtonClick} style={{ color: config.color }}>
{config.text}
</button>
)
})
但是骚揍,當(dāng)這樣寫完之后,會發(fā)現(xiàn)Child還是會發(fā)生改變啰挪,這是由于input會導(dǎo)致name發(fā)生改變信不,而name的改變會導(dǎo)致myCount組件也發(fā)生改變。而myCount一改變亡呵,config就會被重新聲明抽活。引用的地址就會改變,所以這時(shí)候Child還是會被重新渲染锰什。
這時(shí)候就需要用到useMemo了下硕。useMemo來判斷config是否改變丁逝,從而判斷是否要返回一個(gè)新的對象,這時(shí)候我們對config的聲明做一下修改卵牍。
import { useMemo } from 'react'
const config = useMemo(
() => ({
text: `count is ${count}`,
color: count > 3 ? 'red' : 'blue'
}),
[count]
)
第一個(gè)參數(shù)是一個(gè)方法果港,返回一個(gè)值,第二個(gè)參數(shù)和useEffect一樣糊昙,根據(jù)該值來判斷是否需要返回一個(gè)新的值辛掠。
但這時(shí)候還是不行,輸入input的時(shí)候释牺,Child還是渲染了萝衩。這是因?yàn)閛nButtonClick={() => dispatchCount({ type: 'add' })}傳入的是一個(gè)匿名函數(shù),每次myCount更新的時(shí)候地址也會改變没咙。
這時(shí)候可以用useMemo返回一個(gè)函數(shù)猩谊,或者用useCallback來進(jìn)行返回一個(gè)函數(shù)。第二個(gè)參數(shù)也是依賴項(xiàng)祭刚,根據(jù)某個(gè)值來判斷是否要更新牌捷。
import { useCallback } from 'react'
const handleButtonClick = useCallback(
() => dispatchCount({ type: 'add' }),
[]
)
假如使用useMemo就需要多包裹一層用來返回一個(gè)函數(shù)
const handleButtonClick = useMemo(
() => () => dispatchCount({ type: 'add' }),
[]
)
自此當(dāng)input值發(fā)生改變的時(shí)候Child就不會重新渲染,只有當(dāng)count的值發(fā)生改變的時(shí)候才會進(jìn)行重新渲染涡驮,完成了對Child的性能優(yōu)化了暗甥。
7.閉包陷阱
由于是函數(shù)組件,所以很容易出現(xiàn)閉包的情況捉捅,當(dāng)我們使用setTimeout來進(jìn)行延時(shí)調(diào)用來獲取某值的時(shí)候撤防,而該值在延時(shí)的時(shí)候發(fā)生了改變,但是setTimeout獲取到的其實(shí)是沒有改變的當(dāng)時(shí)的值棒口。
function MyCount(){
const [count,setCount] = useState(0)
return <>
<button onClick={() => {setCount(c=>c+1)}}>{count}</button>
<button onClick={()=>{setTimeout(()=>{alert(count)},2000)}}>延時(shí)獲取count</button>
</>
}
點(diǎn)擊延時(shí)后寄月,再在2秒內(nèi)點(diǎn)擊button2次,這時(shí)候彈出的值應(yīng)該是2无牵,但是由于閉包的關(guān)系漾肮,所以彈出來的是0。由于我們在使用class的時(shí)候茎毁,是通過this.state.count來進(jìn)行獲取的克懊,所以不會出現(xiàn)這個(gè)問題。但是在函數(shù)中充岛,往往很容易就會出現(xiàn)和現(xiàn)實(shí)不符的情況保檐。
這時(shí)候可以通過useRef來進(jìn)行解決耕蝉。由于useRef返回的都是同一個(gè)對象崔梗,所以可以將其值掛載到ref.current上,這樣就能獲取到最新的值了垒在。
這時(shí)候我們可以改成這種如下形式:
function MyCount(){
const [count,setCount] = useState(0)
const countRef = useRef()
countRef.current = count
return <>
<button onClick={() => {setCount(c=>c+1)}}>{count}</button>
<button onClick={()=>{setTimeout(()=>{alert(countRef.current)},2000)}}>延時(shí)獲取count</button>
</>
}