自從咱們學習React的第一天起,咱們就知道不要在React中使用太多狀態。咱們也知道應該儘量多使用無狀態的函數式組件,少使用有狀態的類組件。react
這些建議很容易讓咱們造成這樣的判斷,那就是咱們徹底不該該使用狀態,當咱們須要用到狀態的時候,應該使用Redux之類的第三方狀態管理庫。編程
咱們不喜歡使用React原生狀態並非沒有緣由的:設計模式
嚴格來說,prop drilling並不屬於狀態的問題,而是一個設計失誤。它一般是由過多的沒必要要封裝致使的。ide
Redux是最流行的狀態管理庫,它經過將狀態與UI隔離並中心化管理的方式減輕了以上所說的這些痛點。函數式編程
它的整個邏輯十分簡單,但卻蘊含着強大的擴展性。咱們能夠用一個單向數據流圖來描述Redux背後的運行機制:函數
除了store自身,全部其餘的組件都是純函數。這是一個函數式編程中的概念,指的是函數的結果只取決於函數的輸入,而不取決於任何其餘的狀態。性能
純函數更容易測試、理解和調試。經過強制使用函數式編程這一編程風格,Redux減小了維護狀態邏輯的負擔。學習
然而,Redux也有本身特有的麻煩。一我的們廣爲詬病的問題就是在搭建項目初期,它須要寫太多樣板代碼。測試
對於小項目而言,這個問題尤其嚴重。添加一個新的action一般須要咱們定一個新action、添加一個新action creator、修改reducer、更新container等一系列操做。爲了完成這個功能,咱們須要在許多個不一樣的文件之間跳轉,最後把一個5行以內能夠作完的事情擴展成20行。優化
甚至說,咱們到底要不要中心化的store也是一個值得討論的問題,就像咱們要不要用全局變量同樣。
和局部狀態相比,全局狀態更難重用。重構全局狀態可能會意外地致使部分現有代碼不可用。不合理地使用Redux container也會有性能問題。
在另外一邊,近幾年React爲了讓狀態管理更好用作出了許多努力,引入了Context API和Hooks等新特性來解決過去的痛點。
React支持兩種組件:類組件(支持狀態和hook,也就是componentDidMount等函數)和函數式組件(不支持狀態,更加簡單)。
在過去,若是咱們想使用函數式組件,又想維護某些內部狀態,惟一的辦法就是使用Redux之類的狀態管理庫。
在React增長了Context API和hooks以後,咱們有了更多的選擇。
由於這兩個新API是React官方支持的,我建議在使用第三方的狀態管理庫以前,先了解一下它們再作決定。
React hooks API提供了一種在函數式組件中管理狀態的方法。
這句話第一眼看上去自相矛盾,由於函數式在某種程度上就意味着無狀態。可是,咱們先將這一問題擱置在一邊,只把函數式組件看成一種設計模式來看待。
和類組件相比,函數式組件有更緊湊的形式,須要更少的樣板代碼,更可讀,對編譯器來講也更容易分析和優化。
然而,不可以在函數式組件中維護狀態爲咱們帶來了極大的不便:即便是引入一個boolean這樣小的狀態,都須要咱們將整個函數式組件重寫爲類組件。
React hooks API經過容許咱們在函數式組件中使用狀態來解決了這個困境。並且,它也容許咱們將狀態邏輯從UI邏輯中剝離出來,重用到其餘的UI組件中。
下面這個簡短的例子展現了咱們如何在函數式組件中使用React hooks:
import React, { useState } from 'react' const Counter: React.FC = () => { const [counter, setCounter] = useState(0) return ( <div> <p>Counter: {counter}</p> <button onClick={() => setCounter(counter + 1)}> Increment </button> </div> ) }
useState返回當前狀態和一個函數(這個函數能夠用來更新狀態),它的參數是初始狀態。這個函數第一次被調用的時候,它將組件的內部狀態初始化爲給定的初始值。
這個狀態是維護在這個組件以內的,不會被同一個組件的多個實例之間共享。
咱們能夠在一個函數式組件中調用useState許屢次。React hooks API鼓勵咱們將一個複雜的狀態分解成多個可重用的簡單狀態。
咱們能夠進一步將狀態邏輯封裝在單獨的函數中(稱爲custom hook),以便咱們在其餘組件中重用這個狀態邏輯:
import React, {useState} from 'react' // A custom hook const useCounter = (initial: number) => { const [counter, setCounter] = useState(initial) return { counter, increment () { setCounter(counter + 1) }, reset () { setCounter(initial) } } } const Counter: React.FC = () => { const { counter, increment, reset } = useCounter(0) return ( <div> <p>Counter: {counter}</p> <button onClick={increment}> Increment </button> <button onClick={reset}> Reset </button> </div> ) }
每個使用setState的地方,咱們均可以用React hooks代替,由於React hooks全方面地超越了原來的setState:它容許將一個組件的狀態拆分紅小的可重用的狀態,鼓勵咱們多用函數式組件,少用類組件。
React Context API比React hooks更早引入React,可是它是用來解決一個徹底不一樣的問題的:狀態共享和prop drilling。
這個功能可能會讓你聯想到Redux的用途,可是React Context API事實上並不鼓勵用它來維護一個巨大的中心化store。官方文檔中說到:
Context主要是用在數據須要被不一樣層次的多個組件訪問的時候。儘量少用它,由於它會使組件重用更加困難。
React Context API和React hooks的設計哲學是相同的,那就是儘量避免狀態共享。
咱們不得不使用React Context API的典型場景有:
例如,咱們能夠經過把UI主題放到Context中的方法來避免手動逐層傳遞:
import React, { useContext } from 'react' const ThemeContext = React.createContext('light') const UserComponent: React.FC = () => { const theme = useContext(ThemeContext) return ( <div> Current theme: {theme} </div> ) } const App: React.FC = () => { return ( <ThemeContext.Provider value="dark"> <UserComponent /> </ThemeContext.Provider> ) }
經過合理地組合React Context API和React hooks,咱們能夠在徹底不用Redux的狀況下管理程序狀態。
然而,就像編程世界中的其餘開放性問題同樣,狀態管理也沒有萬金油。具體怎麼作取決於業務邏輯的複雜性、程序的規模和其餘各類因素。
咱們應該在實際中選擇最合適的方法。個人建議是,在項目初期,可使用Context API和React hooks做爲開始,隨着項目的進行,只在必要的時候才引入Redux。