ทำความเข้าใจ GraphQl ฝั่งไคลเอ็นต์ด้วย Apollo-Client ในแอป React
เผยแพร่แล้ว: 2022-03-10ตามสถานะของ 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 ฝั่งไคลเอ็นต์คืออะไร
เมื่อวิศวกรส่วนหน้าสร้างส่วนประกอบ 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/
; ของคุณน่าจะเป็นอย่างอื่น
หากทุกอย่างทำงานได้ดี แอปของคุณควรมีลักษณะดังนี้:
คุณจะสังเกตเห็นว่าเราไม่มีสัตว์เลี้ยงที่จะแสดง นั่นเป็นเพราะเรายังไม่ได้สร้างฟังก์ชันดังกล่าว
หากคุณได้ติดตั้ง 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 อื่น
หากทุกอย่างเป็นไปด้วยดี คุณควรเห็นสิ่งนี้:
ข้อมูลการกลายพันธุ์
การกลายพันธุ์ข้อมูลหรือการสร้างข้อมูลใน 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