shopify二次开发教程

by Chris Frewin

克里斯·弗里温(Chris Frewin)

详细教程:如何将Shopify的Storefront API与React和Redux结合使用 (A detailed tutorial: how to use Shopify’s Storefront API with React and Redux)

电子商务为所有人! (…网站,就是?) (E-commerce for all! (…websites, that is ?))

Written by Chris August 2018, updated November, 2018

克里斯(Chris) 2018年8月撰写,2018年11月更新

背景与动机 (Background and Motivation)

So the motivation here was pretty simple. I wanted my site visitors to be able to browse, search, and select products directly on my custom domain without having to go to our Shopify site.

因此,这里的动机非常简单。 我希望网站访问者能够直接在我的自定义域上浏览,搜索和选择产品,而不必访问我们的Shopify网站。

The secondary motivation is that I’d much rather have my own codebase for a website than use one of Shopify’s factory templates. No offense Shopify team! The templates are modern and clean, but they are rather basic. I’m sure those templates are heavily customizable, but it’s not a stack I know at the moment.

次要动机是,我宁愿拥有自己的网站代码库,也不愿使用Shopify的工厂模板之一。 Shopify团队无罪! 模板是现代且干净的,但是它们是非常基本的。 我敢肯定那些模板是高度可定制的,但是目前我还不知道它是一个堆栈。

So this is the best of both worlds — my custom React site (already built and online ?), with the added API and checkout process of Shopify!

因此,这是两全其美的方式-我的自定义React网站(已建立并在线运行?),并添加了Shopify的API和结帐流程!

By the end of this tutorial, you’ll be able to add your Shopify products on any page of your site. The only part of the shopping process that will occur on Shopify is when the user clicks ‘Checkout’.

在本教程结束时,您将可以在网站的任何页面上添加Shopify产品。 Shopify上购物过程的唯一部分是用户单击“结帐”。

I’ve created an empty boilerplate repository for this tutorial as well.

我也为本教程创建了一个空的样板存储库 。

The motivation specifically for writing here on Medium was simply that I couldn’t find a tutorial on this process myself — so I decided to make one!

专门在Medium上写代码的动机仅仅是因为我自己找不到关于此过程的教程-因此,我决定编写一个!

I’ve been a professional developer for 4 years now, and programming for 7. I’ve worked in tech stacks from old-school Fortran and Perl, to React, Javascript, Python, and Node.

我已有4年的专业开发人员经验,并且有7年的编程经验。我从事过从旧式的Fortran和Perl到React,Javascript,Python和Node的技术堆栈。

Siren Apparel is one of my side project / startup / maker companies that I’ve run for 5 years now, and we’ve donated to 5 different police and fire departments so far!

Siren Apparel是我运营了5年的副项目/启动/制造商公司之一,到目前为止,我们已向5个不同的警察和消防部门捐款!

Let’s finally get started with this tutorial.

最后,让我们开始学习本教程。

Shopify的店面API (Shopify’s Storefront API)

The wonderful folks at Shopify have put together the Storefront API. With the Storefront API, you can create React components to add product pictures, product variations, product sizes, a cart, and ‘add to cart’ and ‘checkout’ buttons into your own, non-Shopify site.

Shopify的出色人士汇集了Storefront API 。 使用Storefront API,您可以创建React组件以将产品图片,产品变体,产品尺寸,购物车以及“添加到购物车”和“结帐”按钮添加到您自己的非Shopify网站中。

*Note that this tutorial is NOT about Shopify Polaris, which is used to create components in React for Shopify store management itself.

*请注意,本教程与Shopify Polaris无关 ,后者用于在React for Shopify商店管理本身中创建组件。

入门: react-js-buy存储库 (Getting Started: react-js-buy Repository)

Take a look at this React example built by the Shopify team. Most of the code in this tutorial comes from that repository.

看一下Shopify团队构建的这个React示例 。 本教程中的大多数代码都来自该存储库。

…Did you take a look? Good! ?

…你看了吗? 好! ?

Now we’re going to hop right into code! Head to your React site’s root folder and install the shopify-buy module via the terminal:

现在,我们将直接跳入代码! 转到您的React站点的根文件夹,并通过终端安装shopify-buy模块:

cd my-awesome-react-project/npm install --save shopify-buy

(or yarn add shopify-buy if you prefer yarn)

(或者如果您更喜欢yarn yarn add shopify-buy )

Then, in your frontend index.js, (NOT App.js!) you will need to import Client from the JS Buy SDK:

然后,在前端index.js (不是App.js !)中,您需要从JS Buy SDK导入Client

import Client from 'shopify-buy';

Then add the following configuration object above the ReactDOM.render()call:

然后在ReactDOM.render()调用上方添加以下配置对象:

const client = Client.buildClient({    storefrontAccessToken: 'your-access-token',    domain: 'your-shopify-url.myshopify.com'});

That’s it for index.js for now — we’ll come back to it soon.

目前,这就是index.js的内容-我们将尽快恢复。

Now we’re going to add in all the components needed for a smooth shopping and checkout experience. Copy all the components from the react-js-buy repository:

