最近在些 Flutter 应用时,用到了 GraphQL 分页,返回来的数据大概长下面这个样子,是按 Github API v4 的数据格式来整的:
{"users":{ { "edges": [ { "node": { "id": "1", "name": "ZhangSan" }, "cursor": "MA==" }, { "node": { "id": "2", "name": "Lisi" }, "cursor": "MB==" } ], "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "startCursor": "MA==", "endCursor": "MB==" } } }
上面其实就是获取用户列表,这个列表在 GraphQL 里面有个专用的词语,叫 Connection
,它是嵌套的 JSON 格式数据,其中 node 里面就是实际的数据,比如这里是一个 User
对象,包含用户Id和用户名称。
在 flutter / dart 里面要使用这个数据,就要使用 fromJson
转换成实际的对象,也就是 model。
方案1 专用 Connection(可行,但重复代码多)
假设只有一个 User
需要 Pagination 分页,那就比较简单,直接这样创建一个 User
专用的 UserConnection
:
import 'package:termi/models/user.dart'; class UserConnection { UserConnection({ this.edges, this.pageInfo, }); List<Edge> edges; PageInfo pageInfo; factory UserConnection.fromJson(Map<String, dynamic> json) => UserConnection( edges: List<Edge>.from(json["edges"].map((x) => Edge.fromJson(x))), pageInfo: PageInfo.fromJson(json["pageInfo"]), ); Map<String, dynamic> toJson() => { "edges": List<dynamic>.from(edges.map((x) => x.toJson())), "pageInfo": pageInfo.toJson(), }; } class Edge { Edge({ this.node, this.cursor, }); User node; String cursor; factory Edge.fromJson(Map<String, dynamic> json) => Edge( node: User.fromJson(json["node"]), cursor: json["cursor"], ); Map<String, dynamic> toJson() => { "node": node.toJson(), "cursor": cursor, }; } class PageInfo { //... }
这个方式 PageInfo
类可以公用,但是 Connection
和 Edge
都必须每个类重新写一遍。
但是我们希望 Connection
是一个公共类,所有对象都能用,所以这是一个方案,但不是最佳的方案。
方案2 泛型(不可行)
如果改成泛型,那是不是就可以呢?如下:
class Connection<T> { //... factory UserConnection.fromJson(Map<String, dynamic> json) => UserConnection( edges: List<Edge<T>>.from(json["edges"].map((x) => Edge.fromJson(x))), //... ); //... } class Edge<T> { //... T node; //... factory Edge.fromJson(Map<String, dynamic> json) => Edge( node: T.fromJson(json["node"]), //.. ); //... }
这样是不可以的,因为泛型没有 fromJson
方法,编译报错。
方案3 接口方式(不可行)
有 Java 或其他类似语言开发经验可能会想到接口类。
先定义一个接口,在 Dart 用抽象类代替接口:
abstract class AbstractModel { factory AbstractModel.fromJson(Map<String, dynamic> json); Map<String, dynamic> toJson(); }
然后每个 model 对象实现这个抽象类,如 User
:
class User implements AbstractModel {
@override
factory User.fromJson(Map<String, dynamic> json) {
// TODO: implement toJson
}
@override
Map<String, dynamic> toJson() {
// TODO: implement toJson
}
}
在 Connection
里面,直接以接口类作为类型,泛型都不用:
//... class Edge { //... AbstractModel node; //... factory Edge.fromJson(Map<String, dynamic> json) => Edge( node: AbstractModel.fromJson(json["node"]), //.. ); //... }
可以看到,这里存在 2
个问题会产生问题:
- 在抽象类中不能用 factory,它实际就是一个静态方法,不能放在抽象类中,所以这个抽象类就会报错。
- 在 Edge 类中,我们用都是类都 fromJson,所以这里还是不能直接指定到实际类,比如 User 类的 fromJson,只能用 AbstractModel.fromJson,但是这个是抽象类,也不能调用。
所以,这个方案不可行。
方案4 传递函数方式(可行,推荐)
其实 Dart 的函数可以最为参数来传递, Edge 里面的 fromJson 和具体的 Model 类相关,那我们把函数传进来。
第1步,把 User 类的 fromJson 由 factory 改为静态方法:
static fromJson(Map<String, dynamic> json) => User( id: json["id"], name: json["name"], );
第2步,Connection
和 Edge
的 fromJson
方法都多传递一个 nodeFromJson
参数,类型是 Function
:
class Connection { //... factory UserConnection.fromJson(Map<String, dynamic> json, Function nodeFromJson) => Connection( edges: List<Edge>.from(json["edges"].map((x) => Edge.fromJson(x, nodeFromJson))), //... ); //... } class Edge { //... dynamic node; //... factory Edge.fromJson(Map<String, dynamic> json, , Function nodeFromJson) => Edge( node: nodeFromJson(json["node"]), //.. ); //... }
第3步,使用
Connection connection = Connection.fromJson(data["users"], Question.fromJson); bool hasNextPage = connection.pageInfo.hasNextPage; List<User> users = connection.edges .map<User>((edge) => edge.node) .toList();
这样即能确保不会编写重复代码,用起来也很清晰明了。
参考资料: