React Hooks是React 16.7.0-alpha版本推出的新特性,目的是解決React的狀態共享問題。稱之爲狀態共享可能描述的並非很恰當,稱爲狀態邏輯複用可能會更恰當,由於React Hooks只共享數據處理邏輯,並不會共享數據自己。
在React應用開發中,狀態管理是組件開發必不可少的內容。之前,爲了對狀態進行管理,最一般的作法是使用類組件或者直接使用redux等狀態管理框架。例如:react
class Hook 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> ); } }
如今,開發者能夠直接使用React Hooks提供的State Hook來處理狀態,針對那些已經存在的類組件,也可使用State Hook很好地進行重構。例如:ios
import React, { useState } from 'react'; function Hook() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
從上面的示例能夠發現,Example變成了一個函數組件,此函數組件有本身的狀態,而且還能夠更新本身的狀態。之因此能夠如此操做,是由於使用了useState,useState是react自帶的一個hook函數,它的做用就是用來聲明狀態變量。編程
一直以來,React就在致力於解決一個問題,即狀態組件的複用問題。在React應用開發中,咱們都是經過組件和自上而下傳遞的數據流來將大型的視圖拆分紅獨立的可複用組件。可是在實際項目開發中,複用一個帶有業務邏輯的組件依然是一個難題。
衆所周知,React提供了兩種建立組件的方式,即函數組件和類組件。函數組件是一個普通的JavaScript函數,接受props對象並返回React元素,函數組件更符合React數據驅動視圖的開發思想。不過,函數組件一直以來都由於缺少類組件諸如狀態、生命週期等種種特性,也由於這些緣由函數組件得不到開發者的青睞,而Hooks的出現就是讓函數式組件擁有類組件的特性。
爲了讓函數組件擁有類組件的諸如狀態、生命週期等特性,React 提供了3個核心的api,即State Hooks、Effect Hooks和Custom Hooks。
useState就是React提供最基礎、最經常使用的Hook,主要用來定義和管理本地狀態。例如,下面是使用useState實現一個最簡單的計數器,代碼以下:redux
import React, { useState } from 'react' function App() { const [count, setCount] = useState(0); return ( <div> <button onClick={()=> setCount(count + 1)}>+</button> <span>{count}</span> <button onClick={() => setCount((count) => count - 1)}>-</button> </div> ); } export default App;
在上面的示例中,使用useState來定義一個狀態,與類組件的狀態不一樣,函數組件的狀態能夠是對象也能夠是基礎類型值。useState返回的是一個數組,數組的第一個對象表示當前狀態的值,第二個對象表示用於更改狀態的函數,相似於類組件的setState。
函數組件中若是存在多個狀態,既能夠經過一個useState聲明對象類型的狀態,也能夠經過useState屢次聲明狀態。例如:axios
//聲明對象類型狀態 const [count, setCount] = useState({ count1: 0, count2: 0 }); //屢次聲明 const [count1, setCount1] = useState(0); const [count2, setCount2] = useState(0);
能夠發現,相比於聲明對象類型狀態,屢次聲明狀態的方式更加方便,主要是由於更新函數是採用的替換的方式而不是合併的方式。
不過,若是要在函數組件中處理多層嵌套數據邏輯時,使用useState就顯得力不從心了。值得慶幸的是,開發者可使用React提供的useReducer來處理此類問題。例如:api
import React, {useReducer} from 'react' const reducer = function (state, action) { switch (action.type) { case "increment": return {count: state.count + 1}; case "decrement": return {count: state.count - 1}; default: return {count: state.count} } }; function Example() { const [state, dispatch] = useReducer(reducer, {count: 0}); const {count} = state; return ( <div> <button onClick={() => dispatch({type: "increment"})}>+</button> <span>{count}</span> <button onClick={() => dispatch({type: "decrement"})}>-</button> </div> ); } export default Example;
能夠發現,useReducer接受reducer函數和默認值兩個參數,並返回當前狀態state和dispatch函數的數組,使用方式與Redux基本一致,不一樣之處在於,Redux的默認值是經過給reducer函數設置默認參數的方式給定的。
useReducer之因此沒有采用Redux的方式設置默認值,是由於React認爲狀態的的默認值多是來自於函數組件的props。例如:數組
function Example({initialState = 0}) { const [state, dispatch] = useReducer(reducer, { count: initialState }); ... }
解決了函數組件中內部狀態的問題,接下來亟待解決的就是函數組件中生命週期函數的問題。在React的函數式編程思想中,生命週期函數是溝通函數式和命令式的橋樑,根據生命週期函數開發者能夠執行相關的操做,如網絡請求和操做DOM。瀏覽器
import React, {useState, useEffect} from 'react'; function Example() { const [count, setCount] = useState(0); useEffect(() => { console.log('componentDidMount...') console.log('componentDidUpdate...') return () => { console.log('componentWillUnmount...') } }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } export default Example;
執行上面的代碼,而後點擊按鈕執行加法操做時,生命週期函數調用狀況下圖所示。
能夠看到,每次執行組件更新時useEffect中的回調函數都會被調用。而且在點擊按鈕執行更新操做時還會觸發componentWillUnmount生命週期,之因此在從新繪製前執行銷燬操做,是爲了不形成內存泄露。
所以能夠將useEffect視爲componentDidMount、componentDidUpdate和componentWillUnmount的組合,並用它關聯函數組件的生命週期。
須要說明是,類組件的componentDidMount或componentDidUpdate生命週期函數都是在DOM更新後同步執行的,但useEffect並不會在DOM更新後同步執行。所以,使用useEffect的並不會阻塞瀏覽器更新界面。若是須要模擬生命週期的同步執行,可使用React提供的useLayoutEffect Hook。網絡
衆所周知,要在類組件之間共享一些狀態邏輯是很是麻煩的,常規的作法都是經過高階組件或者是函數的屬性來解決。不過,新版的React容許開發者建立自定義Hook來封裝共享狀態邏輯,且不須要向組件樹中增長新的組件。
所謂的自定義Hook,其實就是指函數名以use開頭並調用其餘Hook的函數,自定義Hook的每一個狀態都是徹底獨立的。例如,下面是使用axios實現網絡請求的示例,代碼以下:app
import axios from 'axios' export const useAxios = (url, dependencies) => { const [isLoading, setIsLoading] = useState(false); const [response, setResponse] = useState(null); const [error, setError] = useState(null); useEffect(() => { setIsLoading(true); axios.get(url).then((res) => { setIsLoading(false); setResponse(res); }).catch((err) => { setIsLoading(false); setError(err); }); }, dependencies); return [isLoading, response, error]; };
如上所示,就是使用axios開發請求數據的自定義Hook。使用方法和系統提供的Hook相似,直接調用便可。例如:
function Example() { let url = 'http://api.douban.com/v2/movie/in_theaters'; const [isLoading, response, error] = useAxios(url, []); return ( <div> {isLoading ? <div>loading...</div> : (error ? <div> There is an error happened </div> : <div> Success, {response} </div>)} </div> ) } export default Example;
能夠發現,相比於函數屬性和高階組件等方式,自定義Hook則更加的簡潔易讀,不只於此,自定義Hook也不會引發之組件嵌套地獄問題。
雖然React的Hooks有着諸多的優點。不過,在使用Hooks的過程當中,須要注意如下兩點:
• 不要在循環、條件或嵌套函數中使用Hook,而且只能在React函數的頂層使用Hook。之因此要這麼作,是由於React須要利用調用順序來正確更新相應的狀態,以及調用相應的生命週期函數函數。一旦在循環或條件分支語句中調用Hook,就容易致使調用順序的不一致性,從而產生難以預料到的後果。
• 只能在React函數式組件或自定義Hook中使用Hook。
同時,爲了不在開發中形成一些低級的錯誤,能夠安裝一個eslint插件,安裝命令以下:
yarn add eslint-plugin-react-hooks --dev
而後,在eslint的配置文件中添加以下一些配置。
{ "plugins": [ // ... "react-hooks" ], "rules": { // ... "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn" } }
藉助於React提供的Hooks API,函數組件能夠實現絕大部分的類組件功能,而且Hooks在共享狀態邏輯、提升組件複用性上也具備必定的優點。能夠預見的是,Hooks將是React將來發展的重要方向。