ทำความเข้าใจ GraphQl ฝั่งไคลเอ็นต์ด้วย Apollo-Client ในแอป React

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ เคยพยายามโต้ตอบกับเซิร์ฟเวอร์ GraphQL ในแอปพลิเคชันฝั่งไคลเอ็นต์และรู้สึกอยากยอมแพ้ก่อนจะไปไหนมาไหนอีกไหม เคยปฏิเสธคำเชิญเข้าร่วมฐานรหัสที่ต้องทำงานกับ GraphQL API เพราะคุณไม่มีความคิดใช่หรือไม่? เคยรู้สึกเหมือนเป็นวิศวกรส่วนหน้าเพียงคนเดียวที่ไม่ได้เรียนรู้วิธีใช้ GraphQL API หรือไม่ หากคุณตอบว่าใช่สำหรับคำถามเหล่านี้ บทแนะนำนี้เหมาะสำหรับคุณ เราจะพิจารณาข้อมูลพื้นฐานบางประการของ GraphQL และ Apollo Client อย่างละเอียดยิ่งขึ้น ตลอดจนวิธีทำงานกับทั้งสองอย่าง ในตอนท้าย เราจะสร้างแอพร้านขายสัตว์เลี้ยงที่ใช้ Apollo Client จากนั้น คุณสามารถสร้างโครงการต่อไปได้

ตามสถานะของ JavaScript 2019 นักพัฒนา 38.7% ต้องการใช้ GraphQL ในขณะที่ 50.8% ของนักพัฒนาต้องการเรียนรู้ GraphQL

เนื่องจากเป็นภาษาที่ใช้ในการสืบค้น GraphQL ช่วยลดความซับซ้อนของขั้นตอนการทำงานในการสร้างแอปพลิเคชันไคลเอนต์ มันขจัดความซับซ้อนของการจัดการปลายทาง API ในแอปฝั่งไคลเอ็นต์เพราะจะเปิดเผยจุดปลาย HTTP เดียวเพื่อดึงข้อมูลที่จำเป็น ดังนั้นจึงขจัดการดึงข้อมูลมากเกินไปและการดึงข้อมูลน้อยเกินไป เช่นในกรณีของ REST

แต่ GraphQL เป็นเพียงภาษาที่ใช้ค้นหา เพื่อให้ใช้งานได้ง่าย เราจำเป็นต้องมีแท่นยกของหนักสำหรับเรา หนึ่งในแพลตฟอร์มดังกล่าวคืออพอลโล

แพลตฟอร์ม Apollo คือการใช้งาน GraphQL ที่ถ่ายโอนข้อมูลระหว่างคลาวด์ (เซิร์ฟเวอร์) ไปยัง UI ของแอปของคุณ เมื่อคุณใช้ Apollo Client ตรรกะทั้งหมดสำหรับการดึงข้อมูล การติดตาม การโหลด และการอัปเดต UI จะถูกห่อหุ้มด้วย useQuery hook (เช่นในกรณีของ React) ดังนั้นการดึงข้อมูลจึงเป็นการประกาศ นอกจากนี้ยังมีแคชการกำหนดค่าเป็นศูนย์ เพียงแค่ตั้งค่า Apollo Client ในแอปของคุณ คุณก็จะได้แคชอัจฉริยะที่พร้อมใช้งานทันที โดยไม่ต้องมีการกำหนดค่าเพิ่มเติม

Apollo Client ยังสามารถทำงานร่วมกับเฟรมเวิร์กอื่นๆ เช่น Angular, Vue.js และ React

หมายเหตุ : บทช่วยสอนนี้จะเป็นประโยชน์สำหรับผู้ที่เคยทำงานกับ RESTful หรือ API รูปแบบอื่นๆ ในอดีตทางฝั่งไคลเอ็นต์ และต้องการดูว่า GraphQL คุ้มค่าหรือไม่ ซึ่งหมายความว่าคุณควรเคยทำงานกับ API มาก่อน เมื่อนั้นคุณจะสามารถเข้าใจได้ว่า GraphQL มีประโยชน์ต่อคุณอย่างไร แม้ว่าเราจะกล่าวถึงพื้นฐานบางประการของ GraphQL และ Apollo Client ความรู้ที่ดีเกี่ยวกับ JavaScript และ React Hooks จะมีประโยชน์

ข้อมูลพื้นฐานเกี่ยวกับ GraphQL

บทความนี้ไม่ใช่คำ แนะนำที่สมบูรณ์เกี่ยว กับ GraphQL แต่เราจะกำหนดข้อตกลงสองสามข้อก่อนดำเนินการต่อ

GraphQL คืออะไร?

GraphQL เป็นข้อกำหนดที่อธิบายภาษาการสืบค้นที่เปิดเผยซึ่งลูกค้าของคุณสามารถใช้เพื่อขอข้อมูลที่แน่นอนที่พวกเขาต้องการจาก API ซึ่งทำได้โดยการสร้างสคีมาประเภทที่แข็งแกร่งสำหรับ API ของคุณ โดยมีความยืดหยุ่นสูงสุด นอกจากนี้ยังช่วยให้แน่ใจว่า API จะแก้ไขข้อมูลและการสืบค้นของไคลเอ็นต์ได้รับการตรวจสอบกับสคีมา คำจำกัดความนี้หมายความว่า GraphQL มีข้อกำหนดบางอย่างที่ทำให้เป็นภาษาการสืบค้นแบบเปิดเผย โดยมี API ที่พิมพ์แบบคงที่ (สร้างขึ้นจาก Typescript) และทำให้ลูกค้าสามารถใช้ระบบประเภทเหล่านั้นเพื่อขอข้อมูลที่แน่นอนจาก API ได้ .

