前端從零開始學習Graphql

来源:https://www.cnblogs.com/liuming666/archive/2019/07/14/11185554.html
-Advertisement-
Play Games

學習本姿勢需要電腦裝有node,vue-cli相關環境,以及要有node,express,koa,vue相關基礎 本文相關demo的github地址: 一 Graphql概述 它是什麼?從哪裡來?要乾什麼? 簡單地講,對於前端,它就是讓你舒舒服服發請求的 嚴格的說,它是一種api設計思想,用來取代r ...


學習本姿勢需要電腦裝有node,vue-cli相關環境,以及要有node,express,koa,vue相關基礎

本文相關demo的github地址:

node服務:https://github.com/liuming888/graphql_node_demo.git
vue項目:https://github.com/liuming888/graphql_vue_demo.git
					

一 Graphql概述

它是什麼?從哪裡來?要乾什麼?

簡單地講,對於前端,它就是讓你舒舒服服發請求的

嚴格的說,它是一種api設計思想,用來取代restful api的一種前端處於主導地位的api規範。它把前端所需要的api用類似圖數據結構(graph)的方式展現出來,讓前端很方便的獲取所需要的數據。

 

特點

需要什麼就獲取什麼數據

支持關係數據的查詢

API無需定義各種路由,完全數據驅動

無需管理API版本,一個版本持續演進

支持大部分主流開發語言和平臺

強大的配套開發工具

 

起源,restful api的問題

如果採用restful api的話,後端要持續維護api doc,但是實際場景是這樣的:

1.來了新需求,後端先評估和開發,後端弄得差不多,前端才開始,然後中間後端一頓猛改

2.由於多種原因,後端經常自己偷偷改掉傳參或者返回值,完了介面報錯,測試姐姐把前端叫過去一頓批,前端一臉懵圈,仔細檢查,發現問題,找後端撕X,這樣一個迴圈非常影響開發效率。

gql出現

由於上面這一堆的問題,facebook公司2012年內部實踐了GraphQL,15年剛開源的時候引起了很多大公司和社區關註,落地了很多規範和框架。需要瞭解詳細歷史可以看看底下的youtube視頻。

這種叫GraphQL的東西幫助人們經過一系列定義和規範,可以發送gql請求非常方便的拿到想要的數據,甚至還可以修改數據,而不用後臺的配合,而且一旦Schema確定(資料庫那邊定義好),前後端就可以快速並行開發,例如下圖獲得某個用戶的信息,我想要這個用戶的什麼屬性就寫什麼,graphiQl工具可以進行完整的詳細的提示,請求主體簡單明瞭

    query{
          student{
            id
            name
            age
          }
          course{
            id
            title
          }
        }

gql理想使用場景

資料庫建好模型,前後端可以同步開始開發需求,前端只有少數需要依賴後端介面,前端開發過程中可以方便的拿到任何想要的數據,從而節省大量聯調介面的時間,迅速的完成一個項目。

實現原理

gql的實現挺複雜的,代碼有點難懂,不過原理說起來比較簡單易懂

var { graphql, buildSchema } = require('graphql');

 

// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
type Query {
    hello: String
  }
