useState
1、定義
:useState是用來代替class Component里的state
2饲嗽、useState的性質(zhì)
-
useState的初始值只在第一次有效
,這要特別注意不然容易采坑深胳,下面看個例子:import React, { useState,memo } from 'react' const Child =memo(({data})=>{ const [name,setName]=useState(data); return( <div> <div>Child</div> <div>{name}------{data}</div> </div> ) }) function Index(params) { const [count, setCount] = useState(0) const [name, setName] = useState('mike') return( <div> <div> {count} </div> <button onClick={()=>setCount(count+1)}>update count </button> <button onClick={()=>setName('jack')}>update name </button> <Child data={name}/> </div> ) } export default Index;
當我點擊update name
時會更新父組件中的值同時name也會在子組件中顯示融虽,但是子組件中的name還是一開始的mike
,這說明useState只在初次渲染被使用一次荔茬。這里還要注意一點的就是如果你的初始state需要通過復(fù)雜的計算來獲得废膘,則可以傳入一個函數(shù),在函數(shù)中計算并返回初始state慕蔚,此函數(shù)也只會在初次渲染時被調(diào)用丐黄。
-
每次渲染都是獨立的閉包
:這句話怎么理解呢?簡單點說就是每次渲染都有自己獨立的Props孔飒、State和事件處理函數(shù)灌闺,這樣就意味著每次更新狀態(tài)的時候函數(shù)組件就會重新被調(diào)用,那么每次渲染就是獨立的坏瞄,取到的值不會受到后面操作的影響桂对。
function Index(params) { let [count, setCount]=useState(0); function showCount(){ setTimeout(()=>{ console.log(count) },3000) } return ( <div> <div>{count}</div> <button onClick={()=>{setCount(count+1)}}>change count</button> <button onClick={showCount}>click show count</button> </div> ) }
3翼馆、useState和setState差異
:
在setState的時候纹磺,我們可以只修改state中的局部變量,而不需要將整個修改后的state傳進去怕膛,舉個例子:
//簡單點我就直接寫重點
this.state = {
count: 0,
age: 18,
}
handleClick = () => {
// 我們只需要傳入修改的局部變量
this.setState({
count: 1
});
}
而使用useState后缀棍,我們修改state必須將整個修改后的state傳入去宅此,因為它會直接覆蓋之前的state,而不是合并之前state對象爬范。
const [data, setData] = useState({
count: 0,
name: 'cjg',
age: 18,
});
const handleClick = () => {
const { count } = data;
// 這里必須將完整的state對象傳進去
setData({
...data,
count: count + 1,
})
};
useEffect
定義
:useEffect被稱為副作用父腕,指那些沒有發(fā)生在數(shù)據(jù)向視圖轉(zhuǎn)化過程中的邏輯,比如ajax
請求青瀑、訪問原生dom璧亮、本地持久化緩存痢法、綁定/解綁事件、添加訂閱杜顺,設(shè)置定時器等
使集集合
:
1、作為只在第一次使用的 componentDidMount 蘸炸,用來異步請求數(shù)據(jù)
//將useEffect的第二個參數(shù)設(shè)置為[]
useEffect(()=>{
const list = fetch(...);
},[])
2躬络、作為每次更新都會使用的 componentDidUpdate
//不設(shè)置useEffect的第二個參數(shù)
useEffect(()=>{
const list = fetch(...);
})
3、作為監(jiān)聽器去監(jiān)聽某個值或者某些值的變化搭儒,然后執(zhí)行相應(yīng)的操作穷当。
//將userEffect的第二個參數(shù)設(shè)為一個數(shù)組,在這個數(shù)組中添加想要監(jiān)聽的值
useEffect(()=>{
const list = name
},[name])
4淹禾、作為組件消亡時調(diào)用的 componentWillUnmount 馁菜,這里可以去取消一些
//在useEffect的最后返回一個回調(diào)函數(shù)
useEffect(() => {
let timer=setTimeout()
return () => {
clearTimeout(timer)
}
},[])
為什么要取消訂閱,因為每次render都會執(zhí)行一次useEffect铃岔,正如我上面的例子汪疮,如果每次render都去設(shè)置一個定時器,這是一個很龐大的開銷毁习,所以每次render之前都要把上一次的定時器clear智嚷。
useEffect的潛規(guī)則
-
useEffect中每一次使用的state值都固定在useEffect內(nèi)部,不會改變纺且,除非useEffect刷新來獲得最新的值盏道。
const [count, setCount] = useState(0) useEffect(() => { console.log('use effect...',count) const timer = setInterval(() => { console.log('timer...count:', count) setCount(count + 1) }, 1000) return ()=> clearInterval(timer) },[])
-
useEffect不能被判斷語句包裹
const [count, setCount] = useState(0) if(2 < 5){ useEffect(() => { .... }) }
useEffect不能被打斷
useRef
1、useref返回一個可變的ref對象载碌, 其 current
屬性被初始化為傳入的參數(shù)
const refContainer = useRef(initialValue);
useref返回的ref對象在組件的整個生命周期內(nèi)保持不變猜嘱,也就是說每次重新渲染函數(shù)組件時,返回的ref對象都是同一個(使用React.creatRef嫁艇,每次重新渲染組件都會重新創(chuàng)建ref)再看看上面那個例子
const [count, setCount] = useState(0)
const countRef = useRef(0)
useEffect(() => {
console.log('use effect...',count)
const timer = setInterval(() => {
console.log('timer...count:', countRef.current)
setCount(++countRef.current)
}, 1000)
return ()=> clearInterval(timer)
},[])
這就能正常顯示朗伶。
2、類組件步咪、react元素用React.creatRef腕让,函數(shù)式組件使用useRef來操作DOM
function Index(params) {
const [count, setCount] = useState(0);
const btnRef=useRef(null);
useEffect(()=>{
console.log("use effect");
console.log(btnRef.current)
const onClick=()=>{
setCount(count+1);
}
btnRef.current.addEventListener('click',onClick,false);
return ()=>{
btnRef.current.removeEventListener('click',onClick,false)
}
},[count])
return(
<div>
<div>
{count}
</div>
<button ref={btnRef}>click</button>
</div>
)
}
最后別忘了取消事件綁定,這樣就可以獲取到我們想要的DOM值歧斟,同時給相應(yīng)的DOM添加事件纯丸。
memo
1、定義
:memo對標類組件中的PureComponent
静袖,可以減少重新render的次數(shù)觉鼻。
先看一個例子
function Child({name}){
console.log("this is Child");
return(
<div>{name}</div>
)
}
function Index(params) {
const [count,setCount]=useState(0);
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child name="小明"></Child>
</div>
)
}
首先第一次渲染會正常打印出"this is Child",然后我們點擊click按鈕改變count,這樣父組件中的count就會更新,但是問題來了我子組件也會重新渲染队橙,你可能會想傳遞給子組件的props沒有變坠陈,要是子組件不重新渲染就好了萨惑,為什么會這么想呢?我們可以假設(shè)一下我們抽出去的子組件是一個非常龐大的組件仇矾,渲染一次會消耗很多性能庸蔼,那么我們應(yīng)該盡量減少這個組件的渲染,那么我們該怎么實現(xiàn)呢贮匕?答案就是memo
姐仅,在給定相同props的情況下渲染相同的結(jié)果,但是這里要注意一個問題就是memo是淺比較
刻盐,意思就是對象只會比較內(nèi)存地址掏膏,只要內(nèi)存地址沒變,管你對象中的值怎么變化都不會觸發(fā)render敦锌。
下面來包裝一下上面那個例子馒疹。
const Child=memo(({name})=>{
console.log("this is Child");
return(
<div>{name}</div>
)
})
現(xiàn)在就實現(xiàn)了上面的功能。
memo高級用法:
默認情況下只會對props的對象進行淺層比較(淺層比較就是只會對比前后兩次props對象的引用是否相同乙墙,不會對比對象里面的內(nèi)容是否相同)如果想自己控制比較的過程那就需要自定義比較函數(shù)颖变,通過第二個參數(shù)傳入來實現(xiàn),下面是一個簡單的例子:
function areEqual(prevProps,nextProps){
return prevProps.data!==nextProps.data
}
const Child=memo((props)=>{
return(
<div>{props.data.count}</div>
)
},areEqual)
function Index(params) {
const [count,setCount]=useState(0);
const useCount=useRef({count:1})
const Countt=useRef(2)
useEffect(()=>{
let timer=setInterval(()=>{
setCount(++useCount.current.count)
},1000)
return ()=>{
clearInterval(timer)
}
},[])
return(
<div>
<div>{count}</div>
<button onClick={()=>{Countt.current++}}>click</button>
<Child data={useCount.current}></Child>
</div>
)
)
如果不加上判斷函數(shù)听想,子組件就不會去更新悼做,因為每次傳入的對象地址沒有變,但是對象中的數(shù)據(jù)發(fā)生改變哗魂。
useMemo
前面介紹的memo
是用來減少render次數(shù)肛走,那接下來介紹的就是用來減少計算次數(shù)。
先看一個例子:
const Child=memo((props)=>{
console.log("this is Child")
return(
<div>{props.data.name}</div>
)
})
function Index(params) {
const [count,setCount]=useState(0);
const [name,setName]=useState("小紅")
const data={
name
}
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child data={data}></Child>
</div>
)
}
這個例子和上面memo
講的例子有點相似录别,這個例子中我們每點擊一次父組件就會重新渲染一次朽色,那么就會重新生成一個data
對象,這個對象和上次的對象地址是不一樣的组题,所以子組件就檢測到了葫男,他發(fā)現(xiàn)你父組件傳過來的對象地址不一樣就會判斷上一次和這一次的對象是不一樣的然后就會刷新一次,但是這兩次對象的值是一樣的我再去重新渲染一次子組件實在是浪費性能崔列,所以又該怎么解決呢梢褐?有人可能會結(jié)合上面那個例子,把這個對象定義在useRef
中赵讯,然后再去定義一個判斷函數(shù)不就行了盈咳,這樣是可以實現(xiàn)我們的功能,但是再想想有沒有什么方法在父組件就給我們的對象做處理边翼,如果傳入子組件的對象沒有改變就不去重新生成一個新的對象鱼响,useMemo
這個API就出現(xiàn)了,我們看看他是怎么工作的组底。
usememo
有著暫存的能力丈积,會記住一些值筐骇,在重新渲染時會將依賴數(shù)組中的值取出來和上一次記錄的值進行比較, 如果不相等才會重新執(zhí)行回調(diào)函數(shù)江滨,否則直接返回記住的值 铛纬,對于這個例子沒有新的對象就沒有新的地址。
下面是改進后的:
const Child=memo((props)=>{
console.log("this is Child")
return(
<div>{props.data.name}</div>
)
})
function Index(params) {
const [count,setCount]=useState(0);
const [name,setName]=useState("小紅")
const data=useMemo(()=>{
return {
name
}
},[name])
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child data={data}></Child>
</div>
)
}
最后要提醒一下唬滑,如果沒有給usememo
提供依賴組告唆,他就會在每次渲染時都會重新計算新的值。
useCallback
usememo
和useCallback
相似间雀,都有著緩存的作用,但是他們本質(zhì)的區(qū)別就在于一個是緩存值的镊屎,一個是緩存函數(shù)的惹挟,這里要注意一下useCallback
有沒有后面的依賴很重要,如果沒有依賴缝驳,每次渲染還是會重新生成新的函數(shù)连锯。來看個例子:
const Child=memo((props)=>{
console.log("this is Child")
return(
<div>
<div>{props.name}</div>
<button onClick={props.click}>click child</button>
</div>
)
})
function Index(params) {
const [count,setCount]=useState(0);
const [age,setAge]=useState(12)
const callback=()=>{
setAge(age+1);
}
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child name="小明" click={callback}></Child>
</div>
)
}
我們來按操作一遍,頁面首次渲染頁面正常顯示同時也會打印出this is Child
用狱,當我們再次點擊父組件中的按鈕运怖,父組件中的count改變,但是控制臺又會打印一次this is Child
夏伊,說明子組件有重新渲染了一次摇展,但是子組件卻沒有什么變化,也沒有去觸發(fā)子組件中的事件溺忧,說明這次渲染是多余的咏连。那原因是什么呢?這其實我們呢上面也說過了鲁森,函數(shù)是組建每次渲染函數(shù)組件都會重頭開始重新執(zhí)行祟滴,那么這兩次創(chuàng)建的callback函數(shù)肯定發(fā)生變化了,所以導(dǎo)致子組件重新渲染歌溉。
然后就進入主題了垄懂,使用我們的useCallback
方法,改進下代碼
const Child=memo((props)=>{
console.log("this is Child")
return(
<div>
<div>{props.name}</div>
<button onClick={props.click}>click child</button>
</div>
)
})
function Index(params) {
const [count,setCount]=useState(0);
const [age,setAge]=useState(12)
const callback=useCallback(()=>{
setAge(age+1);
},[age])
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child name="小明" click={callback}></Child>
</div>
)
}