现在,我们将添加所需的所有组件,以实现顺畅的购物和结帐体验。 从react-js-buy存储库复制所有组件:

Cart.js

Cart.js

LineItem.js

LineItem.js

Product.js

Product.js

Products.js

Products.js

VariantSelector.js

VariantSelector.js

We will paste these components into acomponents/shopify/ folder in your src/ folder. You could put these component files anywhere else in the src/ folder, if you wished. The rest of the tutorial assumes you have put them in components/shopify/ .

我们会将这些组件粘贴到src/文件夹中的components/shopify/文件夹中。 如果需要,可以将这些组件文件放在src/文件夹中的其他任何位置。 本教程的其余部分假定您已将它们放在components/shopify/

修改App.js (Modifying App.js)

App.js will need extensive changes. First, import that Cart component you just copied into your own project:

App.js将需要进行大量更改。 首先,将刚复制的Cart组件导入到自己的项目中:

import Cart from './components/shopify/Cart';

If your App.js component was stateless, like mine, you should be safe copying this entire constructor() function:

如果您的App.js组件像我的一样是无状态的,则应该安全地复制以下整个constructor()函数:

constructor() {    super();    this.updateQuantityInCart = this.updateQuantityInCart.bind(this);    this.removeLineItemInCart = this.removeLineItemInCart.bind(this);    this.handleCartClose = this.handleCartClose.bind(this);}

If you already have state, copy only those bind lines. Those three lines are event handler functions that the Shopify cart needs to function properly.

如果您已经有状态,则仅复制那些bind线。 这三行是Shopify购物车正常运行所需的事件处理函数。

“But what about state for the cart!?”

“但是购物车的状态呢??”

You may ask; or:

您可能会问; 要么:

“What about defining those event handlers for the cart!?”

“如何为购物车定义那些事件处理程序呢??”

Indeed, that’s coming, but not yet! ?

确实,那是即将到来的,但还没有! ?

You can then append the <Cart/> component to the bottom of your render() function, before the ending div.

然后,您可以将<Car t />组件附加到your re render()函数的底部,在结束div之前。

In my opinion, the cart should be accessible anywhere in your app. I think it makes sense, then, to put the <Cart/> component in the root component of your app — in other words, App.js:

我认为,购物车应该可以在您应用的任何位置访问。 因此,我认为将<Car t />组件放入应用程序的根组件中是有意义的—在其他代码中ords, App.js:

return (<div>...<Cart    checkout={this.state.checkout}    isCartOpen={this.state.isCartOpen}    handleCartClose={this.handleCartClose}    updateQuantityInCart={this.updateQuantityInCart}    removeLineItemInCart={this.removeLineItemInCart} /></div>);

Again, I haven’t included any code on the event handlers for the cart yet. Additionally, I didn’t address the lack of state components for the cart in App.js.

同样,我还没有在购物车的事件处理程序中包含任何代码。 此外,我没有解决App.js中购物车缺少状态组件的问题。

There is good reason for this.

这有充分的理由。

About halfway through this project, I realized my products component was of course not in my App.js file.

在这个项目进行到一半的时候,我意识到我的产品组件当然不在我的App.js文件中。

Instead, it was buried about three children components down.

相反,它被埋在大约三个子组件中。

So instead of passing products three levels down to children, and then function handlers all the way back up…

因此,与其将产品的三个层次传递给子级,然后再将函数处理程序一路备份……

I decided to use…

我决定使用...

? Redux!!! ?

Redux !!!

Ugh! I know, I know, Redux, while not being very difficult, is a pain in the %*$! to wire up initially with all the boilerplate required. But, if you are a developer working on an E-commerce store or an E-commerce store owner, think of it this way: Redux will enable you to access the state of the cart from any component or page in our website or webapp.

啊! 我知道,虽然Redux并不困难,但它对%* $来说是一个痛苦! 首先连接所需的所有样板。 但是,如果您是在电子商务商店中工作的开发人员或电子商务商店所有者,请这样考虑:Redux将使您能够从我们网站或webapp中的任何组件或页面访问购物车的状态。

This ability will be essential as Siren Apparel expands and we develop more products. As we create more products, I’ll make a separate dedicated store page with all products, while leaving just a handful of featured products on the homepage.

随着Siren服装的扩展以及我们开发更多产品,这一能力将至关重要。 随着我们创建更多产品,我将在所有产品上创建一个单独的专用商店页面,同时在首页上仅保留少数特色产品。

The ability to access the cart is essential if a user shops around a bit, reads some stories or info about Siren Apparel, and then decides to checkout. It doesn’t matter how much they navigate around, nothing from their cart will be lost!

如果用户四处逛逛,阅读一些有关Siren Apparel的故事或信息, 然后决定结帐, 那么访问购物车的能力至关重要。 不管导航多少,他们的购物车都不会丢失!

So, in short, I decided it’s probably better to implement Redux now while the codebase for our site isn’t too large.

因此,简而言之,我认为在我们站点的代码库不太大的情况下,现在最好实现Redux。

使用裸机最小模板为Shopify Buy SDK实施Redux (Implementing Redux for Shopify Buy SDK With Bare Minimum Boilerplate)

