Vue、Type escript和RxJS与Vue-Rx的结合
发布在前端大锅饭2018年11月7日view:391前端开发web框架Vue.js面试web
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

目前我正在写这篇文章,有很多方法来解决一个具体的问题。

在本文中,我想给您一个灵感,它可能会改变您在应用程序中使用的当前方法。

RxJS可能与非常复杂的实现、测试和维护相关。

如果我告诉您,集成、实现和测试实际上非常容易,即使是在一个通用的(小型、中型、大型)VueJS应用程序中也是如此呢?

在本文中,我将演示如何将VueJS、type escript和RxJS结合起来。我真的相信这是一个很棒的组合,还有一个插件,它真的很有用:Vue-Rx .

你为什么要安装和使用这个插件?

因为它正在释放你的应用程序的超能力,它正在创造整个体验。略显更流畅,作为初级或高级开发人员。

有了一个通用应用程序,第一个开始的命令如下:

npm install vue-rx rxjs --save 很明显,这将Vue-Rx和RxJS安装到项目的依赖项中。

当然,我们需要告诉VueJS安装它(在全球范围内,是的):

i

mport Vue from 'vue'
import VueRx from 'vue-rx'
Vue.use(VueRx)
根据官方文件,

它导入最小数量的Rx操作符,并确保小的包大小。

我亲自确认。

一旦所有的一切都设置好了,你就可以使用这个伟大的插件所暴露的任何东西了。如果我们深入研究类型记录定义,您就可以快速看到将添加到VueJS中的内容:

declare module "vue/types/vue" {
 interface Vue {
 $observables: Observables;
 $watchAsObservable(expr: string, options?: WatchOptions): Observable<WatchObservable<any>>
 $watchAsObservable<T>(fn: (this: this) => T, options?: WatchOptions): Observable<WatchObservable<T>>
 $eventToObservable(event: string): Observable<{name: string, msg: any}>
 $subscribeTo<T>(
 observable: Observable<T>,
 next: (t: T) => void,
 error?: (e: any) => void,
 complete?: () => void): void
 $fromDOMEvent(selector: string | null, event: string): Observable<Event>
 $createObservableMethod(methodName: string): Observable<any>
 }
}

这看起来可能有点奇怪,但我将很快突出这些特性:

美元可观测值它将指出已登记的订阅; $观察例如,一个可观察的功能可以帮助应用于该属性的流更改,而不是让一个泛型函数监视一个反应性属性; 美元-可观察:可以将自定义事件处理程序或生命周期挂钩转换为可观察的; 来自DOMEVENT的$:这是另一种很好的转换DOM事件的方法(例如,键向上,输入,点击等等)变成可观察的; 美元-观察-从方法出发*这也是一个很好的特性;如果将一个方法转换为一个可观察的值并将接收到的值处理为一个反应流(例如回调),这将很有帮助; $订阅:它提供了手动注册一个可观察到的(声明下一个错误、完全回调)的机会,VueRx将管理注册并在不再有用的情况下处理它(例如已销毁的组件)。 在上面,这个插件公开了一个 v流指令 ,可与以下内容相结合:

DOM事件(如:v流:单击(对于单击处理程序) 自定义事件来自儿童组件(例如:V-溪流:变化为了捕捉排放物) 很酷,但是如何正确地使用它呢?

在很多情况下,可以使用上述特性,在本文中,我将介绍一个简单的案例:一个泛型输入字段,它触发对给定端点执行搜索的请求。在这种情况下,有很多事情和特性需要考虑:

它可以避免每次输入新字符时发送大量请求; 并发请求呢?哪一个应该优先考虑? 如果响应包含的数据可能不包含我们所需要的所有信息呢?我们在哪里处理? 这是一个基本的场景。在一个特定的项目中,你可能会有另外万亿的分数。你是如何组合/处理它们的?你如何确保所有的东西都是声明性的和良好的阅读?另外,单元测试呢?

让我们开始为我们的组件实现基本代码:

// Search.vue
<template>
 <div>
 <label for="search">Search for something:</label>
 <input type="text" id="search" class="input-field">
 </div>
</template>
<scriponent
 export default class Search extends Vue {}</script>
<style scoped>
 .input-field {
 ...pt lang="ts">
 import { Component, Vue } from 'vue-property-decorator';
 @Com
 }</style>

因为我们依赖打字稿,所以我用的是房产装饰师‘这使我们能够在组件声明中使用装饰器(例如组件、Prop等,…)。。

在我们的例子中,我们希望实现一个简单的行为:键入某样东西并作为查询进行搜索。

首先,我们需要创建一个可观察的,它将所有来自字段的输入流。一种方法是依赖于输入标记中的keyup事件:

<em>import </em>{ Component, Vue } <em>from </em>'vue-property-decorator';
@Component<Search>({
 subscriptions() {
 return ({
 news: this.$fromDOMEvent('input', 'keyup')
 });
 }
})
export default class Search extends Vue {
 news: any; // we will specifically type it later on
}

在使用类型记录时,注册对VueJS组件的订阅的方法,就是上面声明的方法。

从“订阅”函数返回的对象包含一个属性“news”,该属性是一个数据属性,它将与注册的可观测值相关联(在本例中为.$fromDOMEVENT…)。。

当输入字段中输入某项内容时,与“news”相关联的可观察到的事件将接收到DOM事件。现在,让我们稍微调整一下逻辑,转而依赖一个字符串(例如,字段的值)。

import { Component, Vue } from 'vue-property-decorator';
import { pluck } from 'rxjs/operators';
@Component<Search>({
 subscriptions() {
 return ({
 news: this.$fromDOMEvent('input', 'keyup').pipe(
 pluck<Event, string>('target', 'value')
 )
 });
 }
})
export default class Search extends Vue {
 news: string;
}

为了从接收到的事件中检索输入的值,我们可以使用‘拔毛一个由RxJS公开的操作符,它允许我们从定义的对象中查看所需的属性(即使是嵌套的路径)。

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-pluck

即使有一个字符串,它对我们也没有太大的帮助,因为我们需要一个结果列表。下一步可能是一个简单的请求,在HackerNewsAPI上搜索给定的查询(我们的输入字段值)。

import axios from 'axios';
import { Component, Vue } from 'vue-property-decorator';
import { from } from 'rxjs';
import { pluck, switchMap } from 'rxjs/operators';
interface HackerNewsResult {
 objectID: string;
 title?: string;
 url?: string;
}
interface HackerNewsSearchResponse {
 hits: Array<HackerNewsResult>
}
const hackerNewsEndpoint: string = 'http://hn.algolia.com/api/v1/search?query=';
@Component<Search>({
 subscriptions() {
 return ({
 news: this.$fromDOMEvent('input', 'keyup').pipe(
 pluck<Event, string>('target', 'value'),
 switchMap(value => from(
 axios.get<HackerNewsSearchResponse>(`${hackerNewsEndpoint}${value}`)
 )
 )
 )
 });
 }
})
export default class Search extends Vue {
 news: Array<HackerNewsResult>;
}

它开始采取一些实际的行为,从行中可以看到发生了什么:我们从呈现的输入中流出DOM事件,我们选择它的值,并使用SwitMap来执行针对端点的请求。根据文档,SwitchMap将每个源值投影到可观察的可观测值,该值合并到可观测的输出中,仅从最近预测的可观测值中发出值。为了澄清,唯一的最新承诺将被追查到最后(解决-拒绝)。好的,漂亮的

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-switchMap

我们的探索还没结束!

现在,最好只针对特定的时间窗口执行请求,而不是每次用户输入时都执行请求。为了完成这个案子,我们可以用借债时间它从源发出一个值,只有在特定的时间跨度过去之后才能观察到,而没有另一个源发射。

如果您已经使用过Lodash/下划线,那么“退出”可能听起来很熟悉。

import { debounceTime, pluck, switchMap } from 'rxjs/operators';
...
@Component<Search>({
 subscriptions() {
 return ({
 news: this.$fromDOMEvent('input', 'keyup').pipe(
 debounceTime(300),
 pluck<Event, string>('target', 'value'),
 switchMap(value => from(
 axios.get<HackerNewsSearchResponse>(`${hackerNewsEndpoint}${value}`)
 )
 )
 )
 });
 }
})
export default class Search extends Vue {
 news: Array<HackerNewsResult>;
}

为了解决上面提到的问题,向管道中添加了decouneTime。

export function debounceTime<T>(dueTime: number, scheduler: SchedulerLike = async): MonoTypeOperatorFunction<T> { return (source: Observable<T>) => source.lift(new DebounceTimeOperator(dueTime, scheduler)); } 作为参数传递给函数的数字表示时间窗口,以毫秒为单位。每次用户输入某项内容时,下一个值将在300 ms后发出(因此,它将聚合下一个条目,以防万一,并在所需的时间之后触发下一个值)

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-debounceTime

快好了。现在,我们需要接受响应,使用有效的信息过滤结果并将其呈现给用户。

<template>
 <div>
 <label for="search">Search for something:</label>
 <input type="text" id="search" class="input-field">
 <ul v-for="item in news">
 <li :key="item.objectID">
 <a :href="item.url">{{ item.title }}</a>
 </li>
 </ul>
 </div>
</template>
<script lang="ts">
 ...
import { debounceTime, map, pluck, switchMap } from 'rxjs/operators';
 ...
 @Component<Search>({
 subscriptions(this: Vue) {
 return ({
 news: this.$fromDOMEvent('input', 'keyup').pipe(
 debounceTime(300),
 pluck<Event, string>('target', 'value'),
 switchMap(value => from(
 axios.get<HackerNewsSearchResponse>(`${hackerNewsEndpoint}${value}`)
 )
 ),
 pluck<AxiosResponse, Array<HackerNewsResult>>('data', 'hits'),
 map((results: Array<HackerNewsResult>) => results.filter((news: HackerNewsResult) => Boolean(news.title && news.url)))
 )
 });
 }
 })
 export default class Search extends Vue {
 news: Array<HackerNewsResult> = [];
 }
</script>

首先,我在管道中增加了两个额外的步骤:

考虑到来自Axios承诺的成功响应,我们从JSON“data”中选择“HITS”(这包含基于查询的结果); 后来,我介绍了地图遍历流中的每个值(包含结果的数组),只过滤包含有效标题和URL的有效值; 为了呈现数组,我在模板中创建了一个简单的列表,它迭代新闻数组,并呈现一个简单的链接,显示新闻标题并在href中实现URL。

就这样IMHO,它看起来非常整洁和“容易”阅读。我知道这可能不是简单的,但是一旦熟悉了RxJS和反应性方法,维护/测试就变得更加容易了。现在停下来好好想想。传统的声明式方法很容易出错,而可维护性/测试将处于边缘。

现在,感谢RxJS,您还可以添加额外的步骤,例如区别 , 重试如果出现错误和许多其他特性。此外,如果您热衷于测试流,我建议您使用大理石测试并对所观察到的结果进行测试,得到一定的结果。

interface HandleObservableOptions {
 time?: number;
 scheduler?: SchedulerLike;
}
export const handleObservable = function (observable: Observable<Event>, options: HandleObservableOptions = {}): Observable<Array<HackerNewsResult>> {
 const { time = 300, scheduler } = options;
 return observable.pipe(
 debounceTime(time, scheduler),
 ... 
 );
};

https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md
// This test will actually run *synchronously*
it('generate the stream correctly', () => {
 scheduler.run(helpers => {
 const { cold, expectObservable, expectSubscriptions } = helpers;
 const obs = handleObservable(cold('-a--b--c---|'), { 
 time: 300, 
 scheduler
 });
 const subs = '^----------!';
 const expected = '-a-----c---|';
 expectObservable(obs).toBe(expected);
 expectSubscriptions(obs.subscriptions).toBe(subs);
 });

上面的片段仅仅是测试看起来的一个例子(注意:根据您的环境,真正的测试可能略有不同)。多亏了大理石策略,才有可能测试你能想到的所有可能的路径。也许这是下一篇()文章的一个有效主题,因为有很多事情要讨论。

无论如何,完整的解决方案可能是这样的。

<template>
 <div>
 <label for="search">Search for something:</label>
 <input type="text" id="search" class="input-field">
 <ul v-for="item in news">
 <li :key="item.objectID">
 <a :href="item.url">{{ item.title }}</a>
 </li>
 </ul>
 </div>
</template>
<script lang="ts">
 import axios, { AxiosResponse } from 'axios';
 import { Component, Vue } from 'vue-property-decorator';
 import { from, Observable, SchedulerLike } from 'rxjs';
 import { debounceTime, map, pluck, switchMap } from 'rxjs/operators';
 interface HackerNewsResult {
 objectID: string;
 title?: string;
 url?: string;
 }
 interface HackerNewsSearchResponse {
 hits: Array<HackerNewsResult>
 }
 interface HandleObservableOptions {
 time?: number;
 scheduler?: SchedulerLike;
 }
 const hackerNewsEndpoint: string = 'http://hn.algolia.com/api/v1/search?query=';
 export const handleObservable = function (observable: Observable<Event>, options: HandleObservableOptions = {}): Observable<Array<HackerNewsResult>> {
 const { time = 300, scheduler } = options;
 return observable.pipe(
 debounceTime(time, scheduler),
 pluck<Event, string>('target', 'value'),
 switchMap(value => from(
 axios.get<HackerNewsSearchResponse>(`${hackerNewsEndpoint}${value}`)
 )
 ),
 pluck<AxiosResponse, Array<HackerNewsResult>>('data', 'hits'),
 map((results: Array<HackerNewsResult>) => results.filter((news: HackerNewsResult) => Boolean(news.title && news.url)))
 );
 };
 @Component<Search>({
 subscriptions(this: Vue) {
 return ({
 news: handleObservable(this.$fromDOMEvent('input', 'keyup'))
 });
 }
 })
 export default class Search extends Vue {
 news: Array<HackerNewsResult> = [];
 }
</script>
<style scoped>
 .input-field {
 width: 50%;
 }
</style>

这可能是一个起点,许多东西可能会得到增强,但我建议您在应用程序中考虑这个策略,并在代码中替换疯狂的流。

干杯

评论
发表评论
暂无评论
WRITTEN BY
PUBLISHED IN
前端大锅饭

平台每天更新最实用,最新颖的网页设计、网站制作技巧精品文章,分享html+css、JavaScript、jQuery,ps等直播课录像,国内知名设计师亲授!无论你身在何处,和大家一起来利用零碎的时间学习知识,涨见识、涨工资吧!

我的收藏