flux react_使用React.js和Flux创建一个简单的购物车
flux react 介绍 (Introduction)Welcome to the fourth and final installment of the Learning React series! Up to this point, we've learned how React's API allows us to create rich stateful components, how.
flux react
介绍 (Introduction)
Welcome to the fourth and final installment of the Learning React series! Up to this point, we've learned how React's API allows us to create rich stateful components, how to use them in practice & how Facebook's Flux architecture works.
欢迎来到Learning React系列的第四期也是最后一部分! 到目前为止,我们已经了解了React的API如何使我们能够创建丰富的有状态组件,如何在实践中使用它们以及Facebook的Flux架构如何工作。
Today we are going to put all of it together to create a basic shopping cart application. In a typical e-commerce website, the product detail page has several moving parts that rely on one another and React really helps simplify and organize the co-dependency between them.
今天,我们将所有这些放在一起以创建一个基本的购物车应用程序。 在一个典型的电子商务网站中,产品详细信息页面包含多个相互依赖的活动部分,而React确实有助于简化和组织它们之间的相互依赖性。
If you haven't already, I highly recommend checking out the first three parts of this series:
如果您还没有,我强烈建议您查看本系列的前三部分:
- Learning React: Getting Started and Concepts 学习React:入门和概念
- Build A Real-Time Twitter Stream with Node and React.js 使用Node和React.js构建实时Twitter流
- Get To Know Flux, the React.js Architecture 了解Flux,React.js架构
ReactJS 0.12 (ReactJS 0.12)
During the writing of this series, React released version 0.12 which made some fairly significant changes. This tutorial will be written in 0.12 syntax. Some of the changes include:
在撰写本系列文章期间,React发布了0.12版本,该版本进行了一些相当重大的更改。 本教程将以0.12语法编写。 一些更改包括:
- The
/** @jsx React.DOM */
header is no longer required when writing JSX syntax 编写JSX语法时不再需要/** @jsx React.DOM */
标头 renderComponent
is nowrender
renderComponent
现在被render
renderComponentToString
is nowrenderToString
renderComponentToString
现在是renderToString
You can check out the entire changelog on the official blog post.
您可以在官方博客文章中查看整个变更日志。
入门 (Getting Started)
Our first step to architecting an application is defining what it should do. We want to:
构建应用程序的第一步是定义应用程序应该做什么。 我们想:
- Display a product with several options 显示带有多个选项的产品
- Change the price when selecting an option 选择选项时更改价格
- Add items to the cart 将商品添加到购物车
- Remove items from the cart 从购物车中取出物品
- Display the number of items in the cart 显示购物车中的物品数量
- Display the total price of the items in the cart 显示购物车中物品的总价
- Display the total price for each option in the cart based upon quantity 根据数量显示购物车中每个选项的总价
- Change our "Add To Cart" button caption to "Sold Out" and disable it when inventory is depleted for a given option 将给定选项的库存耗尽时,将我们的“添加到购物车”按钮标题更改为“售罄”,并禁用它
- Display the cart after adding a product or clicking the "View Cart" button 添加产品或单击“查看购物车”按钮后显示购物车
This is what our finished product should look like:
这是我们的成品外观:
This app is going to be purely client side, so we aren't going to need a server. Instead, we will be using a mock API and mock data in order to focus on the components themselves. Lets take a look at our directory structure:
这个应用程序将完全是客户端,因此我们不需要服务器。 相反,我们将使用模拟API和模拟数据,以便专注于组件本身。 让我们看一下我们的目录结构:
目录结构 (Directory Structure)
css/
---- app.css
img/
---- scotch-beer.png
js/
---- actions/
-------- FluxCartActions.js // Our app's action creators
---- components/
-------- FluxCart.react.js // Cart Component
-------- FluxCartApp.react.js // Main Controller View
-------- FluxProduct.react.js // Product Component
---- constants/
-------- FluxCartConstants.js // Our app's action constants
---- dispatcher/
-------- AppDispatcher.js // Our app's dispatcher
---- stores/
-------- CartStore.js // Cart Store
-------- ProductStore.js // Product Store
---- utils/
-------- CartAPI.js // Mock API
---- app.js // Main app.js file
---- ProductData.js // Mock Data
index.html
package.json
Below, check out our package.json
file. We will be using the following modules:
在下面,查看我们的package.json
文件。 我们将使用以下模块:
- Browserify 浏览器
- Reactify React
- React React
- Flux 助焊剂
- Watchify 注意
- Uglify 丑化
- Underscore 下划线
- Envify 环保
We can run npm install
to install all of our dependencies, and then use the npm start
command to start a process that watches our project and bundles our source on save.
我们可以运行npm install
来安装所有依赖项,然后使用npm start
命令启动一个npm start
我们的项目并在保存时捆绑源代码的进程。
package.json (package.json)
{
"name": "flux-pricing",
"version": "0.0.1",
"description": "Pricing component with flux",
"main": "js/app.js",
"dependencies": {
"flux": "^2.0.0",
"react": "^0.12.0",
"underscore": "^1.7.0"
},
"devDependencies": {
"browserify": "~6.2.0",
"envify": "~3.0.0",
"react": "^0.12.0",
"reactify": "^0.15",
"watchify": "~2.1.0"
},
"scripts": {
"start": "watchify -o js/bundle.js -v -d .",
"build": "browserify . | uglifyjs -cm > js/bundle.min.js"
},
"author": "Ken Wheeler",
"browserify": {
"transform": [
"reactify",
"envify"
]
}
}
API和模拟数据 (API & Mock Data)
In the interests of keeping us focused on Flux & React, we will be using a mock API and mock data for our product that we are going to display. That said, writing our product data and our API similarly to the way we would work with a real API adds the benefit that we could have an easier time plugging in a real API down the road if we had wanted to.
为了使我们专注于Flux&React,我们将使用将要显示的产品的模拟API和模拟数据。 就是说,与使用真实API的方式类似地编写产品数据和API的好处是,如果我们愿意的话,我们可以更轻松地将真实的API插入道路。
Lets have a look at what our sample Product Data looks like:
让我们看一下我们的样本产品数据是什么样的:
ProductData.js (ProductData.js)
module.exports = {
// Load Mock Product Data Into localStorage
init: function() {
localStorage.clear();
localStorage.setItem('product', JSON.stringify([
{
id: '0011001',
name: 'Scotch.io Signature Lager',
image: 'scotch-beer.png',
description: 'The finest lager money can buy. Hints of keyboard aerosol, with a whiff of iKlear wipes on the nose. If you pass out while drinking this beverage, Chris Sevilleja personally tucks you in.',
variants: [
{
sku: '123123',
type: '40oz Bottle',
price: 4.99,
inventory: 1
},
{
sku: '123124',
type: '6 Pack',
price: 12.99,
inventory: 5
},
{
sku: '1231235',
type: '30 Pack',
price: 19.99,
inventory: 3
}
]
}
]));
}
};
As you can see above, we define a product that has options called variants. Our schema mirrors the type of data you might typically get back from a call to a restful API. We go ahead and load this data into localStorage
, so that our mock API can grab that data and load it into our app.
正如您在上面看到的,我们定义了一种具有称为变体的选项的产品。 我们的模式反映了您通常从调用静态API可能返回的数据类型。 我们继续将数据加载到localStorage
,以便我们的模拟API可以获取该数据并将其加载到我们的应用程序中。
See below how our mock API grabs our data from localStorage
and then uses Flux actions to send that data to our ProductStore:
参见下文,我们的模拟API如何从localStorage
抓取我们的数据,然后使用Flux动作将该数据发送到我们的ProductStore:
CartAPI.js (CartAPI.js)
var FluxCartActions = require('../actions/FluxCartActions');
module.exports = {
// Load mock product data from localStorage into ProductStore via Action
getProductData: function() {
var data = JSON.parse(localStorage.getItem('product'));
FluxCartActions.receiveProduct(data);
}
};
So now that we have our sample product data, and a sample API call, how do we use this to bootstrap our application with "server" data?
现在,我们有了示例产品数据和示例API调用,我们如何使用它来通过“服务器”数据来引导应用程序?
It's really as simple as just initializing our data, running our API call, and then mounting our controller view. Our main app.js
file, shown below, is responsible for this process:
这真的非常简单,只需初始化我们的数据,运行我们的API调用,然后安装我们的控制器视图。 我们的主要app.js
文件(如下所示)负责此过程:
app.js (app.js)
window.React = require('react');
var ProductData = require('./ProductData');
var CartAPI = require('./utils/CartAPI')
var FluxCartApp = require('./components/FluxCartApp.react');
// Load Mock Product Data into localStorage
ProductData.init();
// Load Mock API Call
CartAPI.getProductData();
// Render FluxCartApp Controller View
React.render(
<FluxCartApp />,
document.getElementById('flux-cart')
);
调度员 (Dispatcher)
Since we are using the Flux architecture for this application, we are going to need to create our own instance of Facebook's Dispatcher library. We also add a handleAction
helper method to our Dispatcher instance, so that we can identify where this action came from.
由于我们为此应用程序使用Flux架构,因此我们需要创建我们自己的Facebook Dispatcher库实例。 我们还向我们的Dispatcher实例添加了handleAction
帮助器方法,以便我们可以识别此操作的来源。
While this isn't explicitly required for our current application, if we wanted to hook into a real API or handle actions from places other than views, it is nice to have this architecture in place if we needed to handle those actions differently than those that originated from views.
尽管我们当前的应用程序并没有明确要求这样做,但是如果我们想挂接到一个真实的API或从视图之外的其他地方处理操作,那么,如果我们需要以不同于处理其他操作的方式来处理这些操作,那么就可以采用这种架构。源于视图。
AppDispatcher.js (AppDispatcher.js)
var Dispatcher = require('flux').Dispatcher;
// Create dispatcher instance
var AppDispatcher = new Dispatcher();
// Convenience method to handle dispatch requests
AppDispatcher.handleAction = function(action) {
this.dispatch({
source: 'VIEW_ACTION',
action: action
});
}
module.exports = AppDispatcher;
In our handleAction
method, we receive an action from an action creator and then have our Dispatcher dispatch the action with a source
property and the action that was supplied as an argument.
在handleAction
方法中,我们从动作创建者处接收动作,然后让Dispatcher分派带有source
属性的动作以及作为参数提供的动作。
动作 (Actions)
Now that we have our dependencies, data and our Dispatcher set up, it's time to start working on our project's functional requirements. Actions are a great place to start. Let's define some action constants in order to define which actions our app will perform:
现在我们已经建立了依赖关系,数据和Dispatcher,现在该开始处理项目的功能需求了。 行动是一个很好的起点。 让我们定义一些动作常量,以定义我们的应用将执行的动作:
FluxCartConstants.js (FluxCartConstants.js)
var keyMirror = require('react/lib/keyMirror');
// Define action constants
module.exports = keyMirror({
CART_ADD: null, // Adds item to cart
CART_REMOVE: null, // Remove item from cart
CART_VISIBLE: null, // Shows or hides the cart
SET_SELECTED: null, // Selects a product option
RECEIVE_DATA: null // Loads our mock data
});
After defining our constants, we need to create their corresponding action creator methods. These are methods that we can call from our views/components that will tell our Dispatcher to broadcast the action to our Stores.
定义常量之后,我们需要创建它们对应的动作创建者方法。 这些是我们可以从视图/组件中调用的方法,它们将告诉Dispatcher将操作广播到我们的商店。
The action itself consists of an object containing our desired action constant and a data payload. Our Stores then update and fire change events, which our Controller View listens to in order to know when to begin a state update.
动作本身包含一个对象,该对象包含我们所需的动作常量和数据有效负载。 然后,我们的商店会更新并触发更改事件,我们的Controller View会监听这些事件,以便知道何时开始状态更新。
Below, check out how we use our Dispatcher's handleAction
method to pass an actionType
constant and associated data to our Dispatcher:
在下面,检查我们如何使用Dispatcher的handleAction
方法将actionType
常量和关联的数据传递到Dispatcher:
FluxCartActions.js (FluxCartActions.js)
var AppDispatcher = require('../dispatcher/AppDispatcher');
var FluxCartConstants = require('../constants/FluxCartConstants');
// Define actions object
var FluxCartActions = {
// Receive inital product data
receiveProduct: function(data) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.RECEIVE_DATA,
data: data
})
},
// Set currently selected product variation
selectProduct: function(index) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.SELECT_PRODUCT,
data: index
})
},
// Add item to cart
addToCart: function(sku, update) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.CART_ADD,
sku: sku,
update: update
})
},
// Remove item from cart
removeFromCart: function(sku) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.CART_REMOVE,
sku: sku
})
},
// Update cart visibility status
updateCartVisible: function(cartVisible) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.CART_VISIBLE,
cartVisible: cartVisible
})
}
};
module.exports = FluxCartActions;
专卖店 (Stores)
Now that we have our Actions defined, it is time to create our Stores. Each Store manages application state for a domain within an application, so we are going to create one for our product and one for our cart. Let's start with our ProductStore
:
现在我们已经定义了动作,现在该创建商店了。 每个商店都管理应用程序中某个域的应用程序状态,因此我们将为产品创建一个状态,为购物车创建一个状态。 让我们从ProductStore
开始:
ProductStore.js (ProductStore.js)
var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var FluxCartConstants = require('../constants/FluxCartConstants');
var _ = require('underscore');
// Define initial data points
var _product = {}, _selected = null;
// Method to load product data from mock API
function loadProductData(data) {
_product = data[0];
_selected = data[0].variants[0];
}
// Method to set the currently selected product variation
function setSelected(index) {
_selected = _product.variants[index];
}
// Extend ProductStore with EventEmitter to add eventing capabilities
var ProductStore = _.extend({}, EventEmitter.prototype, {
// Return Product data
getProduct: function() {
return _product;
},
// Return selected Product
getSelected: function(){
return _selected;
},
// Emit Change event
emitChange: function() {
this.emit('change');
},
// Add change listener
addChangeListener: function(callback) {
this.on('change', callback);
},
// Remove change listener
removeChangeListener: function(callback) {
this.removeListener('change', callback);
}
});
// Register callback with AppDispatcher
AppDispatcher.register(function(payload) {
var action = payload.action;
var text;
switch(action.actionType) {
// Respond to RECEIVE_DATA action
case FluxCartConstants.RECEIVE_DATA:
loadProductData(action.data);
break;
// Respond to SELECT_PRODUCT action
case FluxCartConstants.SELECT_PRODUCT:
setSelected(action.data);
break;
default:
return true;
}
// If action was responded to, emit change event
ProductStore.emitChange();
return true;
});
module.exports = ProductStore;
Above, we define two private methods, loadProductData
and setSelected
. We use loadProductData
to, unsurprisingly, load our mock product data into our _product
object. Our setSelected
method is used to set which product variant is currently selected.
上面,我们定义了两个私有方法, loadProductData
和setSelected
。 毫无疑问,我们使用loadProductData
将模拟产品数据加载到_product
对象中。 我们的setSelected
方法用于设置当前选择的产品变型。
We expose this data using the public methods getProduct
and getSelected
, which return their respective internal objects. These methods can be called after require
'ing our Store within a view.
我们使用公共方法getProduct
和getSelected
公开此数据,这些方法返回它们各自的内部对象。 在视图中require
我们的商店后,可以调用这些方法。
Lastly, we register a callback to our AppDispatcher
that uses a switch statement to determine if the supplied payload matches an action we want to respond to. In the event that it does, we call our private methods with the supplied action data, and fire a change event, forcing our view to retrieve the new state and update its display.
最后,我们向AppDispatcher
注册一个回调,该回调使用switch语句确定所提供的有效负载是否与我们要响应的动作匹配。 如果发生这种情况,我们将使用提供的操作数据调用私有方法,并触发change事件,从而迫使我们的视图检索新状态并更新其显示。
Next up, let's create our CartStore
:
接下来,让我们创建我们的CartStore
:
var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var FluxCartConstants = require('../constants/FluxCartConstants');
var _ = require('underscore');
// Define initial data points
var _products = {}, _cartVisible = false;
// Add product to cart
function add(sku, update) {
update.quantity = sku in _products ? _products[sku].quantity + 1 : 1;
_products[sku] = _.extend({}, _products[sku], update)
}
// Set cart visibility
function setCartVisible(cartVisible) {
_cartVisible = cartVisible;
}
// Remove item from cart
function removeItem(sku) {
delete _products[sku];
}
// Extend Cart Store with EventEmitter to add eventing capabilities
var CartStore = _.extend({}, EventEmitter.prototype, {
// Return cart items
getCartItems: function() {
return _products;
},
// Return # of items in cart
getCartCount: function() {
return Object.keys(_products).length;
},
// Return cart cost total
getCartTotal: function() {
var total = 0;
for(product in _products){
if(_products.hasOwnProperty(product)){
total += _products[product].price * _products[product].quantity;
}
}
return total.toFixed(2);
},
// Return cart visibility state
getCartVisible: function() {
return _cartVisible;
},
// Emit Change event
emitChange: function() {
this.emit('change');
},
// Add change listener
addChangeListener: function(callback) {
this.on('change', callback);
},
// Remove change listener
removeChangeListener: function(callback) {
this.removeListener('change', callback);
}
});
// Register callback with AppDispatcher
AppDispatcher.register(function(payload) {
var action = payload.action;
var text;
switch(action.actionType) {
// Respond to CART_ADD action
case FluxCartConstants.CART_ADD:
add(action.sku, action.update);
break;
// Respond to CART_VISIBLE action
case FluxCartConstants.CART_VISIBLE:
setCartVisible(action.cartVisible);
break;
// Respond to CART_REMOVE action
case FluxCartConstants.CART_REMOVE:
removeItem(action.sku);
break;
default:
return true;
}
// If action was responded to, emit change event
CartStore.emitChange();
return true;
});
module.exports = CartStore;
Above, we laid out our store similarly to the way we laid out our ProductStore
. We use the _products
object to store the products that are currently in our cart, and the _cartVisibility
boolean to define the current visibility status of our cart.
上面,我们布置商店的方式类似于布置ProductStore
。 我们使用_products
对象存储当前在购物车中的产品,并使用_cartVisibility
布尔值定义购物车的当前可见性状态。
We added some more complex public methods that allow our Controller View to retrieve application state:
我们添加了一些更复杂的公共方法,这些方法允许我们的Controller View检索应用程序状态:
getCartItems
- Returns the items in our cartgetCartItems
返回购物车中的物品getCartCount
- Returns a count of how many items are in our cartgetCartCount
返回购物车中有多少商品的计数getCartTotal
- Returns the total price of all of our cart itemsgetCartTotal
返回所有购物车项目的总价
Now that we have our Stores built, it's time to get our hands dirty and build out our Views.
现在我们已经建立了自己的商店,是时候动手整理并构建视图了。
控制器视图 (Controller View)
Our Controller View is our top level component that listens for changes on our stores and then updates our application's state by calling our Store's public methods. This state is then passed down to child components via props.
Controller View是我们的顶级组件,它侦听商店中的更改,然后通过调用商店的公共方法来更新应用程序的状态。 然后,此状态通过prop传递给子组件。
The Controller View is responsible for:
控制器视图负责:
- Setting our applications state by calling Store's public methods 通过调用Store的公共方法来设置我们的应用程序状态
- Composing child components and passing state properties via props 组成子组件并通过prop传递状态属性
- Listening for Store's change events 聆听商店的变更事件
FluxCartApp.react.js (FluxCartApp.react.js)
var React = require('react');
var CartStore = require('../stores/CartStore');
var ProductStore = require('../stores/ProductStore');
var FluxProduct = require('./FluxProduct.react');
var FluxCart = require('./FluxCart.react');
// Method to retrieve state from Stores
function getCartState() {
return {
product: ProductStore.getProduct(),
selectedProduct: ProductStore.getSelected(),
cartItems: CartStore.getCartItems(),
cartCount: CartStore.getCartCount(),
cartTotal: CartStore.getCartTotal(),
cartVisible: CartStore.getCartVisible()
};
}
// Define main Controller View
var FluxCartApp = React.createClass({
// Get initial state from stores
getInitialState: function() {
return getCartState();
},
// Add change listeners to stores
componentDidMount: function() {
ProductStore.addChangeListener(this._onChange);
CartStore.addChangeListener(this._onChange);
},
// Remove change listers from stores
componentWillUnmount: function() {
ProductStore.removeChangeListener(this._onChange);
CartStore.removeChangeListener(this._onChange);
},
// Render our child components, passing state via props
render: function() {
return (
<div className="flux-cart-app">
<FluxCart products={this.state.cartItems} count={this.state.cartCount} total={this.state.cartTotal} visible={this.state.cartVisible} />
<FluxProduct product={this.state.product} cartitems={this.state.cartItems} selected={this.state.selectedProduct} />
</div>
);
},
// Method to setState based upon Store changes
_onChange: function() {
this.setState(getCartState());
}
});
module.exports = FluxCartApp;
We start by creating a public method called getCartState
. We use this method to call public methods on Stores to retrieve their current state and set our applications state with the results. We call this method first during the getInitialState
method, and also when a Store's change event is received.
我们首先创建一个名为getCartState
的公共方法。 我们使用此方法来调用商店上的公共方法以检索其当前状态,并使用结果设置我们的应用程序状态。 我们首先在getInitialState
方法期间以及在收到商店的change事件时调用此方法。
In order to receive these change events, we add listeners on our Stores during the mounting process, so that we know when they change. We remove these events when/if our component is unmounted.
为了接收这些更改事件,我们在挂载过程中在我们的商店中添加了侦听器,以便我们知道它们何时更改。 当/如果我们的组件已卸载,我们将删除这些事件。
In our render
method, we compose our component using the FluxCart
and FluxProduct
components. Here, we pass our state props down to them using component properties, or props.
在我们的render
方法中,我们使用FluxCart
和FluxProduct
组件来组成我们的组件。 在这里,我们使用组件属性或道具将状态道具传递给他们。
产品浏览 (Product View)
It's time to get down to the meat and potatoes of this app, and that is our Product view. We want to take the props that got passed from our Controller View, and make a rich, interactive product display.
现在该深入了解此应用程序的内容了,这就是我们的“产品”视图。 我们希望采用从Controller View中传递的道具,并进行丰富的交互式产品展示。
Lets get this party started.
我们开始这个派对吧。
FluxProduct.react.js (FluxProduct.react.js)
var React = require('react');
var FluxCartActions = require('../actions/FluxCartActions');
// Flux product view
var FluxProduct = React.createClass({
// Add item to cart via Actions
addToCart: function(event){
var sku = this.props.selected.sku;
var update = {
name: this.props.product.name,
type: this.props.selected.type,
price: this.props.selected.price
}
FluxCartActions.addToCart(sku, update);
FluxCartActions.updateCartVisible(true);
},
// Select product variation via Actions
selectVariant: function(event){
FluxCartActions.selectProduct(event.target.value);
},
// Render product View
render: function() {
var ats = (this.props.selected.sku in this.props.cartitems) ?
this.props.selected.inventory - this.props.cartitems[this.props.selected.sku].quantity :
this.props.selected.inventory;
return (
<div className="flux-product">
<img src={'img/' + this.props.product.image}/>
<div className="flux-product-detail">
<h1 className="name">{this.props.product.name}</h1>
<p className="description">{this.props.product.description}</p>
<p className="price">Price: ${this.props.selected.price}</p>
<select onChange={this.selectVariant}>
{this.props.product.variants.map(function(variant, index){
return (
<option key={index} value={index}>{variant.type}</option>
)
})}
</select>
<button type="button" onClick={this.addToCart} disabled={ats > 0 ? '' : 'disabled'}>
{ats > 0 ? 'Add To Cart' : 'Sold Out'}
</button>
</div>
</div>
);
},
});
module.exports = FluxProduct;
Prior to our render
method, we define Action methods that we bind to elements in our component. By importing our Actions, we can then call them from these methods, and kick off the update process:
在render
方法之前,我们定义要绑定到组件中元素的Action方法。 通过导入我们的动作,我们可以从以下方法中调用它们,并启动更新过程:
selectProduct
- Sets which product option is currently selectedselectProduct
设置当前选择哪个产品选项addToCart
- Adds the currently selected product to the cart and opens the cartaddToCart
将当前选定的产品添加到购物车并打开购物车
Inside of our render method, we calculate how many units of the selected product are available to sell (ats) by checking how many are in the cart vs the selected products inventory. We use this to toggle the "Add To Cart" button's state.
在我们的渲染方法内部,我们通过检查购物车中有多少产品与选定产品库存相比,计算出可以出售(销售)选定产品的数量。 我们用它来切换“添加到购物车”按钮的状态。
购物车视图 (Cart View)
Gotta have a cart. So lets put one together. In our app, when a product is added to the cart, a single line item represents the selected option. The quantity of that option can be increased, but it won't create new line items. Instead, the quantity being purchased is displayed and the line item's price adjusts accordingly.
必须有一个购物车。 因此,让我们放在一起。 在我们的应用程序中,将产品添加到购物车后,一个订单项代表所选选项。 可以增加该选项的数量,但不会创建新的订单项。 而是显示要购买的数量,并相应地调整订单项的价格。
Let's make this happen:
让我们做到这一点:
FluxCart.react.js (FluxCart.react.js)
var React = require('react');
var FluxCartActions = require('../actions/FluxCartActions');
// Flux cart view
var FluxCart = React.createClass({
// Hide cart via Actions
closeCart: function(){
FluxCartActions.updateCartVisible(false);
},
// Show cart via Actions
openCart: function(){
FluxCartActions.updateCartVisible(true);
},
// Remove item from Cart via Actions
removeFromCart: function(sku){
FluxCartActions.removeFromCart(sku);
FluxCartActions.updateCartVisible(false);
},
// Render cart view
render: function() {
var self = this, products = this.props.products;
return (
<div className={"flux-cart " + (this.props.visible ? 'active' : '')}>
<div className="mini-cart">
<button type="button" className="close-cart" onClick={this.closeCart}>×</button>
<ul>
{Object.keys(products).map(function(product){
return (
<li key={product}>
<h1 className="name">{products[product].name}</h1>
<p className="type">{products[product].type} x {products[product].quantity}</p>
<p className="price">${(products[product].price * products[product].quantity).toFixed(2)}</p>
<button type="button" className="remove-item" onClick={self.removeFromCart.bind(self, product)}>Remove</button>
</li>
)
})}
</ul>
<span className="total">Total: ${this.props.total}</span>
</div>
<button type="button" className="view-cart" onClick={this.openCart} disabled={Object.keys(this.props.products).length > 0 ? "" : "disabled"}>View Cart ({this.props.count})</button>
</div>
);
},
});
module.exports = FluxCart;
And now we have a cart! Our cart component has three event methods:
现在我们有了购物车! 我们的购物车组件具有三种事件方法:
closeCart
- Close the cartcloseCart
关闭购物车openCart
- Opens the cartopenCart
打开购物车removeFromCart
- Removes item from the cart and closes the cartremoveFromCart
从购物车中删除商品并关闭购物车
When rendering our cart, we use the map
method to render out our line items. Notice on the <li>
tag, we added the key
attribute. This is a special attribute used when adding dynamic children to a component. It is used internally in React to uniquely identify said children, so that they retain the proper order and state during reconciliation. If you remove this and open your console, you can see that React will throw a warning telling you that key
hasn't been set, and you will likely experience rendering anomalies.
渲染购物车时,我们使用map
方法渲染订单项。 注意,在<li>
标记上,我们添加了key
属性。 这是在向组件添加动态子代时使用的特殊属性。 它在React内部使用,以唯一地标识所述子代,以便他们在对帐期间保持正确的顺序和状态。 如果删除它并打开控制台,您会看到React会发出警告,告诉您尚未设置key
,并且您可能会遇到渲染异常的情况。
For our cart's hide and show functionality, all that we are doing is adding and removing an active
class, and letting CSS do the rest.
对于购物车的隐藏和显示功能,我们要做的就是添加和删除active
类,然后让CSS完成其余工作。
结语 (Wrap Up)
If you have been following along with the source or building from scratch, hit index.html
and you can see our app in action. Otherwise, check out the link to the demo below. Add items to the cart until inventory depletes to see our buttons' states change and the totals update in the cart.
如果您一直在跟踪源代码或从头开始构建,请点击index.html
,即可看到我们的应用程序正在运行。 否则,请查看以下演示的链接。 将项目添加到购物车中,直到库存耗尽为止,以查看按钮状态发生变化,并且购物车中的总计更新。
Don't stop there though, take the demo source and try to add some new features to our cart, like a product grid display using react-router or adding more than 1 selectable option per product. The sky's the limit folks.
不过,不要止步于此,请使用演示源并尝试向我们的购物车中添加一些新功能,例如使用react-router的产品网格显示或为每个产品添加1个以上的可选选项。 天空才是极限。
This marks the end of the Learning React series, and I hope everybody has had as much fun learning it as I did writing it. I'm of the firm belief that 2015 will be the year of React, so take what you have learned here, get out there and build some cool stuff.
这标志着Learning React系列学习的结束,我希望每个人都能像学习它一样开心。 我坚信2015年将是React的一年,因此,请从这里学到的东西,走到那里,制作一些很棒的东西。
翻译自: https://scotch.io/tutorials/creating-a-simple-shopping-cart-with-react-js-and-flux
flux react
更多推荐
所有评论(0)