React + Redux 簡單範例了解工作原理

8 月 2, 2018 | | 0 comments

在這篇文章中,我們將揭開Redux 如何使用向後方法以及一個非常簡單的React + Redux示例的神秘面紗,我認為這將有助於您的理解。正如Redux發布的內容一樣,在解決術語之前,我將嘗試用簡單的術語解釋Redux。

如果你還不確定Redux的用途或者為什麼要使用它,請閱讀Redux的這個解釋然後再回到這裡。

第一:簡單的反應狀態

我們將從一個簡單的舊React狀態的例子開始,然後逐個添加Redux。

這裡是代碼(我省略了CSS以保持這個簡單,所以它不會像圖像那樣漂亮):

import React from 'react';

class Counter extends React.Component {
  state = { count: 0 }

  increment = () => {
    this.setState({
      count: this.state.count + 1
    });
  }

  decrement = () => {
    this.setState({
      count: this.state.count - 1
    });
  }

  render() {
    return (
      

Counter

{this.state.count}
) } }

快速回顧一下,這是如何工作的:

  • 該count狀態存儲在頂層Counter組分
  • 當用戶單擊“+”時,將onClick調用按鈕的處理程序,該處理程序綁定到組件中的increment函數Counter。
  • 該increment函數使用新計數更新狀態。
  • 由於狀態已更改,React將重新呈現Counter組件(及其子組件),並顯示新的計數器值。

如果您需要有關狀態更改如何工作的更多詳細信息,請閱讀React中的狀態可視指南,然後返回此處。說真的:如果以上不是你的評論,你需要在學習Redux 之前了解React狀態的工作原理。

快速設置

如果您想跟隨代碼,請立即創建項目:

如果沒有,請安裝create-react-app(npm install -g create-react-app)

創建一個項目: create-react-app redux-intro

打開src/index.js並替換它:

import React from 'react';
import { render } from 'react-dom';
import Counter from './Counter';

const App = () => (
  
); render(, document.getElementById('root'));

src/Counter.js使用上面的Counter示例中的代碼創建一個。

關於React Redux示例

如第1部分所述,Redux將您的應用程序狀態保存在單個商店中。然後,您可以提取該狀態的部分並將其作為道具插入組件中。這使您可以將數據保存在一個全局位置(store)並將其直接提供給應用程序中的任何組件,而無需將道具傳遞到多個級別的體操。

旁注:您經常會看到“state”和“store”這兩個詞可互換使用。從技術上講,state是數據,store是保存的地方。

在我們完成以下步驟時,請在編輯器中進行操作!它將幫助您了解其工作原理(我們將共同解決一些錯誤)。

將Redux添加到項目中:

$ yarn add redux react-redux

以下是一個轉換計數器以使用Redux的視頻演練(或者如果視頻不是你的東西,請繼續閱讀!)。

回到Counter應用程序,讓我們想像一下我們將組件的狀態移動到Redux。

我們將從組件中刪除狀態,因為我們很快就會從Redux中獲取該狀態:

import React from 'react';

class Counter extends React.Component {
  increment = () => {
    // fill in later
  }

  decrement = () => {
    // fill in later
  }

  render() {
    return (
      

Counter

{this.props.count}
) } } export default Counter;

請注意,已{this.state.count}更改為{this.props.count}。當然,這還不行,因為櫃檯沒有收到count道具。我們將使用Redux來注入它。

為了計算Redux的數量,我們首先需要connect在頂部導入函數:

import { connect } from 'react-redux';

然後我們需要將Counter組件“連接”到底部的Redux:

// Add this function:
function mapStateToProps(state) {
  return {
    count: state.count
  };
}

// Then replace this:
// export default Counter;

// With this:
export default connect(mapStateToProps)(Counter);

這將失敗並出現錯誤(在一秒鐘內更多)。

以前我們導出組件本身,現在我們用這個connect函數調用包裝它。

什麼connect?
你可能會注意到這個電話看起來很少……很奇怪。為什麼connect(mapStateToProps)(Counter)不connect(mapStateToProps, Counter)和connect(Counter, mapStateToProps)?那是做什麼的?

它是以這種方式編寫的,因為它connect是一個高階函數,這是一種說它在你調用它時返回一個函數的奇特方式。然後使用組件調用該函數將返回一個新的(包裝的)組件。

另一個名稱是高階組件(又名“HOC”)。HOCs最近遇到了一些不好的報導,但它們仍然非常有用,並且connect是一個很有用的例子。

什麼connect是掛鉤Redux,拉出整個狀態,並通過mapStateToProps您提供的功能傳遞它。這需要是一個自定義函數,因為只有你才能知道Redux中狀態的“形狀”。