ดังนั้น หากเราสร้างบางประเภทที่มีบางฟิลด์ในนั้น จากฝั่งไคลเอ็นต์ เราอาจกล่าวได้ว่า “ให้ข้อมูลนี้แก่เราด้วยฟิลด์ที่แน่นอนเหล่านี้” จากนั้น API จะตอบสนองด้วยรูปร่างที่แน่นอนนั้น เหมือนกับว่าเราใช้ระบบประเภทในภาษาที่พิมพ์อย่างชัดเจน คุณสามารถเรียนรู้เพิ่มเติมในบทความ typescript ของฉัน

มาดูข้อตกลงบางประการของ GraphQl ที่จะช่วยเราได้เมื่อเราทำต่อไป

พื้นฐาน

  • ปฏิบัติการ
    ใน GraphQL ทุกการกระทำที่เรียกว่าการดำเนินการ มีการดำเนินการบางอย่าง กล่าวคือ:
    • แบบสอบถาม
      การดำเนินการนี้เกี่ยวข้องกับการดึงข้อมูลจากเซิร์ฟเวอร์ คุณยังสามารถเรียกมันว่าการดึงข้อมูลแบบอ่านอย่างเดียว
    • การกลายพันธุ์
      การดำเนินการนี้เกี่ยวข้องกับการสร้าง อัปเดต และลบข้อมูลออกจากเซิร์ฟเวอร์ เรียกว่าการดำเนินการ CUD (สร้าง อัปเดต และลบ)
    • การสมัครรับข้อมูล
      การดำเนินการใน GraphQL นี้เกี่ยวข้องกับการส่งข้อมูลจากเซิร์ฟเวอร์ไปยังไคลเอ็นต์เมื่อมีเหตุการณ์เฉพาะเกิดขึ้น มักจะใช้กับ WebSockets

ในบทความนี้ เราจะพูดถึงเฉพาะการดำเนินการค้นหาและการกลายพันธุ์เท่านั้น

  • ชื่อ ปฏิบัติการ
    มี ชื่อ เฉพาะ สำหรับการดำเนินการสืบค้นข้อมูลและการกลายพันธุ์ในฝั่งไคลเอ็นต์ของคุณ
  • ตัวแปร และอาร์กิวเมนต์
    การดำเนินการสามารถกำหนดอาร์กิวเมนต์ได้ เช่นเดียวกับฟังก์ชันในภาษาโปรแกรมส่วนใหญ่ ตัวแปรเหล่านั้นสามารถส่งผ่านไปยังคิวรีหรือการเรียกการกลายพันธุ์ภายในการดำเนินการเป็นอาร์กิวเมนต์ได้ ตัวแปรคาดว่าจะได้รับในขณะรันไทม์ระหว่างการดำเนินการจากไคลเอ็นต์ของคุณ
  • นามแฝง
    นี่เป็นแบบแผนใน GraphQL ฝั่งไคลเอ็นต์ที่เกี่ยวข้องกับการเปลี่ยนชื่อฟิลด์แบบละเอียดหรือแบบคลุมเครือด้วยชื่อฟิลด์ที่อ่านง่ายสำหรับ UI ต้องใช้นามแฝงในกรณีการใช้งานที่คุณไม่ต้องการให้มีชื่อฟิลด์ที่ขัดแย้งกัน
ข้อตกลงพื้นฐานของ GraphQL
ข้อตกลงพื้นฐานของ GraphQL (ตัวอย่างขนาดใหญ่)
เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

GraphQL ฝั่งไคลเอ็นต์คืออะไร

เมื่อวิศวกรส่วนหน้าสร้างส่วนประกอบ UI โดยใช้เฟรมเวิร์กใดๆ เช่น Vue.js หรือ (ในกรณีของเรา) React ส่วนประกอบเหล่านั้นจะถูกสร้างแบบจำลองและออกแบบจากรูปแบบบางอย่างบนไคลเอนต์เพื่อให้เหมาะกับข้อมูลที่จะดึงมาจากเซิร์ฟเวอร์

ปัญหาที่พบบ่อยที่สุดประการหนึ่งของ RESTful API คือการดึงข้อมูลมากเกินไปและการดึงข้อมูลน้อยเกินไป สิ่งนี้เกิดขึ้นเนื่องจากวิธีเดียวสำหรับไคลเอนต์ในการดาวน์โหลดข้อมูลคือการกดปุ่มปลายทางที่ส่งคืนโครงสร้างข้อมูล ที่คง ที่ การดึงข้อมูลมากเกินไป ในบริบทนี้หมายความว่าไคลเอ็นต์ดาวน์โหลดข้อมูลมากกว่าที่แอปต้องการ

ในทางกลับกัน ใน GraphQL คุณเพียงแค่ส่งแบบสอบถามเดียวไปยังเซิร์ฟเวอร์ GraphQL ที่มีข้อมูลที่จำเป็น จากนั้นเซิร์ฟเวอร์จะตอบสนองด้วยออบเจ็กต์ JSON ของข้อมูลที่คุณร้องขอ ดังนั้นจึงไม่มีการดึงข้อมูลมากเกินไป Sebastian Eschweiler อธิบายความแตกต่างระหว่าง RESTful API และ GraphQL

