本系列文以製作專案為主軸,紀錄小弟學習React以及GrahQL的過程。主要是記下重點步驟以及我覺得需要記憶的部分,有覺得不明確的地方還請留言多多指教。
之前建立Server時只建立了最簡單的resolver,現在來增加多一點功能,順帶簡介resolver撰寫的要點。
首先再看一次要提供給resolver處理回傳的假資料:
const lists = [
{
id: 1,
title: "list1",
},
{
id: 2,
title: "list2",
},
];
const todos = [
{ id: 1, name: "todo1", listId: 1 },
{ id: 2, name: "todo2", listId: 1 },
{ id: 3, name: "todo3", listId: 2 },
{ id: 4, name: "todo4", listId: 2 },
{ id: 5, name: "todo5", listId: 2 },
];
在建立Apollo Server章節時有建立最基礎的Resolver範例,收到query lists指令後,回傳lists陣列資料。
const resolvers = {
Query: {
lists: () => lists,
},
};
雖然lists陣列中的各list資料包含所有List定義的scalar欄位(id,title),但Server會根據graphQL指令只回傳需求的欄位,不再需要自行編寫過濾多餘欄位的程式碼,只要將整包資料交給Server就好。
像是這個 graphQL 指令:
query {
lists {
id
}
}
收到的Data:
{
"data": {
"lists": [
{
"id": "1"
},
{
"id": "2"
}
]
}
}
由於lists陣列中不包含todos陣列資訊,所以光靠上面的resolver,是無法取得todos資訊的。
需要指明當Server收到 query lists todos時要如何對應,所以resolvers中需要定義 List todos欄位的對應方法:
const resolvers = {
Query: {
lists: () => lists,
},
List: {
todos(parent, args, context, info) {
return todos.filter((todo) => todo.listId === parent.id);
},
},
}
定義對應List todos欄位的查詢,要從todos陣列中撈出listId與 list.id相同的資料。
這裡List.todos resolver帶入了四個預設參數: parent, args, context, info,稍後會完整介紹一遍。先知道這裡只用上了parent參數,並且這個parent代表的是呼叫 todos的 List物件。
試試query效果:
query {
lists {
title
todos{
name
}
}
}
API回傳:
{
"data": {
"lists": [
{
"title": "list1",
"todos": [
{
"name": "todo1"
},
{
"name": "todo2"
}
]
},
{
"title": "list2",
"todos": [
{
"name": "todo3"
},
{
"name": "todo4"
},
{
"name": "todo5"
}
]
}
]
}
}
整個 graphQL指令的處理方式會像這樣:
這個過程被稱為Resolver Chain,理論上這條鍊可以一直接下去,只要欄位有對應的resolver。
為加強示範這個效果,將Todo形別加上參照的List欄位:
type Todo {
id: ID
name: String!
list: List #新增欄位
}
然後給他對應的resolver:
const resolvers = {
//...
Todo: {
list(parent) {
return lists.find((list) => list.id == parent.listId);
},
},
}
最後在 play ground試試這個指令
query {
lists {
todos{
list{
todos{
name
}
}
}
}
}
最後的結果太長這邊就不貼了。
小重點:
Resolver Chain最末端的Object型別(像上面的todos{name}),必須查詢至少一個scalar形別欄位。
不然你也不會需要這個Object型別。
若Resolver Chain有分支的話,就是各分支的末端要至少一個scalar形別。
在進行Query或Mutations時可以帶入參數,像是
type Query {
lists: [List]
todo(id: ID!): Todo //query todo時帶入id參數
}
對應的resolver可以從args參數中取出這些輸入參數,像是todo(id:ID!)對應的resolver:
todo: (parent, args) => {
return todos.find((todo) => todo.id == args.id);
},
可以從args.id取得客戶端輸入的id參數。
實際的指令會像這樣:
query{
todo(id:1){
id
}
}
如果制定了Input Type,像是:
type Mutation {
addTodo(params: addTodoParams): Todo
}
input addTodoParams {
id: ID!
name: String
}
Resolver會變成從Input type的物件中提取參數:
addTodo(parent, args) {
const { id, name } = args.params; //從args.params提取參數
const newTodo = { id, name };
return newTodo;
},
對應的請求指令:
mutation{
addTodo(params: {id:7 ,name:"todo7"}){
id
name
}
}
一個Resolver預設依序有四個參數:
parent :
在resolver chain上前一個resolver取得的資訊,以這個chain為例
List.todos()裡的parent就是Query.list()的回傳值,所以可以取得 list的id。
arg:
query或 mutation請求時輸入的參數,從上面的範例介紹應該很清楚了。
context:
所有resolver都能存取到同一個context,可以建立context存放像是身分驗證情報,或是資料庫連結等共用的資訊。
info:
包含許多關於目前執行的GraphQL請求的情報,像是經由的resolver chain path,具體怎麼利用還不清楚。