`);

 

// The root provides a resolver functionfor each API endpoint
var root = {
  hello: () => {
return'Hello world!';
  },
};

 

// Run the GraphQL query '{ hello }' and print out the response
graphql(schema, '{ hello }', root).then((response) => {
  console.log(response);
});

如上就是一個最簡單的node版的gql伺服器 gql把定義好的schema拿到,用root之類的resolve解析器去解析發送來的'{ hello }'請求,然後返回給相應的json值。上面代碼列印出的response如下

{ 
    data: 
        { 
            hello: 'Hello world!'
        } 
}

當然,resolve逐層解析會有一些問題,如果resolve請求資料庫,就要用到DataLoader

DataLoader是gql伺服器性能的關鍵一環,也是gql社區的主要推動完善方向,就像react裡面的shouldComponentUpdate一樣制約著gql伺服器的性能。

DataLoader 能讓我們從資料庫讀取數據並讓數據能被 GraphQL 處理,我們使用 DataLoader,而不是直接通過 SQL 查詢從資料庫獲取數據,將 DataLoader 作為代理以減少我們實際需要發送給資料庫的 SQL 查詢。 DataLoader 使用批處理和緩存的組合來實現。如果同一個客戶端請求會造成多次請求資料庫,DataLoader 會整合這些問題並從資料庫批量拉取請求數據。DataLoader 會同時緩存這些數據,當有後續請求需要同樣資源時可以直接從緩存獲取到。

具體使用

通過服務端對請求的元數據的type進行嚴格的定義,我們只要在客戶端發送gql請求就能返回期望的相應類型的數據

下圖是請求格式,query【請求】,mutation【修改】和subscribe【訂閱】是三種api發送方式,query用的多一些,mutation相對傳統的restful來說不夠可靠和安全,subscribe類似websocket

query 
{
  schema {
    types {
      id
      name // 獲取根欄位名
      fields {
        id
        name // 獲取欄位名
      }
    }
  }
}
			

和restful比較的優缺點

優點

優點就是後端可以少招幾個寫介面的

前後端一起開發,節約工期

較少維護api文檔,節省精力

說了這些,其實單對於前端來說,幫助不算特別大

缺點和難推廣的地方

後端或者中間層把gql封裝相應業務對接資料庫是難點,需要高端人力

需要前端多少學一點類sql語句,不過大部分場景可以封裝好固定的sql語句

封裝gql不好會產生sql性能問題,三級嵌套聯查還有n+1的老問題又會冒出來,需要持續優化

前端排除bug需要一定的後端知識,前後端架構多少瞭解一些

..

 

二 hello word

簡易版的hello world

npm install graphql

 

然後使用 node hello.js 以運行 hello.js 中的代碼:

 

var { graphql, buildSchema } = require('graphql');

 

var schema = buildSchema(`

type Query {

hello: String

}

`);

 

var root = { hello: () => 'Hello world!' };

 

graphql(schema, '{ hello }', root).then((response) => {

console.log(response);

});

 

控制台列印出Hello world!

 

express版hello world

npm install express express-graphql graphql

 

然後使用 node server.js 以運行 server.js 中的代碼:

 

var express = require('express');

var graphqlHTTP = require('express-graphql');

var { buildSchema } = require('graphql');

 

var schema = buildSchema(`

type Query {

hello: String

}

`);

 

var root = { hello: () => 'Hello world!' };

 

var app = express();

app.use('/graphql', graphqlHTTP({

schema: schema,

rootValue: root,

graphiql: true, // 是否開啟調試模式(生產環境註意得關掉)

}));

app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'));

 

訪問http://localhost:4000/graphql

 

 

koa版hello world

npm install koa graphql koa-graphql koa-mount

 

 

然後使用 node server.js 以運行 server.js 中的代碼:

const Koa = require('koa');

const mount = require('koa-mount');

const { buildSchema } = require('graphql');

const graphqlHTTP = require('koa-graphql');

const app = new Koa();

 

const schema = buildSchema(`

type Query {

hello: String

}

`);

 

const root = {

hello: () => 'Hello world!',

};

 

app.use(

mount(

'/graphql',

graphqlHTTP({

schema: schema,

rootValue: root,

graphiql: true,

})

)

);

 

app.use(async ctx => {

ctx.body = 'Hello World';

});

 

app.listen(3000);

 

訪問 http://localhost:3000/graphql

 

 

三 進階

參考資料:用Node創建GraphQL API:代碼量少,性能高

 

基於express

代碼看 node-graphql-demo項目 然後yarn npm run dev

github地址: https://github.com/liuming888/graphql_node_demo.git master分支

創建 Project

我們現在來創建我們的 project。打開一個新的 terminal,執行以下命令,即可使用預設值創建 package.json 文件:

mkdir node-graphql-demo

cd node-graphql-demo

npm init -y

 

接下來,我們需要安裝以下依賴:

npm install graphql express express-graphql sqlite3 --save

這樣就會安裝 Express 框架,Node.js 上的 GraphQL 實現,Express 和 SQLite3 的 GraphQL 中間件。為簡單起見,我們使用 SQLite3 作為資料庫。

 

graphql 是一個支持庫,並且在我們這裡是一個必要的模塊

 

express 是一個簡潔而靈活的 node.js Web應用框架, 提供了一系列強大特性幫助你創建各種 Web 應用,和豐富的 HTTP 工具。

 

express-graphql GraphQL HTTP伺服器中間件

 

SQLite是一個進程內的庫,實現了自給自足的、無伺服器的、零配置的、事務性的 SQL 資料庫引擎。它是一個零配置的資料庫,這意味著與其他資料庫一樣,您不需要在系統中配置。就像其他資料庫,SQLite 引擎不是一個獨立的進程,可以按應用程式需求進行靜態或動態連接。SQLite 直接訪問其存儲文件

 

創建 GraphQL 伺服器

創建工程並引入基本依賴包之後,現在來創建 API 伺服器。在工程文件夾里,創建 index.js 文件,並引入下列內容:

const express = require('express');

const sqlite3 = require('sqlite3').verbose();

const graphql = require("graphql");

const ExpressGraphQL = require("express-graphql");

上面的代碼的目的是:為Express導入Express,SQLite3,GraphQL和GraphQL中間件。

 

接下來,添加下列代碼,在當前文件夾中創建一個 Express 應用程式和名為 my.db 的 SQLite 3 資料庫:

const app = express();

const database = new sqlite3.Database("./my.db");

 

然後,添加 createContactTable() 方法,在資料庫中創建 contacts 表並馬上調用函數:

const createContactTable = () => {

const query = `

