GraphQL dựa trên việc yêu cầu các trường cụ thể trên các đối tượng. Do đó, ta không thể nói thành công về các truy vấn mà không nói về Trường. Truy vấn là cấu trúc được khách hàng sử dụng để yêu cầu các trường cụ thể từ server .
Cho rằng GraphQL được cấu trúc để hiển thị tối ưu một điểm cuối cho tất cả các yêu cầu, các truy vấn được cấu trúc để yêu cầu các trường cụ thể và server được cấu trúc như nhau để phản hồi với các trường chính xác được yêu cầu.
Hãy xem xét tình huống mà khách hàng muốn yêu cầu các cầu thủ bóng đá từ một điểm cuối API. Truy vấn sẽ có cấu trúc như sau:
{ players { name } }
Đây là một truy vấn GraphQL điển hình. Xem xét kỹ hơn, các truy vấn được tạo thành từ hai phần riêng biệt:
root field
(người chơi): Đối tượng chứa tải trọng.payload
(tên): (Các) trường mà khách hàng yêu cầu.Đây là một phần thiết yếu của GraphQL vì server biết chính xác những trường mà client đang yêu cầu và luôn phản hồi với dữ liệu chính xác đó. Trong trường hợp truy vấn mẫu của ta , ta có thể có phản hồi sau:
{ "players": [ {"name": "Pogba"}, {"name": "Lukaku"}, {"name": "Rashford"}, {"name": "Marshal"} ] }
Các lĩnh vực name
trả về một kiểu String, trong trường hợp này, tên của các cầu thủ Manchester United. Tuy nhiên, ta không giới hạn chỉ Strings: ta có thể có các trường của tất cả các loại dữ liệu, giống như các lĩnh vực root players
trả về một mảng các bản ghi. Vui lòng tìm hiểu thêm về Hệ thống Loại GraphQL trong tài liệu chính thức của GraphQL.
Trong quá trình production , ta muốn làm nhiều hơn là chỉ trả lại tên. Ví dụ: trong truy vấn cuối cùng của ta , ta có thể xác định lại truy vấn để chọn một trình phát riêng lẻ từ danh sách và truy vấn để biết thêm dữ liệu về trình phát đó. Để có thể làm như vậy, ta cần một cách để xác định người chơi đó để ta có thể lấy thông tin chi tiết. Trong GraphQL, ta có thể đạt được điều này bằng Arguments.
Các truy vấn GraphQL cho phép ta truyền các đối số vào các trường truy vấn và các đối tượng truy vấn lồng nhau. Bạn có thể chuyển các đối số cho mọi trường và mọi đối tượng lồng nhau trong truy vấn của bạn để đào sâu thêm yêu cầu của bạn và thực hiện nhiều lần tìm nạp. Các đối số phục vụ cùng mục đích như các query parameters
truyền thống hoặc URL segments
trong API REST. Ta có thể chuyển chúng vào các trường truy vấn của bạn để chỉ định thêm cách server sẽ phản hồi yêu cầu của ta .
Quay lại tình huống trước đây của ta về việc tìm nạp chi tiết bộ dụng cụ của một cầu thủ cụ thể như shirt size
hoặc shirt size
shoe size,
trước tiên, ta sẽ phải chỉ định cầu thủ đó bằng cách chuyển vào một id
đối số để xác định người chơi từ danh sách người chơi và sau đó xác định các trường ta muốn trong tải trọng truy vấn:
{ player(id : "Pogba") { name, kit { shirtSize, bootSize } } }
Ở đây, ta đang yêu cầu các trường mong muốn trên cầu thủ Pogba
vì đối số id
mà ta đã chuyển vào truy vấn. Cũng giống như các trường, không có giới hạn về loại. Lập luận cũng có thể thuộc nhiều loại khác nhau. Kết quả của truy vấn trước đó với đối số id
sẽ giống như sau:
{ "player": { "name": "Pogba", "kit": [ { "shirtSize": "large", "shoeSize": "medium" } ] } }
Một lỗi có thể xảy ra ở đây là các truy vấn GraphQL trông gần như giống nhau cho cả các mục đơn lẻ và danh sách các mục. Trong những tình huống này, hãy nhớ rằng ta luôn biết điều gì sẽ xảy ra dựa trên những gì được xác định trong schemas .
Ngoài ra, các truy vấn GraphQL có tính tương tác, vì vậy bạn có thể thêm nhiều trường hơn vào đối tượng trường root tùy ý. Bằng cách đó, bạn, với quyền là khách hàng, có thể linh hoạt để tránh các chuyến đi khứ hồi và yêu cầu bao nhiêu dữ liệu bạn muốn trong một yêu cầu duy nhất.
Bây giờ, điều gì sẽ xảy ra nếu ta muốn tìm nạp các trường giống nhau cho hai người chơi, không chỉ một? Đó là nơi xuất hiện các alias .
Nếu bạn xem xét kỹ hơn ví dụ cuối cùng của ta , bạn sẽ nhận thấy rằng các trường đối tượng kết quả:
// result.... "player": { "name": "Pogba", "kit": [ { "shirtSize": "large", "shoeSize": "medium" } ] }
trùng với các trường truy vấn:
//query.... has matching fields with the result player(id : "Pogba") { name, kit { shirtSize, bootSize } }
nhưng không có đối số:
(id : "Pogba")
Bởi vì điều này, ta không thể truy vấn trực tiếp cho trường cùng player
với lập luận khác nhau. Đó là, ta không thể làm điều gì đó như thế này:
{ player(id : "Pogba") { name, kit { shirtSize, bootSize } } player(id : "Lukaku") { name, kit { shirtSize, bootSize } } }
Ta không thể làm điều này, nhưng những gì ta có thể làm là sử dụng alias . Họ cho phép ta đổi tên kết quả của một trường thành bất kỳ thứ gì ta muốn. Ví dụ của ta , để truy vấn chi tiết bộ dụng cụ của hai người chơi, ta sẽ xác định truy vấn của bạn như sau:
{ player1: player(id: "Pogba") { name, kit { shirtSize, shoeSize } } player2: player(id: "Lukaku") { name, kit { shirtSize, shoeSize } } }
Ở đây, hai player
lĩnh vực sẽ có mâu thuẫn, nhưng vì ta có thể alias họ tên khác nhau player1
và player2
, ta có thể có được cả hai kết quả trong một yêu cầu:
{ "data": { "player1": { "name": "Luke Skywalker", "kit": [ { "shirtSize": "large", "shoeSize": "medium" } ] }, "player2": { "name": "Lukaku", "kit": [ { "shirtSize": "extralarge", "shoeSize": "large" } ] } } }
Bây giờ sử dụng alias , ta đã truy vấn thành công cùng một trường với các đối số khác nhau và nhận được phản hồi mong đợi.
Lúc này, ta vẫn đang sử dụng cú pháp thao tác viết tắt mà ta không bắt buộc phải xác định rõ ràng tên hoặc kiểu thao tác. Trong quá trình production , cách tốt nhất là sử dụng tên và kiểu hoạt động để giúp làm cho cơ sở mã của bạn ít mơ hồ hơn. Nó cũng giúp gỡ lỗi truy vấn của bạn trong trường hợp có lỗi.
Cú pháp hoạt động bao gồm hai điều trung tâm:
operation type
có thể là truy vấn, đột biến hoặc đăng ký. Nó được sử dụng để mô tả loại hoạt động bạn dự định thực hiện.
Tên operation name
có thể là bất cứ thứ gì giúp bạn liên quan đến thao tác bạn đang cố gắng thực hiện.
Bây giờ, ta có thể viết lại ví dụ trước đó của ta và thêm một loại hoạt động và tên như sau:
query PlayerDetails{ player(id : "Pogba") { name, kit { shirtSize, bootSize } } }
Trong ví dụ này, query
là kiểu hoạt động và PlayerDetails
là tên hoạt động.
Lúc này, ta đã chuyển trực tiếp tất cả các đối số của bạn vào chuỗi truy vấn. Trong hầu hết các trường hợp, các đối số mà ta truyền vào là động. Ví dụ: giả sử trình phát mà khách hàng muốn biết thông tin chi tiết đến từ biểu mẫu nhập văn bản hoặc trình đơn thả xuống. Đối số mà ta truyền vào chuỗi truy vấn phải là đối số động và để làm được điều đó, ta cần sử dụng các biến. Do đó, các biến được sử dụng để tính giá trị động từ các truy vấn và chuyển chúng dưới dạng một từ điển riêng biệt.
Xem xét ví dụ cuối cùng của ta , nếu ta muốn làm cho trình phát động để các chi tiết của người chơi đã chọn được trả lại, ta sẽ phải lưu trữ giá trị ID của người chơi trong một biến và chuyển nó vào tên hoạt động và đối số truy vấn, như sau:
query PlayerDetails ($id: String){ player (id : $id) { name, kit { shirtSize, bootSize } } }
Ở đây, $title: String
là định nghĩa biến và title
là tên biến. Nó có tiền tố là $
theo sau là kiểu, trong trường hợp này là Chuỗi. Điều này nghĩa là ta có thể tránh nội suy các chuỗi theo cách thủ công để tạo các truy vấn động.
Nhìn vào truy vấn của ta , bạn sẽ nhận thấy rằng trường trình player
thực tế giống nhau cho cả hai trình phát:
name, kit { shirtSize, bootSize }
Để có hiệu quả hơn với truy vấn của ta , ta có thể extract mảnh này của logic chia sẻ vào một đoạn tái sử dụng trên player
lĩnh vực, như vậy:
{ player1: player(id: "Pogba") { ...playerKit } player2: player(id: "Lukaku") { ...playerKit } } fragment playerKit on player { name, kit { shirtSize, shoeSize } }
Khả năng extract một đoạn mã được chia sẻ và sử dụng lại trên nhiều trường là một khái niệm quan trọng giúp các nhà phát triển tránh lặp lại chính mình trong quá trình phát triển và thậm chí trong quá trình production . Nếu bạn đang làm việc trên một cơ sở mã phân lớp sâu, bạn sẽ thấy việc sử dụng lại mã khá hữu ích và đúng lúc hơn là lặp lại bản thân của bạn trên nhiều thành phần.
Các chỉ thị GraphQL cung cấp cho ta một cách để thông báo cho server biết nên include
hay skip
một trường cụ thể khi phản hồi truy vấn của ta . Có hai chỉ thị tích hợp trong GraphQL giúp ta đạt được điều đó:
@skip
bỏ qua một trường cụ thể khi giá trị được truyền vào trường đó là true.@include
để bao gồm một trường cụ thể khi giá trị được truyền vào nó là true. Hãy thêm một lệnh Boolean
và bỏ qua nó trên server với lệnh @skip
:
query PlayerDetails ($playerShirtDirective: Boolean!){ player(id: "Pogba") { name, kit { shirtSize @skip(if: $playerShirtDirective) bootSize } } }
Điều tiếp theo ta sẽ làm là tạo chỉ thị playerShirtDirective
trong các biến truy vấn của ta và đặt nó thành true:
// Query Variables { "itemIdDirective": true }
Thao tác này bây giờ sẽ trả về trọng tải mà không có shirtSize
:
"player": { "name": "Pogba", "kit": [ { "shoeSize": "medium" } ] }
Ta có thể đảo ngược tình huống này bằng cách sử dụng lệnh @include
. Nó hoạt động ngược lại với chỉ thị @skip
. Bạn có thể sử dụng nó để đảo ngược hành động bỏ qua này trên server bằng cách thay thế lệnh @skip
bằng @include
trong truy vấn.
Trong bài viết này, ta đã xem xét chi tiết về các truy vấn GraphQL. Nếu bạn muốn tìm hiểu thêm về GraphQL, hãy xem tài liệu chính thức của GraphQL . Nếu bạn muốn thử tạo một dự án với GraphQL, hãy thử Hướng dẫn cách xây dựng và triển khai server GraphQL với Node.js và MongoDB trên Ubuntu 18.04 của ta .