-
apollo 能干什么
-
怎么用(react版)
- 2.1 引入方式
// index.tsx
import { LocaleProvider, message } from 'antd'
import zhCN from 'antd/lib/locale-provider/zh_CN';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client'
import { ApolloLink, NextLink, Observable, Operation } from 'apollo-link';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { onError } from 'apollo-link-error';
import * as React from 'react';
import { ApolloProvider } from 'react-apollo'
import * as ReactDOM from 'react-dom';
import App from './App';
import './index.scss';
import registerServiceWorker from './registerServiceWorker';
// 请求拦截器
const request = async (operation: Operation) => {
// 可以设置token
operation.setContext({
headers: {}
})
return Promise.resolve()
}
const requestLink = new ApolloLink((operation: Operation, forward: NextLink) => {
return new Observable(observer => {
let handle: any;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) {
handle.unsubscribe()
}
}
})
})
const client = new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors }) => {
// 全局错误处理
if (Array.isArray(graphQLErrors)) {
message.error(graphQLErrors[0].message)
}
}),
requestLink,
new BatchHttpLink({ uri: 'http://localhost:7001/graphql' }),
]),
cache: new InMemoryCache(),
})
ReactDOM.render(
,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();
复制代码
-
2.2 官方集成包(apollo-boost, 强烈不推荐,因为没有集成batch)
- 2.2.1 没有使用batch的情况
- 2.2.2 使用了batch的情况 目前应用场景应该是在减少短效token,换长效token时的并发问题
-
2.3 ajax交互(apollo也提供了组件化, 把原本的service服务变成了一个组件)
// apollo component
import { Query } from 'react-apollo'
import { IArticle } from '../../interface/Article'
interface IData {
getArticleList: {
list: IArticle[];
pagination: {
page: number;
totalPage: number;
totalCount: number;
perPage: number;
};
}
}
interface IParams {
page: number;
perPage: number;
}
export default class QueryArticleList extends Query {}
复制代码
页面使用
// apollo query
export const articleList = gql`
query getArticleList (
$page: Int
$perPage: Int
) {
getArticleList (
page: $page
perPage: $perPage
) {
list {
_id
title
tags {
name
color
}
}
pagination {
page
totalPage
totalCount
perPage
}
}
}
`
// page
import * as React from 'react'
import { List, Spin, Tag } from 'antd'
import { CSSTransition } from 'react-transition-group'
import Blank from '../../components/blank/blank'
export default class ArticleListPage extends React.Component {
public render(): JSX.Element {
const { pagination } = this.state;
return (
{
(({ data, error, loading, fetchMore }) => {
if (loading) {
return "large" />
}
if (error) {
return {error}
}
if (data) {
// 渲染列表
return (
in={loading}
classNames="list-move"
timeout={500}>
"large"
itemLayout="vertical"
split={false}
pagination={resPagination}
dataSource={data.getArticleList.list}
renderItem={this.renderListItem} />
)
}
return
})
}
)
}
}
复制代码
- 2.4 分页(结合了antd)
// 上面page里(列表渲染部分替换)
const resPagination: IStatePagination = {
pageSize: pagination.pageSize,
current: data.getArticleList.pagination.page,
onChange: (pageNumber: number) => {
fetchMore({
variables: {
page: pageNumber
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return prev;
} else {
return fetchMoreResult;
}
}
})
},
total: data.getArticleList.pagination.totalPage,
}
return (
in={loading}
classNames="list-move"
timeout={500}>
"large"
itemLayout="vertical"
split={false}
pagination={resPagination}
dataSource={data.getArticleList.list}
renderItem={this.renderListItem} />
)
复制代码
- 2.5 读写缓存(状态管理)
// 读还是跟网络请求时候的一样, apollo默认启用了cache
// 不想启用,需要配置在请求时配置fetchPolicy字段
// 写的话需要用到updateQuery字段
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return prev;
} else {
return fetchMoreResult;
}
}
复制代码
-
怎么用(angular版)
- 3.1 引入apollo(这里推荐apollo-angular这个集成包)
// app.module.ts 部分代码
import { ApolloModule, Apollo } from 'apollo-angular';
import { HttpBatchLinkModule, HttpBatchLink } from 'apollo-angular-link-http-batch';
import { ApolloLink, from } from 'apollo-link';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { onError } from 'apollo-link-error';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
AppRoutingModule,
ApolloModule,
HttpBatchLinkModule,
NgbModule.forRoot(),
ThemeModule.forRoot(),
CoreModule.forRoot(),
],
bootstrap: [AppComponent],
})
export class AppModule {
constructor (
public apollo: Apollo,
public httpLink: HttpBatchLink,
) {
const http = httpLink.create({
uri: 'http://localhost:7001/graphql',
batchInterval: 500,
});
/**
* @name 请求拦截器
* @date 2019-01-15
* */
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
operation.setContext({
headers: new HttpHeaders().set('Authorization', localStorage.getItem('token') || null),
});
return forward(operation);
});
/**
* 还可以继续追加拦截器
*/
/**
* @name 响应拦截器
* @date 2019-01-15
**/
const logoutLink = onError(({ networkError }) => {
window.console.log('networkError', networkError);
});
apollo.create({
link: from([authMiddleware, http, logoutLink]),
cache: new InMemoryCache(),
});
}
}
复制代码
- 3.1 ajax交互(service)
// 部分截图 (没有做分页。。)
// graphql query基本上给react 的一样
import { Apollo } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { IArticle, articleList, article, addArticle, updateArticle } from '../../interface/Article';
import { IPagination } from '../../interface/Pagination';
import { DatePipe } from '../../pipes/date.pipe';
interface IArticleList {
list: IArticle[];
pagination: IPagination;
}
@Injectable()
export class ArticleService {
constructor(private apollo: Apollo) {
}
getArticleList(): Observable {
return this.apollo
.watchQuery<{ getArticleList: IArticleList }, { page: number, perPage: number }>({
query: articleList,
variables: {
page: 1,
perPage: 999,
},
fetchPolicy: 'network-only',
})
.valueChanges
.pipe(
map(res => {
res.data.getArticleList.list.forEach(item => {
item.createTime = new DatePipe().transform(item.createTime);
item.updateTime = new DatePipe().transform(item.updateTime);
});
return res.data.getArticleList.list;
}),
);
}
}
复制代码
- 3.2 页面使用服务
import { ArticleService } from '../../../@core/data/articles.service';
@Component({
selector: 'ngx-article-list',
templateUrl: './article-list.component.html',
styles: [`
nb-card {
transform: translate3d(0, 0, 0);
}
`],
})
export class ArticleListComponent implements OnInit {
constructor(
private service: ArticleService,
private router: Router,
) {}
ngOnInit() {
this.service.getArticleList().subscribe((data) => this.source.load(data));
}
}
复制代码
- 3.3 读写缓存
// 依旧是service文件
// update 字段用来更新数据
@Injectable()
export class Live2dService {
constructor(private apollo: Apollo) {
}
updateLive2d(name: string): Observable {
interface IRes {
data: {
updateLive2d: ILive2d;
};
}
return this.apollo
.mutate<{ updateLive2d: ILive2d }, { name: string }>({
mutation: updateLive2d,
variables: {
name,
},
update (proxy, res: IRes) {
// 取缓存
const live2dName: string = res.data.updateLive2d.name;
const data = proxy.readQuery({ query: admin }) as { admin: IUser };
data.admin.live2d = live2dName;
// 写缓存
proxy.writeQuery({ query: admin, data });
},
})
.pipe(
map(res => res.data.updateLive2d),
);
}
}
复制代码
-
服务端使用(egg)
- 4.1 引入apollo
// extends/application.ts
import { Application } from "egg";
import GraphQL from "../graphql";
const TYPE_GRAPHQL_SYMBOL = Symbol("Application#TypeGraphql");
export default {
get graphql(this: Application): GraphQL {
if (!this[TYPE_GRAPHQL_SYMBOL]) {
this[TYPE_GRAPHQL_SYMBOL] = new GraphQL(this);
}
return this[TYPE_GRAPHQL_SYMBOL];
}
};
// app.ts
import "reflect-metadata";
import { Application } from "egg";
export default async (app: Application) => {
await app.graphql.init();
app.logger.info("started");
}
// graphql/index.ts
import * as path from "path";
import * as jwt from 'jsonwebtoken';
import { ApolloServer, AuthenticationError } from "apollo-server-koa";
import { Application } from "egg";
import { GraphQLSchema } from "graphql";
import { buildSchema } from "type-graphql";
export interface GraphQLConfig {
router: string;
graphiql: boolean;
}
export default class GraphQL {
private readonly app: Application;
private graphqlSchema: GraphQLSchema;
private config: GraphQLConfig;
constructor(app: Application) {
this.app = app;
this.config = app.config.graphql;
}
getResolvers() {
const isLocal = this.app.env === "local";
return [path.resolve(this.app.baseDir, `app/graphql/schema/**/*.${isLocal ? "ts" : "js"}`)];
}
async init() {
this.graphqlSchema = await buildSchema({
resolvers: this.getResolvers(),
dateScalarMode: "timestamp"
});
const server = new ApolloServer({
schema: this.graphqlSchema,
tracing: false,
context: async ({ ctx }) => {
// token验证放在这里
// 将 egg 的 context 作为 Resolver 传递的上下文
return ctx
},
playground: {
settings: {
"request.credentials": "include"
}
} as any,
introspection: true
});
server.applyMiddleware({
app: this.app,
path: this.config.router,
cors: true
});
this.app.logger.info("graphql server init");
}
get schema(): GraphQLSchema {
return this.graphqlSchema;
}
}
interface IJwt {
exp: string | number,
data: string
}
复制代码
- 4.2 编写graphql类型系统
// enum类型
import { registerEnumType } from 'type-graphql';
export enum ImgType {
// banner图片
'banner' = 'banner',
// 文章图片
'article' = 'article',
// 其他
'other' = 'other'
}
registerEnumType(ImgType, {
name: 'ImgType',
description: '图片类型'
})
// 这里推荐下type-graphql
import { ObjectType, Field, ID, InputType } from 'type-graphql';
import { ImgType } from '../enum/imgType';
import { Status } from '../enum/status';
import { IsString, IsNotEmpty } from 'class-validator';
@ObjectType({ description: 'image model' })
export class Image {
@Field(() => ID, { nullable: true })
_id?: number;
@Field({ description: '链接地址', nullable: true })
url?: string;
@Field(() => ImgType, { description: '图片类型', nullable: true })
type?: ImgType;
@Field(() => Status, { description: '图片启用状态', nullable: true })
status?: Status;
@Field({ description: '创建时间', nullable: true })
createTime?: string;
@Field({ description: '更新时间', nullable: true })
updateTime?: string;
}
@InputType({ description: 'add image model' })
export class AddImage {
@Field({ description: '链接地址' })
@IsString()
@IsNotEmpty()
url: string;
@Field(() => ImgType, { description: '图片类型' })
@IsNotEmpty()
type: ImgType;
@Field(() => Status, { description: '图片启用状态', nullable: true })
status?: Status;
}
@InputType({ description: 'update image model' })
export class UpdateImage extends AddImage {
@Field(() => ID)
@IsNotEmpty()
_id: number;
}
复制代码
- 4.3 graphql服务
import { Resolver, Query, ID, Arg, Ctx, Mutation } from 'type-graphql';
import { ImgType } from '../../enum/imgType';
import { Image, AddImage, UpdateImage } from '../../interface/image';
import { Context } from 'egg'
@Resolver()
export default class ImageResolve {
@Query(() => [Image], { description: '获取图片列表' })
async getImageList (
@Arg("type", () => ImgType) type: ImgType,
@Ctx() ctx: Context
): Promise {
try {
const imgList = await ctx.model.Image.find({ type })
return imgList as Image[]
} catch (e) {
ctx.logger.error(e)
return Error('系统异常')
}
}
@Mutation(() => Image, { description: '添加图片' })
async addImage (
@Arg('data') image: AddImage,
@Ctx() ctx: Context,
): Promise {
try {
const newImage = new ctx.model.Image(image)
return await newImage.save() as Image
} catch (e) {
ctx.logger.error(e)
return Error('系统异常')
}
}
复制代码
- 4.4 接口测试
- 4.5 接口文档