connect 通過整個州似乎說,“嘿,告訴我你需要從這混亂的混亂中得到什麼。”

您返回的對象mapStateToProps將作為props提供給您的組件。上面的示例將state.count作為countprop 的值傳遞:對像中的鍵成為prop名稱,其對應的值將成為props的值。所以你看,這個函數字面上定義了從狀態到道具的映射。

錯誤意味著進步!

如果您正在跟隨,您將在控制台中看到如下錯誤:

無法在“連接(計數器)”的上下文或道具中找到“存儲”。將根組件包裝在一個,或明確地將“store”作為道具傳遞給“Connect(Counter)”。

由於connect從Redux商店中提取數據,並且我們沒有設置store或告訴應用程序如何找到它,因此這個錯誤非常符合邏輯。Redux沒有任何想法,現在正在發生什麼。

提供store

終極版擁有全局狀態為整個應用程序,並通過包裝與整個應用程序Provider的組件react-redux,每個組件在應用程序中的樹,就可以使用connect,如果要訪問Redux的商店。

這意味著App,App(和Counter)的孩子,以及他們孩子的孩子等等 – 所有人現在都可以訪問Redux商店,但前提是他們被調用明確包裝connect。

我並不是說實際上這樣做 – connect每一個組件都是一個壞主意(設計凌亂,而且速度慢)。

這Provider件事現在看起來像是魔法。它有點; 它實際上使用了React的“上下文”功能。

它就像一個連接到每個組件的秘密通道,並使用connect打開通道的門。

想像一下,把糖漿倒在一堆煎餅上,即使你把它倒在上面,它也能如何成為所有的煎餅。Provider為Redux做那件事。

在src/index.js,導入Provider並包裝它的內容App。

import { Provider } from 'react-redux';

...

const App = () => (
  
    
  
);

我們仍然會收到這個錯誤 – 那是因為Provider需要商店才能使用。它將商店作為道具,但我們需要先創建一個。

創建stroe

Redux帶有一個方便的功能,可以創建商店,並且它被稱為createStore。是的。讓我們做一個商店並將其傳遞給提供者:

import { createStore } from 'redux';

const store = createStore();

const App = () => (
  
    
  
);

另一個錯誤,但這次不同:

所以,這是關於Redux的事情:它不是很聰明。您可能希望通過創建store,它會為該store內的state提供一個很好的默認值。也許是一個空對象?

但不是:Redux 對您所在state的形狀做出零假設。隨你便!它可以是一個對象,一個數字,一個字符串,或者你需要的任何東西。所以我們必須提供一個返回狀態的函數。該功能稱為減速器(我們將在一分鐘內看到原因)。因此,讓我們做最簡單的一個,傳遞進去createStore,看看會發生什麼:

function reducer() {
  // just gonna leave this blank for now
  // which is the same as return undefined;
}

const store = createStore(reducer);

Reducer應該返回值

錯誤現在不同了:

無法讀取未定義的屬性’count’

它正在破碎,因為我們正在嘗試訪問state.count,但state未定義。Redux期望我們的reducer函數返回一個值state,除了它(隱式)返回undefined。事情被理所當然地打破了。

預計減速機將返回該州。它實際上應該採取當前狀態並返回新狀態,但沒關係; 我們會回過頭來看看。

讓減速器返回與我們需要的形狀匹配的東西:具有count屬性的對象。

function reducer() {
  return {
    count: 42
  };
}

嘿! 有用!計數現在顯示為“42”。真棒。

但只有一件事:伯爵永遠陷入42。

目前為止做的事情

在我們開始實際更新計數器之前,讓我們看看我們現在做了些什麼:

  • 我們寫了一個mapStateToProps函數來執行名稱所說的:將Redux狀態轉換為包含props的對象。
  • 我們連接Redux的店我們Counter與組件connect的功能react-redux,使用mapStateToProps功能配置的連接是如何工作的。
  • 我們創建了一個reducer函數來告訴Redux我們的狀態應該是什麼樣的。
  • 我們使用巧妙命名的createStore函數來創建一個商店,並將其傳遞給它reducer。
  • 我們將整個應用程序包裝在Provider隨附的組件中react-redux,並將其作為支柱傳遞給我們的商店。
  • 該應用程序完美無缺,除了計數器停留在42的事實。

互動(使其發揮作用)

到目前為止,我知道這是非常蹩腳的。您可以在60秒內編寫一個帶有數字“42”的靜態HTML頁面和2個破壞的按鈕,但是在這裡,您將閱讀如何使用React和Redux完全相同的東西以及誰知道還有什麼。

我保證下一節將使這一切都值得。

