咱們知道,React 提供的單向數據流以及組件化幫助咱們將一個龐大的項目變爲小型、獨立、可複用的組件。但有時,咱們沒法進一步拆分很複雜的組件,由於它們內部的邏輯是有狀態的,沒法抽象爲函數式組件。因此有時咱們可能會寫出很是不適合複用性開發的:react
但謝天謝地,Hooks 的出現,讓咱們把組件內部的邏輯組織成爲了可複用的隔離單元。api
跨組件地複用包含狀態的邏輯,經過 Hooks 能夠將含有 state 的邏輯從組建抽象出來,同時也能夠幫助咱們在不重寫組件結構的狀況下複用邏輯。Hooks 通常是用於函數式組件的,在類class組件中無效。讓咱們根據代碼的做用將它們拆分,而不是生命週期。簡而言之, Hooks 實現了咱們在函數式組件中使用狀態變量與相似於生命週期的操做。數組
useState
建立Hookimport {useState} from 'react';
function hooks(){
// 聲明一個名爲 count 的新狀態變量
const [count, setCount] = useState(0);
// 第二個參數 setCount 爲一個能夠更新狀態的函數
// useState 的參數即爲初始值
return (
<div>
<p>當前的狀態量爲: {count}</p>
<button onClick={() => setCount(count + 1)}>點擊加一</button>
</div>
)
}
複製代碼
useEffect
來執行相應操做import {useState, useEffect} from 'react';
function hooks(){
const [count, setCount] = useState(0);
// 相似於 componentDidMount 和 componentDidUpdate
// 在 useEffect 中可使用組建的 state 和 props
// 在每次渲染後都執行 useEffect
useEffect(() => {
window.alert(`You have clicked ${count} times`);
})
return (
<div>
<p>當前的狀態量爲: {count}</p>
<button onClick={() => setCount(count + 1)}>點擊加一</button>
</div>
)
}
複製代碼
咱們在兩個不一樣的組件使用同一個鉤子,他們是相互獨立的,甚至在一個組件使用兩個鉤子他們也是相互獨立的。瀏覽器
React 實際上是根據useState
傳出現的順序來保證useState
之間相互獨立。緩存
// 首次渲染
const [num, setNum] = useState(1); // 將num初始化爲1
const [str, setStr] = useState('string'); // 將str初始化爲'string'
const [obj, setObj] = useState({id:1}); // ....
// 第二次渲染
const [num, setNum] = useState(1); // 讀取狀態變量num的值, 此時傳入的參數已被忽略,下同
const [str, setStr] = useState('string'); // 讀取狀態變量str的值
const [obj, setObj] = useState({id:1}); // ....
複製代碼
同時正是因爲根據順序保證獨立,因此 React 規定咱們必須把 hooks 寫在最外層,而不能寫在條件語句之中,來確保hooks的執行順序一致,若要進行條件判斷,咱們應該在 useEffect
的函數中寫入條件閉包
useEffect 來傳遞給 React 一個方法,React會在進行了 DOM 更新以後調用。咱們一般將 useEffect 放入組件內部,這樣咱們能夠直接訪問 state 與 props。記得,useEffect 在每次 render 後都要調用。異步
咱們有時須要從外部數據源獲取數據,此時咱們就要保證清理Effect來避免內存泄露 ,此時咱們須要在 effect 中返回一個函數來清理它, React 會在組件每次接觸掛載的時候清理。一個比較使用的場景就是咱們在 useEffect
中若執行了異步請求,因爲異步的時間不肯定性,咱們很須要在執行下一次異步請求時先結束上一次的請求,所以咱們就須要清理。async
useEffect(() => {
let canceled = false;
const getData = async () => {
const res = await fetch(api);
if(!canceled) {
// 展現 res
}
}
getData();
// return 的即爲咱們的清理函數
return () => {
canceled = true;
}
});
複製代碼
此時咱們在進行從新渲染時,就能夠避免異步請求帶來的競態問題,從而避免數據的不穩定性。函數
咱們能夠給useEffect
傳入第二個參數只有當第二個參數(數組)裏的全部的state 值發生變化時,才從新執行Effect組件化
useEffect(() => {
window.alert(`you had clicked ${count} times`);
}, [count]); //只有當 count 發生變化時纔會從新執行effect
複製代碼
因爲函數式組件中沒有 this ,因此咱們沒法使用ref,但hooks幫助咱們解決了這個問題,他提供了useRef
方法來爲咱們建立一個實例,而傳入的參數會被掛載在這個實例的.current
屬性上,返回的實例會持續到整個生命週期結束爲止。
function RefExample() {
const ref1 = useRef(null);
return (
<div>
<input ref={ref1} type="text" />
<button onClick={() => {ref1.current.focus()}}
</div>
)
}
複製代碼
若是比起上面的狀態變量類型,你更想要使用 Redux 類型的狀態管理,OK,React 也給咱們提供了useReducer
這個方法。做爲useState
的一種替代,咱們可使用dispatch
方法來改變狀態變量。
// 初始化的狀態變量
const initState = {count:0};
// 編寫 reducer 處理函數
function reducer(state, action) {
switch(action.type) {
case 'increment': return {count: state.count + 1};
case 'decrement': return {count: state.count - 1};
}
}
function counter({initState}) {
const [state, dispatch] = useReducer(reducer, initState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</div>
)
}
複製代碼
咱們能夠經過監聽狀態變量並在變換後執行回調函數來執行 Effect ,此時你可能會問,爲何使用 Hooks 會使用這麼多的 inline 函數,豈不是很影響性能? 謝天謝地,JavaScript 中的閉包函數的性能十分的快,它幫助了咱們不少。回調形式的 Hooks 有兩種,useCallback
與useMemo
.
兩者的轉換關係爲:
useCallback(fn, inputs) === useMemo(() => fn, inputs)
useCallback
是如何幫助咱們提高性能的呢? 實際上,它實際上是緩存了每次渲染時的 inline 回調函數的實例,以後不管是配合shouldComponentUpdate
或者是 React.memo
都可以達到減小沒必要要的渲染的做用。這也提示咱們,React.memo
和React.useCallback
通常是配合使用,缺了其一均可能沒法達到提高性能的功效。
下面以一個表單組件表示使用方法
function FormComponent() {
const [text, setText] = useState(' ');
const handleSubmit = useCallback(() => {
console.log(`new test is ${text}`);
}, [text]);
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<BigTree onSubmit={handleSubmit} /> // 巨大無比的組件,不優化卡的不行
</div>
)
}
複製代碼
但此時有一個很嚴重的問題,就是咱們的 BigTree 依賴於一個太容易變化的 state, 只要咱們在input框隨意輸入, BigTree 就會從新渲染好屢次來獲取最新的callback,此時這個callback就沒法使用緩存了。
一個解決辦法是咱們定義一個新的實例,這個實例只有在 re-render 時纔會更新最新的值,這樣咱們就能夠不根據一個常常變換的state,而是根據一個在 useLayoutEffect
中更新的ref實例來更新。
function FormComponent() {
const [text, setText] = useState(' ');
const textRef = useRef();
useLayoutEffect(() => {
textRef.current = text;
})
const handleSubmit = useCallback(() => {
console.log(`new test is ${text}`);
}, [textRef]); // 只根據 textRef 的變化而產生變化,並不會在 text 改變就變化
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<BigTree onSubmit={handleSubmit} /> // 巨大無比的組件,不優化卡的不行
</div>
)
}
複製代碼
DOM 突變以後,從新繪製以前同步觸發
它與 useEffect
的做用相同,都是用來執行反作用的,但不一樣的是,它會在全部的 DOM 變動結束後同步地調用 effect。一個與 useEffect
很大的區別是,useLayoutEffect
是同步地,而useEffect
是異步的,在瀏覽器從新繪製頁面佈局前,useLayoutEffect
內部的更新將會同步刷新,但官方給出的建議是儘可能使用useEffect
來避免阻塞視覺更新。
render props
與 高階組件
時產生的誇張的層級嵌套。