Install NPM packages redux and react-redux:

安装NPM软件包reduxreact-redux

npm install --save redux react-redux

npm install --save redux react-redux

In index.js , import Provider from react-redux and your store from ./store:

index.js ,从react-redux导入Provider ,并从./store导入您的store

import { Provider } from 'react-redux';import store from './store';

import { Provider } from 'react-redux'; import store from './store';

Wrap the <Provider> component with the passed store around your&lt;App>;in index.jsto hook up your App to your Redux store:

包裹<Provid ER>组件与p assed商店不要蜘蛛d you [R&L t;App> ;在index.jsto勾你的应用程序到您的终极版店:

ReactDOM.render(<Provider store={store}>    <IntlProvider locale={locale} messages={flattenMessages(messages[locale.substring(0, 2)])}>      <App locale={locale}/>    </IntlProvider> </Provider>,document.getElementById('root'));

ReactDOM.render( <Provider store={store}> <IntlProvider locale={locale} messages={flattenMessages(messages[locale.substring(0, 2)])}> <App locale={locale}/> </IntlProvider> </Provider>, document.getElementById('root') );

(Note that I also have a <IntlProvider>, but that’s in a different post about how I applied internationalization and localization to dynamically render the content on Siren Apparel’s site. A different story for a different day.)

(请注意,我也有一个<IntlProvid er>,但这是在另一篇有关如何应用国际化和本地化来动态呈现Siren Apparel网站上内容的文章中 。关于另一天的不同故事。)

Now of course we haven’t made a ./store.js file yet. Create your store in store.jsin the src/ root and put this in it:

当然,现在我们还没有制作./store.js文件。 src/根目录下的store.js中创建商店, store.js其放入其中:

import {createStore} from 'redux';import reducer from './reducers/cart';export default createStore(reducer);

import {createStore} from 'redux'; import reducer from './reducers/cart';export default createStore(reducer);

Create your reducers file in src/reducers/cart.js and paste this code:

src/reducers/cart.js创建您的reducers文件,然后粘贴以下代码:

// initial stateconst initState = {  isCartOpen: false,  checkout: { lineItems: [] },  products: [],  shop: {}}// actionsconst CLIENT_CREATED = 'CLIENT_CREATED'const PRODUCTS_FOUND = 'PRODUCTS_FOUND'const CHECKOUT_FOUND = 'CHECKOUT_FOUND'const SHOP_FOUND = 'SHOP_FOUND'const ADD_VARIANT_TO_CART = 'ADD_VARIANT_TO_CART'const UPDATE_QUANTITY_IN_CART = 'UPDATE_QUANTITY_IN_CART'const REMOVE_LINE_ITEM_IN_CART = 'REMOVE_LINE_ITEM_IN_CART'const OPEN_CART = 'OPEN_CART'const CLOSE_CART = 'CLOSE_CART'// reducersexport default (state = initState, action) => {  switch (action.type) {    case CLIENT_CREATED:      return {...state, client: action.payload}    case PRODUCTS_FOUND:      return {...state, products: action.payload}    case CHECKOUT_FOUND:      return {...state, checkout: action.payload}    case SHOP_FOUND:      return {...state, shop: action.payload}    case ADD_VARIANT_TO_CART:      return {...state, isCartOpen: action.payload.isCartOpen, checkout: action.payload.checkout}    case UPDATE_QUANTITY_IN_CART:      return {...state, checkout: action.payload.checkout}    case REMOVE_LINE_ITEM_IN_CART:      return {...state, checkout: action.payload.checkout}    case OPEN_CART:      return {...state, isCartOpen: true}    case CLOSE_CART:      return {...state, isCartOpen: false}    default:      return state  }}

// initial state const initState = { isCartOpen: false, checkout: { lineItems: [] }, products: [], shop: {} }// actions const CLIENT_CREATED = 'CLIENT_CREATED' const PRODUCTS_FOUND = 'PRODUCTS_FOUND' const CHECKOUT_FOUND = 'CHECKOUT_FOUND' const SHOP_FOUND = 'SHOP_FOUND' const ADD_VARIANT_TO_CART = 'ADD_VARIANT_TO_CART' const UPDATE_QUANTITY_IN_CART = 'UPDATE_QUANTITY_IN_CART' const REMOVE_LINE_ITEM_IN_CART = 'REMOVE_LINE_ITEM_IN_CART' const OPEN_CART = 'OPEN_CART' const CLOSE_CART = 'CLOSE_CART'// reducers export default (state = initState, action) => { switch (action.type) { case CLIENT_CREATED: return {...state, client: action.payload} case PRODUCTS_FOUND: return {...state, products: action.payload} case CHECKOUT_FOUND: return {...state, checkout: action.payload} case SHOP_FOUND: return {...state, shop: action.payload} case ADD_VARIANT_TO_CART: return {...state, isCartOpen: action.payload.isCartOpen, checkout: action.payload.checkout} case UPDATE_QUANTITY_IN_CART: return {...state, checkout: action.payload.checkout} return {...state, checkout: action.payload.checkout} case REMOVE_LINE_ITEM_IN_CART: return {...state, checkout: action.payload.checkout} case OPEN_CART: return {...state, isCartOpen: true} case CLOSE_CART: return {...state, isCartOpen: false} default: return state } }

Don’t worry, I’m not going to just post this big reducer and not discuss what is going on; we’ll get to each event! There are a few things to note here.

不用担心,我不会只发布这个大型的reducer,而不会讨论正在发生的事情。 我们将参加每个活动! 这里有几件事要注意。

We take the initial state from what the state is written as in the Shopify GitHub example and put it in our initState, namely the following four parts of state:

我们从Shopify GitHub示例中编写的状态中获取初始状态,并将其放入我们的initState ,即状态的以下四个部分:

isCartOpen: false,checkout: { lineItems: [] },products: [],shop: {}

isCartOpen: false, checkout: { lineItems: [] }, products: [], shop: {}

However, in my implementation, I also create a client part of the state. I call the createClient() function once and then immediately set it in the Redux state in index.js . So let’s head into index.js:

但是,在实现中,我还创建了状态的client部分。 我调用一次createClient()函数,然后立即在index.js中将其设置为Redux状态。 因此,让我们进入index.js

返回index.js (Back to index.js)

const client = Client.buildClient({  storefrontAccessToken: 'your-shopify-token',  domain: 'your-shopify-url.myshopify.com'});store.dispatch({type: 'CLIENT_CREATED', payload: client});

const client = Client.buildClient({ storefrontAccessToken: 'your-shopify-token', domain: 'your-shopify-url.myshopify.com' }); store.dispatch({type: 'CLIENT_CREATED', payload: client});

In the Shopify buy SDK example, there are a few async calls to get information about the products and store information in React’s componentWillMount() function. That example code looks like this:

在Shopify购买SDK示例中,有一些异步调用来获取有关产品的信息并将信息存储在React的componentWillMount()函数中。 该示例代码如下所示:

componentWillMount() {    this.props.client.checkout.create().then((res) => {      this.setState({        checkout: res,      });    });this.props.client.product.fetchAll().then((res) => {      this.setState({        products: res,      });    });this.props.client.shop.fetchInfo().then((res) => {      this.setState({        shop: res,      });    });  }

componentWillMount() { this.props.client.checkout.create().then((res) => { this.setState({ checkout: res, }); });this.props.client.product.fetchAll().then((res) => { this.setState({ products: res, }); });this.props.client.shop.fetchInfo().then((res) => { this.setState({ shop: res, }); }); }

I opted to do that instead as far upstream of a site load as possible, directly in index.js. Then, I issued a corresponding event when each part of the response has been received:

我选择直接在index.js这样做,而不是在站点负载的上游进行。 然后,在收到响应的每个部分时,我发出了一个相应的事件:

// buildClient() is synchronous, so we can call all these after!client.product.fetchAll().then((res) => {  store.dispatch({type: 'PRODUCTS_FOUND', payload: res});});client.checkout.create().then((res) => {  store.dispatch({type: 'CHECKOUT_FOUND', payload: res});});client.shop.fetchInfo().then((res) => {  store.dispatch({type: 'SHOP_FOUND', payload: res});});

// buildClient() is synchronous, so we can call all these after! client.product.fetchAll().then((res) => { store.dispatch({type: 'PRODUCTS_FOUND', payload: res}); }); client.checkout.create().then((res) => { store.dispatch({type: 'CHECKOUT_FOUND', payload: res}); }); client.shop.fetchInfo().then((res) => { store.dispatch({type: 'SHOP_FOUND', payload: res}); });

By now the reducer is created, and the initialization of the Shopify API client is complete all for index.js.

至此,已经创建了reducer,并且已经针对index.js完成了Shopify API client的初始化。

回到App.js (Back to App.js)

Now in App.js, wire up Redux’s store to the App state:

现在在App.js ,将Redux的商店连接到App状态:

import { connect } from 'react-redux';

import { connect } from 'react-redux';

and don’t forget to import the store as well:

并且不要忘记导入商店:

import store from './store';

import store from './store';

At the bottom where export default App should be, modify it to this:

在应该export default App的底部, export default App其修改为:

export default connect((state) => state)(App);

export default connect((state) => state)(App);

This connects the Redux state to the App component.

这会将Redux状态连接到App组件。

Now in the render() function we are able to access the Redux’s state with Redux’s getState() (as apposed to using vanilla react’s this.state):

现在,在render()函数中,我们可以使用Redux的getState()访问Redux的getState()与使用vanilla react的this.state ):