實際上,沒有。我收回之前說過的話。一個簡單的Counter應用程序是一個很好的教學工具,但Redux對於這樣的東西絕對有點過分。對於這麼簡單的事情,React狀態非常好。哎呀,即使是簡單的JS也會很棒。為工作選擇合適的工具。Redux並不總是那個工具。但我離題了。

初始狀態

所以我們需要一種方法來告訴Redux改變計數器。

還記得reducer我們寫的功能嗎?(當然你這樣做,它是2分鐘前)

還記得我提到它如何處理當前狀態並返回新狀態?好吧,我再次撒謊。它實際上採用當前狀態和一個動作,然後它返回新狀態。我們應該這樣寫:

function reducer(state, action) {
  return {
    count: 42
  };
}

Redux第一次調用此函數時,它將undefined作為state。這是你返回初始狀態的提示。對我們來說,這可能是一個count0 的對象。

將初始狀態寫在reducer之上是很常見的,並使用ES6的默認參數功能為state未定義的參數提供值。

const initialState = {
  count: 0
};

function reducer(state = initialState, action) {
  return state;
}

試試吧。它應該仍然可以工作,除非現在計數器被卡在0而不是42.真棒。

行動

我們終於準備好談論這個action參數了。它是什麼?它從何而來?我們怎樣用它來改變該死的counter?

“action”是一個JS對象,它描述了我們想要進行的更改。唯一的要求是對象需要有一個type屬性,其值應該是一個字符串。這是一個動作的例子:

{
  type: "INCREMENT"
}

這是另一個:

{
  type: "DECREMENT"
}

齒輪是否在轉動?你知道接下來我們要做什麼嗎?

回應行動

請記住,reducer的工作是採取當前狀態和動作並找出新狀態。因此,如果減速器收到類似的動作{ type: “INCREMENT” },您想要返回什麼樣的新狀態?

如果你回答了這樣的話,你就走在了正確的軌道上:

function reducer(state = initialState, action) {
  if(action.type === "INCREMENT") {
    return {
      count: state.count + 1
    };
  }

  return state;
}

對於要處理的每個操作,使用switch帶有cases 的語句是很常見的。將您的減速器更改為如下所示:

function reducer(state = initialState, action) {
  switch(action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1
      };
    case 'DECREMENT':
      return {
        count: state.count - 1
      };
    default:
      return state;
  }
}

你會注意到它總是存在所有它所做的後備情況return state。這很重要,因為Redux可以(將)使用不知道如何處理的操作調用您的reducer。事實上,你將收到的第一個行動是{ type: “@@redux/INIT” }。試著把它放在console.log(action)上方switch並看看。

請記住,reducer的工作是返回一個新狀態,即使該狀態與當前狀態不變。你永遠不想從“有一個state”到“state=未定義”,對吧?如果你離開這個default案子就會發生這種情況。不要那樣做。

永遠不改變狀態

還有一件事永遠不會做:不變異的state。國家是不變的。你永遠不能改變它。這意味著你不能這樣做:

function brokenReducer(state = initialState, action) {
  switch(action.type) {
    case 'INCREMENT':
      // NO! BAD: this is changing state!
      state.count++;
      return state;

    case 'DECREMENT':
      // NO! BAD: this is changing state too!
      state.count--;
      return state;

    default:
      // this is fine.
      return state;
  }
}

行動來自哪裡?

這個難題的一部分仍然存在:我們需要一種方法將動作提供給我們的減速器功能,以便我們可以增加和減少計數器。

行動不是天生的,但它們是 派遣的,有一個方便的功能叫做dispatch。

該dispatch函數由Redux存儲的實例提供。也就是說,你不能只是import { dispatch }在路上。你可以打電話store.dispatch(someAction),但這不是很方便,因為store實例只在一個文件中可用。

幸運的是,這個connect功能有了我們的支持。除了注入mapStateToProps作為道具的結果外,connect 還注入了作為道具的dispatch功能。有了這些知識,我們終於可以讓計數器再次運作。

這是其最重要的組成部分。如果你一直在關注一起,改變了唯一的東西的實現increment和decrement:他們現在所說的dispatch道具,通過它的動作。

import React from 'react';
import { connect } from 'react-redux';

class Counter extends React.Component {
  increment = () => {
    this.props.dispatch({ type: 'INCREMENT' });
  }

  decrement = () => {
    this.props.dispatch({ type: 'DECREMENT' });
  }

  render() {
    return (
      

Counter

{this.props.count}
) } } function mapStateToProps(state) { return { count: state.count }; } export default connect(mapStateToProps)(Counter);

可以在Github上找到整個項目的代碼(它的所有兩個文件)。

使用CDN React+Redux範例展示

翻譯文章來源:How Redux Works: A Counter-Example