本身在掘金上看了也看了不少關於hooks的文章,感受都講得不是很詳細。並且也有不少的水文。最近本身打算重學react,系統性的再把hooks給學習一遍。html
類組件的缺點:(來自官網動機)react
在組件之間複用狀態邏輯很難es6
複雜組件變得難以理解編程
難以理解的 classapi
你必須去理解 JavaScript 中 this
的工做方式,這與其餘語言存在巨大差別。還不能忘記綁定事件處理器。沒有穩定的語法提案,代碼很是冗餘。數組
hooks的出現,解決了上面的問題。另外,還有一些其餘的優勢:緩存
1. 只在最頂層使用 Hook,不要在循環,條件或嵌套函數中調用 Hook安全
確保老是在你的 React 函數的最頂層調用他們。遵照這條規則,你就能確保 Hook 在每一次渲染中都按照一樣的順序被調用。這讓 React 可以在屢次的 useState
和 useEffect
調用之間保持 hook 狀態的正確。性能優化
2. 只在 React 函數中調用 Hookbabel
不要在普通的 JavaScript 函數中調用 Hook,你能夠:
至於爲何會有這些規則,若是你感興趣,請參考Hook 規則
const [state, setState] = useState(initialState)
import React,{useState} from "react";
function Example() {
const [count, setCount] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
);
}
export default Example;
複製代碼
useState
Hook。它讓咱們在函數組件中存儲內部 state。Example
組件內部,咱們經過調用 useState
Hook 聲明瞭一個新的 state 變量。它返回一對值給到咱們命名的變量上。咱們把變量命名爲 count
,由於它存儲的是點擊次數。咱們經過傳 0
做爲 useState
惟一的參數來將其初始化爲 0
。第二個返回的值自己就是一個函數。它讓咱們能夠更新 count
的值,因此咱們叫它 setCount
。setCount
。React 會從新渲染 Example
組件,並把最新的 count
傳給它。// 聲明多個 state 變量
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '學習 Hook' }]);
複製代碼
你沒必要使用多個 state 變量。State 變量能夠很好地存儲對象和數組,所以,你仍然能夠將相關數據分爲一組。
import React,{useState} from "react";
function Example() {
const [count, setCount] = useState(0);
const [person, setPerson] = useState({name:'jimmy',age:22});
return (
<div> <p>name {person.name} </p> // 若是新的 state 須要經過使用先前的 state 計算得出,那麼能夠將回調函數當作參數傳遞給 setState。 // 該回調函數將接收先前的 state,並返回一個更新後的值。 <button onClick={() => setCount(count=>count+1)}>Click me</button> <button onClick={() => setPerson({name:'chimmy'})}>Click me</button> </div>
);
}
export default Example;
複製代碼
setPerson更新person時,不像 class 中的 this.setState
,更新 state 變量老是替換它而不是合併它。上例中的person爲{name:'chimmy'} 而不是{name:'chimmy',age:22}
Effect Hook 可讓你在函數組件中執行反作用(數據獲取,設置訂閱以及手動更改 React 組件中的 DOM 都屬於反作用)操做
useEffect(fn, array)
useEffect在初次完成渲染以後都會執行一次, 配合第二個參數能夠模擬類的一些生命週期。
若是你熟悉 React class 的生命週期函數,你能夠把 useEffect
Hook 看作 componentDidMount``componentDidUpdate
和 componentWillUnmount
這三個函數的組合。
若是第二個參數爲空數組,useEffect至關於類組件裏面componentDidMount。
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("我只會在組件初次掛載完成後執行");
}, []);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
);
}
export default Example;
複製代碼
頁面渲染完成後,會執行一次useEffect。打印「我只會在組件初次掛載完成後執行」,當點擊按鈕改變了state,頁面從新渲染後,useEffect不會執行。
若是不傳第二個參數,useEffect 會在初次渲染和每次更新時,都會執行。
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("我會在初次組件掛載完成後以及從新渲染時執行");
});
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
);
}
export default Example;
複製代碼
初次渲染時,會執行一次useEffect,打印出「我會在初次組件掛載完成後以及從新渲染時執行」。 當點擊按鈕時,改變了state,頁面從新渲染,useEffect都會執行,打印出「我會在初次組件掛載完成後以及從新渲染時執行」。
effect 返回一個函數,React 將會在執行清除操做時調用它。
useEffect(() => {
console.log("訂閱一些事件");
return () => {
console.log("執行清除操做")
}
},[]);
複製代碼
注意:這裏不僅是組件銷燬時纔會打印「執行清除操做」,每次從新渲染時也都會執行。至於緣由,我以爲官網解釋的很清楚,請參考 解釋: 爲何每次更新的時候都要運行 Effect
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
const [number, setNumber] = useState(1);
useEffect(() => {
console.log("我只會在cout變化時執行");
}, [count]);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click cout</button> <button onClick={() => setNumber(number + 1)}>Click number</button> </div>
);
}
export default Example;
複製代碼
上面的例子,在點擊 click cout按鈕時,纔會打印「我只會在cout變化時執行」。 由於useEffect 的第二個參數的數組裏面的依賴是cout,因此,只有cout發生改變時,useEffect 纔會執行。若是數組中有多個元素,即便只有一個元素髮生變化,React 也會執行 effect。
使用 Hook 其中一個目的就是要解決 class 中生命週期函數常常包含不相關的邏輯,但又把相關邏輯分離到了幾個不一樣方法中的問題。
import React, { useState, useEffect } from "react";
function Example() {
useEffect(() => {
// 邏輯一
});
useEffect(() => {
// 邏輯二
});
useEffect(() => {
// 邏輯三
});
return (
<div> useEffect的使用 </div>
);
}
export default Example;
複製代碼
Hook 容許咱們按照代碼的用途分離他們, 而不是像生命週期函數那樣。React 將按照 effect 聲明的順序依次調用組件中的每個 effect。
useEffect是不能直接用 async await 語法糖的
/* 錯誤用法 ,effect不支持直接 async await*/
useEffect(async ()=>{
/* 請求數據 */
const res = await getData()
},[])
複製代碼
useEffect
的回調參數返回的是一個清除反作用的 clean-up
函數。所以沒法返回 Promise
,更沒法使用 async/await
那咱們應該如何讓useEffect
支持async/await
呢?
const App = () => {
useEffect(() => {
(async function getDatas() {
await getData();
})();
}, []);
return <div></div>;
};
複製代碼
useEffect(() => {
const getDatas = async () => {
const data = await getData();
setData(data);
};
getDatas();
}, []);
複製代碼
經過使用這個 Hook,你能夠告訴 React 組件須要在渲染後
執行某些操做。React 會保存你傳遞的函數(咱們將它稱之爲 「effect」),而且在執行 DOM 更新以後調用它。
useEffect
?將 useEffect
放在組件內部讓咱們能夠在 effect 中直接訪問 count
state 變量(或其餘 props)。咱們不須要特殊的 API 來讀取它 —— 它已經保存在函數做用域中。Hook 使用了 JavaScript 的閉包機制,而不用在 JavaScript 已經提供瞭解決方案的狀況下,還引入特定的 React API。
const value = useContext(MyContext);
接收一個 context 對象(React.createContext
的返回值)並返回該 context 的當前值。當組件上層最近的 <MyContext.Provider>
更新時,該 Hook 會觸發重渲染,並使用最新傳遞給 MyContext
provider 的 context value
值。即便祖先使用 React.memo
或 shouldComponentUpdate
,也會在組件自己使用 useContext
時從新渲染。
別忘記 useContext
的參數必須是 context 對象自己:
useContext(MyContext)
useContext(MyContext.Consumer)
useContext(MyContext.Provider)
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// 建立兩個context
export const UserContext = React.createContext();
export const TokenContext = React.createContext();
ReactDOM.render(
<UserContext.Provider value={{ id: 1, name: "chimmy", age: "20" }}> <TokenContext.Provider value="我是token"> <App /> </TokenContext.Provider> </UserContext.Provider>,
document.getElementById("root")
);
複製代碼
app.js
import React, { useContext } from "react";
import { UserContext, TokenContext } from "./index";
function Example() {
let user = useContext(UserContext);
let token = useContext(TokenContext);
console.log("UserContext", user);
console.log("TokenContext", token);
return (
<div> name:{user?.name},age:{user?.age} </div>
);
}
export default Example;
複製代碼
打印的值以下
若是你在接觸 Hook 前已經對 context API 比較熟悉,那應該能夠理解,useContext(MyContext)
至關於 class 組件中的 static contextType = MyContext
或者 <MyContext.Consumer>
。
useContext(MyContext)
只是讓你可以讀取 context 的值以及訂閱 context 的變化。你仍然須要在上層組件樹中使用 <MyContext.Provider>
來爲下層組件提供 context。
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState
的替代方案。它接收一個形如 (state, action) => newState
的 reducer,並返回當前的 state 以及與其配套的 dispatch
方法。(若是你熟悉 Redux 的話,就已經知道它如何工做了。)
在某些場景下,useReducer
會比 useState
更適用,例如 state 邏輯較複雜且包含多個子值,或者下一個 state 依賴於以前的 state 等。而且,使用 useReducer
還能給那些會觸發深更新的組件作性能優化,由於你能夠向子組件傳遞 dispatch
而不是回調函數
React 會確保
dispatch
函數的標識是穩定的,而且不會在組件從新渲染時改變。這就是爲何能夠安全地從useEffect
或useCallback
的依賴列表中省略dispatch
。
import React, { useReducer } from "react";
export default function Home() {
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, counter: state.counter + 1 };
case "decrement":
return { ...state, counter: state.counter - 1 };
default:
return state;
}
}
const [state, dispatch] = useReducer(reducer, { counter: 0 });
return (
<div> <h2>Home當前計數: {state.counter}</h2> <button onClick={(e) => dispatch({ type: "increment" })}>+1</button> <button onClick={(e) => dispatch({ type: "decrement" })}>-1</button> </div>
);
}
複製代碼
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
複製代碼
返回一個 [memoized]回調函數。
把內聯回調函數及依賴項數組做爲參數傳入 useCallback
,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時纔會更新。當你把回調函數傳遞給通過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子組件時,它將很是有用。
import React, { useState } from "react";
// 子組件
function Childs(props) {
console.log("子組件渲染了");
return (
<> <button onClick={props.onClick}>改標題</button> <h1>{props.name}</h1> </>
);
}
const Child = React.memo(Childs);
function App() {
const [title, setTitle] = useState("這是一個 title");
const [subtitle, setSubtitle] = useState("我是一個副標題");
const callback = () => {
setTitle("標題改變了");
};
return (
<div className="App"> <h1>{title}</h1> <h2>{subtitle}</h2> <button onClick={() => setSubtitle("副標題改變了")}>改副標題</button> <Child onClick={callback} name="桃桃" /> </div>
);
}
複製代碼
執行結果以下圖
當我點擊改副標題這個 button 以後,副標題會變爲「副標題改變了」,而且控制檯會再次打印出子組件渲染了
,這就證實了子組件從新渲染了,可是子組件沒有任何變化,那麼此次 Child 組件的從新渲染就是多餘的,那麼如何避免掉這個多餘的渲染呢?
咱們在解決問題的以前,首先要知道這個問題是什麼緣由致使的?
我們來分析,一個組件從新從新渲染,通常三種狀況:
接下來用排除法查出是什麼緣由致使的:
第一種很明顯就排除了,當點擊改副標題 的時候並無去改變 Child 組件的狀態;
第二種狀況,咱們這個時候用 React.memo
來解決了這個問題,因此這種狀況也排除。
那麼就是第三種狀況了,當父組件從新渲染的時候,傳遞給子組件的 props 發生了改變,再看傳遞給 Child 組件的就兩個屬性,一個是 name
,一個是 onClick
,name
是傳遞的常量,不會變,變的就是 onClick
了,爲何傳遞給 onClick 的 callback 函數會發生改變呢?其實在函數式組件裏每次從新渲染,函數組件都會重頭開始從新執行,那麼這兩次建立的 callback 函數確定發生了改變,因此致使了子組件從新渲染。
const callback = () => {
doSomething(a, b);
}
const memoizedCallback = useCallback(callback, [a, b])
複製代碼
把函數以及依賴項做爲參數傳入 useCallback
,它將返回該回調函數的 memoized 版本,這個 memoizedCallback 只有在依賴項有變化的時候纔會更新。
那麼只需這樣將傳給Child組件callback函數的改造一下就OK了
const callback = () => { setTitle("標題改變了"); };
// 經過 useCallback 進行記憶 callback,並將記憶的 callback 傳遞給 Child
<Child onClick={useCallback(callback, [])} name="桃桃" />
複製代碼
這樣咱們就能夠看到只會在首次渲染的時候打印出子組件渲染了,當點擊改副標題和改標題的時候是不會打印子組件渲染了的。
const cacheSomething = useMemo(create,deps)
create
:第一個參數爲一個函數,函數的返回值做爲緩存值。deps
: 第二個參數爲一個數組,存放當前 useMemo 的依賴項,在函數組件下一次執行的時候,會對比 deps 依賴項裏面的狀態,是否有改變,若是有改變從新執行 create ,獲得新的緩存值。cacheSomething
:返回值,執行 create 的返回值。若是 deps 中有依賴項改變,返回的從新執行 create 產生的值,不然取上一次緩存值。useMemo 會記錄上一次執行 create 的返回值,並把它綁定在函數組件對應的 fiber 對象上,只要組件不銷燬,緩存值就一直存在,可是 deps 中若是有一項改變,就會從新執行 create ,返回值做爲新的值記錄到 fiber 對象上。
function Child(){
console.log("子組件渲染了")
return <div>Child</div>
}
const Child = memo(Child)
function APP(){
const [count, setCount] = useState(0);
const userInfo = {
age: count,
name: 'jimmy'
}
return <Child userInfo={userInfo}> } 複製代碼
當函數組件從新render時,userInfo每次都將是一個新的對象,不管 count
發生改變沒,都會致使 Child組件的從新渲染。
而下面的則會在 count
改變後纔會返回新的對象。
function Child(){
console.log("子組件渲染了")
return <div>Child</div>
}
function APP(){
const [count, setCount] = useState(0);
const userInfo = useMemo(() => {
return {
name: "jimmy",
age: count
};
}, [count]);
return <Child userInfo={userInfo}> } 複製代碼
實際上 useMemo 的做用不止於此,根據官方文檔內介紹:以把一些昂貴的計算邏輯放到 useMemo 中,只有當依賴值發生改變的時候纔去更新。
import React, {useState, useMemo} from 'react';
// 計算和的函數,開銷較大
function calcNumber(count) {
console.log("calcNumber從新計算");
let total = 0;
for (let i = 1; i <= count; i++) {
total += i;
}
return total;
}
export default function MemoHookDemo01() {
const [count, setCount] = useState(100000);
const [show, setShow] = useState(true);
const total = useMemo(() => {
return calcNumber(count);
}, [count]);
return (
<div> <h2>計算數字的和: {total}</h2> <button onClick={e => setCount(count + 1)}>+1</button> <button onClick={e => setShow(!show)}>show切換</button> </div>
)
}
複製代碼
當咱們去點擊 show切換按鈕時,calcNumber這個計算和的函數並不會出現渲染了.只有count 發生改變時,纔會出現計算.
簡單理解呢 useCallback 與 useMemo 一個緩存的是函數,一個緩存的是函數的返回的結果。useCallback 是來優化子組件的,防止子組件的重複渲染。useMemo 能夠優化當前組件也能夠優化子組件,優化當前組件主要是經過 memoize 來將一些複雜的計算邏輯進行緩存。固然若是隻是進行一些簡單的計算也不必使用 useMemo。
咱們能夠將 useMemo 的返回值定義爲返回一個函數這樣就能夠變通的實現了 useCallback。useCallback(fn, deps)
至關於 useMemo(() => fn, deps)
。
const refContainer = useRef(initialValue);
useRef
返回一個可變的 ref 對象,其 .current
屬性被初始化爲傳入的參數(initialValue
)。返回的 ref 對象在組件的整個生命週期內保持不變
useRef,它有一個參數能夠做爲緩存數據的初始值,返回值能夠被dom元素ref標記,能夠獲取被標記的元素節點.
import React, { useRef } from "react";
function Example() {
const divRef = useRef();
function changeDOM() {
// 獲取整個div
console.log("整個div", divRef.current);
// 獲取div的class
console.log("div的class", divRef.current.className);
// 獲取div自定義屬性
console.log("div自定義屬性", divRef.current.getAttribute("data-clj"));
}
return (
<div> <div className="div-class" data-clj="我是div的自定義屬性" ref={divRef}> 我是div </div> <button onClick={(e) => changeDOM()}>獲取DOM</button> </div>
);
}
export default Example;
複製代碼
useRef還有一個很重要的做用就是緩存數據,咱們知道usestate ,useReducer 是能夠保存當前的數據源的,可是若是它們更新數據源的函數執行一定會帶來整個組件重新執行到渲染,若是在函數組件內部聲明變量,則下一次更新也會重置,若是咱們想要悄悄的保存數據,而又不想觸發函數的更新,那麼useRef是一個很棒的選擇。
下面舉一個,每次換成state 上一次值的例子
import React, { useRef, useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
const numRef = useRef(count);
useEffect(() => {
numRef.current = count;
}, [count]);
return (
<div> <h2>count上一次的值: {numRef.current}</h2> <h2>count這一次的值: {count}</h2> <button onClick={(e) => setCount(count + 10)}>+10</button> </div>
);
}
export default Example;
複製代碼
當 ref 對象內容發生變化時,useRef
並不會通知你。變動 .current
屬性不會引起組件從新渲染。因此,上面的例子中雖然numRef.current的值,已經改變了,可是頁面上仍是顯示的上一次的值,從新更新時,纔會顯示上一次更新的值。
若是文章中有什麼錯誤,歡迎指出。