使用React,Redux和React-DnD构建购物车-第4部分

Welcome to PART 4 of building a simple shopping cart that takes advantage of React DnD. In PART 3, we integrated React DnD into our React App. if you will like to start building from PART4, make sure to clone and download PART3’s code from this branch as PART 4 is a continuation.

w ^迎阅读构建一个简单的购物车,它利用的第4部 阵营的DnD 。 在第3部分中 ,我们将React DnD集成到了我们的React App中。 如果您想从PART4开始构建,请确保从该分支克隆并下载PART3的代码,因为PART 4是续篇。

入门 (Getting Started)

In this article, we shall continue building our shopping cart by using React Actions and Reducers to move items from the drag source to the drop target in response to the React DnD endDrag event. At the end of this article, we shall learn

在本文中,我们将继续使用React Actions和Reducers来构建购物车,以响应React DnD endDrag事件将项目从拖动源移动到放置目标。 在本文的结尾,我们将学习

All code for this section is found in the branch here

本节的所有代码都在 这里 的分支中 找到

我们想要建立什么 (What we want to build)

As of now, whenever we drag an item from the drag source to the drop target, nothing special happens order than some console log messages.

到目前为止,每当我们将一个项目从拖动源拖动到放置目标时,除了一些控制台日志消息外,没有什么特别的顺序发生。

Image for post
Fig2 The current state of our React App 图2 React App的当前状态

What we are trying to build now is to enable the items dropped into the drop target to actually move into it as shown in Fig1 above.

我们现在试图构建的是使放置到放置目标中的物品能够真正移入其中,如上图1所示。

动作 (Actions)

The way we integrated our React DnD, whenever we drop a compatible item into the drop target, the endDrag event is triggered. We want to respond to this event by informing the store on which action to take.

我们集成React DnD的方式,每当我们将兼容的项目放入放置目标时, endDrag触发endDrag事件。 我们希望通过通知商店要采取的措施来响应此事件。

Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch().

操作是信息的有效负载,这些信息将数据从应用程序发送到商店。 它们是商店的唯一信息来源。 您可以使用store.dispatch()将它们发送到商店。

Move to src/actions/phones.js and modify it as below

移至src/actions/phones.js并进行如下修改

export const RECEIVE_PHONES = 'RECEIVE_PHONES'export const MOVE_INCART = 'MOVE_INCART'export function receivePhones(phones){
return{
type: RECEIVE_PHONES,
phones
}
}export function moveIncart(phone_id){
return{
type: MOVE_INCART,
id: phone_id
}
}

Our action is a plain JavaScript object with a compulsory type property which typically takes a string constant value that indicates the type of action being performed. In this case, MOVE_INCART. All we need next is the item’s id we want to move phone_id which is passed to the moveIncart() function as an argument.

我们的操作是一个带有强制type属性的普通JavaScript对象,该属性通常采用字符串常量值来指示要执行的操作的类型。 在这种情况下, MOVE_INCART. 接下来我们需要的是我们要移动的phone_id的项目ID,该id将作为参数传递给moveIncart()函数。

减速器 (Reducer)

After the action has been sent to the store, We need a reducer to specify how the state will change in response to the action.

在将动作发送到商店之后,我们需要一个减速器来指定状态如何响应该动作而改变。

Reducers specify how the application’s state changes in response to actions sent to the store. Remember that actions only describe what happened, but don’t describe how the application’s state changes.

精简器指定应用程序的状态如何响应发送到商店的操作而改变。 请记住,动作仅描述发生了什么 ,而没有描述应用程序状态如何变化。

Move to src/reducers/phones.js and modify it as below

移至src/reducers/phones.js并进行如下修改

import { RECEIVE_PHONES } from '../actions/phones'export const MOVE_INCART = 'MOVE_INCART'export default function phones(state={}, action){
switch(action.type){
case RECEIVE_PHONES:
return {
...state,
...action.phones
}
case MOVE_INCART:
return{
...state,
[action.id]:{
...state[action.id],
inCart: 'true'
}
}
default:
return state
}
}

The above code basically modify the inCart property of the specific item from ‘false’ to ‘true'

上面的代码基本上将特定项目的inCart属性从' false '修改为' true '

调度员 (Dispatcher)

