Hook是 React16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。Hook 是一些可以讓你在函數元件裡“鈎入” React state 及生命周期等特性的函數。Hook 不能在 class 元件中使用。
一、Hook的優點
Hook 是一個特殊的函數,它可以讓你“鈎入” React 的特性。例如,useState 是允許你在 React 函數元件中添加 state 的 Hook。如果你在編寫函數元件并意識到需要向其添加一些 state,以前的做法是必須将其它轉化為 class。現在你可以在現有的函數元件中使用 Hook。
- 簡化邏輯複用:在之前的React使用中難以實作邏輯的複用,必須借助于高階元件等複雜的設計模式。但是高階元件會産生備援的元件節點,讓調試變得困難。是以Hooks的好處就是簡化了邏輯複用。
- 有助于關注分離:意思是說Hooks能夠讓針對用一個業務邏輯的代碼盡可能聚合在一塊。在過去的Class元件中是很難做到的。因為Class元件中,不得不把同一個業務邏輯的代碼分散在類元件的不同生命周期的方法中。是以通過Hooks的方式,把業務邏輯清晰地隔離開,能夠讓代碼更加容易了解和維護。
二、useState狀态鈎子
useState()用于為函數元件引入狀态(state)。純函數不能有狀态,是以把狀态放在鈎子裡面。
useState讓函數式元件支援state狀态。通過在函數元件裡調用它來給元件添加一些内部 state。React 會在重複渲染時記住它目前state的值,并且提供最新的值給我們的函數。
useState 傳回一個隻有兩個元素的數組:
- 第一個元素是目前的 state 的值。
- 第二個元素是一個函數,用來替換原來state中的值,這個函數的修改state和setState一樣是異步的,你可以在事件處理函數中或其他一些地方調用這個函數。它類似 class 元件的 this.setState。
useState 唯一的參數就是初始 state,這個初始 state 參數隻有在第一次渲染時會被用到。
import { useState } from 'react'
export default function App() {
// 聲明一個叫 count 的 state 變量 useState(0) 傳參 0 為設定的初始值
const [count, setCount] = useState(0);//[] 數組解構 取到數組中對應位置的值 賦給相應變量
const [isHot, setIsHot] = useState(true);
const changeCount = () => {
setCount(count + 1)
console.log(count, "1111");
}
const changeHot = () => {
setIsHot(!isHot)
console.log(isHot, "1111");
}
console.log("元件被重新渲染了");
return (
<div>
<h1>累加的值是{count}</h1>
<h2>天氣真的{isHot ? "熱啊" : "冷a"}</h2>
<button onClick={changeCount}>累加</button>
<button onClick={changeHot}>變天</button>
</div>
)
}
三、useEffect副作用鈎子
useEffect 就是一個 Effect Hook,可以讓你在函數元件中執行副作用操作。
useEffect可以告訴 React 元件需要在挂載完成、更新完成、解除安裝前執行某些操作。它跟 class 元件中的componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,隻不過被合并成了一個 API。
它的常見用途有下面幾種。
- 擷取資料(data fetching)
- 事件監聽或訂閱(setting up a subscription)
- 改變 DOM(changing the DOM)
- 輸出日志(logging)
useEffect接受兩個參數:
- 第一個參數是一個回調函數,當達到條件的時候,會觸發目前的回調函數。
- 第二個參數是一個數組,數組中傳入state,則目前state發生改變的時候觸發目前effect中的回調函數。如果傳遞的是一個空數組,則useEffct回調函數隻有在初始化階段才執行。如果不傳遞第二個參數,則無論初始化還是更新的時候都會執行。
import React, { useEffect, useState } from 'react'
export default function App() {
const [count, setCount] = useState(0);
const [isHot, setIsHot] = useState(true);
useEffect(() => {
console.log("what happened?");
});
useEffect(() => {
console.log("元件渲染了");
}, []);
useEffect(() => {
console.log("isHot執行了");
}, [isHot]);
useEffect(() => {
console.log("count執行了");
}, [count])
return (
<div>
<h1>count的值是{count}</h1>
<button onClick={() => { setCount(count + 1) }}>累加</button>
<hr />
<h1>今天的天氣真{isHot ? "晴朗" : "下雨"}</h1>
<button onClick={() => { setIsHot(!isHot) }}>修改天氣</button>
</div>
)
}
如何使用不需要清除的副作用,還有一些副作用是需要清除的。例如訂閱外部資料源。這種情況下,清除工作是非常重要的,可以防止引起記憶體洩露!
useEffect的回調函數可以傳回一個函數,當元件被解除安裝的時候,或再次渲染的時候,會執行這個函數,用來做收尾工作。
import { useState } from "react";
import { useEffect } from "react";
export default function App() {
let [opacity, setOpacity] = useState(1);
const [count, setCount] = useState(0);
//初始化的時候添加一個定時器
useEffect(() => {
const opacityTimer = setInterval(() => {
opacity -= 0.1;
if (opacity <= 0) {
opacity = 1;
}
setOpacity(opacity);
}, 200);
return () => {
console.log("清空定時器" + opacityTimer);
clearInterval(opacityTimer);
};
}, []);
useEffect(() => {
console.log("count初始或更新了");
return () => {
console.log("gogogo");
};
}, [count]);
return (
<div>
<h1>{count}</h1>
<hr />
<h1 style={{ opacity }}>ReactHook 真好用</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
累加
</button>
</div>
);
}
為什麼要在 effect 中傳回一個函數?這是 effect 可選的清除機制。每個 effect 都可以傳回一個清除函數。如此可以将添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分。
React 何時清除 effect?React 會在元件解除安裝的時候執行清除操作。effect 在每次渲染的時候都會執行。這就是為什麼 React 會在執行目前 effect 之前對上一個 effect 進行清除。
四、useContext共享狀态鈎子
如果需要在元件之間共享狀态,可以使用useContext()。
第一步
React Context API,在元件外部建立一個 Context
export const AppContext = React.createContext();//可以接受一個初始值
第二步
AppContext.Provider提供了一個 Context 對象,這個對象可以被子元件共享。共享對象AppContext上有一個Provide屬性是一個元件,他可以給元件内部包含的元件提供資料,提供的資料放在value屬性上。
<AppContext.Provider value={count}>
<List />
</AppContext.Provider>
第三步
useContext()鈎子函數用來引入 AppContext對象,從中擷取count的值。
import Item from "./Item"
export default function List() {
return (
<div>
<Item />
</div>
)
}
import { useContext } from 'react'
import { AppContext } from '../../../App'
export default function Item() {
const count = useContext(AppContext);
return (
<div>
<h1>Item{count}</h1>
</div>
)
}
調用了 useContext 的元件總會在 context 值變化時重新渲染。
五、useMemo
useMemo 的基本概念就是:它能幫助我們 “記錄” 每次渲染之間的計算值。并在元件重新渲染時,做出一些不同的決策。
useMemo接受2個參數:
- 第一個參數:需要執行的一些計算處理工作,包裹在一個函數中。
- 第二個參數:一個依賴數組。
當沒使用useMemo時,元件的渲染情況:
import { useEffect, useState } from "react";
import { format } from "date-fns";
function App() {
const [num, setNum] = useState("0");
const [time, setTime] = useState(new Date());
useEffect(() => {
setTimeout(() => {
setTime(new Date());
}, 1000);
}, [time]);
const getNum = () => {
console.log("正在進行大量運算!!!");
return num;
};
return (
<div>
<h1>App</h1>
<p>{format(time, "hh:mm:ss a")}</p>
<input
type="text"
value={num}
onChange={(e) => {
setNum(e.target.value);
}}
/>
<h2>{getNum()}</h2>
</div>
);
}
export default App;
此時當time更新,就會導緻元件重新渲染,緻使getNum函數一直在重新調用,但擷取的num的值卻沒有變化。那麼我們就可以使用useMemo這個Hook。
import { useMemo, useEffect, useState } from "react";
import { format } from "date-fns";
function App() {
const [num, setNum] = useState("0");
const [time, setTime] = useState(new Date());
useEffect(() => {
setTimeout(() => {
setTime(new Date());
}, 1000);
}, [time]);
const getNum = () => {
console.log("正在進行大量運算!!!");
return num;
};
const result = useMemo(() => {
return getNum();
}, [num]);
return (
<div>
<h1>App</h1>
<p>{format(time, "hh:mm:ss a")}</p>
<input
type="text"
value={num}
onChange={(e) => {
setNum(e.target.value);
}}
/>
<h2>{result}</h2>
{/* <h2>{getNum()}</h2> */}
</div>
);
}
export default App;
對于每一個後續的渲染,React 都要從以下兩種情況中做出選擇:
1.再次調用 useMemo 中的計算函數,重新計算數值;
2.重複使用上一次已經計算出來的資料。
為了做出一個正确的選擇,React 會判斷你傳入的依賴數組,這個數組中的每個變量是否在兩次渲染間 值是否改變了 ,如果發生了改變,就重新執行計算的邏輯去擷取一個新的值,否則不重新計算,直接傳回上一次計算的值。
useMemo 本質上就像一個小的緩存,而依賴數組就是緩存的失效政策。
六、useCallback
簡單概括:useMemo 和 useCallback 是一個東西,隻是将傳回值從 數組/對象 替換為了 函數。
父元件:
import React, { useState } from "react";
import Child from "./Child";
function App() {
const [count, setCount] = useState(0);
const [num, setNum] = useState(3);
const addNum = () => {
setNum(num + 3);
};
const addCount = () => {
setCount(count + 1);
};
return (
<div>
<h1>App</h1>
<p>count:{count}</p>
<p>num:{num}</p>
<button onClick={addCount}>addCount累加</button>
{/* 更新count的方法由子元件觸發,傳遞方法給子元件 */}
<Child add={addNum} />
</div>
);
}
export default App;
子元件:
import React, { memo, useState } from "react";
interface ChildProps {
add(): void;
}
export default memo(function Child({ add }: ChildProps) {
console.log("Child render");
return (
<div>
<h1>Child</h1>
<button onClick={add}>addNum累加</button>
</div>
);
});
父元件重新渲染會重新定義addNum、addCount兩個函數,子元件接受到的porps會發生變化,便也會重新渲染。當我們調用父元件的addCount事件,隻渲染目前的count,傳遞給子元件的addNum函數不需要重新生成,子元件也不用重新渲染。這時候就可以用到useCallback Hook,專門用來緩存函數的,必須要指定依賴項(第二個參數)。
import React, { useCallback, useState } from "react";
import Child from "./Child";
function App() {
const [count, setCount] = useState(0);
const [num, setNum] = useState(3);
// const addNum = () => {
// setNum(num + 3);
// };
const addNum = useCallback(() => {
setNum(num + 3);
}, [num]);
const addCount = () => {
setCount(count + 1);
};
return (
<div>
<h1>App</h1>
<p>count:{count}</p>
<p>num:{num}</p>
<button onClick={addCount}>addCount累加</button>
{/* 更新count的方法由子元件觸發,傳遞方法給子元件 */}
<Child add={addNum} />
</div>
);
}
export default App;
useCallback的作用:
1.用來緩存函數的;
2.當依賴項資料發生變化,才會生成新的函數,否則一直使用之前的函數
3.注意使用場景:如果函數功能很簡單,就沒必要緩存了;如果這個函數要傳遞給其他元件使用,需要緩存;如果這個函數本身功能比較複雜,需要緩存。
總結
本文主要介紹了目前React當中的常見基礎Hook,分别有useState、useEffect、useContext、useMemo、useCallback。useState通過在函數元件裡調用它來給元件添加一些内部 state。React 會在重複渲染時保留這個 state。useEffect給函數元件增加了操作副作用的能力。useContext給函數元件之間共享狀态。useMemo 和 useCallback 都是用來幫助我們優化 重新渲染 的工具 Hook。它們通過以下兩種方式實作優化的效果。減少在一次渲染中需要完成的工作量。減少一個元件需要重新渲染的次數。