GraphQL ฝั่งไคลเอ็นต์เป็นโครงสร้างพื้นฐานฝั่งไคลเอ็นต์ที่เชื่อมต่อกับข้อมูลจากเซิร์ฟเวอร์ GraphQL เพื่อทำหน้าที่ต่อไปนี้:

  • มันจัดการข้อมูลโดยการส่งแบบสอบถามและการเปลี่ยนแปลงข้อมูลโดยที่คุณไม่ต้องสร้างคำขอ HTTP ทั้งหมดด้วยตัวเอง คุณสามารถใช้ข้อมูลประปาน้อยลงและมีเวลามากขึ้นในการสร้างแอปพลิเคชันจริง
  • มันจัดการความซับซ้อนของแคชสำหรับคุณ ดังนั้น คุณสามารถจัดเก็บและเรียกข้อมูลที่ดึงมาจากเซิร์ฟเวอร์ โดยไม่มีการแทรกแซงจากบุคคลที่สาม และหลีกเลี่ยงการดึงทรัพยากรที่ซ้ำกันได้อย่างง่ายดาย ดังนั้นจึงระบุเมื่อทรัพยากรทั้งสองเหมือนกัน ซึ่งเหมาะสำหรับแอปที่ซับซ้อน
  • มันทำให้ UI ของคุณสอดคล้องกับ Optimistic UI ซึ่งเป็นแบบแผนที่จำลองผลลัพธ์ของการกลายพันธุ์ (เช่น ข้อมูลที่สร้างขึ้น) และอัปเดต UI ก่อนได้รับการตอบกลับจากเซิร์ฟเวอร์ เมื่อได้รับการตอบสนองจากเซิร์ฟเวอร์ ผลลัพธ์ในแง่ดีจะถูกโยนทิ้งและแทนที่ด้วยผลลัพธ์จริง

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ GraphQL ฝั่งไคลเอ็นต์ โปรดใช้เวลาหนึ่งชั่วโมงกับ cocreator ของ GraphQL และคนอื่นๆ ที่น่าสนใจใน GraphQL Radio

Apollo Client คืออะไร?

Apollo Client เป็นไคลเอนต์ GraphQL ที่ขับเคลื่อนโดยชุมชนที่ทำงานร่วมกันได้ ยืดหยุ่นเป็นพิเศษสำหรับ JavaScript และแพลตฟอร์มดั้งเดิม คุณลักษณะที่น่าประทับใจ ได้แก่ เครื่องมือการจัดการสถานะที่มีประสิทธิภาพ (Apollo Link) ระบบแคชที่ไม่มีการกำหนดค่า วิธีการดึงข้อมูลแบบเปิดเผย การแบ่งหน้าที่ใช้งานง่าย และ Optimistic UI สำหรับแอปพลิเคชันฝั่งไคลเอ็นต์ของคุณ

Apollo Client ไม่เพียงแต่เก็บสถานะจากข้อมูลที่ดึงมาจากเซิร์ฟเวอร์ แต่ยังรวมถึงสถานะที่สร้างไว้บนไคลเอนต์ของคุณ ดังนั้นจึงจัดการสถานะสำหรับทั้งข้อมูล API และข้อมูลในเครื่อง

สิ่งสำคัญที่ควรทราบคือ คุณสามารถใช้ Apollo Client ควบคู่ไปกับเครื่องมือการจัดการสถานะอื่นๆ เช่น Redux ได้โดยไม่มีข้อขัดแย้ง นอกจากนี้ยังสามารถโยกย้ายการจัดการสถานะของคุณจาก Redux ไปยัง Apollo Client (ซึ่งอยู่นอกเหนือขอบเขตของบทความนี้) ท้ายที่สุด จุดประสงค์หลักของ Apollo Client คือการช่วยให้วิศวกรสามารถสืบค้นข้อมูลใน API ได้อย่างราบรื่น

คุณสมบัติของ Apollo Client

Apollo Client ชนะใจวิศวกรและบริษัทต่างๆ มากมาย เนื่องจากมีคุณสมบัติที่เป็นประโยชน์อย่างยิ่งที่ทำให้การสร้างแอปพลิเคชันที่ทนทานและทันสมัยเป็นเรื่องง่าย คุณสมบัติดังต่อไปนี้มาอบใน:

  • เก็บเอาไว้
    Apollo Client รองรับการแคชได้ทันที
  • UI ในแง่ดี
    Apollo Client ได้รับการสนับสนุนที่ยอดเยี่ยมสำหรับ Optimistic UI มันเกี่ยวข้องกับการแสดงสถานะสุดท้ายของการดำเนินการ (การกลายพันธุ์) ชั่วคราวในขณะที่ดำเนินการอยู่ เมื่อดำเนินการเสร็จสิ้น ข้อมูลจริงจะแทนที่ข้อมูลในแง่ดี
  • การแบ่งหน้า
    Apollo Client มีฟังก์ชันการทำงานในตัวที่ทำให้ง่ายต่อการใช้การแบ่งหน้าในแอปพลิเคชันของคุณ จะดูแลปัญหาทางเทคนิคส่วนใหญ่ในการดึงรายการข้อมูล ไม่ว่าจะในแพตช์หรือในครั้งเดียว โดยใช้ฟังก์ชัน fetchMore ซึ่งมาพร้อมกับ useQuery hook

ในบทความนี้ เราจะพิจารณาคุณลักษณะเหล่านี้บางส่วน

ทฤษฎีพอได้ คาดเข็มขัดนิรภัยให้แน่นแล้วหยิบกาแฟสักแก้วไปทานกับแพนเค้กเพราะมือเราสกปรก

การสร้างเว็บแอปของเรา

โปรเจ็กต์นี้ได้รับแรงบันดาลใจจาก Scott Moss

เราจะสร้างเว็บแอปร้านขายสัตว์เลี้ยงแบบง่ายๆ ซึ่งมีคุณสมบัติดังนี้:

  • ดึงสัตว์เลี้ยงของเราจากฝั่งเซิร์ฟเวอร์
  • การสร้างสัตว์เลี้ยง (ซึ่งเกี่ยวข้องกับการสร้างชื่อ ประเภทสัตว์เลี้ยง และภาพ)
  • ใช้ Optimistic UI;
  • ใช้การแบ่งหน้าเพื่อแบ่งกลุ่มข้อมูลของเรา

ในการเริ่มต้น ให้โคลนที่เก็บเพื่อให้แน่ใจว่าสาขา starter คือสิ่งที่คุณได้โคลนไว้