We have our action and reducer set up, it’s time to trigger a state change. For our App, we shall dispatch an action when the endDrag event gets triggered. This way, we get the specific item’s id and pass it into the action function, which is in turn passed into the dispatch function.

我们已经设置了动作和减速器,是时候触发状态更改了。 对于我们的应用,当endDrag事件被触发时,我们将调度一个动作。 这样,我们获取特定项目的ID,并将其传递给action函数,然后将其传递给dispatch函数。

A store holds the whole state tree of your application. The only way to change the state inside it is to dispatch an action on it.

商店拥有应用程序的整个状态树 。 更改其内部状态的唯一方法是在其上调度操作

Open src/components/Phone.js and modify the code as below

打开src/components/Phone.js并修改如下代码

Wherever I use // Lines of code stay the same here , please refer to PART3 for the complete section of the code or compare with this branch. Since they are the same, I don’t want to be repetitive.

无论我在哪里使用// Lines of code stay the same here 行都 // Lines of code stay the same here 请参阅PART3以获得完整的代码部分,或与该分支进行比较。 由于它们相同,所以我不想重复。

import React, { Component } from 'react'
import { DragSource } from 'react-dnd';import { connect } from 'react-redux'import { ItemTypes } from './Constants';import { moveIncart } from '../actions/phones'// phone DnD spec
const phoneSpec = {
beginDrag(props){
return{
name: props.brand,
id: props.id

}
},
endDrag(props, monitor, component){
if (monitor.didDrop()){
const dragItem = monitor.getItem();
const dropResult = monitor.getDropResult();
console.log("You dropped ", dragItem.name, ' into '+ dropResult.name)
// Move action goes here
props.dispatch(moveIncart(dragItem.id))
}else{
return;
}
}
}// Lines of code stay the same here.export default connect()(DragSource(ItemTypes.PHONE, phoneSpec, collect)(Phone));

We imported connect from ‘react-redux’ which will provide us with the function dispatchused to dispatch actions to the store.

我们进口的connect ,从“React-终极版”,这将为我们提供功能dispatch用来调度行动商店。

  • dragItem.id is got from beginDrag's returned object, which is made accessible from monitor.getItem()

    dragItem.id是从beginDrag's返回对象获得的,该对象可以从monitor.getItem()访问。

  • If monitor.didDrop() is true, meaning if the drag source is compatible with the drop target, we dispatch an action.

    如果monitor.didDrop()为true,则意味着如果拖动源与放置目标兼容,则我们调度一个动作。

最后的润色 (Final Touches)

Our dispatch above will send an action to the reducer that will change the state of the item’s inCart property. However, we have a few things to handle in order to complete this section.

我们上面的调度将向inCart器发送一个操作,该操作将更改项目的inCart属性的状态。 但是,为了完成本节,我们需要处理一些事情。

  • We need to fetch all phone items and filter them base on whether their inCart property is ‘true’ or ‘false’. Those with ‘true’ will be displayed in the target list section, while those with ‘false’ will remain in the shopping list section.

    我们需要获取所有电话项,并根据其inCart属性是“ true”还是“ false”对它们进行过滤。 具有“ true”的那些将显示在target list部分,而具有“ false”的那些将保留在shopping list部分。

Check the image named resulting UI with Semantic UI from PART2to understand the locations of Shopping List and Target Space

resulting UI with Semantic UI PART2中的 resulting UI with Semantic UI检查名为resulting UI with Semantic UI的图像,以了解Shopping ListTarget Space的位置

App.js

一个 pp.js

Move to scr/components/App.js and modify the code as below

移至scr/components/App.js并修改如下代码

// Lines of code stay the same here.
render(){

return (
<Container />
);
}
}export default connect()(App);

Notice that we removed our mapStateToProps . Also, we don’t need to manually pass our store to the Container component as we did in PART3. Since, <Provider > in src/index.jshandles that for us.

注意,我们删除了mapStateToProps 。 而且,我们不需要像在PART3中那样将商店手动传递给Container组件。 因为, src/index.js <Provider >为我们处理了。

The <Provider /> makes the Redux store available to any nested components that have been wrapped in the connect() function.

<Provider />使Redux store可用于connect()函数中包装的任何嵌套组件。

DisplayPhone.js

D isplayPhone.js

We mentioned above that we have to display phone items in both the target space and shopping list sections. So, Instead of having repetitive code, we will create a component that we will use for each case.

