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 <Car
t/> component to the bottom of your re
nder() 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 <Car
t/> 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軟件包redux
和react-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 <Provid
er> component with the passed
store around you
r<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 <IntlProvid
er>, 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.js
in 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 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}
? ?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
中我們只需要三個事件處理程序,因為購物車僅使用三個事件處理程序: updateQuantityInCart
, removeLineItemInCart
和handleCartClose
。 示例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 <Na
v/> 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 <Product
s/> 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.js
和App.js
,以及超基本的GenericStorePage
和Nav
組件。
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二次開發教程