เริ่มต้น

  • ติดตั้งส่วนขยาย Apollo Client Developer Tools สำหรับ Chrome
  • ใช้อินเตอร์เฟสบรรทัดคำสั่ง (CLI) นำทางไปยังไดเร็กทอรีของที่เก็บโคลน และรันคำสั่งเพื่อรับการพึ่งพาทั้งหมด: npm install
  • เรียกใช้คำสั่ง npm run app เพื่อเริ่มแอป
  • ขณะที่ยังอยู่ในโฟลเดอร์รูท ให้รันคำสั่ง npm run server การดำเนินการนี้จะเริ่มต้นเซิร์ฟเวอร์ส่วนหลังของเรา ซึ่งเราจะใช้เมื่อเราดำเนินการต่อไป

แอปควรเปิดขึ้นในพอร์ตที่กำหนดค่าไว้ ของฉันคือ https://localhost:1234/ ; ของคุณน่าจะเป็นอย่างอื่น

หากทุกอย่างทำงานได้ดี แอปของคุณควรมีลักษณะดังนี้:

โคลนสาขาเริ่มต้น UI
โคลน UI สาขาเริ่มต้น (ตัวอย่างขนาดใหญ่)

คุณจะสังเกตเห็นว่าเราไม่มีสัตว์เลี้ยงที่จะแสดง นั่นเป็นเพราะเรายังไม่ได้สร้างฟังก์ชันดังกล่าว

หากคุณได้ติดตั้ง Apollo Client Developer Tools อย่างถูกต้องแล้ว ให้เปิดเครื่องมือสำหรับนักพัฒนาและคลิกที่ไอคอนถาด คุณจะเห็น “Apollo” และบางสิ่งเช่นนี้:

Apollo Client Developer Tools
เครื่องมือนักพัฒนาไคลเอนต์ Apollo (ตัวอย่างขนาดใหญ่)

เช่นเดียวกับเครื่องมือสำหรับนักพัฒนา Redux และ React เราจะใช้ Apollo Client Developer Tools เพื่อเขียนและทดสอบการสืบค้นและการกลายพันธุ์ของเรา ส่วนขยายนี้มาพร้อมกับ GraphQL Playground

กำลังเรียกสัตว์เลี้ยง

มาเพิ่มฟังก์ชันที่ดึงสัตว์เลี้ยงเข้ามากันเถอะ ย้ายไปที่ client/src/client.js เราจะเขียน Apollo Client เชื่อมโยงไปยัง API ส่งออกเป็นไคลเอนต์เริ่มต้น และเขียนข้อความค้นหาใหม่

คัดลอกโค้ดต่อไปนี้แล้ววางใน client.js :

 import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' import { HttpLink } from 'apollo-link-http' const link = new HttpLink({ uri: 'https://localhost:4000/' }) const cache = new InMemoryCache() const client = new ApolloClient({ link, cache }) export default client

นี่คือคำอธิบายของสิ่งที่เกิดขึ้นด้านบน:

  • ApolloClient
    นี่จะเป็นฟังก์ชันที่รวมแอปของเราไว้ และด้วยเหตุนี้ อินเทอร์เฟซกับ HTTP แคชข้อมูล และอัปเดต UI
  • InMemoryCache
    นี่คือที่เก็บข้อมูลมาตรฐานใน Apollo Client ที่ช่วยจัดการแคชในแอปพลิเคชันของเรา
  • HttpLink
    นี่คืออินเทอร์เฟซเครือข่ายมาตรฐานสำหรับการแก้ไขโฟลว์การควบคุมของคำขอ GraphQL และดึงผลลัพธ์ GraphQL มันทำหน้าที่เป็นมิดเดิลแวร์ โดยดึงผลลัพธ์จากเซิร์ฟเวอร์ GraphQL ทุกครั้งที่มีการเริ่มลิงก์ นอกจากนี้ยังเป็นทางเลือกที่ดีสำหรับตัวเลือกอื่นๆ เช่น Axios และ window.fetch
  • เราประกาศตัวแปรลิงค์ที่กำหนดให้กับอินสแตนซ์ของ HttpLink ใช้คุณสมบัติ uri และค่ากับเซิร์ฟเวอร์ของเรา ซึ่งก็คือ https://localhost:4000/
  • ถัดไปคือตัวแปรแคชที่เก็บอินสแตนซ์ใหม่ของ InMemoryCache
  • ตัวแปรไคลเอนต์ยังใช้อินสแตนซ์ของ ApolloClient และตัด link และ cache
  • สุดท้ายนี้ เราส่งออก client เพื่อให้สามารถใช้งานได้ทั่วทั้งแอปพลิเคชัน

ก่อนที่เราจะได้เห็นการดำเนินการนี้ เราต้องตรวจสอบให้แน่ใจว่าแอปทั้งหมดของเราเปิดเผยต่อ Apollo และแอปของเราสามารถรับข้อมูลที่ดึงมาจากเซิร์ฟเวอร์และสามารถเปลี่ยนข้อมูลนั้นได้

เพื่อให้บรรลุสิ่งนี้ ไปที่ client/src/index.js :

 import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter } from 'react-router-dom' import { ApolloProvider } from '@apollo/react-hooks' import App from './components/App' import client from './client' import './index.css' const Root = () => ( <BrowserRouter>
 <ApolloProvider client={client}> <App /> </ApolloProvider>
 </BrowserRouter> ); ReactDOM.render(<Root />, document.getElementById('app')) if (module.hot) { module.hot.accept() }

ดังที่คุณจะสังเกตเห็นในโค้ดที่ไฮไลต์ เราได้รวมองค์ประกอบ App ไว้ใน ApolloProvider และส่งผ่านไคลเอ็นต์เป็นส่วนประกอบไปยัง client ApolloProvider คล้ายกับ Context.Provider ของ React มันรวมแอพ React ของคุณและวางไคลเอนต์ในบริบท ซึ่งช่วยให้คุณเข้าถึงได้จากทุกที่ในแผนผังองค์ประกอบของคุณ

