shopify二次开发教程_详细教程:如何将Shopify的Storefront API与React和Redux结合使用...
shopify二次開發(fā)教程
by Chris Frewin
克里斯·弗里溫(Chris Frewin)
詳細教程:如何將Shopify的Storefront API與React和Redux結合使用 (A detailed tutorial: how to use Shopify’s Storefront API with React and Redux)
電子商務為所有人! (…網(wǎng)站,就是?) (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.
因此,這里的動機非常簡單。 我希望網(wǎng)站訪問者能夠直接在我的自定義域上瀏覽,搜索和選擇產(chǎn)品,而不必訪問我們的Shopify網(wǎng)站。
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.
次要動機是,我寧愿擁有自己的網(wǎng)站代碼庫,也不愿使用Shopify的工廠模板之一。 Shopify團隊無罪! 模板是現(xiàn)代且干凈的,但是它們是非常基本的。 我敢肯定那些模板是高度可定制的,但是目前我還不知道它是一個堆棧。
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網(wǎng)站(已建立并在線運行?),并添加了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’.
在本教程結束時,您將可以在網(wǎng)站的任何頁面上添加Shopify產(chǎn)品。 Shopify上購物過程的唯一部分是用戶單擊“結帳”。
I’ve created an empty boilerplate repository for this tutorial as well.
我也為本教程創(chuàng)建了一個空的樣板存儲庫 。
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年的專業(yè)開發(fā)人員經(jīng)驗,并且有7年的編程經(jīng)驗。我從事過從舊式的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 ‘a(chǎn)dd to cart’ and ‘checkout’ buttons into your own, non-Shopify site.
Shopify的出色人士匯集了Storefront API 。 使用Storefront API,您可以創(chuàng)建React組件以將產(chǎn)品圖片,產(chǎn)品變體,產(chǎn)品尺寸,購物車以及“添加到購物車”和“結帳”按鈕添加到您自己的非Shopify網(wǎng)站中。
*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商店管理本身中創(chuàng)建組件。
入門: 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示例 。 本教程中的大多數(shù)代碼都來自該存儲庫。
…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:
現(xiàn)在,我們將直接跳入代碼! 轉到您的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()調(diào)用上方添加以下配置對象:
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的內(nèi)容-我們將盡快恢復。
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:
現(xiàn)在,我們將添加所需的所有組件,以實現(xiàn)順暢的購物和結帳體驗。 從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組件像我的一樣是無狀態(tài)的,則應該安全地復制以下整個constructor()函數(shù):
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.
如果您已經(jīng)有狀態(tài),則僅復制那些bind線。 這三行是Shopify購物車正常運行所需的事件處理函數(shù)。
“But what about state for the cart!?”“但是購物車的狀態(tài)呢??”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 />組件附加到y(tǒng)our re render()函數(shù)的底部,在結束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中購物車缺少狀態(tài)組件的問題。
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.
在這個項目進行到一半的時候,我意識到我的產(chǎn)品組件當然不在我的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…
因此,與其將產(chǎn)品的三個層次傳遞給子級,然后再將函數(shù)處理程序一路備份……
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并不困難,但它對%* $來說是一個痛苦! 首先連接所需的所有樣板。 但是,如果您是在電子商務商店中工作的開發(fā)人員或電子商務商店所有者,請這樣考慮:Redux將使您能夠從我們網(wǎng)站或webapp中的任何組件或頁面訪問購物車的狀態(tài)。
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服裝的擴展以及我們開發(fā)更多產(chǎn)品,這一能力將至關重要。 隨著我們創(chuàng)建更多產(chǎn)品,我將在所有產(chǎn)品上創(chuàng)建一個單獨的專用商店頁面,同時在首頁上僅保留少數(shù)特色產(chǎn)品。
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.
因此,簡而言之,我認為在我們站點的代碼庫不太大的情況下,現(xiàn)在最好實現(xiàn)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 <Provider> component with the passed store around your<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>,但這是在另一篇有關如何應用國際化和本地化來動態(tài)呈現(xiàn)Siren Apparel網(wǎng)站上內(nèi)容的文章中 。關于另一天的不同故事。)
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:
當然,現(xiàn)在我們還沒有制作./store.js文件。 在src/根目錄下的store.js中創(chuàng)建商店, 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創(chuàng)建您的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.
不用擔心,我不會只發(fā)布這個大型的reducer,而不會討論正在發(fā)生的事情。 我們將參加每個活動! 這里有幾件事要注意。
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示例中編寫的狀態(tài)中獲取初始狀態(tài),并將其放入我們的initState ,即狀態(tài)的以下四個部分:
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:
但是,在實現(xiàn)中,我還創(chuàng)建了狀態(tài)的client部分。 我調(diào)用一次createClient()函數(shù),然后立即在index.js中將其設置為Redux狀態(tài)。 因此,讓我們進入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示例中,有一些異步調(diào)用來獲取有關產(chǎn)品的信息并將信息存儲在React的componentWillMount()函數(shù)中。 該示例代碼如下所示:
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這樣做,而不是在站點負載的上游進行。 然后,在收到響應的每個部分時,我發(fā)出了一個相應的事件:
// 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.
至此,已經(jīng)創(chuàng)建了reducer,并且已經(jīng)針對index.js完成了Shopify API client的初始化。
回到App.js (Back to App.js)
Now in App.js, wire up Redux’s store to the App state:
現(xiàn)在在App.js ,將Redux的商店連接到App狀態(tài):
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狀態(tài)連接到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):
現(xiàn)在,在render()函數(shù)中,我們可以使用Redux的getState()訪問Redux的getState()與使用vanilla react的this.state ):
render() { ? ?... ? ? ? ?const state = store.getState();}
render() { ... const state = store.getState(); }
最后:事件處理程序(我們?nèi)栽贏pp.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存儲庫中使用本地組件狀態(tài)的原始購物車事件處理程序如下所示:
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:
我們可以重構它們以將事件調(diào)度到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!
如果您一直在學習,我已經(jīng)提到我添加了自己的handleCartOpen函數(shù),因為我將該函數(shù)作為道具傳遞給了<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商店中的相應產(chǎn)品? 哈! 擺脫這些,并用您的品牌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/)
首先,將組件導入您商店標記應在的任何位置(請記住,在我的代碼庫中,我已經(jīng)將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):
這將是您當前產(chǎn)品的任何位置。 (在我制作的樣板存儲庫中 ,將其放入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();:
現(xiàn)在終于可以了,過去15到20分鐘的redux樣板代碼編輯取得了回報:我們可以獲取狀態(tài)的products部分-而不是通過props一遍又一遍傳遞的香草React狀態(tài)-而是通過Redux進行獲取狀態(tài),在一個整潔的線性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()函數(shù)中應該放置的位置。 對我來說,該位置埋在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):
最后,我們需要一個事件函數(shù)addVariantToCart ,購物車才能與此產(chǎn)品組件一起使用。 再次,作為參考,這里是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:
不要忘記將其綁定到構造函數(shù)中:
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!
大! 現(xiàn)在,無論埋在組件中的深度如何,或者無論您的產(chǎn)品組件在哪里聲明,它都可以與購物車的狀態(tài)進行通信!
最終獎金示例:標題或導航中的購物車 (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函數(shù)中(同樣,這是我當前站點的示例,該示例具有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:
在構造函數(shù)中。 然后,當您在App.js中(或您放置Nav的任何地方)引用Nav組件時,您將傳遞函數(shù)處理程序:
<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.
但是誰知道呢,也許我最終會在此發(fā)布—但是我發(fā)現(xiàn)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購物車的事件功能和狀態(tài),并使其易于從任何其他組件訪問購物車的狀態(tài)。 這比將狀態(tài)傳遞給子級并使用多個事件處理程序將事件傳遞回整個React應用程序的父函數(shù)要容易得多。
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部分中輕松訪問購物車的狀態(tài)。 一旦Siren Apparel做好了準備,我還可以輕松地將其添加到“特色”產(chǎn)品部分中。
查找代碼 (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.
可在此處找到此實現(xiàn)的樣板存儲庫。 這是一個幾乎空白的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的網(wǎng)站全部開源。 因此,如果您想閑逛我的實現(xiàn),請刪除存儲庫!
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二次開發(fā)教程
總結
以上是生活随笔為你收集整理的shopify二次开发教程_详细教程:如何将Shopify的Storefront API与React和Redux结合使用...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 女人梦到银龙鱼是什么征兆
- 下一篇: 梦到掉牙齿流血是什么预兆