拒絕Redux?使用原生React管理狀態

Banner

自從咱們學習React的第一天起,咱們就知道不要在React中使用太多狀態。咱們也知道應該儘量多使用無狀態的函數式組件,少使用有狀態的類組件。react

這些建議很容易讓咱們造成這樣的判斷,那就是咱們徹底不該該使用狀態,當咱們須要用到狀態的時候,應該使用Redux之類的第三方狀態管理庫。編程

咱們不喜歡使用React原生狀態並非沒有緣由的:設計模式

  1. 當項目變大的時候,散落在許多組件中的局部狀態很快會變得沒法追蹤;
  2. 組件之間難以共享狀態;
  3. 從一個外部組件一層層將prop傳遞到內部組件很麻煩;這個問題被稱做prop drilling
  4. 狀態邏輯一般和UI邏輯混合在一塊兒,致使調試和重用狀態邏輯都很困難;
  5. 在編譯時,類組件比函數式組件更難優化。

嚴格來說,prop drilling並不屬於狀態的問題,而是一個設計失誤。它一般是由過多的沒必要要封裝致使的。ide

Redux的優缺點

Redux是最流行的狀態管理庫,它經過將狀態與UI隔離並中心化管理的方式減輕了以上所說的這些痛點。函數式編程

它的整個邏輯十分簡單,但卻蘊含着強大的擴展性。咱們能夠用一個單向數據流圖來描述Redux背後的運行機制:函數

Redux Mechanism

除了store自身,全部其餘的組件都是純函數。這是一個函數式編程中的概念,指的是函數的結果只取決於函數的輸入,而不取決於任何其餘的狀態。性能

純函數更容易測試、理解和調試。經過強制使用函數式編程這一編程風格,Redux減小了維護狀態邏輯的負擔。學習

然而,Redux也有本身特有的麻煩。一我的們廣爲詬病的問題就是在搭建項目初期,它須要寫太多樣板代碼。測試

對於小項目而言,這個問題尤其嚴重。添加一個新的action一般須要咱們定一個新action、添加一個新action creator、修改reducer、更新container等一系列操做。爲了完成這個功能,咱們須要在許多個不一樣的文件之間跳轉,最後把一個5行以內能夠作完的事情擴展成20行。優化

甚至說,咱們到底要不要中心化的store也是一個值得討論的問題,就像咱們要不要用全局變量同樣。

和局部狀態相比,全局狀態更難重用。重構全局狀態可能會意外地致使部分現有代碼不可用。不合理地使用Redux container也會有性能問題。

在另外一邊,近幾年React爲了讓狀態管理更好用作出了許多努力,引入了Context APIHooks等新特性來解決過去的痛點。

原生React

React支持兩種組件:類組件(支持狀態和hook,也就是componentDidMount等函數)和函數式組件(不支持狀態,更加簡單)。

在過去,若是咱們想使用函數式組件,又想維護某些內部狀態,惟一的辦法就是使用Redux之類的狀態管理庫。

在React增長了Context API和hooks以後,咱們有了更多的選擇。

由於這兩個新API是React官方支持的,我建議在使用第三方的狀態管理庫以前,先了解一下它們再作決定。

React Hooks

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 Context API比React hooks更早引入React,可是它是用來解決一個徹底不一樣的問題的:狀態共享prop drilling

這個功能可能會讓你聯想到Redux的用途,可是React Context API事實上並不鼓勵用它來維護一個巨大的中心化store。官方文檔中說到:

Context主要是用在數據須要被不一樣層次的多個組件訪問的時候。儘量少用它,由於它會使組件重用更加困難。

React Context API和React hooks的設計哲學是相同的,那就是儘量避免狀態共享

咱們不得不使用React Context API的典型場景有:

  • 用戶登陸信息;
  • UI主題;
  • locale設置。

例如,咱們能夠經過把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。

相關文章
相關標籤/搜索