ในการดึงสัตว์เลี้ยงของเราจากเซิร์ฟเวอร์ เราต้องเขียนข้อความค้นหาที่ร้องขอ ฟิลด์ ที่เราต้องการ ตรงไปที่ client/src/pages/Pets.js แล้วคัดลอกและวางโค้ดต่อไปนี้ลงไป:

 import React, {useState} from 'react' import gql from 'graphql-tag' import { useQuery, useMutation } from '@apollo/react-hooks' import PetsList from '../components/PetsList' import NewPetModal from '../components/NewPetModal' import Loader from '../components/Loader'

const GET_PETS = gql` query getPets { pets { id name type img } } `;

export default function Pets () { const [modal, setModal] = useState(false)
 const { loading, error, data } = useQuery(GET_PETS); if (loading) return <Loader />; if (error) return <p>An error occured!</p>;


 const onSubmit = input => { setModal(false) } if (modal) { return <NewPetModal onSubmit={onSubmit} onCancel={() => setModal(false)} /> } return ( <div className="page pets-page"> <section> <div className="row betwee-xs middle-xs"> <div className="col-xs-10"> <h1>Pets</h1> </div> <div className="col-xs-2"> <button onClick={() => setModal(true)}>new pet</button> </div> </div> </section> <section>
 <PetsList pets={data.pets}/>
 </section> </div> ) }

ด้วยรหัสไม่กี่บิต เราสามารถดึงสัตว์เลี้ยงจากเซิร์ฟเวอร์ได้

gql คืออะไร?

สิ่งสำคัญที่ควรทราบคือ การดำเนินการใน GraphQL โดยทั่วไปจะเป็นอ็อบเจ็กต์ JSON ที่เขียนด้วย graphql-tag และ backticks

แท็ก gql คือแท็กตามตัวอักษรของเทมเพลต JavaScript ที่แยกวิเคราะห์สตริงการสืบค้น GraphQL ลงใน GraphQL AST (แผนผังไวยากรณ์นามธรรม)

  • การดำเนินการสอบถาม
    ในการดึงสัตว์เลี้ยงของเราจากเซิร์ฟเวอร์ เราจำเป็นต้องดำเนินการค้นหา
    • เนื่องจากเรากำลังดำเนินการ query เราจึงต้องระบุ type ของการดำเนินการก่อนที่จะตั้งชื่อ
    • ชื่อของข้อความค้นหาของเราคือ GET_PETS เป็นการตั้งชื่อแบบแผนการตั้งชื่อของ GraphQL เพื่อใช้ camelCase สำหรับชื่อฟิลด์
    • ชื่อของฟิลด์ของเราคือ pets ดังนั้นเราจึงระบุฟิลด์ที่แน่นอนที่เราต้องการจากเซิร์ฟเวอร์ (id, name, type, img)
    • useQuery เป็น React hook ที่เป็นพื้นฐานสำหรับการดำเนินการค้นหาในแอปพลิเคชัน Apollo ในการดำเนินการสืบค้นข้อมูลในองค์ประกอบ React เราเรียก useQuery hook ซึ่งเริ่มนำเข้าจาก @apollo/react-hooks ต่อไป เราจะส่งต่อสตริงการสืบค้น GraphQL ซึ่งก็คือ GET_PETS ในกรณีของเรา
  • เมื่อองค์ประกอบของเราแสดงผล useQuery จะส่งคืนการตอบสนองของวัตถุจาก Apollo Client ที่มีคุณสมบัติการโหลด ข้อผิดพลาด และข้อมูล ดังนั้นพวกมันจึงถูกทำลาย เพื่อให้เราสามารถใช้มันเพื่อแสดงผล UI
  • useQuery นั้นยอดเยี่ยม เราไม่จำเป็นต้องรวม async-await มีการดูแลในเบื้องหลังแล้ว สวยเย็นไม่ได้หรือไม่
    • loading
      คุณสมบัตินี้ช่วยให้เราจัดการสถานะการโหลดของแอปพลิเคชัน ในกรณีของเรา เราจะส่งคืนส่วนประกอบ Loader ในขณะที่แอปพลิเคชันของเราโหลด ตามค่าเริ่มต้น การโหลดจะเป็น false
    • error
      ในกรณีที่เราใช้คุณสมบัตินี้เพื่อจัดการกับข้อผิดพลาดที่อาจเกิดขึ้น
    • data
      ข้อมูลนี้มีข้อมูลจริงของเราจากเซิร์ฟเวอร์
    • สุดท้ายนี้ ในองค์ประกอบ PetsList เราส่งอุปกรณ์ประกอบฉาก pets โดยมี data.pets เป็นค่าออบเจ็กต์

ณ จุดนี้ เราได้ทำการสอบถามเซิร์ฟเวอร์ของเราสำเร็จแล้ว

ในการเริ่มแอปพลิเคชันของเรา ให้เรียกใช้คำสั่งต่อไปนี้:

  • เริ่มแอปไคลเอ็นต์ รันคำสั่ง npm run app ใน CLI ของคุณ
  • เริ่มเซิร์ฟเวอร์ รันคำสั่ง npm run server ใน CLI อื่น
VScode CLI ถูกแบ่งพาร์ติชันเพื่อเริ่มต้นทั้งไคลเอนต์และเซิร์ฟเวอร์
VScode CLI ถูกแบ่งพาร์ติชันเพื่อเริ่มต้นทั้งไคลเอนต์และเซิร์ฟเวอร์ (ตัวอย่างขนาดใหญ่)

หากทุกอย่างเป็นไปด้วยดี คุณควรเห็นสิ่งนี้:

สัตว์เลี้ยงสอบถามจากเซิร์ฟเวอร์
สัตว์เลี้ยงสอบถามจากเซิร์ฟเวอร์

ข้อมูลการกลายพันธุ์

การกลายพันธุ์ข้อมูลหรือการสร้างข้อมูลใน Apollo Client เกือบจะเหมือนกับการสืบค้นข้อมูล โดยมีการเปลี่ยนแปลงเล็กน้อย