render() {    ...        const state = store.getState();}

render() { ... const state = store.getState(); }

最后:事件处理程序(我们仍在App.js中) (Finally: the Event Handlers (We’re Still in App.js))

From above, you know that there are only three event handlers that we need in App.js, because the cart uses only three: updateQuantityInCart, removeLineItemInCart, and handleCartClose. The original cart event handlers from the example GitHub repository, which used local component state looked like this:

从上面知道,在App.js中我们只需要三个事件处理程序,因为购物车仅使用三个事件处理程序: updateQuantityInCartremoveLineItemInCarthandleCartClose 示例GitHub存储库中使用本地组件状态的原始购物车事件处理程序如下所示:

updateQuantityInCart(lineItemId, quantity) {  const checkoutId = this.state.checkout.id  const lineItemsToUpdate = [{id: lineItemId, quantity: parseInt(quantity, 10)}]return this.props.client.checkout.updateLineItems(checkoutId, lineItemsToUpdate).then(res => {    this.setState({      checkout: res,    });  });}removeLineItemInCart(lineItemId) {  const checkoutId = this.state.checkout.idreturn this.props.client.checkout.removeLineItems(checkoutId, [lineItemId]).then(res => {    this.setState({      checkout: res,    });  });}handleCartClose() {  this.setState({    isCartOpen: false,  });}

