React Hooks
useState
申明響應式變量時我們會使用到的hook,
const [ ①state , ②dispatch ] = useState(③initData)
這邊使用到 array 的解構語法,也就是將 useState 回傳的 array 中的值個別取出來,counter 為 state 變數,setCounter 則是負責改變 state 變數的函式,一般命名傳統習慣將此函式命名為 set+state 變數名稱,需要特別注意的是更改state一律只能透過set(setCounter)函式更改,useState所帶的參數為這個 state 的初始值,而一個component中可以有多個state,如果兩次 dispatchAction 傳入相同的 state 值,那麼組件就不會更新。
注意事項:
- 僅頂層調用 Hook :不能在循環,條件,嵌套函數等中調用useState()。在多個useState()調用中,渲染之間的調用順序必須相同。
- React 18 後的版本, setState 都是以非同步的方式更新
setState本身一直一個同步函數, 我們指的是調用完setState後react會同步的去執行後續的步驟還是會異步的去執行後續的步驟
來看一個例子:
下面的 code,即使點擊一次 button,counter
也只會 + 1
export default function App() {
const [counter, setCounter] = useState(0);
const handleClick = () => {
setCounter(counter + 1);
setCounter(counter + 1);
}
return (
<div className='App'>
<h1>Function Component</h1>
<div>
counter: {counter}
</div>
<br/>
<button onClick={handleClick}>Click me</button>
</div>
);
}
當這段 code 被送出去時,state 的指向的是過去的 state,換句話說,這段可以看成這樣:
setCounter(0 + 1);
setCounter(0 + 1);
如果上面的 Code 要改成 +2,則可以:
const increase = prevCounter => prevCounter + 1
const handleClick = () => {
setCounter(increase);
setCounter(increase);
}
// (推薦) 在setCount裡的函數中傳入一個方法
const handleButton = () => {
setCounter(prev => prev + 1)
setCounter(prev => prev + 1)
setCounter(prev => prev + 1)
setCounter(prev => prev + 1)
}
接下來在看下另外個可能遇到的坑,實際會發現count並沒有像想像中一樣輸出1,而是0
const [count, setCount] = useState(0)
const onCountup = () => {
setCount( count + 1 );
console.log(count)
}
以下為幾個可以用到的解決方法
1. 使用useEffect
const [count, setCount] = useState(0)
useEffect(() => {
console.log(count)
}, [count])
<button onClick={() => {
setCount(count + 1)
}}>加一</button>
2. 將變量存起來
const [square, setSquare] = useState(0)
const [count, setCount] = useState(0)
const onCountup = () => {
const newCount = count + 1
setCount( newCount );
setSquare( newCount * newCount );
}
3. 推薦 在setCount裡的函數中傳入一個方法
const onCountup = () => {
setCount(preCount => {
const newCount = preCount + 1;
setSquare( newCount * newCount );
return newCount;
});
}
在React 18更新中,將所有狀態更新都會執行automatic batching。因此以下例子在React 18中, count依然會是1,而且只會re-render一次。
export function Counter(){
const [count, setCount] = useState(0)
const handleClick = ()=>{
async function callServer(){
const res = await fetch('/count')
const result = await res.json()
setCount(count + 1)
setCount(count + 1)
// Automatic batching
}
}
return (
<div>
<button onClick={handleClick}>
</div>
)
}
useEffect✅
在function組件中,當想在組件完成掛載,dom渲染完成,做一些操縱dom,請求數據,那麼useEffect是一個不二選擇。useEffect是一個接受兩個參數的函數。傳遞給useEffect的第一個參數是一個名為effect的函數(你可以猜到為什麼這個鉤子叫useEffect),第二個參數(是可選的)是一個存儲依賴關係的數組。
特別注意的是,如果不給useEffect執行加入限定條件,函數組件每一次更新都會觸發effect ,那麼也就說明每一次state更新,或是props的更新都會觸發useEffect執行,此時的effect又充當了componentDidUpdate和componentwillreceiveprops,所以說合理的用於useEffect就要給effect加入限定執行的條件,也就是useEffect的第二個參數,這裡說是限定條件,也可以說是上一次useeffect更新收集的某些記錄數據變化的記憶,在新的一輪更新,useeffect會拿出之前的記憶值和當前值做對比,如果發生了變化就執行新的一輪useEffect的副作用函數,useEffect第二個參數是一個數組,用來收集多個限制條件 。
useEffect 第一個參數 callback, 返回的 destory , destory 作爲下一次 callback 執行之前調用,用於清除上一次 callback 產生的副作用。
第二個參數作爲依賴項,是一個數組,可以有多個依賴項,依賴項改變,執行上一次 callback 返回的 destory ,和執行新的 effect 第一個參數 callback 。
對於 useEffect 執行, React 處理邏輯是採用異步調用 ,對於每一個 effect 的 callback, React 會向 setTimeout 回調函數一樣,放入任務隊列,等到主線程任務完成,DOM 更新,js 執行完成,視圖繪製完畢,才執行。所以 effect 回調函數不會阻塞瀏覽器繪製視圖。
useEffect(()=>{
return destory
},dep)
如下在 useEffect 中做的功能如下:
-
① 請求數據。
-
② 設置定時器, 延時器等。
-
③ 操作 dom , 在 React Native 中可以通過 ref 獲取元素位置信息等內容。
-
④ 註冊事件監聽器, 事件綁定,在 React Native 中可以註冊 NativeEventEmitter 。
-
⑤ 還可以清除定時器,延時器,解綁事件監聽器等。
記得要清除副作用
function getUserInfo(a){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({
name:a,
age:16,
})
},500)
})
}
const Demo = ({ a }) => {
const [ userMessage , setUserMessage ] :any= useState({})
const div= useRef()
const [number, setNumber] = useState(0)
/* 模擬事件監聽處理函數 */
const handleResize =()=>{}
/* useEffect使用 ,這裏如果不加限制 ,會是函數重複執行,陷入死循環*/
useEffect(()=>{
/* 請求數據 */
getUserInfo(a).then(res=>{
setUserMessage(res)
})
/* 定時器 延時器等 */
const timer = setInterval(()=>console.log(666),1000)
/* 操作dom */
console.log(div.current) /* div */
/* 事件監聽等 */
window.addEventListener('resize', handleResize)
/* 此函數用於清除副作用 */
return function(){
clearInterval(timer)
window.removeEventListener('resize', handleResize)
}
/* 只有當props->a和state->number改變的時候 ,useEffect副作用函數重新執行 ,如果此時數組爲空[],證明函數只有在初始化的時候執行一次相當於componentDidMount */
},[ a ,number ])
return (<div ref={div} >
<span>{ userMessage.name }</span>
<span>{ userMessage.age }</span>
<div onClick={ ()=> setNumber(1) } >{ number }</div>
</div>)
}
依舊來整理下可能遇到的坑
function App(props){
const [data, setData] = useState(null);
const fetchData = () => {
//fetch some data
}
useEffect(() => {
fetchData(); //Invoked inside useEffect
}, [fetchData])
}
在外面定義一個函數,然後在effect裡面調用它。上面的情況會導致每次渲染都會調用fetchData,因為傳遞的依賴是一個函數,而函數是對象。 React會比較上一次和當前渲染的fetchData,兩者是不一樣的,因此會觸發對effect的調用。
我們可以使用下面方法代替
useEffect(() => {
// declare the data fetching function
const fetchData = async () => {
const data = await fetch('https://yourapi.com');
}
// call the function
fetchData()
// make sure to catch any error
.catch(console.error);
}, [])
useEffect 清理機制
import React, { useState, useEffect } from "react";
export default function Post() {
const [posts, setPosts] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
.then((res) => res.json())
.then((res) => setPosts(res))
.catch((err) => {
if (err.name === "AbortError") {
console.log("successfully aborted");
} else {
setError(err);
}
});
return () => controller.abort();
}, []);
return (
<div>
{!error ? (
posts.map((post) => (
<ul key={post.id}>
<li>{post.title}</li>
</ul>
))
) : (
<p>{error}</p>
)}
</div>
);
}
React 常見CSS管理方式
- 一:namespaces
- 二:CSS in JS
- 三:CSS Modules
- 四:utility-first CSS Framework
那麼如果我們想再要useEffect中調用個異步的方法時
useCallback✅
useContext✅
useLayoutEffect ✅
useMemo ✅
避免重複進行複雜耗時的計算
useState派發更新函數的執行,就會讓整個function組件從頭到尾執行一次,所以需要配合useMemo,usecallback等api配合使用
useReducer✅
useRef✅
flushSync
以上不定時更新