CREATE TABLE IF NOT EXISTS contacts (

id integer PRIMARY KEY,

firstName text,

lastName text,

email text UNIQUE)`;

return database.run(query);

}

createContactTable();

我們創建了一個 SQL 表來存儲 contacts的基本信息。每個 contact 的基本信息包括:唯一的標識、名、姓和 email。

 

接下來,添加下列代碼來定義一個 GraphQL 類型:

const ContactType = new graphql.GraphQLObjectType({

name: "Contact",

fields: {

id: { type: graphql.GraphQLID },

firstName: { type: graphql.GraphQLString },

lastName: { type: graphql.GraphQLString },

email: { type: graphql.GraphQLString }

}

});

我們使用基本的內置 GraphQL 類型,如 GraphQLID 和 GraphQLString 來創建我們自定義類型,對應資料庫中的 contact。

相關鏈接:

GraphQLID: https://graphql.github.io/graphql-spec/draft/#sec-ID

GraphQLString: https://graphql.github.io/graphql-spec/draft/#sec-String

 

接著,定義查詢類型,如下所示:

var queryType = new graphql.GraphQLObjectType({

name: 'Query',

fields: {

contacts: {

type: graphql.GraphQLList(ContactType),

resolve: (root, args, context, info) => {

return new Promise((resolve, reject) => {

database.all("SELECT * FROM contacts;", function (err, rows) {

if (err) {

reject([]);

}

resolve(rows);

});

});

 

}

},

contact: {

type: ContactType,

args: {

id: {

type: new graphql.GraphQLNonNull(graphql.GraphQLID)

}

},

resolve: (root, {

id

}, context, info) => {

return new Promise((resolve, reject) => {

 

database.all("SELECT * FROM contacts WHERE id = (?);", [id], function (err, rows) {

if (err) {

reject(null);

}

resolve(rows[0]);

});

});

}

}

}

});

我們的查詢有兩個欄位: contacts,可以用來獲取資料庫中的所有 contacts,而 contact 則根據 id 獲取一個 contact 信息。 contact 欄位允許所需的 id 參數為 GraphQLID 類型。

每個欄位都有一個 type,用來說明返回數據的類型,args 定義期望從客戶端得到的參數, resolve 則定義了在獲取數據邏輯中實際使用的方法。

對於前兩個欄位, resolve() 方法是實際邏輯發生的地方—— 我們簡單調用 database.all() 和 database.run() 方法來執行正確的 SQL 查詢,以便從 SQLite 獲取數據,返回一個 Promise 來處理得到的數據。

我們可以從resolve()方法的第二個參數訪問任何傳遞的參數。

接下來,我們創建一個 mutation 類型,用於創建、更新和刪除操作: https://graphql.github.io/graphql-spec/draft/#sec-Mutation

var mutationType = new graphql.GraphQLObjectType({

name: 'Mutation',

fields: {

createContact: {

type: ContactType,

args: {

firstName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

lastName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

email: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

}

},

resolve: (root, {

firstName,

lastName,

email

}) => {

return new Promise((resolve, reject) => {

database.run('INSERT INTO contacts (firstName, lastName, email) VALUES (?,?,?);', [firstName, lastName, email], (err) => {

if (err) {

reject(null);

}

database.get("SELECT last_insert_rowid() as id", (err, row) => {

 

resolve({

id: row["id"],

firstName: firstName,

lastName: lastName,

email: email

});

});

});

})

 

}

},

updateContact: {

type: graphql.GraphQLString,

args: {

id: {

type: new graphql.GraphQLNonNull(graphql.GraphQLID)

},

firstName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

lastName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

email: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

}

},

resolve: (root, {

id,

firstName,

lastName,

email

}) => {

return new Promise((resolve, reject) => {

database.run('UPDATE contacts SET firstName = (?), lastName = (?), email = (?) WHERE id = (?);', [firstName, lastName, email, id], (err) => {

if (err) {

reject(err);

}

resolve(`Contact #${id} updated`);

 

});

})

}

},