上面我们提到,我们必须在target spaceshopping list部分中显示电话项目。 因此,我们将创建一个用于每种情况的组件,而不是使用重复的代码。

Create the component src/components/DisplayPhone.js and insert the code

创建组件src/components/DisplayPhone.js并插入代码

import React, { Component } from 'react';
import { connect } from 'react-redux'import Phone from './Phone'class DisplayPhone extends Component {
render(){
const { phones, displayPhones } = this.props
return(
<div>
{displayPhones
.map((phone) =>(
<Phone
key={phone}
id={phone}
brand={phones[phone].brand} />

))}
</div>
)
}
}function mapStateToProps({phones}){return{
phones,
}
}export default connect(mapStateToProps)(DisplayPhone);
  • It takes in our store(phones) and an array of phone IDs(displayPhones) to display

    它需要我们的商店( phones )和一系列电话ID( displayPhones )来显示

  • It then uses the Phone component to create the phone structure for display.

    然后,它使用“ Phone组件创建要显示的电话结构。

Container.js

C ontainer.js

Our container component will be modified to feed each section(target space and shopping list) with the right array of IDs to be displayed.

我们的容器组件将被修改为向每个部分( target spaceshopping list )提供要显示的ID的正确数组。

Move to scr/components/Container.js and modify the code as below

移至scr/components/Container.js并修改如下代码

  • We added two imports on line 4,9.

    我们在第4,9行添加了两个导入

  • Our connect function on line 57 will provide our container with the store and uses mapStateToProps to modify our store before it is fed into our container as props.

    第57行的 connect函数将为我们的容器提供商店,并使用mapStateToProps修改我们的商店,然后再将其作为props输入到我们的容器中。

  • Our mapStateToProps() function takes in our store and creates two arrays, inCart_phones and outCart_phone , each holding phone item’s ids. The former holds ids of phones with inCart property equals to ‘true’ while the later holds ids of phones with inCart property equals to ‘false’.

    我们的mapStateToProps()函数进入我们的商店,并创建两个数组inCart_phonesoutCart_phone ,每个数组都包含电话项的ID。 前者持有inCart属性等于“ true”的电话的ID,而后者持有inCart属性等于“ false”的电话的ID。

  • It extracts these arrays from props on line 13 and passes them to the components DisplayPhone and ShoppingCart on line 34 and 36 respectively.

    它从第13行的 props提取这些数组,并将它们分别传递到第34行36 行的 DisplayPhoneShoppingCart组件。

  • Line 34 will display phone items in the shopping list section while line 36 will display phone items in the target space section.

    第34行将在shopping list部分显示电话项目,而第36行将在target space部分显示电话项目。

ShoppingCart.js

S HoppingCart.js

This component will receive the arrayinCart_phones containing ids of phone items with inCart property equals to ‘true’, and display them with the help of the DisplayPhone container.

此组件将接收包含inCart属性等于'true'的电话项目ID的数组inCart_phones ,并在DisplayPhone容器的帮助下显示它们。

import React, { Component } from 'react'
import { DropTarget } from 'react-dnd'import { ItemTypes } from './Constants'import DisplayPhone from './DisplayPhone'// Lines of code stay the same here.return connectDropTarget(
<div className="shopping-cart" style={ style } >
{ !inCart_phones.length &&
(isActive
? 'Humm, phone!'
: 'Drag here to order!')
}
{ inCart_phones.length
? <DisplayPhone displayPhones = {inCart_phones} />
: null
}
</div>

)
}
}export default DropTarget(ItemTypes.PHONE, ShoppingCartSpec, collect)(ShoppingCart);

中间件 (Middleware)

To make sure that our app is working properly and for easy debugging, we will introduce a middleware logger that will console log our activities as we interact with our app.

为了确保我们的应用程序正常运行并易于调试,我们将引入一个中间件logger ,该logger将在与应用程序交互时控制台记录我们的活动。

Middleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more

中间件提供了在调度动作与到达减速器之间的第三方扩展点。 人们使用Redux中间件进行日志记录,崩溃报告,与异步API对话,路由等

Middleware: Logging

中间件:记录

As stated above, we want to monitor every action that is dispatched to our reducer. So, our logger function will print the following.

