先理解什麼是hook,官網給的是:javascript
Hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。css
hooks經過function抽離的方式,實現了複雜邏輯的內部封裝:java
規則:react
// class組件 import React from "react"; import "./styles.css"; class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } }
// hooks import React, { useState } from 'react'; function Example() { // 聲明一個叫 "count" 的 state 變量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
讀取 Stateajax
//class <p>You clicked {this.state.count} times。</p> // hook <p>You clicked {count} times</p>
更新 State算法
//class <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> // hook <button onClick={() => setCount(count + 1)}> Click me </button>
useState能夠接收函數參數,並將函數的返回值做爲初始值(便於複雜計算)編程
const [count, setCount] = useState(() => { return Math.random() * 10; });
useState的更新函數能夠接收函數做爲參數,函數的參數是前一狀態的state值。api
setCount((count)=> count + 1)
使用當前的值,對state進行更新不會觸發渲染。數組
const [state, setState] = useState(0); // ... // 更新 state 不會觸發組件從新渲染,(使用Object.is比較) setState(0); setState(0);
// class組件 class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } }
// hook import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
class FriendStatus extends React.Component { constructor(props) { super(props); this.state = { isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } render() { if (this.state.isOnline === null) { return 'Loading...'; } return this.state.isOnline ? 'Online' : 'Offline'; } }
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // Specify how to clean up after this effect: return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
class FriendStatusWithCounter extends React.Component { constructor(props) { super(props); this.state = { count: 0, isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { document.title = `You clicked ${this.state.count} times`; ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } // ...
function FriendStatusWithCounter(props) { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); // ... }
componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); }
componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate(prevProps) { // 取消訂閱以前的 friend.id ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // 訂閱新的 friend.id ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); }
function FriendStatus(props) { // ... useEffect(() => { // ... ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); //...
// Mount with { friend: { id: 100 } } props ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 運行第一個 effect // Update with { friend: { id: 200 } } props ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一個 effect ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 運行下一個 effect // Update with { friend: { id: 300 } } props ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一個 effect ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 運行下一個 effect // Unmount ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最後一個 effect
componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { document.title = `You clicked ${this.state.count} times`; } }
useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // 僅在 count 更改時更新
Context 提供了一個無需爲每層組件手動添加 props,就能在組件樹間進行數據傳遞的方法。瀏覽器
在一個典型的 React 應用中,數據是經過 props 屬性自上而下(由父及子)進行傳遞的,但這種作法對於某些類型的屬性而言是極其繁瑣的(例如:地區偏好,UI 主題),這些屬性是應用程序中許多組件都須要的。Context 提供了一種在組件之間共享此類值的方式,而沒必要顯式地經過組件樹的逐層傳遞 props。
class App extends React.Component { render() { return <Toolbar theme="dark" />; } } function Toolbar(props) { // Toolbar 組件接受一個額外的「theme」屬性,而後傳遞給 ThemedButton 組件。 // 若是應用中每個單獨的按鈕都須要知道 theme 的值,這會是件很麻煩的事, // 由於必須將這個值層層傳遞全部組件。 return ( <div> <ThemedButton theme={props.theme} /> </div> ); } class ThemedButton extends React.Component { render() { return <Button theme={this.props.theme} />; } }
使用 context, 咱們能夠避免經過中間元素傳遞 props:
// Context 可讓咱們無須明確地傳遍每個組件,就能將值深刻傳遞進組件樹。 // 爲當前的 theme 建立一個 context(「light」爲默認值)。 const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // 使用一個 Provider 來將當前的 theme 傳遞給如下的組件樹。 // 不管多深,任何組件都能讀取這個值。 // 在這個例子中,咱們將 「dark」 做爲當前的值傳遞下去。 return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // 中間的組件不再必指明往下傳遞 theme 了。 function Toolbar() { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // 指定 contextType 讀取當前的 theme context。 // React 會往上找到最近的 theme Provider,而後使用它的值。 // 在這個例子中,當前的 theme 值爲 「dark」。 static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }
const ThemeContext = React.createContext("light"); export default function APP() { // 使用一個 Provider 來將當前的 theme 傳遞給如下的組件樹。 // 不管多深,任何組件都能讀取這個值。 // 在這個例子中,咱們將 「dark」 做爲當前的值傳遞下去。 return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } // 中間的組件不再必指明往下傳遞 theme 了。 function Toolbar() { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { const theme = useContext(ThemeContext); return <div theme={theme}>{theme}</div>; }
useContext接收一個 context 對象(React.createContext 的返回值)並返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider> 的 value prop 決定。當組件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發重渲染,並使用最新傳遞給 MyContext provider 的 context value 值。即便祖先使用 React.memo 或 shouldComponentUpdate,也會在組件自己使用 useContext 時從新渲染。
useContext(MyContext) 只是讓你可以讀取 context 的值以及訂閱 context 的變化。你仍然須要在上層組件樹中使用 <MyContext.Provider> 來爲下層組件提供 context。
useState 的替代方案。它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法。(若是你熟悉 Redux 的話,就已經知道它如何工做了。)
在某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較複雜且包含多個子值,或者下一個 state 依賴於以前的 state 等。而且,使用 useReducer 還能給那些會觸發深更新的組件作性能優化,由於你能夠向子組件傳遞 dispatch 而不是回調函數 。
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
有兩種不一樣初始化 useReducer state 的方式,你能夠根據使用場景選擇其中的一種。
const [state, dispatch] = useReducer( reducer, {count: initialCount} );
function init(initialCount) { return {count: initialCount}; } function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; case 'reset': return init(action.payload); default: throw new Error(); } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
若是 Reducer Hook 的返回值與當前 state 相同,React 將跳過子組件的渲染及反作用的執行。(React 使用 Object.is 比較算法 來比較 state。)
import React, { useReducer, useContext, useEffect } from "react"; const store = { user: null, books: null, movies: null }; function reducer(state, action) { switch (action.type) { case "setUser": return { ...state, user: action.user }; case "setBooks": return { ...state, books: action.books }; case "setMovies": return { ...state, movies: action.movies }; default: throw new Error(); } } const Context = React.createContext(null); function App() { const [state, dispatch] = useReducer(reducer, store); const api = { state, dispatch }; return ( <Context.Provider value={api}> <User /> <hr /> <Books /> <Movies /> </Context.Provider> ); } function User() { const { state, dispatch } = useContext(Context); useEffect(() => { ajax("/user").then(user => { dispatch({ type: "setUser", user: user }); }); }, []); return ( <div> <h1>我的信息</h1> <div>name: {state.user ? state.user.name : ""}</div> </div> ); } function Books() { const { state, dispatch } = useContext(Context); useEffect(() => { ajax("/books").then(books => { dispatch({ type: "setBooks", books: books }); }); }, []); return ( <div> <h1>個人書籍</h1> <ol> {state.books ? state.books.map(book => <li key={book.id}>{book.name}</li>) : "加載中"} </ol> </div> ); } function Movies() { const { state, dispatch } = useContext(Context); useEffect(() => { ajax("/movies").then(movies => { dispatch({ type: "setMovies", movies: movies }); }); }, []); return ( <div> <h1>個人電影</h1> <ol> {state.movies ? state.movies.map(movie => <li key={movie.id}>{movie.name}</li>) : "加載中"} </ol> </div> ); } // 幫助函數 // 兩秒鐘後,根據 path 返回一個對象,一定成功不會失敗 function ajax(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path === "/user") { resolve({ id: 1, name: "Frank" }); } else if (path === "/books") { resolve([ { id: 1, name: "JavaScript 高級程序設計" }, { id: 2, name: "JavaScript 初級程序設計" } ]); } else if (path === "/movies") { resolve([ { id: 1, name: "信條" }, { id: 2, name: "八佰" } ]); } }, 2000); }); }
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
function App() { const [n, setN] = useState(0); const [m, setM] = useState(0); const onClick = () => { setN(n + 1); }; return ( <div className="App"> <div> {/*點擊button會從新執行Child組件*/} <button onClick={onClick}>update n {n}</button> </div> <Child data={m}/> {/* <Child2 data={m}/> */} </div> ); } function Child(props) { console.log("child 執行了"); console.log('假設這裏有大量代碼') return <div>child: {props.data}</div>; } const Child2 = React.memo(Child);
將代碼中的 Child 用React.memo(Child) 代替
function App() { const [n, setN] = useState(0); const [m, setM] = useState(0); const onClick = () => { setN(n + 1); }; return ( <div className="App"> <div> {/*點擊button會從新執行Child組件*/} <button onClick={onClick}>update n {n}</button> </div> <Child data={m}/> </div> ); } const Child = React.memo(props => { console.log("child 執行了"); return <div>child: {props.data}</div>; });
function App() { const [n, setN] = useState(0); const [m, setM] = useState(0); const onClick = () => { setN(n + 1); }; const onClickChild = () => {} return ( <div className="App"> <div> {/*點擊button會從新執行Child組件*/} <button onClick={onClick}>update n {n}</button> </div> {/*可是若是傳了一個引用,則React.memo無效。由於引用是不相等的*/} <Child data={m} onClick={onClickChild}/> </div> ); } //使用React.memo能夠解決從新執行Child組件的問題 const Child = React.memo(props => { console.log("child 執行了"); return <div onClick={props.onClick}>child: {props.data}</div>; });
import React, { useState, useMemo } from "react"; import "./styles.css"; export default function App() { const [n, setN] = useState(0); const [m, setM] = useState(0); const onClick = () => { setN(n + 1); }; const onClick1 = () => { setM(m + 1); }; // const onClickChild = () => {}; const onClickChild1 = useMemo(() => { return () => { console.log(`on click child m: ${m}`); }; }, [m]); return ( <div className="App"> <div> {/*點擊button會從新執行Child組件*/} <button onClick={onClick}>update n {n}</button> <button onClick={onClick1}>update m {m}</button> </div> {/*可是若是傳了一個引用,則React.memo無效。由於引用是不相等的*/} {/*<Child data={m} onClick={onClickChild}/>*/} {/*onClickChild1使用useMemo能夠消除此bug*/} <Child data={m} onClick={onClickChild1} /> </div> ); } //使用React.memo能夠解決從新執行Child組件的問題 const Child = React.memo((props) => { console.log("child 執行了"); return <div onClick={props.onClick}>child: {props.data}</div>; });
useCallback(fn, deps) 至關於 useMemo(() => fn, deps)。
把內聯回調函數及依賴項數組做爲參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時纔會更新。當你把回調函數傳遞給通過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將很是有用。
const refContainer = useRef(initialValue);
<div ref={myRef}></div>
形式傳入組件,則不管該節點如何改變,React 都會將 ref 對象的 .current 屬性設置爲相應的 DOM 節點。class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } render() { return <div ref={this.myRef} />; } }
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` 指向已掛載到 DOM 上的文本輸入元素 inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
import React, { useState, useEffect, useRef } from "react"; import "./styles.css"; export default function APP() { const [count, setCount] = useState(0); const prevCountRef = useRef(); const prevCount = prevCountRef.current; useEffect(() => { prevCountRef.current = count; }); const onClick = () => { setCount(count + 1); }; return ( <> <h1> Now: {count}, before: {prevCount} </h1> <div onClick={onClick}>onClick div</div> </> ); }
import React, { useState, useEffect, useMemo, useRef } from "react"; export default function App() { const [count, setCount] = useState(0); const doubleCount = useMemo(() => { return 2 * count; }, [count]); const timerID = useRef(); useEffect(() => { timerID.current = setInterval(() => { setCount((count) => count + 1); }, 1000); }, []); useEffect(() => { if (count > 10) { clearInterval(timerID.current); } }); return ( <> <button onClick={() => { setCount(count + 1); }} > Count: {count}, double: {doubleCount} </button> </> ); }
const [count, setCount] = useState(0); useLayoutEffect(() => { if (count === 0) { setCount(10 + Math.random()*200); } }, [count]); return ( <div onClick={() => setCount(0)}>{count}</div> );
const [count, setCount] = useState(0); useEffect(() => { if (count === 0) { setCount(10 + Math.random()*200); } }, [count]); return ( <div onClick={() => setCount(0)}>{count}</div> );
經過自定義 Hook,能夠將組件邏輯提取到可重用的函數中。
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
import React, { useState, useEffect } from 'react'; function FriendListItem(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
當咱們想在兩個函數之間共享邏輯時,咱們會把它提取到第三個函數中。而組件和 Hook 都是函數,因此也一樣適用這種方式。
自定義 Hook 是一個函數,其名稱以 「use」 開頭,函數內部能夠調用其餘的 Hook。 例如,下面的 useFriendStatus 是咱們第一個自定義的 Hook:
import { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; } function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
目前暫時尚未對應不經常使用的 getSnapshotBeforeUpdate,getDerivedStateFromError 和 componentDidCatch 生命週期的 Hook 等價寫法
可使用一個可變的 ref 手動存儲一個布爾值來表示是首次渲染仍是後續渲染,而後在你的 effect 中檢查這個標識。