برنامج تعليمي مفصل: كيفية استخدام واجهة برمجة تطبيقات Shopify’s Storefront مع React و Redux

التجارة الإلكترونية للجميع! (... المواقع ، وهذا هو )

كتبها كريس أغسطس 2018 ، تم تحديثه في نوفمبر 2018

من باب المجاملة السلبية الفضاء على pexels.com

الخلفية والدافع

لذلك كان الدافع هنا بسيط جدا. أردت أن يتمكن زائرو موقعي من تصفح المنتجات والبحث فيها وتحديدها مباشرةً على نطاقي المخصص دون الاضطرار إلى الانتقال إلى موقع شوبيفي الخاص بنا.

يتمثل الدافع الثانوي في أنه يفضل وجود قاعدة بيانات خاصة بي لموقع ويب بدلاً من استخدام أحد قوالب المصانع الخاصة بـ Shopify. لا جرم القوالب حديثة ونظيفة ، لكنها أساسية إلى حد ما. أنا متأكد من أن هذه القوالب قابلة للتخصيص بشدة ، لكنها ليست مكدسًا أعرفه في الوقت الحالي.

هذا هو أفضل ما في العالمين - موقع React المخصص (المدمج بالفعل وعبر الإنترنت ) ، مع واجهة برمجة التطبيقات الإضافية وعملية الخروج من شوبيفي!

بحلول نهاية هذا البرنامج التعليمي ، ستتمكن من إضافة منتجات Shopify الخاصة بك في أي صفحة من موقعك. الجزء الوحيد من عملية التسوق الذي سيحدث في Shopify هو عندما ينقر المستخدم على "Checkout".

لقد أنشأت مستودعًا فارغًا لهذا الملف التعليمي أيضًا.

كان الدافع الخاص بالكتابة هنا على "المتوسط" ببساطة هو أنني لم أتمكن من العثور على برنامج تعليمي حول هذه العملية بنفسي - لذلك قررت أن أجعل منها واحدة!

لقد كنت مطورًا محترفًا منذ 4 سنوات ، وأقوم بالبرمجة لمدة 7 سنوات. عملت في مجموعات تقنية من مدرسة Fortran و Perl القديمة إلى React و Javascript و Python و Node.

إن Siren Apparel هي واحدة من شركات المشاريع / الشركات الناشئة / التي أديرها منذ خمس سنوات وحتى الآن ، وقد تبرعنا لخمسة أقسام مختلفة من الشرطة والإطفاء حتى الآن!

لنبدأ أخيرًا في هذا البرنامج التعليمي.

Shopify’s Storefront API

لقد جمع الأشخاص الرائعون في Shopify واجهة برمجة تطبيقات Storefront. باستخدام واجهة واجهة برمجة تطبيقات Storefront ، يمكنك إنشاء مكونات React لإضافة صور المنتجات وأشكال المنتج وأحجام المنتجات وعربة إضافة إلى أزرار "الإضافة إلى السلة" و "تسجيل الخروج" إلى موقعك الخاص بدون التسوق.

* لاحظ أن هذا البرنامج التعليمي ليس حول Shopify Polaris ، والذي يستخدم لإنشاء مكونات في React for Shopify Store management نفسها.

بدء الاستخدام: react-js-buy Repository

ألق نظرة على مثال React هذا الذي صممه فريق Shopify. معظم التعليمات البرمجية في هذا البرنامج التعليمي تأتي من هذا المستودع.

... هل ألقيت نظرة؟ حسن!

سننتقل الآن إلى الشفرة! توجه إلى المجلد الجذر لموقع React الخاص بك وقم بتثبيت الوحدة النمطية shopify-buy عبر المحطة الطرفية:

مؤتمر نزع السلاح my-awesome-react-project /
تثبيت npm - حفظ shopify- شراء

(أو أضف غزلًا إلى shopify-buy إذا كنت تفضل الغزل)

بعد ذلك ، في index.js الواجهة الأمامية ، (NOT App.js!) ستحتاج إلى استيراد Client من JS Buy SDK:

استيراد عميل من 'shopify-buy' ؛

ثم أضف كائن التكوين التالي أعلى استدعاء ReactDOM.render ():

عميل العميل = Client.buildClient ({
    storefrontAccessToken: 'your-access-to-token' ،
    النطاق: "your-shopify-url.myshopify.com"
})؛

هذا كل شيء من أجل index.js في الوقت الحالي - سنعود إليه قريبًا.

سنقوم الآن بإضافة جميع المكونات اللازمة لتجربة التسوق والخروج السلسة. انسخ كل المكونات من مستودع react-js-buy:

Cart.js

LineItem.js

Product.js

Products.js

VariantSelector.js

سنقوم بلصق هذه المكونات في acomponents / shopify / مجلد في src / المجلد الخاص بك. يمكنك وضع ملفات المكونات هذه في أي مكان آخر في src / folder ، إذا كنت ترغب في ذلك. ما تبقى من البرنامج التعليمي يفترض أنك وضعت لهم في المكونات / شوبيفي /.

تعديل App.js

سوف تحتاج App.js إلى تغييرات شاملة. أولاً ، قم باستيراد مكون Cart هذا الذي قمت بنسخه للتو في مشروعك الخاص:

استيراد السلة من './components/shopify/Cart' ؛

إذا كان مكون App.js الخاص بك بلا جنسية ، مثل المكون الخاص بي ، فيجب أن تكون آمنًا في نسخ وظيفة المنشئ () بأكملها:

البناء() {
    ممتاز()؛
    this.updateQuantityInCart = this.updateQuantityInCart.bind (this)؛
    this.removeLineItemInCart = this.removeLineItemInCart.bind (this)؛
    this.handleCartClose = this.handleCartClose.bind (this)؛
}

إذا كان لديك بالفعل حالة ، فقم بنسخ خطوط الربط فقط. هذه الأسطر الثلاثة هي وظائف معالج الأحداث التي تحتاج عربة Shopify للعمل بشكل صحيح.

"لكن ماذا عن حالة العربة؟"

يمكنك السؤال؛ أو:

"ماذا عن تعريف معالجات الأحداث للعربة؟"

في الحقيقة ، هذا قادم ، لكن ليس بعد!

يمكنك بعد ذلك إلحاق المكون بأسفل وظيفة render () ، قبل نهاية div.

في رأيي ، يجب أن تكون العربة متاحة في أي مكان في تطبيقك. أعتقد أنه من المنطقي إذن وضع المكون في المكون الجذر للتطبيق - بمعنى آخر ، App.js:

إرجاع (
... <العربة     الخروج = {this.state.checkout}     isCartOpen = {this.state.isCartOpen}     handleCartClose = {this.handleCartClose}     updateQuantityInCart = {this.updateQuantityInCart}     removeLineItemInCart = {this.removeLineItemInCart}  />

مرة أخرى ، لم أقم بتضمين أي كود على معالجات الأحداث في سلة التسوق حتى الآن. بالإضافة إلى ذلك ، لم أتناول مشكلة عدم وجود مكونات الحالة للعربة في App.js.

هناك سبب وجيه لهذا.

خلال منتصف هذا المشروع تقريبًا ، أدركت أن مكون منتجاتي لم يكن بالطبع في ملف App.js الخاص بي.

بدلا من ذلك ، تم دفن حوالي ثلاثة أطفال المكونات أسفل.

لذا ، بدلاً من تمرير المنتجات إلى ثلاثة مستويات للأطفال ، ثم معالجات الوظائف على طول الطريق احتياطيًا ...

قررت استخدام ...

استرجاع!

قرف! أعرف ، أعرف ، Redux ، بينما لا يكون صعبًا للغاية ، هو ألم في٪ * $! لسلك ما يصل في البداية مع جميع المرجل المطلوبة. ولكن ، إذا كنت مطورًا يعمل في متجر للتجارة الإلكترونية أو مالك متجر للتجارة الإلكترونية ، فكر في الأمر بهذه الطريقة: Redux ستمكنك من الوصول إلى حالة العربة من أي مكون أو صفحة في موقعنا على الويب أو webapp.

ستكون هذه القدرة ضرورية مع توسع Siren Apparel ونطور المزيد من المنتجات. نظرًا لأننا نقوم بإنشاء المزيد من المنتجات ، فسوف أقوم بإنشاء صفحة متجر منفصلة مخصصة تحتوي على جميع المنتجات ، مع ترك عدد قليل من المنتجات المميزة على الصفحة الرئيسية.

تعد القدرة على الوصول إلى العربة أمرًا ضروريًا إذا قام المستخدم بالتسوق حول بعض الشيء ، وقراءة بعض القصص أو المعلومات حول Siren Apparel ، ثم قرر الخروج. لا يهم كم يتنقلون ، لن يتم فقد أي شيء من عربتهم!

لذلك ، باختصار ، قررت أنه من الأفضل تطبيق Redux الآن بينما قاعدة الشفرات لموقعنا ليست كبيرة جدًا.

تنفيذ Redux لـ Shopify Buy SDK مع Bare Minimum Boilerplate

تثبيت حزم NPM redux وإعادة رد الفعل:

تثبيت npm - حفظ رد فعل رد - رد

في index.js ، قم باستيراد موفر من react-redux ومتجرك من ./store:

استيراد {Provider} من 'react-redux' ؛
استيراد متجر من ". / المتجر" ؛

قم بلف المكون باستخدام المتجر الذي تم تمريره حول في index.jsto قم بتوصيل تطبيقك إلى متجر Redux الخاص بك:

ReactDOM.render (
<موفر store = {store}>
    
      
    
 ،
document.getElementById ( 'جذور')
)؛

(لاحظ أن لدي أيضًا ، ولكن هذا في منشور مختلف حول كيفية تطبيق التدويل والتوطين لعرض المحتوى على موقع Siren Apparel ديناميكيًا. قصة مختلفة ليوم مختلف.)

بالطبع لم نقم بإنشاء ملف ./store.js بعد. إنشاء متجرك في store.jsin src / الجذر ووضع هذا فيه:

استيراد {createStore} من 'redux' ؛
استيراد المخفض من ". / المخفضات / عربة" ؛
createStore الافتراضي للتصدير (المخفض) ؛

قم بإنشاء ملف مخفضات في src / مخفضات / cart.js والصق هذا الرمز:

// الحالة الأولية
const initState = {
  isCartOpen: false ،
  الخروج: {lineItems: []}،
  منتجات: []،
  متجر: {}
}
// أجراءات
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'
// مخفضات
التصدير الافتراضي (الحالة = initState ، الإجراء) => {
  رمز التبديل (action.type) {
    الحالة CLIENT_CREATED:
      إرجاع {... الحالة ، العميل: action.payload}
    الحالة PRODUCTS_FOUND:
      إرجاع {... الحالة ، المنتجات: action.payload}
    الحالة CHECKOUT_FOUND:
      إرجاع {... الحالة ، الخروج: action.payload}
    الحالة SHOP_FOUND:
      إرجاع {... ولاية ، متجر: action.payload}
    الحالة ADD_VARIANT_TO_CART:
      return {... state، isCartOpen: action.payload.isCartOpen، check: action.payload.checkout}
    الحالة UPDATE_QUANTITY_IN_CART:
      ارجع {... الحالة ، الخروج: action.payload.checkout}
    الحالة REMOVE_LINE_ITEM_IN_CART:
      ارجع {... الحالة ، الخروج: action.payload.checkout}
    الحالة OPEN_CART:
      return {... state ، isCartOpen: true}
    الحالة CLOSE_CART:
      return {... state ، isCartOpen: false}
    الإفتراضي:
      عودة الدولة
  }
}

لا داعي للقلق ، لن أقوم فقط بنشر هذا المخفض الكبير ولن أتحدث عما يحدث ؛ سنصل إلى كل حدث! هناك بعض الأشياء التي يجب ملاحظتها هنا.

نأخذ الحالة الأولية مما كتبته الحالة كما في مثال Shopify GitHub ونضعها في initState ، أي الأجزاء الأربعة التالية من الحالة:

isCartOpen: false ،
الخروج: {lineItems: []}،
منتجات: []،
متجر: {}

ومع ذلك ، في تطبيقي ، أقوم أيضًا بإنشاء جزء عميل من الحالة. أدعو الدالة createClient () مرة واحدة ثم على الفور في حالة Redux في index.js. لذلك دعونا نتوجه إلى index.js:

العودة إلى index.js

عميل العميل = Client.buildClient ({
  storefrontAccessToken: 'your-shopify-token' ،
  النطاق: "your-shopify-url.myshopify.com"
})؛
store.dispatch ({type: 'CLIENT_CREATED'، payload: client})؛

في مثال Shopify buy SDK ، هناك بعض المكالمات غير المتزامنة للحصول على معلومات حول المنتجات وتخزين المعلومات في وظيفة React’s componentWillMount (). يشبه رمز المثال هذا:

componentWillMount () {
    this.props.client.checkout.create (). ثم ((res) => {
      this.setState ({
        الخروج: الدقة ،
      })؛
    })؛
this.props.client.product.fetchAll (). ثم ((res) => {
      this.setState ({
        المنتجات: الدقة ،
      })؛
    })؛
this.props.client.shop.fetchInfo (). ثم ((res) => {
      this.setState ({
        متجر: الدقة ،
      })؛
    })؛
  }

لقد اخترت أن أقوم بذلك بدلاً من تحميل الموقع قدر الإمكان ، مباشرة في index.js. بعد ذلك ، أصدرت حدثًا مطابقًا عند تلقي كل جزء من الاستجابة:

// buildClient () متزامن ، حتى نتمكن من استدعاء كل هذه بعد!
client.product.fetchAll (). ثم ((res) => {
  store.dispatch ({type: 'PRODUCTS_FOUND'، payload: res})؛
})؛
client.checkout.create (). ثم ((res) => {
  store.dispatch ({type: 'CHECKOUT_FOUND'، payload: res})؛
})؛
client.shop.fetchInfo (). ثم ((res) => {
  store.dispatch ({type: 'SHOP_FOUND'، payload: res})؛
})؛

الآن يتم إنشاء المخفض ، وتمت تهيئة عميل Shopify API بالكامل لـ index.js.

رجوع إلى App.js

الآن في App.js ، قم بتحويل متجر Redux إلى حالة التطبيق:

استيراد {connect} من 'react-redux' ؛

ولا تنس استيراد المتجر أيضًا:

استيراد متجر من ". / المتجر" ؛

في الجزء السفلي حيث يجب أن يكون التطبيق الافتراضي للتصدير ، قم بتعديله ليصبح:

الاتصال الافتراضي التصدير ((state) => state) (App)؛

هذا يربط حالة Redux بمكون التطبيق.

الآن في وظيفة render () يمكننا الوصول إلى حالة Redux باستخدام getState () من Redux (كما هو موضح في استخدام this.state لكانون رد الفعل):

يجعل() {
    ...
    const state = store.getState () ؛
}

أخيرًا: معالجات الأحداث (ما زلنا في App.js)

من الأعلى ، تعلم أن هناك ثلاثة معالجات أحداث فقط نحتاجها في App.js ، لأن العربة تستخدم ثلاثة فقط: updateQuantityInCart و removeLineItemInCart و handleCart. تبدو معالجات أحداث العربة الأصلية من مثال مستودع جيثب ، الذي استخدم حالة المكون المحلي كما يلي:

updateQuantityInCart (lineItemId ، الكمية) {
  const checkoutId = this.state.checkout.id
  const lineItemsToUpdate = [{id: lineItemId، quantity: parseInt (quantity، 10)}]
قم بإرجاع this.props.client.checkout.updateLineItems (checkoutId ، lineItemsToUpdate) .then (res => {
    this.setState ({
      الخروج: الدقة ،
    })؛
  })؛
}
removeLineItemInCart (lineItemId) {
  const checkoutId = this.state.checkout.id
قم بإرجاع this.props.client.checkout.removeLineItems (checkoutId ، [lineItemId]). ثم (res => {
    this.setState ({
      الخروج: الدقة ،
    })؛
  })؛
}
handleCartClose () {
  this.setState ({
    isCartOpen: false ،
  })؛
}

يمكننا إعادة تشكيلها لإرسال الأحداث إلى متجر Redux على النحو التالي:

updateQuantityInCart (lineItemId ، الكمية) {
    const state = store.getState () ؛ // الدولة من متجر الإعادة
    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 () ؛ // الدولة من متجر الإعادة
    const checkoutId = state.checkout.id
    state.client.checkout.removeLineItems (checkoutId، [lineItemId]). ثم (res => {
      store.dispatch ({type: 'REMOVE_LINE_ITEM_IN_CART'، payload: {checkout: res}})؛
    })؛
}
handleCartClose () {
    store.dispatch ({type: 'CLOSE_CART'}) ؛
}
handleCartOpen () {
    store.dispatch ({type: 'OPEN_CART'}) ؛
}

إذا كنت تتابع ذلك ، فقد ذكرت بالفعل أنني قمت بإضافة وظيفة handleCartOpen الخاصة بي ، لأنني أسقط هذه الوظيفة لأسفل كدعم لمكون