ยังอยู่ใน client/src/pages/Pets.js ให้คัดลอกและวางโค้ดที่ไฮไลต์:

 .... const GET_PETS = gql` query getPets { pets { id name type img } } `;

const NEW_PETS = gql` mutation CreateAPet($newPet: NewPetInput!) { addPet(input: $newPet) { id name type img } } `;

 const Pets = () => { const [modal, setModal] = useState(false) const { loading, error, data } = useQuery(GET_PETS);
 const [createPet, newPet] = useMutation(NEW_PETS);
 const onSubmit = input => { setModal(false)
 createPet({ variables: { newPet: input } }); } if (loading || newPet.loading) return <Loader />; if (error || newPet.error) return <p>An error occured</p>;
  
 if (modal) { return <NewPetModal onSubmit={onSubmit} onCancel={() => setModal(false)} /> } return ( <div className="page pets-page"> <section> <div className="row betwee-xs middle-xs"> <div className="col-xs-10"> <h1>Pets</h1> </div> <div className="col-xs-2"> <button onClick={() => setModal(true)}>new pet</button> </div> </div> </section> <section> <PetsList pets={data.pets}/> </section> </div> ) } export default Pets

ในการสร้างการกลายพันธุ์ เราจะดำเนินการตามขั้นตอนต่อไปนี้

1. mutation

ในการสร้าง อัปเดต หรือลบ เราจำเป็นต้องดำเนินการ mutation การดำเนินการ mutation มีชื่อ CreateAPet โดยมีหนึ่งอาร์กิวเมนต์ อาร์กิวเมนต์นี้มีตัวแปร $newPet โดยมีประเภทเป็น NewPetInput ที่ ! หมายความว่าจำเป็นต้องมีการดำเนินการ ดังนั้น GraphQL จะไม่ดำเนินการใดๆ เว้นแต่ว่าเราจะส่งตัวแปร newPet ที่มีประเภทเป็น NewPetInput

2. addPet

ฟังก์ชัน addPet ซึ่งอยู่ภายในการดำเนินการ mutation รับอาร์กิวเมนต์ของ input และตั้งค่าเป็นตัวแปร $newPet ของเรา ชุดฟิลด์ที่ระบุในฟังก์ชัน addPet ของเราต้องเท่ากับชุดฟิลด์ในแบบสอบถามของเรา ชุดฟิลด์ในการดำเนินงานของเราคือ:

  • id
  • name
  • type
  • img

3. useMutation

useMutation React hook เป็น API หลักสำหรับดำเนินการการกลายพันธุ์ในแอปพลิเคชัน Apollo เมื่อเราต้องการเปลี่ยนข้อมูล เราเรียก useMutation ในองค์ประกอบ React และส่งผ่านสตริง GraphQL (ในกรณีของเรา NEW_PETS )

เมื่อองค์ประกอบของเราแสดงผล useMutation มันจะส่งคืน tuple (นั่นคือชุดข้อมูลที่เรียงลำดับซึ่งประกอบเป็นบันทึก) ในอาร์เรย์ที่ประกอบด้วย:

  • ฟังก์ชัน mutate ที่เราสามารถเรียกใช้ได้ตลอดเวลาเพื่อดำเนินการกลายพันธุ์
  • วัตถุที่มีฟิลด์ที่แสดงสถานะปัจจุบันของการดำเนินการของการกลายพันธุ์

useMutation hook ถูกส่งผ่านสตริงการกลายพันธุ์ของ GraphQL (ซึ่งก็คือ NEW_PETS ในกรณีของเรา) เราทำลายโครงสร้าง tuple ซึ่งเป็นฟังก์ชัน ( createPet ) ที่จะเปลี่ยนข้อมูลและฟิลด์วัตถุ ( newPets )

4. createPet

ในฟังก์ชัน onSubmit ของเรา หลังจากสถานะ setModal ไม่นาน เราได้กำหนด createPet ของเรา ฟังก์ชันนี้รับ variable ที่มีคุณสมบัติอ็อบเจ็กต์ของค่าที่ตั้งเป็น { newPet: input } input แสดงถึงช่องป้อนข้อมูลต่างๆ ในแบบฟอร์มของเรา (เช่น ชื่อ ประเภท ฯลฯ)

เมื่อเสร็จแล้วผลลัพธ์ควรมีลักษณะดังนี้:

การกลายพันธุ์โดยไม่ต้องอัปเดตทันที
การกลายพันธุ์โดยไม่ต้องอัปเดตทันที

หากคุณสังเกต GIF อย่างใกล้ชิด คุณจะสังเกตเห็นว่าสัตว์เลี้ยงที่สร้างขึ้นของเราไม่ปรากฏขึ้นทันที เฉพาะเมื่อมีการรีเฟรชหน้าเท่านั้น อย่างไรก็ตาม มีการอัปเดตบนเซิร์ฟเวอร์แล้ว

คำถามใหญ่คือ ทำไมสัตว์เลี้ยงของเราไม่อัปเดตทันที ลองหาในหัวข้อถัดไป

การแคชใน Apollo Client

สาเหตุที่แอปของเราไม่อัปเดตโดยอัตโนมัติก็คือข้อมูลที่สร้างขึ้นใหม่ของเราไม่ตรงกับข้อมูลแคชใน Apollo Client ดังนั้นจึงมีข้อขัดแย้งเกี่ยวกับสิ่งที่จำเป็นต้องอัปเดตจากแคชอย่างแน่นอน

พูดง่ายๆ ก็คือ หากเราทำการกลายพันธุ์ที่อัปเดตหรือลบหลายรายการ (โหนด) เรามีหน้าที่รับผิดชอบในการอัปเดตการสืบค้นใด ๆ ที่อ้างอิงถึงโหนดนั้น เพื่อให้มันแก้ไขข้อมูลที่ แคช ของเราให้ตรงกับการแก้ไขที่การกลายพันธุ์ทำกับส่วน หลัง ของเรา ข้อมูล สิ้นสุด

