在這篇文章中,我們將揭開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上找到整個項目的代碼(它的所有兩個文件)。