React Context API: 輕鬆管理狀態

OFED發表於2018-08-27

原文連結:React Context API: Managing State with Ease

譯者:OFED

使用最新的 React Context API 管理狀態非常容易。現在就跟隨我一起學習下它和 Redux 的區別以及它是如何使用的吧。

綜述:React Context API 在 React 生態系統中並不是個新鮮事物。不過,在 React 16.3.0 版本中做了一些改進。這些改進是如此巨大,以至於大大減少了我們對 Redux 和其他高階狀態管理庫的需求。在本文中,你將通過一個實用教程瞭解到新的 React Context API 是如何取代 Redux 完成小型應用的狀態管理的。

Redux 快速回顧

在直奔主題之前,我們先來快速回顧下 Redux,以便我們更好的比較兩者的區別。redux 是一個便於狀態管理的 JavaScript 庫。Redux 本身和 React 並沒有關係。來自世界各地的眾多開發者選擇在流行的前端框架(比如 ReactAngular )中使用 Redux。

說明一點,在本文中,狀態管理指的是處理單頁面應用(SPA)中產生的基於特定事件而觸發的狀態變化。比如,一個按鈕的點選事件或者一條來自伺服器的非同步資訊等,都可以觸發應用狀態的變化。

在 Redux 中,你尤其需要注意下面幾點:

  1. 整個 app 的狀態儲存在單個物件中(該物件被稱作資料來源)。
  2. 如果要改變狀態,你需要通過 dispatch 方法觸發 actions,actions 描述了應該發生的事情。
  3. 在 Redux 中,你不能更改物件的屬性或更改現有陣列,必須始終返回新物件或新陣列。

如果你對 Redux 並不熟悉並且你想要了解更多,請移步 Redux 的實用教程學習。

React Context API 指南

The React Context API 提供了一種通過元件樹傳遞資料的方法,而不必通過 props 屬性一層層的傳遞。在 React 中,資料通常會作為一個屬性從父元件傳遞到子元件。

使用最新的 React Context API 需要三個關鍵步驟:

  1. 將初始狀態傳遞給 React.createContext。這個方法會返回一個帶有 ProviderConsumer 的物件。
  2. 使用 Provider 元件包裹在元件樹的最外層,並接收一個 value 屬性。value 屬性可以是任何值。
  3. 使用 Consumer 元件,在元件樹中 Provider 元件內部的任何地方都能獲取到狀態的子集。

如你所見,所涉及的概念實際上與 Redux 沒有什麼不同。事實上,甚至 Redux 也在其公共 API 的底層使用了 React Context API。然而,直到最近,Context API 才達到了足夠成熟的水平。

使用 Redux 建立 React 應用

如上所述,本文的目標是向你展示新的 Context API 如何在小型應用中替代 Redux。因此,你首先要用 Redux 建立一個簡單的 React app,然後,你將學習如何刪除這個狀態管理庫,以便更好地利用 React Context API。

你將構建的示例應用是一個處理一些流行食物及其來源的列表。這個應用還將包括一個搜尋功能,使使用者能夠根據一些關鍵詞過濾列表。

最終,你將建立一個類似下面所述的應用:

專案要求

由於本文僅使用 React 和一些 NPM 庫,因此除了 Node.js 和 NPM 之外,你什麼都不需要。如果你還沒有安裝 Node.js 和 NPM,請前往官網下載並安裝。

安裝這些依賴後,你還需要安裝 create-react-app 工具。這個工具幫助開發人員建立 React 專案。開啟一個終端並執行以下命令來安裝:

npm i -g create-react-app
複製程式碼

搭建 React 應用

安裝完 create-react-app 後,進入專案所在目錄,執行以下命令:

create-react-app redux-vs-context
複製程式碼

幾秒鐘後,create-react-app 將完成應用程式的建立。在此之後,進入該工具建立的新目錄,並安裝 Redux:

# 進入應用目錄
cd redux-vs-context

# 安裝 Redux
npm i --save redux react-redux
複製程式碼

注意: redux 是主庫,react-redux 是促進 React 和 Redux 之間互動的庫。簡而言之,後者充當 React 和 Redux 之間的代理。

使用 Redux 開發 React 應用

你已經搭建好了你的 React 應用,安裝好了 Redux,現在,在你喜歡的開發工具中開啟你的專案。然後在 src 資料夾中建立三個檔案:

  • foods.json :此檔案包含一個用於儲存食物及其來源資訊的靜態陣列
  • reducers.js:此檔案用於管理應用中 Redux 狀態
  • actions.js:此檔案用於儲存應用中觸發 Redux 狀態改變的方法

所以,首先,開啟 foods.json 檔案,新增如下內容:

[
  {
    "name": "Chinese Rice",
    "origin": "China",
    "continent": "Asia"
  },
  {
    "name": "Amala",
    "origin": "Nigeria",
    "continent": "Africa"
  },
  {
    "name": "Banku",
    "origin": "Ghana",
    "continent": "Africa"
  },
  {
    "name": "Pão de Queijo",
    "origin": "Brazil",
    "continent": "South America"
  },
  {
    "name": "Ewa Agoyin",
    "origin": "Nigeria",
    "continent": "Africa"
  }
]
複製程式碼

如你所見,檔案儲存的資料並沒有什麼特別。僅僅是一個包含著不同國家不同食物的陣列。

在定義了 foods.json 檔案後,你可以專注於建立你的 Redux store 了。回顧一下,store 是儲存你的應用真實狀態的唯一來源。開啟你的 reducers.js 檔案,新增以下程式碼:

import Food from './foods';

const initialState = {
  food: Food,
  searchTerm: '',
};

export default function reducer(state = initialState, action) {
  // 根據 action type 區分
  switch (action.type) {
    case 'SEARCH_INPUT_CHANGED':
      const {searchTerm} = action.payload;
      return {
        ...state,
        searchTerm: searchTerm,
        food: searchTerm ? Food.filter(
          (food) => (food.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1)
        ) : Food,
      };
    default:
      return state;
  }
}
複製程式碼

在上面的程式碼中,你可以看到 reducer 方法接收兩個引數:stateaction。當你啟動你的 React 應用,這個方法將獲得它之前定義的 initialState,當你 dispatch 一個 action 的例項時,這個方法將獲得當前狀態(不再是 initialState)。然後,基於這些 actions 的內容,reducer 方法將為你的應用生成一個新的狀態。

接下來,你需要定義這些 actions 做什麼。實際上,為了簡單起見,你將定義一個單一的 action ,當使用者在你的應用中輸入搜尋詞時,這個 action 會被觸發。因此,開啟 actions.js 檔案,並在其中插入以下程式碼:

function searchTermChanged(searchTerm) {
  return {
    type: 'SEARCH_INPUT_CHANGED',
    payload: {searchTerm},
  };
}

export default {
  searchTermChanged,
};
複製程式碼

action 建立好之後,你需要做的下一件事就是將你的 App 元件包裝到 react-redux 提供的 Provider 元件中。Provider 統一負責 React 應用的資料(即 store)傳遞。

要使用 provider ,首先,你將使用 reducers.js 中定義的 initialState 建立 store。然後,通過 Provider 元件,你將把 store 傳給你的 App。要完成這些任務,你必須開啟 index.js 檔案,並將其內容替換為:

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import reducers from './reducers';
import App from './App';

// 使用 reducers 資訊建立 store。
// 這是因為 reducers 是 Redux Store 的控制中心。
const store = createStore(reducers);

ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.getElementById('root')
);
複製程式碼

就是這樣!你剛剛在 React 應用中配置完 Redux。現在,你必須實現 UI (使用者介面),這樣你的使用者就可以使用本節中實現的功能了。

建立 React 介面

現在,你已經完成了應用中的核心程式碼,你可以專注於構建你的使用者介面。為此,開啟你的 App.js 檔案,用下方程式碼替換它的內容:


import React from 'react';
import {connect} from 'react-redux';
import actions from './actions';
import './App.css';

function App({food, searchTerm, searchTermChanged}) {
  return (
    <div>
      <div className="search">
        <input
          type="text"
          name="search"
          placeholder="Search"
          value={searchTerm}
          onChange={e => searchTermChanged(e.target.value)}
        />
      </div>
      <table>
        <thead>
        <tr>
          <th>Name</th>
          <th>Origin</th>
          <th>Continent</th>
        </tr>
        </thead>
        <tbody>
        {food.map(theFood => (
          <tr key={theFood.name}>
            <td>{theFood.name}</td>
            <td>{theFood.origin}</td>
            <td>{theFood.continent}</td>
          </tr>
        ))}
        </tbody>
      </table>
    </div>
  );
}

export default connect(store => store, actions)(App);

複製程式碼

對於未用過 Redux 的人來說,他們唯一不熟悉的是用於封裝 App 元件的 connect 方法。這個方法實際上是一個高階元件( HOC ),充當應用程式和 Redux 之間的粘合劑。

使用以下命令啟動你的應用,你將能夠在瀏覽器中訪問你的應用:


npm run start

複製程式碼

然而,正如你所看到的,這個應用現在很難看。因此,為了讓它看起來更好一點,你可以開啟 App.css 檔案,用以下內容替換它的內容:


