30天入坑React ---------------day19 Redux
這篇文章是系列的一部分 。
在本系列中,我們將從非常基礎開始,逐步瞭解您需要了解的所有內容,以便開始使用React。如果您曾經想學習React,那麼這裡就是您的最佳選擇!
使用Redux進行資料管理
完整程式碼演示
線上演示 :我的示例
憑藉flux和Redux的知識,讓我們將Redux整合到我們的應用程式中,並瀏覽連線的應用程式。
昨天,我們(詳細地)討論了Flux模式的原因,它是什麼,我們可以使用的不同選項,以及Redux。
今天,我們將回到程式碼並在我們的應用程式中新增Redux。我們現在用它構建的應用程式很簡單,只會向我們顯示頁面最後一次獲取當前時間。為簡單起見,我們不會使用JavaScript Date
我們要使用Redux的第一件事就是安裝庫。我們可以使用npm
包管理器進行安裝redux
。在我們之前構建的應用程式的根目錄中,讓我們執行npm install
命令來安裝redux:
npm install --save redux
我們還需要安裝的軟體包,我們將與Redux使用時,react-redux
這將幫助我們react
和redux
綁在一起:
npm install --save react-redux
配置和設定
我們需要做的下一步工作是在我們的應用程式內部設定Redux。我們需要執行以下操作來設定它:
- Define reducers 定義減速器
- Create a store 建立一個商店
- Create action creators 建立動作建立者
- Tie the store to our React views 將商店與我們的React檢視聯絡起來
- Profit 利潤
第5步沒有promises,但它會很好,是嗎?
Precursor( 先導)
我們將繼續談論術語,所以請輕輕地進行這種設定討論(實現更重要的是讓我們的手指移動)。我們稍微重構我們的應用程式(煩人,我知道......但這是最後一次)所以我們可以建立一個 包裝器元件( can create a wrapper component )來通過我們的應用程式提供資料。
完成後,我們的應用樹將具有以下形狀:
[Root] -> [App] -> [Router/Routes] -> [Component]
不再拖延,讓我們src/App.js
進入src/containers
目錄,我們需要同時更新我們的匯入中的一些路徑。我們將使用幾天前討論的反應路由器材料。
要安裝路由的庫
npm install --save react-router-dom
我們將在<Switch />
宣告中包含一些路線,以確保一次只顯示一個。
App.js
import React from 'react';
import {
BrowserRouter as Router,
Route,
Switch
} from 'react-router-dom'
// We'll load our views from the `src/views`
// directory
import Home from './views/Home/Home';
import About from './views/About/About';
const App = props => {
return (
<Router>
<Switch>
<Route
path="/about"
component={About} />
<Route
path="*"
component={Home} />
</Switch>
</Router>
)
}
export default App;
先將Home.js 和About.js寫為如下,看下效果 下方程式碼是Redux案例中使用的
const Home = (props) => {
return (
<div className="home">
<h1>Welcome home!</h1>
<p>Current time: {props.currentTime}</p>
</div>
);
}
const About = (props) => (
<div className="about">
<h2>About route</h2>
</div>
)
在views/Home/Home.js && view/About/About
import React from 'react';
import { connect } from 'react-redux';
const Home = (props) => {
return (
<div className="home">
<h1>Welcome home!</h1>
<p>Current time: {props.currentTime}</p>
</div>
);
}
const mapStateToProps = state => {
return {
currentTime: state.currentTime
}
}
export default connect(
mapStateToProps
)(Home);
import React from 'react';
const About = (props) => (
<div className="about">
<h2>About route</h2>
</div>
)
export default About
此外,我們需要建立一個我們將呼叫的新容器Root
,它將包裝整個<App />
元件並使 store 商店可用於應用程式的其餘部分。讓我們建立src/containers/Root.js
檔案:
touch src/containers/Root.js
目前,我們將在此處使用佔位符元件,但在討論store 商店時我們將替換此內容。現在,讓我們輸出一些東西:
import React from 'react';
import App from './App';
const Root = (props) => {
return (
<App />
);
}
export default Root;
最後,讓我們更新我們在src/index.js
檔案中呈現應用程式的路徑,以使用我們的新Root
容器而不是App
之前使用的容器。
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './containers/Root';
import './index.css';
ReactDOM.render(
<Root />,
document.getElementById('root')
);
新增Redux
現在有了堅實的應用程式結構,我們可以開始新增Redux了。我們將在一些Redux結構中採取的步驟通常對於我們將構建的大多數應用程式都是相同的。我們需要:
- Write a root reducer
- Write actionCreators
- Configure the store with the rootReducer, the store, and the app
- Connect the views to the actionCreators
- 寫一個根減速器
- 寫actionCreators
- 使用rootReducer,商店和應用程式配置商店
- 將檢視連線到actionCreators
我們會有目的地保持這個高級別的介紹有點短,所以如果這是一個滿口的話,請緊緊抓住它,這一切都會很快變得更有意義。
讓我們設定結構以允許我們新增redux。我們幾乎可以在一個src/redux
目錄中完成所有工作。讓我們建立該目錄。
mkdir -p src/redux
touch src/redux/configureStore.js
touch src/redux/reducers.js
讓我們首先建立我們的Reducer減速器。雖然聽起來很複雜,但是Reducer減速器實際上非常簡單,具有一定的經驗。減速器實際上只是一個功能。它的唯一責任是返回下一個state狀態的代表。
在Redux模式中,與flux不同,我們只為整個應用程式處理一個全域性儲存。這使事情變得更容易處理,因為我們的應用程式的資料只有一個地方可以存在。
該root reducer(根減速器)功能是負責返回應用程式的當前全域性狀態的表示。當我們在store商店上dispatch排程一個action動作時,將使用應用程式呼叫此reducer函式的action動作導致 current state當前狀態和the state to update狀態更新。
讓我們在一個檔案中構建我們的根reducer src/redux/reducers.js
。
// Initial (starting) state
export const initialState = {
currentTime: new Date().toString()
}
// Our root reducer starts with the initial state
// and must return a representation of the next state
const rootReducer = (state = initialState, action) => {
return state;
}
export default rootReducer
這個初始值是要匯出的以後設定值會用到
// Initial (starting) state
export const initialState = {
currentTime: new Date().toString()
}
在函式中,我們將第一個引數定義為初始狀態(第一次執行時,rootReducer
呼叫時不帶引數,因此它將始終返回initialState
第一次執行)。
那是現在的rootReducer。就目前而言,State(狀態)始終與initialState具有相同的值。在我們的例子中,這意味著我們的 data tree(DOM資料樹)只有一個single key (單獨的鍵)currentTime
。
什麼是action(動作)?
這裡的第二個引數是從商店排程的操作。我們很快就會回到這意味著什麼。現在,讓我們來看看這個action動作。
至少,一個action動作必須包括一個type
鍵。該type
鍵可以是任何我們想要的value(值),但是它必須存在。例如,在我們的應用程式中,我們偶爾會發送一個動作,我們想告訴store商店獲取新的當前時間。我們可能將此操作稱為字串值FETCH_NEW_TIME
。
我們可能從我們的store(商店)dispatch(傳送)處理此更新的操作如下所示:
{
type: 'FETCH_NEW_TIME'
}
因為我們通過輸入這個字串很多並且我們希望避免在某處可能出現錯誤拼寫,所以建立一個types.js
將操作型別匯出為常量的檔案是很常見的。讓我們遵循這個約定並建立一個src/redux/types.js
檔案:
export const FETCH_NEW_TIME = 'FETCH_NEW_TIME';
我們將從types.js
檔案中引用它,而不是使用硬編碼的'FETCH_NEW_TIME'字串呼叫操作:
建立一個src/redux/actionCreators.js
檔案
import * as types from './types';
export const fetchNewTime = () => ({
type: types.FETCH_NEW_TIME,
})
當我們想要將資料與我們的操作一起傳送時,我們可以向操作新增我們想要的any keys任何鍵。我們通常會看到這個被稱為payload
,但它可以被稱為任何東西。將有效的資訊稱為負載payload
是一種慣例。
我們的FETCH_NEW_TIME
操作將使用新的當前時間傳送有效負載。由於我們希望通過我們的操作傳送可序列化值( serializable value),因此我們將傳送新當前時間的字串值。
{
type: types.FETCH_NEW_TIME,
payload: new Date().toString() // Any serializable value
}
回到我們的reducer中,我們可以檢查操作型別並採取適當的步驟來建立下一個狀態。在我們的例子中,我們只會儲存payload
。如果type
動作是FETCH_NEW_TIME
,我們將返回新的currentTime(from our action payload 來自我們的動作有效負載)和狀態的其餘部分(using the ES6 spread syntax 使用ES6擴充套件語法):
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case types.FETCH_NEW_TIME:
return { ...state, currentTime: action.payload}
default:
return state;
}
}
完整的reducers.js程式碼
import * as types from './types';
export const initialState = {
currentTime: new Date().toString(),
}
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case types.FETCH_NEW_TIME:
return { ...state, currentTime: action.payload}
default:
return state;
}
}
請記住,reducers 必須返回一個狀態,因此在預設情況下,請確保至少返回當前狀態。
保持清醒
由於每次排程操作時都會執行reducer函式,因此我們希望確保這些函式儘可能簡單快速。我們不希望它們引起任何副作用或者有很多延遲。
我們將在動作建立者中處理reducer 之外的副作用。
在我們檢視動作建立者(以及為什麼我們稱之為動作建立者)之前,讓我們將我們的商店連線到我們的應用程式。
我們將使用該react-redux
包將我們的檢視連線到我們的redux商店。讓我們確保使用npm
以下命令安裝此軟體包:
npm install --save react-redux
連線商店到檢視
該react-redux
包匯出一個名為的元件Provider
。該Provider
元件使我們的應用程式中的所有容器元件都可以使用該儲存,而無需我們每次都需要手動傳遞它。
該Provider
元件需要一個store
它期望成為有效的redux儲存的prop,因此我們需要configureStore
在應用程式執行之前完成一個函式而不會出錯。現在,讓Provider
我們在我們的應用程式中連線元件。我們將通過更新Root
我們之前建立的包裝器元件來使用該Provider
元件。
Root.js 修改
import { Provider } from 'react-redux';
// ...
const Root = (props) => {
// ...
return (
<Provider store={store}>
<App />
</Provider>
);
}
現在還沒有註釋掉的那兩句,需要配置Store才有
現在Root.js程式碼如下
import React from 'react';
import { Provider } from 'react-redux';
import App from './App';
// import configureStore from '../redux/configureStore'
const Root = (props) => {
// const store = configureStore();
return (
<Provider store={store}>
<App />
</Provider>
);
}
export default Root;
請注意,我們將store
值傳送給我們的Provider
元件......但我們還沒有建立商店!我們現在解決這個問題。
配置商店
為了建立商店,我們將使用new src/redux/configureStore.js
來匯出一個負責建立商店的函式。
我們如何建立商店?
該redux
包匯出一個函式createStore
,它將為我們建立實際的儲存,所以讓我們開啟src/redux/configureStore.js
檔案並匯出一個函式(我們將很快定義)呼叫configureStore()
並匯入createStore
幫助器:
import {createStore} from 'redux';
// ...
export const configureStore = () => {
// ...
}
// ...
export default configureStore;
我們實際上還沒有在商店中返回任何東西,所以讓我們實際redux
使用createStore
從redux匯入的函式建立商店:
import {createStore} from 'redux';
export const configureStore = () => {
const store = createStore();
return store;
}
export default configureStore;
然後開啟路由中的註釋程式碼Root.js如下
Root.js
import React from 'react';
import { Provider } from 'react-redux';
import App from './App';
import configureStore from '../redux/configureStore'
const Root = (props) => {
const store = configureStore();
return (
<Provider store={store}>
<App />
</Provider>
);
}
export default Root;
如果我們在瀏覽器中載入頁面,我們會看到有一個巨大的錯誤,沒有頁面被渲染。
error redux告訴我們,我們的商店內沒有減速器。如果沒有reducer,它將不知道如何處理動作或如何建立狀態等。為了超越這個錯誤,我們需要引用我們建立的rootReducer。
該createStore
函式希望我們將rootReducer作為第一個引數傳遞。它還會期望初始狀態作為第二個引數傳入。我們將從reducers.js
我們建立的檔案中匯入這兩個值。
import { rootReducer, initialState } from './reducers'
// ...
export const configureStore = () => {
const store = createStore(
rootReducer, // root reducer
initialState, // our initialState
);
return store;
}
現在讓我們通過呼叫函式建立Root.js
一個例項來更新我們的檔案。store
configureStore()
const Root = (props) => {
const store = configureStore();
return (
<Provider store={store}>
<App />
</Provider>
);
}
連線檢視(續)
我們的應用程式中的所有內容都設定為使用Redux而不會產生太多開銷。提供的另一個便利redux
是使用包匯出的函式將狀態樹的各個部分繫結到不同的元件。connect()
react-redux
該connect()
函式返回一個函式,該函式期望第一個引數是元件的引數。這通常被稱為高階元件。
該connect()
函式希望我們將至少一個引數傳遞給函式(但通常我們將傳入兩個引數)。它期望的第一個引數是一個將被呼叫的函式,state
並期望一個物件將資料連線到檢視。讓我們看看我們是否可以在程式碼中揭開這種行為的神祕面紗。
我們將此函式稱為mapStateToProps
函式。因為它的職責是將狀態對映到與元件原始合併的物件props
。
讓我們建立Home檢視,src/views/Home.js
並使用此connect()
函式繫結currentTime
我們的狀態樹中的值。
import { connect } from 'react-redux';
// ...
const mapStateToProps = state => {
return {
currentTime: state.currentTime
}
}
export default connect(
mapStateToProps
)(Home);
此connect()
功能自動傳遞任何在該函式的第一個引數的按鍵作為props
給Home
元件。
在我們的演示currentTime
中,Home
元件中的prop 將對映到狀態樹鍵currentTime
。讓我們更新Home
元件以顯示以下值currentTime
:
const Home = (props) => {
return (
<div className="home">
<h1>Welcome home!</h1>
<p>Current time: {props.currentTime}</p>
</div>
);
}
雖然這個演示不是很有趣,但它表明我們的Redux
應用程式已設定為我們data
致力於全域性狀態和我們的檢視元件對映資料。
明天我們將通過動作建立者開始觸發我們的 global state 全域性狀態更新,並通過將多個redux模組組合在一起工作。
完整程式碼演示
學習REACT正確的方法
React和朋友的最新,深入,完整的指南。
下一章:
Redux動作
本教程系列的完整原始碼可以在GitHub repo上找到,其中包括所有樣式和程式碼示例。
如果您在任何時候感到困難,還有其他問題,請隨時通過以下方式與我們聯絡: