ReactiveX 學習筆記(25)使用 RxJS + Vue.js 呼叫 REST API
JSON : Placeholder
JSON : Placeholder (https://jsonplaceholder.typicode.com/) 是一個用於測試的 REST API 網站。
以下使用 RxJS6 + Angular 6 呼叫該網站的 REST API,獲取字串以及 JSON 資料。
- GET /posts/1
- GET /posts
- POST /posts
- PUT /posts/1
- DELETE /posts/1
所有 GET API 都返回JSON資料,格式(JSON-Schema)如下:
{ "type":"object", "properties": { "userId": {"type" : "integer"}, "id": {"type" : "integer"}, "title": {"type" : "string"}, "body": {"type" : "string"} } }
建立工程
# 安裝 Vue CLI
$ npm install --global @vue/cli
# 建立新的應用程式 RxExample
$ vue create rx-example
# 選擇 Manually select features
# 選擇 Babel 和 TypeScript Linter/Formatter
$ cd rx-example
$ npm run serve
開啟 Intellij IDEA, File / New / Project From Existing Sources...,然後選中工程所在資料夾
在嚮導的第1頁選擇 Create project from existing sources
完成嚮導後在點選 Finish 建立工程。
點選 Add Configurations, 點選 +npm
Name: Vue CLI Server
Scripts: serve
點選 OK 完成配置。
點選 Vue CLI Server 啟動程式。
http://localhost:8080/ 可開啟網頁。
Post
在 src 資料夾下新增 post.ts,內容如下
export class Post { userId!: number; id!: number; title!: string; body!: string; toString(): string { return `Post {userId = ${this.userId}, id = ${this.id}, title = "${this.title}", body = "${this.body.replace(/\n/g, '\\n')}"}`; } }
安裝 vue-typescript-inject
vue-typescript-inject 是一個在 Vue.js 中實現 DI(依賴注入) 的包。
需要安裝 vue-typescript-inject 和 reflect-metadata
$ npm i vue-typescript-inject reflect-metadata
開啟 tsconfig.json 在 compilerOptions 中
新增關於 emitDecoratorMetadata 的設定。
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
開啟 main.ts 在 Vue 物件生成的程式碼之前
新增以下三行程式碼。
import 'reflect-metadata';
import VueTypeScriptInject from 'vue-typescript-inject';
Vue.use(VueTypeScriptInject); // register vue-typescript-inject
新增 Rxios
訪問 API 採用將 rxjs 和 axios 合為一體的 Rxios 元件,但是該元件尚未升級到 rxjs6。
這裡我們只安裝 rxjs 和 axios,然後直接以原始碼形式引入 Rxios,並將其升級到 rxjs6。
$ npm install axios rxjs
在 src 資料夾下新增 rxios.ts,內容如下
// https://github.com/davguij/rxios/blob/master/src/index.ts
// ported to rxjs6
import axios, { AxiosInstance, AxiosRequestConfig, AxiosPromise } from 'axios';
// import { Observable } from 'rxjs/Observable';
import { Observable } from 'rxjs';
export interface rxiosConfig extends AxiosRequestConfig {
localCache?: boolean;
}
class rxios {
private _httpClient: AxiosInstance;
constructor(private options: rxiosConfig = {}) {
this._httpClient = axios.create(options);
}
private _makeRequest<T>(method: string, url: string, queryParams?: object, body?: object) {
let request: AxiosPromise<T>;
switch (method) {
case 'GET':
request = this._httpClient.get<T>(url, {params: queryParams});
break;
case 'POST':
request = this._httpClient.post<T>(url, body, {params: queryParams});
break;
case 'PUT':
request = this._httpClient.put<T>(url, body, {params: queryParams});
break;
case 'PATCH':
request = this._httpClient.patch<T>(url, body, {params: queryParams});
break;
case 'DELETE':
request = this._httpClient.delete(url, {params: queryParams});
break;
default:
throw new Error('Method not supported');
}
return new Observable<T>(subscriber => {
request.then(response => {
subscriber.next(response.data);
subscriber.complete();
}).catch((err: Error) => {
subscriber.error(err);
subscriber.complete();
});
});
}
public get<T>(url: string, queryParams?: object) {
return this._makeRequest<T>('GET', url, queryParams);
}
public post<T>(url: string, body: object, queryParams?: object) {
return this._makeRequest<T>('POST', url, queryParams, body);
}
public put<T>(url: string, body: object, queryParams?: object) {
return this._makeRequest<T>('PUT', url, queryParams, body);
}
public patch<T>(url: string, body: object, queryParams?: object) {
return this._makeRequest<T>('PATCH', url, queryParams, body);
}
public delete(url: string, queryParams?: object) {
return this._makeRequest('DELETE', url, queryParams);
}
}
export {rxios, rxios as Rxios};
post 服務
在 src 資料夾下新增 post.service.ts,內容如下
import { injectable } from 'vue-typescript-inject';
import {of, Observable, from} from 'rxjs';
import {map, mergeAll, take, tap} from 'rxjs/operators';
import { Post } from './post';
import { Rxios } from '@/rxios';
@injectable()
export class PostService {
private readonly http = new Rxios();
private readonly baseUrl = 'http://jsonplaceholder.typicode.com/';
constructor() {
this.getPostAsString().subscribe();
this.getPostAsJson().subscribe();
this.getPosts(2).subscribe();
this.createPost().subscribe();
this.updatePost().subscribe();
this.deletePost().subscribe();
}
private getPostAsString(): Observable<string> {
const url = `${this.baseUrl}posts/1`;
return this.http.get(url, { responseType: 'text' })
.pipe(
tap((result: any) => console.log(result)),
);
}
private getPostAsJson(): Observable<Post> {
const url = `${this.baseUrl}posts/1`;
return this.http.get<Post>(url)
.pipe(
map((result: any) => Object.assign(new Post(), result)),
tap((result: any) => console.log('' + result)),
);
}
private getPosts(n: number): Observable<Post> {
const url = `${this.baseUrl}posts`;
return from(this.http.get<Post[]>(url))
.pipe(
mergeAll(),
map((result: any) => Object.assign(new Post(), result)),
take(n),
tap((result: any) => console.log('' + result)),
);
}
private createPost(): Observable<string> {
const url = `${this.baseUrl}posts`;
return this.http.post(url, {
params: {
userId: 101,
title: 'test title',
body: 'test body',
}
})
.pipe(
map((result: any) => JSON.stringify(result)),
tap((result: any) => console.log(result)),
);
}
private updatePost(): Observable<string> {
const url = `${this.baseUrl}posts/1`;
return this.http.put(url, {
params: {
userId: 101,
title: 'test title',
body: 'test body',
}
})
.pipe(
map((result: any) => JSON.stringify(result)),
tap((result: any) => console.log(result)),
);
}
private deletePost(): Observable<string> {
const url = `${this.baseUrl}posts/1`;
return this.http.delete(url)
.pipe(
map((result: any) => JSON.stringify(result)),
tap((result: any) => console.log(result)),
);
}
}
- getPostAsString 方法取出第1個Post,返回字串
- getPostAsJson 方法取出第1個Post,返回Post物件
- getPosts 方法取出前n個Post,返回n個Post物件
- createPost 方法建立1個Post,返回字串
- updatePost 方法更新第1個Post,返回字串
- deletePost 方法刪除第1個Post,返回字串
app.module.ts 中新增 PostService 和 HttpClientModule 的引用。
import { PostService } from './post.service';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
],
providers: [
PostService,
],
})
刪除 HelloWorld 元件,即 src/components/HelloWorld.vue 檔案。
開啟 App.vue,將其改為
<template>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { PostService } from './post.service';
@Component({
providers: [
PostService,
],
})
export default class App extends Vue {}
</script>
<style>
</style>
輸出結果
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
Post {userId = 1, id = 2, title = "qui est esse", body = "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
{"params":{"userId":101,"title":"test title","body":"test body"},"id":1}
{"params":{"userId":101,"title":"test title","body":"test body"},"id":101}
{}