如上所述,我们要监视分派给我们的减速器的每个动作。 因此,我们的记录器功能将打印以下内容。

  • The action type that is about to be dispatched.

    即将调度的动作type

  • The new state of our store after the action got dispatched.

    采取行动后,我们商店的新状态。

The benefits of this logger() middleware function are immense while developing an application. In this case, it will intercept all dispatch calls and log out what the action is that’s been dispatched and what the state changes to after the reducer has run. We can then use this info to track what is happening in our app and easily notice any bugs that creep in.

在开发应用程序时,此logger()中间件功能的好处是巨大的。 在这种情况下,它将拦截所有调度调用并注销已运行的动作以及在reducer运行后状态变为何种状态。 然后,我们可以使用此信息来跟踪应用程序中发生的情况,并轻松地注意到所有潜入的错误。

Create the file src/middleware/logger.js and insert the code

创建文件src/middleware/logger.js并插入代码

const logger = (store) => (next) => (action) =>{
console.group(action.type)
console.log("The action: ",action)
const result = next(action);
console.log("New state is: ", store.getState())
console.groupEnd()
return result
}export default logger

I know right? Our logger() middleware function looks weird. Actually what it does is:

我知道,对吧? 我们的logger()中间件功能看起来很奇怪。 实际上,它的作用是:

  • Our logger is a function that takes in a store, and returns a function that takes in next, next here could either be another middleware or dispatch. This, in turn, returns another function that takes in our action. All of these give us access to these three features which we can use.

    我们的记录器是一个接受store的函数,并返回一个接受next的函数,其次这里可以是另一个中间件或调度。 反过来,这返回另一个执行我们action函数。 所有这些使我们可以使用这三个功能。

  • It then prints our action’s type, then it calls next,in this case dispatch, passing to it our action and gets the new state of our store with store.getState()

    然后打印操作的type ,然后调用next ,在本例中为dispatch,将操作传递给它,并使用store.getState()获取商店的新状态。

Applying Middleware

应用中间件

To apply our logger() middleware function into our redux, we will pass it in when creating the store into createStore() . But first, we will use the applyMiddleware() function which is a store enhancer, before passing it into our createStore() function.

要将logger()中间件功能应用到我们的redux中,我们将在创建商店时将其传递到createStore() 。 但首先,在将其传递到createStore()函数之前,我们将使用applyMiddleware()函数(它是一种商店增强程序 createStore()

Index.js

我的 ndex.js

Let’s add our logger() middleware function into the store enhancer applyMiddleware() . Create the file src/middleware/index.js and insert the code

让我们将logger()中间件功能添加到商店增强程序applyMiddleware() 。 创建文件src/middleware/index.js并插入代码

import {applyMiddleware} from 'redux'import logger from './logger'export default applyMiddleware(
logger
)

Note that we can add as many middleware functions as we want into the applyMiddleware() function! Middleware is called in the order in which they were provided to the store enhancer.

注意,我们可以根据需要将applyMiddleware()中间件函数添加到applyMiddleware()函数中! 中间件按照它们被提供给商店增强器的顺序被调用。

Next, we will add our middleware to createStore() function. Move to src/index.js and modify the code as below

接下来,我们将中间件添加到createStore()函数中。 移至src/index.js并修改如下代码

import React from 'react';
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'import reducer from './reducers'import middleware from './middleware'import './index.css';
import App from './components/App';let store = createStore(reducer, middleware)render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('root')
);

Check this branch’s readme to see what info logger() will log when a phone item is dropped into the target space.

检查此分支的自述文件,以查看将电话项放入目标空间时将记录什么信息logger()

结论 (Conclusion)

The fourth part of this journey was to show the use of redux action, reducer, dispatch, and middleware. You can get the complete code for this part here. Next, we will update the My cart section such that when a phone item is dropped in the target space, it’s info is displayed in that section. Check this branch readme to have a taste of what we will be looking at next.

此旅程的第四部分是展示redux动作,reducer,调度和中间件的使用。 您可以在此处获得此部分的完整代码。 接下来,我们将更新“ My cart 部分,以便在将电话项放入目标空间时,该信息将显示在该部分中。 检查此分支 自述文件, 以了解下一步的内容

Don’t miss part 5 which I will release very soon. Promise!

不要错过第5部分,我将很快发布它。 答应

翻译自: https://itnext.io/build-a-shopping-cart-with-react-redux-and-react-dnd-part-4-6a77f0a39c0c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值