การรักษาแคชในการซิงค์

มีสองสามวิธีในการทำให้แคชของเราซิงค์ทุกครั้งที่เราทำการกลายพันธุ์

อย่างแรกคือการดึงข้อความค้นหาที่ตรงกันหลังจากการกลายพันธุ์ โดยใช้คุณสมบัติของวัตถุ refetchQueries (วิธีที่ง่ายที่สุด)

หมายเหตุ: หากเราใช้วิธีนี้ มันจะใช้คุณสมบัติของอ็อบเจ็กต์ในฟังก์ชัน createPet ของเราที่เรียกว่า refetchQueries และจะมีอาร์เรย์ของอ็อบเจ็กต์ที่มีค่าของเคียวรี: refetchQueries: [{ query: GET_PETS }]

เนื่องจากเรามุ่งเน้นในส่วนนี้ไม่ใช่เพียงเพื่ออัปเดตสัตว์เลี้ยงที่เราสร้างขึ้นใน UI แต่เพื่อจัดการแคช เราจะไม่ใช้วิธีนี้

วิธีที่สองคือการใช้ฟังก์ชัน update ใน Apollo Client มีฟังก์ชันตัวช่วย update ที่ช่วยแก้ไขข้อมูลแคช เพื่อให้ซิงค์กับการปรับเปลี่ยนที่การกลายพันธุ์ทำกับข้อมูลแบ็คเอนด์ของเรา การใช้ฟังก์ชันนี้ เราสามารถอ่านและเขียนไปยังแคชได้

กำลังอัปเดตแคช