updateQuantityInCart(lineItemId, quantity) { const checkoutId = this.state.checkout.id const lineItemsToUpdate = [{id: lineItemId, quantity: parseInt(quantity, 10)}]return this.props.client.checkout.updateLineItems(checkoutId, lineItemsToUpdate).then(res => { this.setState({ const lineItemsToUpdate = [{id: lineItemId, quantity: parseInt(quantity, 10)}]return this.props.client.checkout.updateLineItems(checkoutId, lineItemsToUpdate).then(res => { this.setState({ checkout: res, }); }); }removeLineItemInCart(lineItemId) { const checkoutId = this.state.checkout.idreturn this.props.client.checkout.removeLineItems(checkoutId, [lineItemId]).then(res => { this.setState({ checkout: res, }); }); }handleCartClose() { this.setState({ isCartOpen: false, }); }

We can refactor them to dispatch events to the Redux store as follows:

我们可以重构它们以将事件调度到Redux存储,如下所示:

updateQuantityInCart(lineItemId, quantity) {    const state = store.getState(); // state from redux store    const checkoutId = state.checkout.id    const lineItemsToUpdate = [{id: lineItemId, quantity: parseInt(quantity, 10)}]    state.client.checkout.updateLineItems(checkoutId, lineItemsToUpdate).then(res => {      store.dispatch({type: 'UPDATE_QUANTITY_IN_CART', payload: {checkout: res}});    });}removeLineItemInCart(lineItemId) {    const state = store.getState(); // state from redux store    const checkoutId = state.checkout.id    state.client.checkout.removeLineItems(checkoutId, [lineItemId]).then(res => {      store.dispatch({type: 'REMOVE_LINE_ITEM_IN_CART', payload: {checkout: res}});    });}handleCartClose() {    store.dispatch({type: 'CLOSE_CART'});}handleCartOpen() {    store.dispatch({type: 'OPEN_CART'});}

updateQuantityInCart(lineItemId, quantity) { const state = store.getState(); // state from redux store const state = store.getState(); // state from redux store const checkoutId = state.checkout.id const state = store.getState(); // state from redux store const checkoutId = state.checkout.id const lineItemsToUpdate = [{id: lineItemId, quantity: parseInt(quantity, 10)}] state.client.checkout.updateLineItems(checkoutId, lineItemsToUpdate).then(res => { store.dispatch({type: 'UPDATE_QUANTITY_IN_CART', payload: {checkout: res}}); }); } removeLineItemInCart(lineItemId) { const state = store.getState(); // state from redux store const state = store.getState(); // state from redux store const checkoutId = state.checkout.id state.client.checkout.removeLineItems(checkoutId, [lineItemId]).then(res => { store.dispatch({type: 'REMOVE_LINE_ITEM_IN_CART', payload: {checkout: res}}); }); } handleCartClose() { store.dispatch({type: 'CLOSE_CART'}); } handleCartOpen() { store.dispatch({type: 'OPEN_CART'}); }

If you were following along, I already mentioned that I added my own handleCartOpen function, because I pass that function down as a prop to my <Nav/> component, so a user is able to open and close the cart from a link in the nav. At a future time, I could move that function to the Nav itself instead of passing it as a prop, since of course the Redux store will also be available there!

如果您一直在学习,我已经提到我添加了自己的handleCartOpen函数,因为我将该函数作为道具传递给了<Na v />组件,因此用户可以从中的链接打开和关闭购物车。导航。 将来,我可以将该功能移至Nav本身,而不是将其作为道具传递,因为当然Redux商店也将在那里可用!

最后添加<Products />组件! (Finally Add that <Products/> Component!)

So, you’ve got a basic store maybe with some simple href’s that link to the corresponding product on your Shopify store? Ha! Get rid of those, and replace them with your brand spankin’ new <Products/> component!

因此,您有一家基本商店,可能带有一些简单的href ,它们链接到Shopify商店中的相应产品? 哈! 摆脱这些,并用您的品牌spankin'新的<Product s />组件替换它们!

First, import the component into wherever your store markup should be (remember, in my code base I’ve put the shopify example components in a folder called shopify/)

首先,将组件导入您商店标记应在的任何位置(请记住,在我的代码库中,我已经将shopify示例组件放在了一个名为shopify/的文件夹中)

This will be wherever your products currently are. (In the boilerplate repository I made, I put this in the GenericProductsPage component, to signal that this code could be applied to any page that has a products section):

这将是您当前产品的任何位置。 (在我制作的样板存储库中 ,将其放入GenericProductsPage组件中,以表明该代码可以应用于具有products部分的任何页面):

import Products from './shopify/Products';

import Products from './shopify/Products';

Now finally, that past 15–20 minutes of redux boilerplate code edits pays off: we can grab the products part of our state — not by way of vanilla React state passed down over and over again through props — but through grabbing by way of Redux state, in a neat one liner const state = store.getState();:

现在终于可以了,过去15到20分钟的redux样板代码编辑取得了回报:我们可以获取状态的products部分-而不是通过props一遍又一遍传递的香草React状态-而是通过Redux进行获取状态,在一个整洁的线性const state = store.getState();

render () {    const state = store.getState(); // state from redux store    let oProducts = <Products      products={state.products}      client={state.client}      addVariantToCart={this.addVariantToCart}    />;

render () { const state = store.getState(); // state from redux store const state = store.getState(); // state from redux store let oProducts = <Products products={state.products} client={state.client} addVariantToCart={this.addVariantToCart} />;

Don’t forget to drop the component itself into where it should go in your render() function. For me, that location was buried in Bootstrap style classes and HTML:

不要忘记将组件本身放到render()函数中应该放置的位置。 对我来说,该位置埋在Bootstrap样式类和HTML中:

...<div className="service-content-one">    <div className="row">        <Products/>    </div>{/*/.row*/}</div>{/*/.service-content-one*/}...

... <div className="service-content-one"> <div className="row"> <Products/> </div>{/*/.row*/} </div>{/*/.service-content-one*/} ...

Finally, we will need a single event function addVariantToCart for the cart to work with this products component. Again, for reference, here is the original, vanilla React local state version of addVariantToCart(again, from the shopify example repository):

最后,我们需要一个事件函数addVariantToCart ,购物车才能与此产品组件一起使用。 再次,作为参考,这里是addVariantToCart的原始香草React本地state版本(同样,来自shopify示例存储库):

addVariantToCart(variantId, quantity){  this.setState({    isCartOpen: true,  });const lineItemsToAdd = [{variantId, quantity: parseInt(quantity, 10)}]  const checkoutId = this.state.checkout.idreturn this.props.client.checkout.addLineItems(checkoutId, lineItemsToAdd).then(res => {    this.setState({      checkout: res,    });  });}

addVariantToCart(variantId, quantity){ this.setState({ isCartOpen: true, });const lineItemsToAdd = [{variantId, quantity: parseInt(quantity, 10)}] const checkoutId = this.state.checkout.idreturn this.props.client.checkout.addLineItems(checkoutId, lineItemsToAdd).then(res => { this.setState({ checkout: res, }); }); }

and the new, Redux-friendly store.dispatch() version:

以及对Redux友好的新store.dispatch()版本:

addVariantToCart(variantId, quantity) {    const state = store.getState(); // state from redux store    const lineItemsToAdd = [{variantId, quantity: parseInt(quantity, 10)}]    const checkoutId = state.checkout.id    state.client.checkout.addLineItems(checkoutId, lineItemsToAdd).then(res => {      store.dispatch({type: 'ADD_VARIANT_TO_CART', payload: {isCartOpen: true, checkout: res}});    });}

addVariantToCart(variantId, quantity) { const state = store.getState(); // state from redux store const state = store.getState(); // state from redux store const lineItemsToAdd = [{variantId, quantity: parseInt(quantity, 10)}] const state = store.getState(); // state from redux store const lineItemsToAdd = [{variantId, quantity: parseInt(quantity, 10)}] const checkoutId = state.checkout.id state.client.checkout.addLineItems(checkoutId, lineItemsToAdd).then(res => { store.dispatch({type: 'ADD_VARIANT_TO_CART', payload: {isCartOpen: true, checkout: res}}); }); }

which is of course the one we will use. ?

当然,这是我们将要使用的那个。

Don’t forget to bind it in the constructor:

不要忘记将其绑定到构造函数中:

this.addVariantToCart = this.addVariantToCart.bind(this);

this.addVariantToCart = this.addVariantToCart.bind(this);

Also, you’ll need to connect this component to the store like you did App.js , and import the store:

另外,您需要像将App.js一样将此组件连接到商店,并导入商店:

import { connect } from 'react-redux'import store from '../store';

import { connect } from 'react-redux' import store from '../store'; import { connect } from 'react-redux' import store from '../store';

at the top, and (assuming the component where you put the Shopify Product component name is GenericProductPage:

在顶部,并且(假设您将Shopify Product组件放在其中的组件名称为GenericProductPage

export default connect((state) => state)(GenericProductsPage);

export default connect((state) => state)(GenericProductsPage);

at the bottom.

在底部。

Great! Now, no matter how deeply buried in components, or wherever your products component is declared, it can communicate with the cart’s state!

大! 现在,无论埋在组件中的深度如何,或者无论您的产品组件在哪里声明,它都可以与购物车的状态进行通信!

最终奖金示例:标题或导航中的购物车 (Final BONUS Example: Cart in Your Header or Nav)

If you want to have a ‘Cart’ button in your header / nav, add this button in your Nav component’s render function (again, an example from my current site, which has Bootstrap styles — a very simple version is in the boilerplate example:

如果您想在标题/导航栏中添加一个“购物车”按钮,请将此按钮添加到Nav组件的render函数中(同样,这是我当前站点的示例,该示例具有Bootstrap样式-一个非常简单的版本在样例中 :

<div className="App__view-cart-wrapper"><button className="App__view-cart" onClick={this.props.handleCartOpen}>    Cart    </button></div>

<div className="App__view-cart-wrapper"> <button className="App__view-cart" onClick={this.props.handleCartOpen}> Cart </button> </div>

where handleCartOpen is a new handler method you’ll have to add to App.js:

其中handleCartOpen是一个新的处理程序方法,您必须将其添加到App.js

constructor() {  super();  ...  this.handleCartOpen = this.handleCartOpen.bind(this);  ...}

constructor() { super(); ... this.handleCartOpen = this.handleCartOpen.bind(this); ... }

in the constructor. Then when you are referencing your Nav component in App.js (or wherever you place your Nav) you pass the function handler:

在构造函数中。 然后,当您在App.js中(或您放置Nav的任何地方)引用Nav组件时,您将传递函数处理程序:

<Nav handleCartOpen={this.handleCartOpen}/>

<Nav handleCartOpen={this.handleCartOpen}/>

This could also be refactored to an event in Redux, but since it was only one child down, I did it the vanilla React way.

也可以将其重构为Redux中的一个事件,但是由于只有一个孩子,我采用了香草的React方法。

样式组件 (Styling Component(s))

I relied on Shopify’s CSS file, app.css, located in the shared/ folder in the storefront-api-example repository (you can’t miss it, it’s the only file in shared/ )!

我依靠ShopifyCSS文件app.css ,该文件位于storefront-api-example存储库的shared/文件夹中(您不能错过它,它是shared/唯一的文件)!

Make sure to copy that into your styles/ folder or wherever it needs to be and include it in your index.js file. In my index.js it looks like this:

确保将其复制到您的styles/文件夹中或需要复制的任何位置,并将其包含在index.js文件中。 在我的index.js它看起来像这样:

import './styles/shopify.css';

import './styles/shopify.css';

Since I renamed the app.css which was in the Shopify example repository to shopify.css , and put it folder styles. This convention is also used in the boilerplate repository code.

由于我改名为app.css这是在Shopify例如存储库shopify.css ,并把它的文件夹styles 在样板存储库代码中也使用此约定。

From here it’s pretty easy to identify where exactly in shopify.css the default bright blue color for the buttons is defined, and so on. I’m going to save detailed CSS customization for you to handle. ?

从这里很容易确定在shopify.css中确切的shopify.css ,为按钮定义了默认的亮蓝色,依此类推。 我将保存详细CSS定制供您处理。

But who knows, maybe I’ll post on that eventually — but I find the styles from Shopify pretty good and easy enough to modify.

但是谁知道呢,也许我最终会在此发布—但是我发现Shopify中的样式非常好并且很容易修改。

外卖 (Takeaways)

In my opinion, this is a perfect (non-todo list ?) use of Redux. Redux cleanly organizes the event functions and state of the Shopify cart and makes it easy to access the cart’s state from any other component. This is much easier to maintain than passing pieces of state to children and using multiple event handlers to pass events back up to parent functions all over a React app.

我认为,这是Redux的完美用法(非待办事项列表?)。 Redux干净利落地组织了Shopify购物车的事件功能和状态,并使其易于从任何其他组件访问购物车的状态。 这比将状态传递给子级并使用多个事件处理程序将事件传递回整个React应用程序的父函数要容易得多。

As shown as an example in the tutorial, the cart’s state is accessed easily in the Nav component and the shop section of the front page. I’ll also be able to easily add it to a sort of ‘featured’ product section as well, once Siren Apparel is ready for that.

如本教程中的示例所示,可以在Nav组件和首页的shop部分中轻松访问购物车的状态。 一旦Siren Apparel做好了准备,我还可以轻松地将其添加到“特色”产品部分中。

查找代码 (Find the Code)

A boilerplate repository of this implementation can be found here. It is a near blank create-react-app app, but with all the changes of this tutorial implemented in index.js and App.js , as well as a super basic GenericStorePage and Nav components.

可在此处找到此实现的样板存储库。 这是一个几乎空白的create-react-app程序,但是本教程的所有更改都在index.jsApp.js ,以及超基本的GenericStorePageNav组件。

I built the code on the repo while re-reading and updating my own tutorial here, to make sure this tutorial makes sense!

在重新阅读和更新自己的教程时,我在仓库上构建了代码,以确保该教程有意义!

Because I am crazy ?, Siren Apparel’s website is all open-sourced. So if you want to fool around with my implementation, check out the repository!

因为我疯了?,Siren Apparel的网站全部开源。 因此,如果您想闲逛我的实现,请删除存储库!

I hope you enjoyed this tutorial! If anything isn’t clear or just plain not working, let me know! I’ll try to assist you!

希望您喜欢本教程! 如果有任何不清楚的地方,或者只是无法正常工作,请通知我! 我会尽力为您服务!

Thanks to Lisa Catalano at CSS-Snippets for the simple Nav example which I used in the boilerplate repository!

感谢CSS-Snippets的Lisa Catalano提供了我在样板存储库中使用的简单Nav示例 !

Cheers! ?

干杯!

Chris

克里斯

翻译自: https://www.freecodecamp.org/news/a-detailed-tutorial-how-to-use-shopifys-storefront-api-with-react-and-redux-37f95cbab7f/

shopify二次开发教程

shopify二次开发教程_详细教程:如何将Shopify的Storefront API与React和Redux结合使用...相关推荐

  1. mysql网络安装教程_详细教程--MySQL的安装与配置

    免安装版的Mysql MySQL关是一种关系数据库管理系统,所使用的 SQL 语言是用于访问数据库的最常用的 标准化语言,其特点为体积小.速度快.总体拥有成本低,尤其是开放源码这一特点,在 Web 应 ...

  2. Python开发环境部署详细教程,附上免费Python开发平台

    Python开发环境部署详细教程来啦!本文将带大家搭建一个Python虚拟环境和基于网页的用于交互计算的应用程序Jupyter Notebook. 如果这么简单的部署教程你还是学不会,没关系,在文章末 ...

  3. linux python2.7安装教程_Linux中RedHat下安装Python2.7开发环境的详细教程-学派吧-

    这篇文章主要为大家详细介绍了Linux RedHat下安装Python2.7.pip.ipython环境.eclipse和PyDev环境,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 Linux ...

  4. 用VS2015开发Linux程序详细教程-配置篇

    VS2015推出了跨平台开发,其中包括了对Linux程序开发的支持,最近刚好需要开发Linux程序,对其进行了一些研究. 首先介绍下涉及到的开发工具 开发工具 VS2015Update3,下文简称VS ...

  5. 微信公众号(二)每日推送详细教程(AI回复助手)

    微信公众号(二)每日推送详细教程(回复助手) 1.准备阶段 1.1 基础性配置 1.2 账号准备 2. 配置阶段 2.1 配置application.yml文件 2.2 注解 3. 部署 效果图如下 ...

  6. CATIA CAA二次开发视频教程 CAA资料教程

    CATIA CAA二次开发视频教程 CAA资料教程 链接:https://pan.baidu.com/s/1B7TcxhDl8KPphDtJwQ1WzQ 提取码:tmum

  7. Eclipse开发Servlet项目详细教程!!!

    Eclipse开发Servlet项目详细教程 最近学校开设了Servlet课程 本篇文章作为学习记录 一来 自己记性不好 日后方便复习 二来 帮助更多的人入门Servlet 默认大家已经下载好了Tom ...

  8. Git安装教程(详细教程)

    Git安装教程(详细教程) 大家好,今天我们来学习一下怎么安装git这个软件,好好看,好好学,超详细的. 第一步 下载git (找到自己需要的版本) 第二步 下载完点击安装包进入 使用许可声明 界面 ...

  9. Discuz二次开发基本知识详细讲解

    Discuz是国内最流行的论坛软件管理系统,今天小编跟大家分享一篇关于Discuz二次开发基本知识详细讲解,感兴趣的朋友跟小编一起来了解一下吧! 一) Discuz!的文件系统目录注:想搞DZ开发,就 ...

最新文章

  1. 人工智能时代,怎样高效关注行业趋势、了解AI技术与落地?
  2. 正则表达式的简单应用
  3. Design Pattern - Memento(C#)
  4. 如何与风险投资商打交道
  5. Python基础教程(一):简介、环境搭建、中文编码
  6. maven零基础从配置到运行helloworld(java maven helloworld)
  7. 如何终止SAP cloud connector的服务
  8. 第一课[编辑器设置-VC++6.0]
  9. 暑假周进度总结报告3
  10. PyCharm中的快捷键不能用怎么办?(复制粘贴)
  11. ---转载---phython资料
  12. 亿图图示:分享你的流程图给你的小伙伴
  13. origin指数拟合
  14. 星星之火-51:LTE的物联网技术,Cat-M与NB-IoT的比较
  15. waning rm -i rm -rvfi
  16. 二维Poisson方程五点差分格式及简单求解方法Python实现
  17. mac 设置优先连接的wifi
  18. 【FCPX插件】56种标题文本输入逐字出现字幕打字机动画效果 Typing Titles,支持M1芯片!
  19. java导出word纸张方向_java导出word的几种方式
  20. java半角转全角_JAVA 半角全角相互转换

热门文章

  1. java基础常问面试题,面试必问
  2. 从草根到百万年薪程序员的十年风雨之路,使用指南
  3. Java作业09-异常
  4. C#使用Json.NET解析Json
  5. spring集合的注入
  6. 自定义配置节与配置节的读取
  7. 以后可能用到的一些OQL
  8. autoencoder自编码器原理以及在mnist数据集上的实现
  9. 2.2 string
  10. zooland 新开源的RPC项目,希望大家在开发的微服务的时候多一种选择,让微服务开发简单,并且容易上手。...