摘要:接下來演示不變性打開終端并啟動輸入。修改代碼如下我們使用在控制臺中打印出當(dāng)前的狀態(tài)。可以在控制臺中確認新的商品已經(jīng)添加了。修改和文件最后,我們在中分發(fā)這兩個保存完代碼之后,可以在瀏覽器的控制臺中檢查修改和刪除的結(jié)果。
典型的Web應(yīng)用程序通常由共享數(shù)據(jù)的多個UI組件組成。通常,多個組件的任務(wù)是負責(zé)展示同一對象的不同屬性。這個對象表示可隨時更改的狀態(tài)。在多個組件之間保持狀態(tài)的一致性會是一場噩夢,特別是如果有多個通道用于更新同一個對象。
舉個?,一個帶有購物車的網(wǎng)站。在頂部,我們用一個UI組件顯示購物車中的商品數(shù)量。我們還可以用另一個UI組件,顯示購物車中商
品的總價。如果用戶點擊添加到購物車按鈕,則這兩個組件應(yīng)立即更新當(dāng)前的數(shù)據(jù)。如果用戶從購物車中刪除商品、更改數(shù)目、使用優(yōu)惠券或者更改送貨地點,則相關(guān)的UI組件都應(yīng)該更新出正確的信息。
可以看到,隨著功能范圍的擴大,一個簡單的購物車將會很難保持數(shù)據(jù)同步。
在這篇文章中,我將介紹Redux框架,它可以幫助你以簡單易用的方式構(gòu)建復(fù)雜項目并進行維護。為了使學(xué)習(xí)更容易,我們將使用一個簡化的購物車項目來學(xué)習(xí)Redux的工作遠離。你需要至少熟悉React庫,因為你以后需要將其與Redux集成。
學(xué)習(xí)前提在我們開始以前,確保你熟悉以下知識:
函數(shù)式JavaScript
面向?qū)ο驤avaScript
JavaScript ES6 語法
同時,確保你的設(shè)備已經(jīng)安裝:
NodeJS
Yarn(或者npm)
什么是ReduxRedux是一個流行的JavaScript框架,為應(yīng)用程序提供一個可預(yù)測的狀態(tài)容器。Redux基于簡化版本的Flux框架,F(xiàn)lux是Facebook開發(fā)的一個框架。在標準的MVC框架中,數(shù)據(jù)可以在UI組件和存儲之間雙向流動,而Redux嚴格限制了數(shù)據(jù)只能在一個方向上流動。 見下圖:
在Redux中,所有的數(shù)據(jù)(比如state)被保存在一個被稱為store的容器中 → 在一個應(yīng)用程序中只能有一個。store本質(zhì)上是一個狀態(tài)樹,保存了所有對象的狀態(tài)。任何UI組件都可以直接從store訪問特定對象的狀態(tài)。要通過本地或遠程組件更改狀態(tài),需要分發(fā)一個action。分發(fā)在這里意味著將可執(zhí)行信息發(fā)送到store。當(dāng)一個store接收到一個action,它將把這個action代理給相關(guān)的reducer。reducer是一個純函數(shù),它可以查看之前的狀態(tài),執(zhí)行一個action并且返回一個新的狀態(tài)。
理解不變性(Immutability)在我們開始實踐之前,需要先了解JavaScript中的不變性意味著什么。在編碼中,我們編寫的代碼一直在改變變量的值。這是可變性。但是可變性常常會導(dǎo)致意外的錯誤。如果代碼只處理原始數(shù)據(jù)類型(numbers, strings, booleans),那么你不用擔(dān)心。但是,如果在處理Arrays和Objects時,則需要小心執(zhí)行可變操作。
接下來演示不變性:
打開終端并啟動node(輸入node)。
創(chuàng)建一個數(shù)組,并將其賦值給另一個變量。
> let a = [1, 2, 3] > let b = a > b.push(8) > b [1, 2, 3, 8] > a [1, 2, 3, 8]
可以看到,更新數(shù)組b也會同時改變數(shù)組a。這是因為對象和數(shù)組是引用數(shù)據(jù)類型 → 這意味著這樣的數(shù)據(jù)類型實際上并不保存值,而是存儲指向存儲單元的指針。
將a賦值給b,其實我們只是創(chuàng)建了第二個指向同一存儲單元的指針。要解決這個問題,我們需要將引用的值復(fù)制到一個新的存儲單元。在Javascript中,有三種不同的實現(xiàn)方式:
使用Immutable.js創(chuàng)建不可變的數(shù)據(jù)結(jié)構(gòu)。
使用JavaScript庫(如Underscore和Lodash)來執(zhí)行不可變的操作。
使用ES6方法執(zhí)行不可變操作。
本文將使用ES6方法,因為它已經(jīng)在NodeJS環(huán)境中可用了,在終端中,執(zhí)行以下操作:
> a = [1,2,3] [ 1, 2, 3 ] > b = Object.assign([],a) [ 1, 2, 3 ] > b.push(8) > b [ 1, 2, 3, 8 ] // b output > a [ 1, 2, 3 ] // a output
在上面的代碼中,修改數(shù)組b將不會影響數(shù)組a。我們使用Object.assign()創(chuàng)建了一個新的副本,由數(shù)組b指向。我們也可以使用操作符(...)執(zhí)行不可變操作:
> a = [1,2,3] [ 1, 2, 3 ] > b = [...a, 4, 5, 6] [ 1, 2, 3, 4, 5, 6 ] > a [ 1, 2, 3 ]
我不會深入這個主題,但是這里還有一些額外的ES6功能,我們可以用它們執(zhí)行不可變操作:
spread syntax - 用于追加操作
map function - 用于更新操作
filter function - 用于刪除操作
配置Redux配置Redux開發(fā)環(huán)境的最快方法是使用create-react-app工具。在開始之前,確保已經(jīng)安裝并更新了nodejs,npm和yarn。我們生成一個redux-shopping-cart項目并安裝Redux:
create-react-app redux-shopping-cart cd redux-shopping-cart yarn add redux # 或者npm install redux
首先,刪除src文件夾中除index.js以外的所有文件。打開index.js,刪除所有代碼,鍵入以下內(nèi)容:
import { createStore } from "redux"; const reducer = function(state, action) { return state; } const store = createStore(reducer);
讓我解釋一下上面的代碼:
首先,我們從redux包中引入createStore()方法。
我們創(chuàng)建了一個名為reducer的方法。第一個參數(shù)state是當(dāng)前保存在store中的數(shù)據(jù),第二個參數(shù)action是一個容器,用于:
type - 一個簡單的字符串常量,例如ADD, UPDATE, DELETE等。
payload - 用于更新狀態(tài)的數(shù)據(jù)。
我們創(chuàng)建一個Redux存儲區(qū),它只能使用reducer作為參數(shù)來構(gòu)造。存儲在Redux存儲區(qū)中的數(shù)據(jù)可以被直接訪問,但只能通過提供的reducer進行更新。
注意到,我在第二點中所提到state。目前,state為undefined或null。要解決這個問題,需要分配一個默認的值給state,使其成為一個空數(shù)組:
const reducer = function(state=[], action) { return state; }
讓我們更進一步。目前我們創(chuàng)建的reducer是通用的。它的名字沒有描述它的用途。那么我們?nèi)绾问褂枚鄠€reducer呢?我們將用到Redux包中提供的combineReducers函數(shù)。修改代碼如下:
// src/index.js import { createStore } from "redux"; import { combineReducers } from "redux"; const productsReducer = function(state=[], action) { return state; } const cartReducer = function(state=[], action) { return state; } const allReducers = { products: productsReducer, shoppingCart: cartReducer } const rootReducer = combineReducers(allReducers); let store = createStore(rootReducer);
在上面的代碼中,我們將通用的reducer修改為productReducer和cartReducer。創(chuàng)建這兩個空的reducer是為了展示如何在一個store中使用combineReducers函數(shù)組合多個reducer。
接下來,我們將為reducer定義一些測試數(shù)據(jù)。修改代碼如下:
// src/index.js … const initialState = { cart: [ { product: "bread 700g", quantity: 2, unitCost: 90 }, { product: "milk 500ml", quantity: 1, unitCost: 47 } ] } const cartReducer = function(state=initialState, action) { return state; } … let store = createStore(rootReducer); console.log("initial state: ", store.getState());
我們使用store.getState()在控制臺中打印出當(dāng)前的狀態(tài)。你可以在終端中執(zhí)行npm start或者yarn start來運行dev服務(wù)器。并在控制臺中查看state。
現(xiàn)在,我們的cartReducer什么也沒做,但它應(yīng)該在Redux的存儲區(qū)中管理購物車商品的狀態(tài)。我們需要定義添加、更新和刪除商品的操作(action)。我們首先定義ADD_TO_CART的邏輯:
// src/index.js … const ADD_TO_CART = "ADD_TO_CART"; const cartReducer = function(state=initialState, action) { switch (action.type) { case ADD_TO_CART: { return { ...state, cart: [...state.cart, action.payload] } } default: return state; } } …
我們繼續(xù)來分析一下代碼。一個reducer需要處理不同的action類型,因此我們需要一個SWITCH語句。當(dāng)一個ADD_TO_CART類型的action在應(yīng)用程序中分發(fā)時,switch中的代碼將處理它。
正如你所看到的,我們將action.payload中的數(shù)據(jù)與現(xiàn)有的state合并以創(chuàng)建一個新的state。
接下來,我們將定義一個action,作為store.dispatch()的一個參數(shù)。action是一個Javascript對象,有一個必須的type和可選的payload。我們在cartReducer函數(shù)后定義一個:
… function addToCart(product, quantity, unitCost) { return { type: ADD_TO_CART, payload: { product, quantity, unitCost } } } …
在這里,我們定義了一個函數(shù),返回一個JavaScript對象。在我們分發(fā)消息之前,我們添加一些代碼,讓我們能夠監(jiān)聽store事件的更改。
… let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();
接下來,我們通過分發(fā)消息到store來向購物車中添加商品。將下面的代碼添加在unsubscribe()之前:
… store.dispatch(addToCart("Coffee 500gm", 1, 250)); store.dispatch(addToCart("Flour 1kg", 2, 110)); store.dispatch(addToCart("Juice 2L", 1, 250));
下面是整個index.js文件:
// src/index.js import { createStore } from "redux"; import { combineReducers } from "redux"; const productsReducer = function(state=[], action) { return state; } const initialState = { cart: [ { product: "bread 700g", quantity: 2, unitCost: 90 }, { product: "milk 500ml", quantity: 1, unitCost: 47 } ] } const ADD_TO_CART = "ADD_TO_CART"; const cartReducer = function(state=initialState, action) { switch (action.type) { case ADD_TO_CART: { return { ...state, cart: [...state.cart, action.payload] } } default: return state; } } function addToCart(product, quantity, unitCost) { return { type: ADD_TO_CART, payload: { product, quantity, unitCost } } } const allReducers = { products: productsReducer, shoppingCart: cartReducer } const rootReducer = combineReducers(allReducers); let store = createStore(rootReducer); console.log("initial state: ", store.getState()); let unsubscribe = store.subscribe(() => console.log(store.getState()) ); store.dispatch(addToCart("Coffee 500gm", 1, 250)); store.dispatch(addToCart("Flour 1kg", 2, 110)); store.dispatch(addToCart("Juice 2L", 1, 250)); unsubscribe();
保存代碼后,Chrome會自動刷新。可以在控制臺中確認新的商品已經(jīng)添加了。
組織Redux代碼index.js中的代碼逐漸變得冗雜。我把所有的代碼都寫在index.js中是為了起步時的簡單易懂。接下來,我們來看一下如何組織Redux項目。首先,在src文件夾中創(chuàng)建一下文件和文件夾:
src/
├── actions
│ └── cart-actions.js
├── index.js
├── reducers
│ ├── cart-reducer.js
│ ├── index.js
│ └── products-reducer.js
└── store.js
然后,我們把index.js中的代碼進行整理:
// src/actions/cart-actions.js export const ADD_TO_CART = "ADD_TO_CART"; export function addToCart(product, quantity, unitCost) { return { type: ADD_TO_CART, payload: { product, quantity, unitCost } } }
// src/reducers/products-reducer.js export default function(state=[], action) { return state; }
// src/reducers/cart-reducer.js import { ADD_TO_CART } from "../actions/cart-actions"; const initialState = { cart: [ { product: "bread 700g", quantity: 2, unitCost: 90 }, { product: "milk 500ml", quantity: 1, unitCost: 47 } ] } export default function(state=initialState, action) { switch (action.type) { case ADD_TO_CART: { return { ...state, cart: [...state.cart, action.payload] } } default: return state; } }
// src/reducers/index.js import { combineReducers } from "redux"; import productsReducer from "./products-reducer"; import cartReducer from "./cart-reducer"; const allReducers = { products: productsReducer, shoppingCart: cartReducer } const rootReducer = combineReducers(allReducers); export default rootReducer;
// src/store.js import { createStore } from "redux"; import rootReducer from "./reducers"; let store = createStore(rootReducer); export default store;
// src/index.js import store from "./store.js"; import { addToCart } from "./actions/cart-actions"; console.log("initial state: ", store.getState()); let unsubscribe = store.subscribe(() => console.log(store.getState()) ); store.dispatch(addToCart("Coffee 500gm", 1, 250)); store.dispatch(addToCart("Flour 1kg", 2, 110)); store.dispatch(addToCart("Juice 2L", 1, 250)); unsubscribe();
整理完代碼之后,程序依然會正常運行。現(xiàn)在我們來添加修改和刪除購物車中商品的邏輯。修改cart-actions.js和cart-reducer.js文件:
// src/reducers/cart-actions.js … export const UPDATE_CART = "UPDATE_CART"; export const DELETE_FROM_CART = "DELETE_FROM_CART"; … export function updateCart(product, quantity, unitCost) { return { type: UPDATE_CART, payload: { product, quantity, unitCost } } } export function deleteFromCart(product) { return { type: DELETE_FROM_CART, payload: { product } } }
// src/reducers/cart-reducer.js … export default function(state=initialState, action) { switch (action.type) { case ADD_TO_CART: { return { ...state, cart: [...state.cart, action.payload] } } case UPDATE_CART: { return { ...state, cart: state.cart.map(item => item.product === action.payload.product ? action.payload : item) } } case DELETE_FROM_CART: { return { ...state, cart: state.cart.filter(item => item.product !== action.payload.product) } } default: return state; } }
最后,我們在index.js中分發(fā)這兩個action:
// src/index.js … // Update Cart store.dispatch(updateCart("Flour 1kg", 5, 110)); // Delete from Cart store.dispatch(deleteFromCart("Coffee 500gm")); …
保存完代碼之后,可以在瀏覽器的控制臺中檢查修改和刪除的結(jié)果。
使用Redux工具調(diào)試如果我們的代碼出錯了,應(yīng)該如何調(diào)試呢?
Redux擁有很多第三方的調(diào)試工具,可用于分析代碼和修復(fù)bug。最受歡迎的是time-travelling tool,即redux-devtools-extension。設(shè)置它只需要三個步驟。
首先,在Chrome中安裝Redux Devtools擴展。
然后,在運行Redux應(yīng)用程序的終端里使用Ctrl+C停止服務(wù)器。并用npm或yarn安裝redux-devtools-extension包。
yarn add redux-devtools-extension
一旦安裝完成,我們對store.js稍作修改:
// src/store.js import { createStore } from "redux"; import { composeWithDevTools } from "redux-devtools-extension"; import rootReducer from "./reducers"; const store = createStore(rootReducer, composeWithDevTools()); export default store;
我們還可以把src/index.js中日志相關(guān)的代碼刪除掉。返回Chrome,右鍵單擊該工具的圖標,打開Redux DevTools面板:
可以看到,Redux Devtools很強大。你可以在action, state和diff(方法差異)之間切換。選擇左側(cè)面板上的不同action,觀察狀態(tài)樹的變化。你還可以通過進度條來播放actions序列。甚至可以通過工具直接分發(fā)操作信息。具體的請查看文檔。
集成React在本文開頭,我提到Redux可以很方便的與React集成。只需要簡單的幾步。
首先,停止服務(wù)器,并安裝react-redux包:
yarn add react-redux
接下來,在index.js中加入React代碼。我們還將使用Provider類將React應(yīng)用程序包裝在Redux容器中:
// src/index.js … import React from "react"; import ReactDOM from "react-dom"; import { Provider } from "react-redux"; const App =Redux Shopping Cart
; ReactDOM.render({ App } , document.getElementById("root") ); …
目前,已經(jīng)完成了集成的第一部分。可以啟動服務(wù)器以查看效果。第二部分涉及到使用剛剛安裝的react-redux包中的幾個方法。通過這些方法將React組件與Redux的store和action相關(guān)聯(lián)。此外,還可以使用Express和Feathers這樣的框架來設(shè)置API。API將為我們的應(yīng)用程序提供對數(shù)據(jù)庫服務(wù)的訪問。
感謝網(wǎng)友整理了本文的相關(guān)代碼,如需要,請移步這里。
在Redux中,我們還可以安裝其他一些包,比如axios等。我們React組件的state將由Redux處理,確保所有組件與數(shù)據(jù)庫API的同步。想要更進一步的學(xué)習(xí),請看Build a CRUD App Using React, Redux and FeathersJS。
總結(jié)我希望本文能對你有所幫助。當(dāng)然,還有很多相關(guān)的內(nèi)容需要學(xué)習(xí)。例如,處理異步操作、身份驗證、日志記錄等。如果覺得Redux適合你,可以看看以下幾篇文章:
Redux State Management in Vanilla JavaScript
Redux Logging in Production with LogRocket
Build a CRUD App Using React, Redux and FeathersJS
Dealing with Asynchronous APIs in Server-rendered React
這篇文章是看到比較簡明的Redux教程。當(dāng)然也是翻譯過來噠,文中提到了很多延伸文章,我還在一個個學(xué)習(xí)當(dāng)中,遇到不錯的依然會翻譯給大家的。?喜歡的話記得收藏哦!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/88909.html
摘要:開發(fā)前需要安裝和以及一些需要用到的中間件如果在要使用的話,還需要引入這個庫或者使用示例下面通過實現(xiàn)一個快速上手。然后開始創(chuàng)建處理這兩個指令的。完成上述三步之后,我們就可以在應(yīng)用的主頁使用相應(yīng)修改并取得新的數(shù)據(jù)了。 本文適合有一定React和Redux基礎(chǔ)的用戶閱讀。 前言的前言 最近被一款來自京東凹凸實驗室的多終端開發(fā)框架Taro吸粉了,官方對 Taro 的簡介是使用React語法,一...
摘要:由于其名氣和穩(wěn)定性獲得了廣泛好評。但是實際上在中并不是非常必要的。因此,這些結(jié)果也是純粹的速度實驗。它是否容易使用,開發(fā)過程是否令人愉快年和年的狀態(tài)報告顯示,和都享有良好的聲譽,大多數(shù)開發(fā)人員表示會再次使用。上手最簡單和最快的學(xué)習(xí)曲線。 翻譯:瘋狂的技術(shù)宅原文:https://www.toptal.com/react/... 本文首發(fā)微信公眾號:jingchengyideng歡迎關(guān)...
摘要:一份開發(fā)者必備的技能清單,請查收。入門查漏補缺深入學(xué)習(xí)查看原圖下載源文件使用快速上手,并了解其中的概念。官方教程入門教程小書文章精讀,問題解答。 一份react開發(fā)者必備的技能清單,請查收。入門、查漏補缺、深入學(xué)習(xí)... showImg(https://segmentfault.com/img/remote/1460000018000950?w=1965&h=3332); 查看原圖 ...
摘要:前言樓主最近在整理的一些資料,為項目重構(gòu)作準備,下午整理成了這篇文章。給傳入的是一個初始值,比如,這個按鈕的最初要顯示的是。取代了提供了一個統(tǒng)一的。 showImg(https://segmentfault.com/img/bVbpUle?w=900&h=550); Hooks are a new addition in React 16.8. They let you use sta...
閱讀 1711·2021-11-22 12:09
閱讀 1452·2019-08-30 13:22
閱讀 2083·2019-08-29 17:00
閱讀 2635·2019-08-29 16:28
閱讀 2945·2019-08-26 13:51
閱讀 1174·2019-08-26 13:25
閱讀 3238·2019-08-26 12:14
閱讀 3007·2019-08-26 12:14