
ยินดีต้อนรับสู่ ชุดเครื่องมือ BSMNT Commerce : แพ็คเกจเพื่อช่วยให้คุณจัดส่งหน้าร้านที่ดีขึ้นเร็วขึ้นและมีความมั่นใจมากขึ้น
ชุดเครื่องมือนี้ช่วยเราได้ - เบส. คิว - หน้าร้านที่เชื่อถือได้ของเรือที่สามารถจัดการกับปริมาณการใช้งานที่บ้าคลั่งได้ บางส่วนของพวกเขารวมถึง: shopmrbeast.com, karljacobs.co, shopmrballen.com และ ranboo.fashion
หากคุณกำลังมองหาตัวอย่างด้วย next.js + Shopify ลองดูตัวอย่างของเราที่นี่
ที่เก็บนี้มีสามแพ็คเกจ:
@bsmnt/storefront-hooks : React Hooks เพื่อจัดการสถานะฝั่งไคลเอ็นต์ storefront
@tanstack/react-query และ localStorage @bsmnt/sdk-gen : A CLI ที่สร้างประเภท SAFE, GraphQL SDK
graphql สำหรับการผลิต @bsmnt/drop : ผู้ช่วยในการจัดการการนับถอยหลัง โดยทั่วไปใช้เพื่อสร้างโฆษณารอบ ๆ การลดลงของพ่อค้า
การเล่นเหล่านี้เข้าด้วยกันได้ดี แต่ยังสามารถใช้แยกต่างหากได้ มาดูกันว่าพวกเขาทำงานอย่างไร!
@bsmnt/storefront-hooks yarn add @bsmnt/storefront-hooks @tanstack/react-queryแพ็คเกจนี้ส่งออก:
createStorefrontHooks : ฟังก์ชั่น ที่สร้างตะขอที่จำเป็นในการโต้ตอบกับรถเข็น import { createStorefrontHooks } from '@bsmnt/storefront-hooks'
export const hooks = createStorefrontHooks ( {
cartCookieKey : '' , // to save cart id in cookie
fetchers : { } , // hooks will use these internally
mutators : { } , // hooks will use these internally
createCartIfNotFound : false , // defaults to false. if true, will create a cart if none is found
queryClientConfig : { } // internal query client config
} )ลองดูตัวอย่าง:
localStorage import { createStorefrontHooks } from '@bsmnt/storefront-hooks'
type LineItem = {
merchandiseId : string
quantity : number
}
type Cart = {
id : string
lines : LineItem [ ]
}
export const {
QueryClientProvider ,
useCartQuery ,
useAddLineItemsToCartMutation ,
useOptimisticCartUpdate ,
useRemoveLineItemsFromCartMutation ,
useUpdateLineItemsInCartMutation
} = createStorefrontHooks < Cart > ( {
cartCookieKey : 'example-nextjs-localstorage' ,
fetchers : {
fetchCart : ( cartId : string ) => {
const cartFromLocalStorage = localStorage . getItem ( cartId )
if ( ! cartFromLocalStorage ) throw new Error ( 'Cart not found' )
const cart : Cart = JSON . parse ( cartFromLocalStorage )
return cart
}
} ,
mutators : {
addLineItemsToCart : ( cartId , lines ) => {
const cartFromLocalStorage = localStorage . getItem ( cartId )
if ( ! cartFromLocalStorage ) throw new Error ( 'Cart not found' )
const cart : Cart = JSON . parse ( cartFromLocalStorage )
// Add line if not exists, update quantity if exists
const updatedCart = lines . reduce ( ( cart , line ) => {
const lineIndex = cart . lines . findIndex (
( cartLine ) => cartLine . merchandiseId === line . merchandiseId
)
if ( lineIndex === - 1 ) {
cart . lines . push ( line )
} else {
cart . lines [ lineIndex ] ! . quantity += line . quantity
}
return cart
} , cart )
localStorage . setItem ( cartId , JSON . stringify ( updatedCart ) )
return {
data : updatedCart
}
} ,
createCart : ( ) => {
const cart : Cart = { id : 'cart' , lines : [ ] }
localStorage . setItem ( cart . id , JSON . stringify ( cart ) )
return { data : cart }
} ,
createCartWithLines : ( lines ) => {
const cart = { id : 'cart' , lines }
localStorage . setItem ( cart . id , JSON . stringify ( cart ) )
return { data : cart }
} ,
removeLineItemsFromCart : ( cartId , lineIds ) => {
const cartFromLocalStorage = localStorage . getItem ( cartId )
if ( ! cartFromLocalStorage ) throw new Error ( 'Cart not found' )
const cart : Cart = JSON . parse ( cartFromLocalStorage )
cart . lines = cart . lines . filter (
( line ) => ! lineIds . includes ( line . merchandiseId )
)
localStorage . setItem ( cart . id , JSON . stringify ( cart ) )
return {
data : cart
}
} ,
updateLineItemsInCart : ( cartId , lines ) => {
const cartFromLocalStorage = localStorage . getItem ( cartId )
if ( ! cartFromLocalStorage ) throw new Error ( 'Cart not found' )
const cart : Cart = JSON . parse ( cartFromLocalStorage )
cart . lines = lines
localStorage . setItem ( cart . id , JSON . stringify ( cart ) )
return {
data : cart
}
}
} ,
logging : {
onError ( type , error ) {
console . info ( { type , error } )
} ,
onSuccess ( type , data ) {
console . info ( { type , data } )
}
}
} )@bsmnt/sdk-gen # Given the following file tree:
.
└── storefront/
├── sdk-gen/
│ └── sdk.ts # generated with @bsmnt/sdk-gen
└── hooks.ts # <- we'll work hereตัวอย่างนี้ขึ้นอยู่กับ @bsmnt/sdk-gen
// ./storefront/hooks.ts
import { createStorefrontHooks } from '@bsmnt/storefront-hooks'
import { storefront } from '../sdk-gen/sdk'
import type {
CartGenqlSelection ,
CartUserErrorGenqlSelection ,
FieldsSelection ,
Cart as GenqlCart
} from '../sdk-gen/generated'
const cartFragment = {
id : true ,
checkoutUrl : true ,
createdAt : true ,
cost : { subtotalAmount : { amount : true , currencyCode : true } }
} satisfies CartGenqlSelection
export type Cart = FieldsSelection < GenqlCart , typeof cartFragment >
const userErrorFragment = {
message : true ,
code : true ,
field : true
} satisfies CartUserErrorGenqlSelection
export const {
QueryClientProvider ,
useCartQuery ,
useAddLineItemsToCartMutation ,
useOptimisticCartUpdate ,
useRemoveLineItemsFromCartMutation ,
useUpdateLineItemsInCartMutation
} = createStorefrontHooks ( {
cartCookieKey : 'example-nextjs-shopify' ,
fetchers : {
fetchCart : async ( cartId ) => {
const { cart } = await storefront . query ( {
cart : {
__args : { id : cartId } ,
... cartFragment
}
} )
if ( cart === undefined ) throw new Error ( 'Request failed' )
return cart
}
} ,
mutators : {
addLineItemsToCart : async ( cartId , lines ) => {
const { cartLinesAdd } = await storefront . mutation ( {
cartLinesAdd : {
__args : {
cartId ,
lines
} ,
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartLinesAdd ?. cart ,
userErrors : cartLinesAdd ?. userErrors
}
} ,
createCart : async ( ) => {
const { cartCreate } = await storefront . mutation ( {
cartCreate : {
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartCreate ?. cart ,
userErrors : cartCreate ?. userErrors
}
} ,
// TODO we could use the same mutation as createCart?
createCartWithLines : async ( lines ) => {
const { cartCreate } = await storefront . mutation ( {
cartCreate : {
__args : { input : { lines } } ,
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartCreate ?. cart ,
userErrors : cartCreate ?. userErrors
}
} ,
removeLineItemsFromCart : async ( cartId , lineIds ) => {
const { cartLinesRemove } = await storefront . mutation ( {
cartLinesRemove : {
__args : { cartId , lineIds } ,
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartLinesRemove ?. cart ,
userErrors : cartLinesRemove ?. userErrors
}
} ,
updateLineItemsInCart : async ( cartId , lines ) => {
const { cartLinesUpdate } = await storefront . mutation ( {
cartLinesUpdate : {
__args : {
cartId ,
lines : lines . map ( ( l ) => ( {
id : l . merchandiseId ,
quantity : l . quantity ,
attributes : l . attributes
} ) )
} ,
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartLinesUpdate ?. cart ,
userErrors : cartLinesUpdate ?. userErrors
}
}
} ,
createCartIfNotFound : true
} ) @bsmnt/sdk-gen yarn add @bsmnt/sdk-gen --dev แพ็คเกจนี้ติดตั้ง CLI ด้วยคำสั่งเดียว: generate การรันมันจะไปถึงจุดสิ้นสุด GraphQL ของคุณและสร้างประเภท typescript จากการสืบค้นและการกลายพันธุ์ของคุณ มันขับเคลื่อนโดย GenQL ดังนั้นอย่าลืมตรวจสอบเอกสารของพวกเขา
# By default, you can have a file tree like the following:
.
└── sdk-gen/
└── config.js // ./sdk-gen/config.js
/**
* @type {import("@bsmnt/sdk-gen").Config}
*/
module . exports = {
endpoint : '' ,
headers : { }
}จากนั้นคุณสามารถเรียกใช้เครื่องกำเนิดไฟฟ้า:
yarn sdk-gen สิ่งนี้จะดูภายใน ./sdk-gen/ สำหรับไฟล์ config.js และสำหรับไฟล์ .{graphql,gql} ของคุณทั้งหมดภายใต้ไดเรกทอรีนั้น
หากคุณต้องการใช้ไดเรกทอรีที่กำหนดเอง (และไม่ใช่ค่าเริ่มต้นซึ่งก็คือ ./sdk-gen/ -gen/) คุณสามารถใช้อาร์กิวเมนต์ --dir
yarn sdk-gen --dir ./my-custom/directoryหลังจากเรียกใช้เครื่องกำเนิดไฟฟ้าคุณควรได้รับผลลัพธ์ต่อไปนี้:
.
└── sdk-gen/
├── config.js
├── documents.gql
├── generated/ # <- generated
│ ├── index.ts
│ └── graphql.schema.json
└── sdk.ts # <- generated ภายใน sdk.ts คุณจะมีการส่งออก bsmntSdk :
import config from './config'
import { createSdk } from './generated'
export const bsmntSdk = createSdk ( config )และนั่นคือทั้งหมด คุณควรจะสามารถใช้สิ่งนั้นเพื่อกด GraphQL API ของคุณในลักษณะที่ปลอดภัย
ประโยชน์เพิ่มเติมคือ SDK นี้ไม่ได้ขึ้นอยู่กับ graphql ไคลเอนต์ GraphQL จำนวนมากต้องการมันเป็นการพึ่งพาเพียร์ (เช่น graphql-request ) ซึ่งเพิ่ม KBS ที่สำคัญให้กับชุด
↳สำหรับวิธีมาตรฐานในการใช้สิ่งนี้กับ Shopify Storefront API ลองดูตัวอย่างของเราด้วย next.js + Shopify
@bsmnt/drop yarn add @bsmnt/dropแพ็คเกจนี้ส่งออก:
CountdownProvider : ผู้ให้บริการบริบท สำหรับ CountdownStoreuseCountdownStore : Hook ที่ใช้บริบท CountdownProvider และส่งคืน CountdownStorezeroPad : ยูทิลิตี้ เพื่อเพิ่มตัวเลขด้วย Zeroes หากต้องการใช้เพียงแค่ห่อ CountdownProvider ไม่ว่าคุณจะต้องการเพิ่มการนับถอยหลังของคุณ ตัวอย่างเช่น next.js:
// _app.tsx
import type { AppProps } from 'next/app'
import { CountdownProvider } from '@bsmnt/drop'
import { Countdown } from '../components/countdown'
export default function App ( { Component , pageProps } : AppProps ) {
return (
< CountdownProvider
endDate = { Date . now ( ) + 1000 * 5 } // set this to 5 seconds from now just to test
countdownChildren = { < Countdown /> }
exitDelay = { 1000 } // optional, just to give some time to animate the countdown before finally unmounting it
startDate = { Date . now ( ) } // optional, just if you need some kind of progress UI
>
< Component { ... pageProps } />
</ CountdownProvider >
)
}จากนั้นการนับถอยหลังของคุณอาจมีลักษณะเช่น:
import { useCountdownStore } from '@bsmnt/drop'
export const Countdown = ( ) => {
const humanTimeRemaining = useCountdownStore ( ) (
( state ) => state . humanTimeRemaining // keep in mind this is zustand, so you can slice this store
)
return (
< div >
< h1 > Countdown </ h1 >
< ul >
< li > Days: { humanTimeRemaining . days } </ li >
< li > Hours: { humanTimeRemaining . hours } </ li >
< li > Minutes: { humanTimeRemaining . minutes } </ li >
< li > Seconds: { humanTimeRemaining . seconds } </ li >
</ ul >
</ div >
)
} หากคุณแสดงผล humanTimeRemaining.seconds มีโอกาสสูงที่เซิร์ฟเวอร์ของคุณจะแสดงบางสิ่งที่แตกต่างจากไคลเอนต์ของคุณเนื่องจากค่านั้นจะเปลี่ยนไปในแต่ละวินาที
ในกรณีส่วนใหญ่คุณสามารถ suppressHydrationWarning อย่างปลอดภัย (ดูปัญหา #21 สำหรับข้อมูลเพิ่มเติม):
import { useCountdownStore } from '@bsmnt/drop'
export const Countdown = ( ) => {
const humanTimeRemaining = useCountdownStore ( ) (
( state ) => state . humanTimeRemaining // keep in mind this is zustand, so you can slice this store
)
return (
< div >
< h1 > Countdown </ h1 >
< ul >
< li suppressHydrationWarning > Days: { humanTimeRemaining . days } </ li >
< li suppressHydrationWarning > Hours: { humanTimeRemaining . hours } </ li >
< li suppressHydrationWarning > Minutes: { humanTimeRemaining . minutes } </ li >
< li suppressHydrationWarning > Seconds: { humanTimeRemaining . seconds } </ li >
</ ul >
</ div >
)
}หากคุณไม่ต้องการรับความเสี่ยงนั้นตัวเลือกที่ปลอดภัยยิ่งขึ้นจะรอจนกว่าแอปของคุณจะให้ความชุ่มชื้นก่อนที่จะแสดงเวลาเรียลไทม์ที่เหลืออยู่:
import { useEffect , useState } from 'react'
import { useCountdownStore } from '@bsmnt/drop'
const Countdown = ( ) => {
const humanTimeRemaining = useCountdownStore ( ) (
( state ) => state . humanTimeRemaining // keep in mind this is zustand, so you can slice this store
)
const [ hasRenderedOnce , setHasRenderedOnce ] = useState ( false )
useEffect ( ( ) => {
setHasRenderedOnce ( true )
} , [ ] )
return (
< div >
< h1 > Countdown </ h1 >
< ul >
< li > Days: { humanTimeRemaining . days } </ li >
< li > Hours: { humanTimeRemaining . hours } </ li >
< li > Minutes: { hasRenderedOnce ? humanTimeRemaining . minutes : '59' } </ li >
< li > Seconds: { hasRenderedOnce ? humanTimeRemaining . seconds : '59' } </ li >
</ ul >
</ div >
)
} ตัวอย่างบางส่วนเพื่อให้คุณเริ่มต้น:
localStorage ยินดีต้อนรับคำขอดึง ยินดีต้อนรับปัญหา สำหรับการเปลี่ยนแปลงครั้งใหญ่โปรดเปิดปัญหาก่อนเพื่อหารือเกี่ยวกับสิ่งที่คุณต้องการเปลี่ยนแปลง
มิกซ์