Hooks
Hooks是React v16.7.0-alpha的新特新,可以與react state相結合, 使函數組件功能類似於class一樣。但是在Class類內部,不可以使用hooks.
React 提供了幾個像useState,useEffect, useContext 等的內置鉤子函數,我們也可以自定義鉤子函數在組件之間來複用state。
useState(內置hook)
import {useState} from 'reaact';
function Example(){
const [count, setCount]=useState(0);
return(
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)}
在函數組件內部,不存在指向函數自身的this, 因此不可以聲明或者調用this.state.
useState hooks提供給函數等同於class內部this.state的作用。state變量通過react進行維護。
useState 是一個HOOK, 在函數內部調用,可以給函數本身添加state,react會保存並渲染頁面。useState返回一組值:當前State, 和更新state的方法,這個方法可以在函數內部或其他地方調用。與react this.setState方法類似,但是不同的是,它並非將舊值與新值進行合併得到新值.
useState只接受一個參數,即state的初始值,eg. useState(0), 0即爲state的初始值。並且只在初次渲染時調用。與this.state不同的是,hook 的state不必一定爲一個對象,但如果想定義爲對象也是可以的。
定義多個State變量
function ManyStates(){
const [age,setAge]=useState(42);
const [fruit,setFruit]=useState('orange');
const [todos,setTodos]=useState([{text:'learn hooks'}])
數組的解構賦值使我們可以定義多個變量。如果我們多次調用useState,react會同時進行編譯。
Effect Hook(內置hook)
在react 組件內部,我們通常會進行一些數據獲取,提交,或手動改變DOM狀態的操作, 這種對其他環境有影響或依賴的又稱爲:side effect(副作用), Effect Hook: useEffect ,提供函數組件同樣的功效,作用等同於class組件內部的componentDidMount, componentDidUpdate, componentWillUnmount,只是將這些統一成單一API。
import {useState, useEffect} from 'react';
function Eample(){
const [count,setCount]=useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
當調用useEffect,即告訴React在DOM變化後執行’effect’方法。Effects在組件內部聲明,因此可以讀取組件內的props, state。默認情況下,react會在每次render後都去執行effects裏的方法,也包括第一次render.
Effects也可以通過返回一個函數選擇性的指定如何’clean up’當函數unMount時。
eg.
import { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
上例中,React在組件卸載時會調用ChatAPI的unsubscribe方法,同樣在隨後的render中也會調用。(如果props.friend.id沒有變化,你也可以告訴React忽略re-subscribing,如果你想。)
與useState類似,你也可以使用多個useEffect在函數組件中:
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...
上例中,多個useEffect裏面傳入的函數是彼此獨立的,由此可以保證effect裏面讀取的state值將會是最新的,在網頁每次重新編譯後,我們執行一個新的effect,替代前面的state值。useEffect並不會阻塞瀏覽器更新頁面,它們爲異步執行。在某些特殊情況下需要同步執行,例如計算頁面佈局,這時可以使用useLayoutEffect Hook 替代useEffect. useLayoutEffect在DOM計算完成後同步執行,與componentDidMount, componentDidUpdate執行時間類似,在不確定用哪種effect時,使用useLayoutEffect風險較小。
Hooks可以將組件中相關的生命週期函數寫在一起,而不是將它們分開在不同的生命週期函數中。
Effects without cleanup
在class組件中我們執行side effects在componentDidMount/componentDidUpdate中,如果我們想執行同一方法在組件每次render之後,必須將相同方法在didMount, didUpdate兩個階段都調用一次。
在Hooks中我們只需要在useEffects中將此方法寫一次即可每次render後都進行調用。
默認情況下,useEffect在首次編譯和每次更新數據都會執行,即每次編譯後都執行。我們也可以進行優化:忽略某些編譯。
在Class組件中,componentDidUpdate可以通過prevProps,prevState來阻止update裏函數的執行eg.
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
在useEffect Hook API中內置了這種比較,你可以向useEffect(()=>{}, [compareState])傳入第二個參數:state數組。eg.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
只有count值發生改變-上次render和此次render count值不同,react纔會忽略此次render,不執行effect裏的方法。
這個也同樣作用於 含有clean up 的effects
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes
如果使用這個優化,請確保數組內包含的值是從外部作用域獲取,並且會隨着時間變化,effect裏面也會使用的。否則你effect裏的代碼將會從之前的編譯中取值。
如果你想執行一個effect和clean up 僅一次,你可以將useEffect第二個參數傳入一個空數組[], 類似componentDidMount, componentWillUnmount.
Effects with Cleanup
useEffects第一個參數會返回一個函數作爲cleanup 執行的方法。會在unmount,和每次re-render時執行。
useContext(內置hook)
const context=useContext(Context);
class組件中context使用,會將組件包含在context.provider && context.consumer之間,consumer之間的組件會以render props的模式(傳遞一個函數作爲children)來獲得provider傳遞的參數,如下
import React from "react";
import ReactDOM from "react-dom";
// Create a Context
const NumberContext = React.createContext();
// It returns an object with 2 values:
// { Provider, Consumer }
function App() {
// Use the Provider to make a value available to all
// children and grandchildren
return (
<NumberContext.Provider value={42}>
<div>
<Display />
</div>
</NumberContext.Provider>
);
}
function Display() {
// Use the Consumer to grab the value from context
// Notice this component didn't get any props!
return (
<NumberContext.Consumer>
{value => <div>The answer is {value}.</div>}
</NumberContext.Consumer>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
在useContext hook 中,我們可以使用useContext(Context)-傳入的參數爲通過React.createContext獲得的context對象,直接獲取provider傳遞的值,改寫consumer組件如下:
// import useContext (or we could write React.useContext)
import React, { useContext } from 'react';
// ...
function Display() {
const value = useContext(NumberContext);
return <div>The answer is {value}.</div>;
}
如果consumer組件中有多層嵌套獲得多個provider值,那麼使用hook將會更簡單:
function HeaderBar() {
return (
<CurrentUser.Consumer>
{user =>
<Notifications.Consumer>
{notifications =>
<header>
Welcome back, {user.name}!
You have {notifications.length} notifications.
</header>
}
}
</CurrentUser.Consumer>
);
}
//useContext改寫如下:
function HeaderBar() {
const user = useContext(CurrentUser);
const notifications = useContext(Notifications);
return (
<header>
Welcome back, {user.name}!
You have {notifications.length} notifications.
</header>
);
}
useReducer Hook(內置)
Hooks規則
- 不要在循環,條件,或嵌套函數中使用,應該在頂層的React function中使用。由此確保在組件編譯過程中,Hooks以相同的順序進行調用,這保證了在衆多useState, useEffect中React正確保存state狀態。
- Hooks只能在React 函數組件調用,常規函數不能調用。
- eslint-plugin-react-hooks插件可以強化前兩條規則: npm install eslint-plugin-react-hooks@next
// ------------
// First render
// ------------
useState('Mary') // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm) // 2. Add an effect for persisting the form
useState('Poppins') // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle) // 4. Add an effect for updating the title
// -------------
// Second render
// -------------
useState('Mary') // 1. Read the name state variable (argument is ignored)
useEffect(persistForm) // 2. Replace the effect for persisting the form
useState('Poppins') // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle) // 4. Replace the effect for updating the title
// ...
自定義Hook
在傳統React class組件中,如果組件之間公用大部分邏輯和頁面,通常會通過props傳值, 或使用高階組件的方式來返回不同組件。
在Hook中,我們不需要寫額外的組件,可以直接將共有部分抽取成自定義的hook函數,如下:
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}`
useFriendStatus主要用於通過傳入id返回online狀態,所有組件共用同一個Hook,state也是相互獨立的。