deleteContact: {

type: graphql.GraphQLString,

args: {

id: {

type: new graphql.GraphQLNonNull(graphql.GraphQLID)

}

},

resolve: (root, {

id

}) => {

return new Promise((resolve, reject) => {

database.run('DELETE from contacts WHERE id =(?);', [id], (err) => {

if (err) {

reject(err);

}

resolve(`Contact #${id} deleted`);

 

});

})

 

}

}

}

});

我們的 mutation 類型有三個欄位:

createContact 創建 contacts;

updateContact 更新 contacts;

deleteContact 刪除 contacts.

所有的欄位都接受符合 args 屬性定義的參數,並由一個 resolve() 方法來獲取傳遞過來的參數,執行相應的 SQL 操作,並返回一個 Promise。

然後,創建 GraphQL schema: https://graphql.github.io/graphql-spec/draft/#sec-Schema

const schema = new graphql.GraphQLSchema({

query: queryType,

mutation: mutationType

});

GraphQL schema 是 GraphQL 的核心概念,它定義了連接到伺服器的客戶端可用的功能。我們傳遞已定義的 query 和 mutation 類型到 schema。

最後,掛載到 /graphql 端點,在 4000 埠運行 Express 伺服器:

app.use("/graphql", ExpressGraphQL({ schema: schema, graphiql: true}));

app.listen(4000, () => {

console.log("GraphQL server running at http://localhost:4000.");

});

保存 index.js 文件,返回到你的 terminal。運行下列命令,啟動伺服器:

node index.js

 

 

怎樣使用 GraphQL API

構建客戶端前,可以使用 GraphQL 介面來測試你的 API。

訪問網址 : http://localhost:4000/graphql ,並運行下列 mutation query:

mutation {

createContact(firstName: "Jon", lastName: "Snow", email: "[email protected]") {

id,

firstName,

lastName,

email

}

}

 

使用下列 mutation,可以更新 id 為 1 的 contact:

mutation {

updateContact(id: 1, firstName: "Aegon", lastName: "Targaryen", email: "[email protected]")

}

 

也可以使用下列 mutation 來刪除 id 為 1 的 contact:

mutation {

deleteContact(id: 1)

}

 

最後,使用如下 query,可以獲取資料庫中所有 contacts 信息:

query {

contacts {

id

firstName

lastName

email

}

}

 

使用下麵 query,可以獲取一個 contact 的信息:

query {

contact(id: 1) {

id

firstName

lastName

email

}

}

 

這裡,我們得到的是 id 為 1 的 contact 信息。

 

 

基於koa (和express類似)

 

代碼看 koa-demo項目 然後yarn npm run dev

github地址: https://github.com/liuming888/graphql_node_demo.git koa分支

 

 

創建 Project

我們現在來創建我們的 project。打開一個新的 terminal,執行以下命令,即可使用預設值創建 package.json 文件:

mkdir koa-demo

cd koa-demo

npm init -y

 

接下來,我們需要安裝以下依賴:

npm install graphql koa koa-graphql koa-mount sqlite3 --save

這樣就會安裝 koa庫,Node.js 上的 GraphQL 實現,koa 和 SQLite3 的 GraphQL 中間件。為簡單起見,我們使用 SQLite3 作為資料庫。

 

graphql 是一個支持庫,並且在我們這裡是一個必要的模塊

 