table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 15px;
  line-height: 25px;
}

th {
  background-color: #eee;
}

td, th {
  text-align: center;
}

td:first-child {
  text-align: left;
}

input {
  min-width: 300px;
  border: 1px solid #999;
  border-radius: 2px;
  line-height: 25px;
}

複製程式碼

Alt text

完成了!你現在有了一個基本的 React 和 Redux 的應用,可以開始學習如何遷移到 Context API 上了。

使用 React Context API 實現應用

在本節,你將要學習如何將你的 Redux 應用遷移到 React Context API 上。

幸運的是,你不需要在 Redux 和 Context API 之間做很多的重構。

首先,你必須從你的應用中移除 Redux 元件。為此,請開啟終端,刪除 reduxreact-redux 庫:


npm rm redux react-redux

複製程式碼

之後,刪除應用中對這些庫的引用程式碼。開啟 App.js 刪除以下幾行:


import {connect} from 'react-redux';
import actions from './actions';

複製程式碼

然後,在相同的檔案中,用下方的程式碼替換最後一行(以 export default 開頭的那一行):


export default App;

複製程式碼

通過這些改變,你可以用 Context API 重寫你的應用了。

從 Redux 遷移到 React Context API

要將之前的應用從 Redux 驅動的應用轉換為使用 Context API,你需要一個 context 來儲存應用的資料(該 context 將替換 Redux Store)。此外,你還需要一個 Context.Provider 元件,該元件包含 stateprops 和正常的 React 元件生命週期。

為此,你需要在 src 目錄中建立一個 providers.js 檔案,並向其中新增以下程式碼:


import React from 'react';
import Food from './foods';

const DEFAULT_STATE = { allFood: Food, searchTerm: '' };

export const ThemeContext = React.createContext(DEFAULT_STATE);

export default class Provider extends React.Component {
  state = DEFAULT_STATE;
  searchTermChanged = searchTerm => {
    this.setState({searchTerm});
  };

  render() {
    return (
      <ThemeContext.Provider value={{
        ...this.state,
        searchTermChanged: this.searchTermChanged,
      }}> {this.props.children} </ThemeContext.Provider>);
  }
}

複製程式碼

上面程式碼中定義的 Provider 類負責將其他元件封裝在 ThemeContext.Provider 中。通過這樣做,你可以讓這些元件訪問應用中的 state 和更改 state 的 searchTermChanged 方法。

若要在元件樹中使用這些值,你需要建立一個 ThemeContext.Consumer 元件。這個元件將需要一個 render 渲染方法,該方法將接收上述 props 值作為引數。

因此,接下來,你需要在 src 目錄中建立一個名為 consumer.js 的檔案,並將以下程式碼寫入其中:


import React from 'react';
import {ThemeContext} from './providers';

export default class Consumer extends React.Component {
  render() {
    const {children} = this.props;

    return (
      <ThemeContext.Consumer>
        {({allFood, searchTerm, searchTermChanged}) => {
          const food = searchTerm
            ? allFood.filter(
              food =>
                food.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1
            )
            : allFood;

          return React.Children.map(children, child =>
            React.cloneElement(child, {
              food,
              searchTerm,
              searchTermChanged,
            })
          );
        }}
      </ThemeContext.Consumer>
    );
  }
}

複製程式碼

現在,為了完成遷移,你將開啟 index.js 檔案,並在 render() 函式中,用 Consumer 元件包裝 App 元件。此外,你需要將 Consumer 包裝在 Provider 元件中。程式碼如下所示:


import React from 'react';
import ReactDOM from 'react-dom';
import Provider from './providers';
import Consumer from './consumer';
import App from './App';

ReactDOM.render(
  <Provider>
    <Consumer>
      <App />
    </Consumer>
  </Provider>,
  document.getElementById('root')
);

複製程式碼

打完收工!你剛剛完成了從 Redux 到 React Context API 的遷移。如果你現在啟動你的應用,你會發現整個應用執行如常。唯一不同的是,你的應用不再使用 Redux 了。

“新增的 React Context API 在減小應用體積方面是 Redux 的優良替代品。”

題外話:使用 Auth0 使你的 React 應用更安全

原文中有關於 Auth0 使用的詳細教程,但譯者認為此處內容和本文主題關係不大,故不作翻譯。感興趣者可移步原文閱讀該部分內容。

總結

Redux 是一個高階狀態管理庫,適合在構建大規模 React 應用時使用。另一方面,Context API 可以用於位元組大小級別資料更改的小規模 React 應用中。通過使用 Context API,你不必像 reducersactions 等一樣編寫大量程式碼,就能通過狀態變化完成邏輯展現。

相關文章