คัดลอกโค้ดที่ไฮไลต์ต่อไปนี้ และวางลงใน client/src/pages/Pets.js :

 ...... const Pets = () => { const [modal, setModal] = useState(false) const { loading, error, data } = useQuery(GET_PETS);
 const [createPet, newPet] = useMutation(NEW_PETS, { update(cache, { data: { addPet } }) { const data = cache.readQuery({ query: GET_PETS }); cache.writeQuery({ query: GET_PETS, data: { pets: [addPet, ...data.pets] }, }); }, } );
 .....

ฟังก์ชัน update ได้รับสองอาร์กิวเมนต์:

  • อาร์กิวเมนต์แรกคือแคชจาก Apollo Client
  • ประการที่สองคือการตอบสนองการกลายพันธุ์ที่แน่นอนจากเซิร์ฟเวอร์ เราทำลายคุณสมบัติ data และตั้งค่าเป็นการกลายพันธุ์ของเรา ( addPet )

ต่อไป ในการอัปเดตฟังก์ชัน เราต้องตรวจสอบว่าข้อความค้นหาใดต้องได้รับการอัปเดต (ในกรณีของเรา คือ แบบสอบถาม GET_PETS ) และอ่านแคช

ประการที่สอง เราต้องเขียน query ที่ถูกอ่าน เพื่อให้รู้ว่าเรากำลังจะอัปเดต เราทำได้โดยส่งผ่านอ็อบเจ็กต์ที่มีคุณสมบัติอ็อบเจกต์ query โดยตั้งค่าเป็นการดำเนินการ query ของเรา ( GET_PETS ) และคุณสมบัติ data ที่มีค่าเป็นวัตถุ pet และมีอาร์เรย์ของการกลายพันธุ์ของ addPet และสำเนาของ ข้อมูลสัตว์เลี้ยง

หากคุณทำตามขั้นตอนเหล่านี้อย่างระมัดระวัง คุณจะเห็นสัตว์เลี้ยงของคุณอัพเดทโดยอัตโนมัติเมื่อคุณสร้างพวกมัน มาดูการเปลี่ยนแปลงกัน:

สัตว์เลี้ยงอัพเดททันที
สัตว์เลี้ยงอัพเดททันที

UI ในแง่ดี

หลายคนเป็นแฟนตัวยงของรถตักและเครื่องปั่นด้าย ไม่มีอะไรผิดปกติกับการใช้ตัวโหลด มีกรณีการใช้งานที่สมบูรณ์แบบซึ่งตัวโหลดเป็นตัวเลือกที่ดีที่สุด ฉันได้เขียนเกี่ยวกับรถตักกับเครื่องปั่นด้ายและกรณีการใช้งานที่ดีที่สุดของพวกเขา

รถตักและสปินเนอร์มีบทบาทสำคัญในการออกแบบ UI และ UX แต่การมาถึงของ Optimistic UI ได้ขโมยสปอตไลท์ไป

UI ในแง่ดีคืออะไร?

Optimistic UI เป็นข้อตกลงที่จำลองผลลัพธ์ของการกลายพันธุ์ (ข้อมูลที่สร้างขึ้น) และอัปเดต UI ก่อนได้รับการตอบกลับจากเซิร์ฟเวอร์ เมื่อได้รับการตอบสนองจากเซิร์ฟเวอร์ ผลลัพธ์ในแง่ดีจะถูกโยนทิ้งและแทนที่ด้วยผลลัพธ์จริง

ในท้ายที่สุด UI ที่มองโลกในแง่ดีนั้นไม่มีอะไรมากไปกว่าวิธีจัดการประสิทธิภาพที่รับรู้และหลีกเลี่ยงสถานะการโหลด

Apollo Client มีวิธีการรวม Optimistic UI ที่น่าสนใจมาก มันทำให้เรามีเบ็ดง่าย ๆ ที่ช่วยให้เราสามารถเขียนแคชในเครื่องหลังจากการกลายพันธุ์ มาดูกันว่ามันทำงานอย่างไร!

ขั้นตอนที่ 1

ตรงไปที่ client/src/client.js และเพิ่มเฉพาะโค้ดที่ไฮไลต์

 import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' import { HttpLink } from 'apollo-link-http'
import { setContext } from 'apollo-link-context' import { ApolloLink } from 'apollo-link' const http = new HttpLink({ uri: "https://localhost:4000/" }); const delay = setContext( request => new Promise((success, fail) => { setTimeout(() => { success() }, 800) }) ) const link = ApolloLink.from([ delay, http ])
const cache = new InMemoryCache() const client = new ApolloClient({ link, cache }) export default client

ขั้นตอนแรกเกี่ยวข้องกับสิ่งต่อไปนี้:

  • เรานำเข้า setContext จาก apollo-link-context ฟังก์ชัน setContext ใช้ฟังก์ชันเรียกกลับและส่งคืนสัญญาที่มีการตั้งค่า setTimeout เป็น 800ms เพื่อสร้างการหน่วงเวลาเมื่อดำเนินการกลายพันธุ์
  • วิธี ApolloLink.from ช่วยให้มั่นใจได้ว่ากิจกรรมเครือข่ายที่แสดงถึงลิงก์ (API ของเรา) จาก HTTP นั้นล่าช้า

ขั้นตอนที่ 2

ขั้นตอนต่อไปคือการใช้เบ็ด Optimistic UI เลื่อนกลับไปที่ client/src/pages/Pets.js และเพิ่มเฉพาะโค้ดที่ไฮไลต์ด้านล่าง

 ..... const Pets = () => { const [modal, setModal] = useState(false) const { loading, error, data } = useQuery(GET_PETS); const [createPet, newPet] = useMutation(NEW_PETS, { update(cache, { data: { addPet } }) { const data = cache.readQuery({ query: GET_PETS }); cache.writeQuery({ query: GET_PETS, data: { pets: [addPet, ...data.pets] }, }); }, } ); const onSubmit = input => { setModal(false) createPet({ variables: { newPet: input },
 optimisticResponse: { __typename: 'Mutation', addPet: { __typename: 'Pet', id: Math.floor(Math.random() * 10000 + ''), name: input.name, type: input.type, img: 'https://via.placeholder.com/200' } }
 }); } .....

วัตถุ optimisticResponse จะใช้ถ้าเราต้องการให้ UI อัปเดตทันทีเมื่อเราสร้างสัตว์เลี้ยง แทนที่จะรอการตอบกลับของเซิร์ฟเวอร์

ข้อมูลโค้ดด้านบนมีดังต่อไปนี้:

  • __typename ถูกใส่โดย Apollo ในการสืบค้นเพื่อดึง type ของเอนทิตีที่สืบค้น Apollo Client ใช้ประเภทเหล่านั้นเพื่อสร้างคุณสมบัติ id (ซึ่งเป็นสัญลักษณ์) เพื่อวัตถุประสงค์ในการแคชใน apollo-cache ดังนั้น __typename จึงเป็นคุณสมบัติที่ถูกต้องของการตอบกลับแบบสอบถาม
  • การกลายพันธุ์ถูกกำหนดเป็น __typename ของ optimisticResponse
  • ตามที่กำหนดไว้ก่อนหน้านี้ ชื่อของการเปลี่ยนแปลงของเราคือ addPet และ __typename คือ Pet
  • ต่อไปเป็นฟิลด์ของการกลายพันธุ์ของเราที่เราต้องการให้การตอบสนองในแง่ดีอัปเดต:
    • id
      เนื่องจากเราไม่รู้ว่ารหัสจากเซิร์ฟเวอร์คืออะไร เราจึงสร้างรหัสขึ้นมาโดยใช้ Math.floor
    • name
      ค่านี้ถูกกำหนดเป็น input.name
    • type
      ค่าของประเภทคือ input.type
    • img
      เนื่องจากเซิร์ฟเวอร์ของเราสร้างภาพให้เรา เราจึงใช้ตัวยึดตำแหน่งเพื่อเลียนแบบภาพของเราจากเซิร์ฟเวอร์

นี่เป็นการเดินทางที่ยาวนาน หากคุณไปถึงจุดสิ้นสุด อย่าลังเลที่จะหยุดพักจากเก้าอี้ด้วยกาแฟสักถ้วย

มาดูผลลัพธ์ของเรากัน พื้นที่เก็บข้อมูลสนับสนุนสำหรับโปรเจ็กต์นี้อยู่ที่ GitHub โคลนและทดลองกับมัน

ผลลัพธ์สุดท้ายของแอพร้านขายสัตว์เลี้ยง
ผลลัพธ์สุดท้ายของแอพของเรา

บทสรุป

คุณสมบัติที่น่าทึ่งของ Apollo Client เช่น Optimistic UI และการแบ่งหน้า ทำให้การสร้างแอปฝั่งไคลเอ็นต์เป็นจริง

แม้ว่า Apollo Client จะทำงานได้ดีกับเฟรมเวิร์กอื่นๆ เช่น Vue.js และ Angular แต่นักพัฒนา React ก็มี Apollo Client Hooks ดังนั้นจึงอดไม่ได้ที่จะสนุกกับการสร้างแอปที่ยอดเยี่ยม

ในบทความนี้ เราแค่ขีดข่วนพื้นผิวเท่านั้น การเรียนรู้ Apollo Client ต้องการการฝึกฝนอย่างต่อเนื่อง ดังนั้น ไปข้างหน้าและโคลนที่เก็บ เพิ่มการแบ่งหน้า และลองใช้คุณสมบัติอื่นๆ ที่มีให้

โปรดแบ่งปันความคิดเห็นและประสบการณ์ของคุณในส่วนความคิดเห็นด้านล่าง นอกจากนี้เรายังสามารถพูดคุยเกี่ยวกับความคืบหน้าของคุณบน Twitter ไชโย!

อ้างอิง

  • “ Client-Side GraphQL In React”, สก็อตต์ มอสส์, Frontend Master
  • “เอกสาร” Apollo Client
  • “ UI ในแง่ดีพร้อมการตอบสนอง” Patryk Andrzejewski
  • “คำโกหกที่แท้จริงของอินเทอร์เฟซผู้ใช้ในแง่ดี”, Smashing Magazine