Koa 是一個新的 web 框架,由 Express 幕後的原班人馬打造, 致力於成為 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。 通過利用 async 函數,Koa 幫你丟棄回調函數,並有力地增強錯誤處理。 Koa 並沒有捆綁任何中間件, 而是提供了一套優雅的方法,幫助您快速而愉快地編寫服務端應用程式

 

koa-graphql GraphQL HTTP伺服器中間件

 

koa-mount 讓多個Koa.js子應用合併成一個父應用,用請求的首碼區分子應用

 

SQLite是一個進程內的庫,實現了自給自足的、無伺服器的、零配置的、事務性的 SQL 資料庫引擎。它是一個零配置的資料庫,這意味著與其他資料庫一樣,您不需要在系統中配置。就像其他資料庫,SQLite 引擎不是一個獨立的進程,可以按應用程式需求進行靜態或動態連接。SQLite 直接訪問其存儲文件

 

創建 GraphQL 伺服器

創建工程並引入基本依賴包之後,現在來創建 API 伺服器。在工程文件夾里,創建 index.js 文件,並引入下列內容:

const Koa = require('koa');

const mount = require('koa-mount');

const graphql = require('graphql');

const graphqlHTTP = require('koa-graphql');

const sqlite3 = require('sqlite3').verbose();

const app = new Koa();

 

const database = new sqlite3.Database('./my.db');

const createContactTable = () => {

const query = `

CREATE TABLE IF NOT EXISTS contacts (

id integer PRIMARY KEY,

firstName text,

lastName text,

email text UNIQUE)`;

return database.run(query);

};

// 創建了一個 SQL 表來存儲 contacts的基本信息。每個 contact 的基本信息包括:唯一的標識、名、姓和 email。

createContactTable();

 

// 定義一個 GraphQL 類型

// 使用基本的內置 GraphQL 類型,如 GraphQLID 和 GraphQLString 來創建我們自定義類型,對應資料庫中的 contact。

const ContactType = new graphql.GraphQLObjectType({

name: 'Contact',

fields: {

id: { type: graphql.GraphQLID },

firstName: { type: graphql.GraphQLString },

lastName: { type: graphql.Grap

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 第一種方法:採用的傳統的方式插入,即insert into 表名VALUES(值) 打開資料庫,插入資料庫,關閉數據,一共插入5000條數據,分別用時41526、39266、39585 (這個方法最主要是耗時用在打開資料庫連接上和關閉資料庫連接上) 第二種方法:採用的先把sql語句用分號拼接起來,最 ...
  • 有關帶scala版本的eclipse4.7的下載, 你可以直接去: http://scala-ide.org/download/sdk.html​下載下來後是:scala-SDK-4.7.0-vfinal-2.12-win32.win32.x86_64.zip,解壓,展開。其實就是一個帶著scala ...
  • CONNECT角色: --是授予最終用戶的典型權利,最基本的 CREATE SESSION --建立會話 RESOURCE角色: --是授予開發人員的 CREATE CLUSTER --建立聚簇 CREATE PROCEDURE --建立過程 CREATE SEQUENCE --建立序列 CREAT ...
  • Windows Eclipse Scala的入門HelloWorld ...
  • 錯誤原因:mysql資料庫只允許本地ip訪問; 解決方法:修改mysql表設置所有ip都可以訪問; 登錄資料庫 使用以下命令: 查看是否修改成功 ...
  • 一、通過命令查看mysql是否是區分大小寫的 lower_case_table_names=1(說明是不區分大小寫的) lower_case_table_names=0(如上圖為0說明區分大小寫的) 二、修改lower_case_table_names的值為1 (1)我用的是寶塔面板所以直接修改配置 ...
  • 背景圖:(相關驗證代碼請查看代碼,在驗證時需將當前不需要驗證的代碼註釋掉) 1.inherit:從父元素繼承屬性設置 2.background-repeat:平鋪(在圖片大小小於元素尺寸時背景圖預設平鋪): no-repeat:取消預設平鋪 repeat-x:橫向平鋪 repeat-y:縱向平鋪 3 ...
  • 本文從JS是單線程開始,到JS為了提高效率,使用非同步,到JS如何實現非同步,再到瀏覽器是如何配合JS執行非同步。最後提到了一個任務隊列的優先順序問題。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...