I. Descripción general
RxJs
Es una biblioteca especialmente diseñada para manejar operaciones asincrónicas JavaScript
. RxJs
Proporciona una clase principal: Observable
, varias clases clave: Observer
, Schedulers
, Subjects
. Piense RxJs
en ello como orientado a eventos Lodash
.
RxJs
La biblioteca se basa en funciones puras, por lo que tiene características potentes y funciones estables.
RxJs
La biblioteca tiene un conjunto completo de operadores para controlar el flujo de eventos en objetos observables, que pueden monitorear y controlar el flujo de eventos en varias etapas.
RxJs
Hay varios conceptos clave en él:
- Observables (
Observable
). Se puede comparar conPromise
un objeto dentro del cual se pueden realizar operaciones asincrónicas y, una vez completada la operación asincrónica, los resultados se transfieren al exterior del objeto observable. Observer
La función de devolución de llamada en el método analógico observador ( )then
se utiliza para recibir datos pasados por el objeto observable.- Método de analogía de suscripción (
Subscribe
)then
, cuando el objeto observable emite datos, el suscriptor puede recibir los datos.
2. observables
- Método de creación:
new Observable()
Creado a través del constructor, recibiendo un método como parámetro. Este método tiene un parámetro de suscriptor, a través del cualnext()
se pueden enviar datos.
const observable = new Observable((subscriber) => {
subscriber.next(1);
})
Observable
El código del objeto es diferido y diferente delPromise
objeto, se ejecuta inmediatamente cuando se crea,Observable
el código del objeto solo se ejecutará cuando esté suscrito.Observable
Las suscripciones a objetos requierenobservable.subscribe()
llamadas a métodos, similares afunction.call()
las llamadas a funciones.observable.subscribe()
El método acepta un objeto como parámetro y contiene una función de devolución de llamada después de que el objeto observable emite datos. CuandoObservable
el objeto ejecuta el método,subscriber.next()
se activaráobserver.next
la función yobserver.next
la función puede recibirsubscriber.next()
los parámetros del método.
const observer = {
next: (value: any) => {
console.log(value);
}
}
observable.subscribe(observer);
Observable
El método del objetosubscriber.next()
se puede llamar varias veces y cada llamada seráobserver.next
monitoreada.Observable
Un objeto se puede suscribir varias veces y el código que contiene se ejecutará cada vez que se suscriba.
Por ejemplo, en el siguiente código,Observable
el objeto está suscrito a dos observadores diferentes.
const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
})
const observer1 = {
next: (value: any) => {
console.log("我是订阅者一号,接收到:"+value);
}
}
const observer2 = {
next: (value: any) => {
console.log("我是订阅者二号,接收到:"+value);
}
}
observable.subscribe(observer1);
observable.subscribe(observer2);
Salida de consola:
observable.subscribe()
El método se puede comparar confunction.call()
, que ejecutará la llamada a la función y obtendrá el valor de retorno de la ejecución de la función. Perocall()
el método solo se puede usarreturn
una vez y solo puede recibir el valor de retorno de un evento sincrónico, mientras quesubscribe()
el método se puede usarnext
varias veces y puede recibir el valor de retorno de todos los eventos sincrónicos y asincrónicos.
const observable = new Observable((subscriber) => {
subscriber.next(1);
setTimeout(function () {
subscriber.next(2);
})
})
const observer1 = {
next: (value: any) => {
console.log(value);
}
}
observable.subscribe(observer1);
Observable
El código en el objeto se ejecuta sincrónicamente.
const observable = new Observable((subscriber) => {
subscriber.next(1);
setTimeout(function () {
subscriber.next(2);
})
})
const observer1 = {
next: (value: any) => {
console.log(value);
}
}
console.log('just before subscribe');
observable.subscribe(observer1);
console.log('just after subscribe');
Observable
subscriber
Además denext
los métodos utilizados para enviar datos, hay dos métodos en el objeto :complete()
el método se utiliza para decirle al observador que se envían los datos;error(errorMessage)
el método se utiliza para decirle al observador que se ha producido un error interno. En consecuencia, además de los atributos,observer
los observadores también tienen atributos y propiedades, que apuntan a eventos y funciones de devolución de llamada de eventos, respectivamente. Los métodos no pueden pasar parámetros. Además, ya sea un método o un método, significa que la observación ha terminado, si se utiliza para enviar datos más tarde, el observador no puede observarlos.next
complete
error
complete
error
complete
subscriber.complete()
subscriber.error(errorMessage)
subscriber.next()
const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.complete();
subscriber.next(2); // 不能输出了
})
const observer1 = {
next: (value: any) => {
console.log(value);
},
complete: () => {
console.log('complete');
}
}
observable.subscribe(observer1);
const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.error('发生了一个致命的错误');
subscriber.next(2);
})
const observer1 = {
next: (value: any) => {
console.log(value);
},
error: (error: any) => {
console.log(error);
}
}
observable.subscribe(observer1);
三、Asunto
Observable
El código del objeto observable creado a través del constructor se ejecutará una vez cada vez que se suscriba un observador. El objeto observable creado al usarlo Subject
es un objeto observable vacío. Al suscribirse, no se ejecutará de inmediato. Debe llamar manualmente a este objeto observable para enviar datos. Los escenarios relacionados con la radiodifusión pueden utilizar este tipo de objeto observable. Puede suscribirse a este objeto observable en varios lugares y llamar al método después de obtener los datos next
. En este momento, todos los suscriptores recibirán los datos al mismo tiempo. El objeto observable creado con Subject
él es de multidifusión, lo que permite pasar valores a múltiples observadores; el Observable
objeto observable creado con él es de unidifusión y cada suscriptor tiene Observable
una ejecución independiente.
// 创建一个空的可观察对象
const subject = new Subject();
// 有多个观察者订阅这个可观察对象
subject.subscribe({
next: (v) => console.log(`observerA: ${
v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${
v}`)
});
// 异步执行next方法获取数据,数据会被发送给所有的观察者
setTimeout(() => subject.next(1), 1000);
setTimeout(() => subject.next(2), 2000);
四、ComportamientoAsunto
BehaviorSubject
Es Subject
una variante, guarda los últimos datos enviados, es decir, el "valor actual". Cuando un observador se suscribe se obtienen los últimos datos enviados. BehaviorSubject
Adecuado para expresar "valor en el tiempo". BehaviorSubject
Se puede recibir un valor predeterminado. El primer observador lo ejecutará inmediatamente al suscribirse y recibirá este valor predeterminado.
const behaviorSubject = new BehaviorSubject('默认值');
// 订阅的时候会立即执行一次,输出:ObserverA默认值
behaviorSubject.subscribe(value => {
console.log('ObserverA'+value)
})
// 执行并被ObserverA观察到,输出:ObserverA第一次改变值
behaviorSubject.next('第一次改变值')
// 执行,被ObserverA观察到,输出:ObserverA第二次改变值
behaviorSubject.next('第二次改变值')
// 又一个订阅,此时behaviorSubject保存的是最新发送出去的值:第二次改变值
// 虽然这个订阅在next执行之后,但是仍然可以获取behaviorSubject里面保存的最新的数据
// 输出:ObserverB第二次改变值
behaviorSubject.subscribe(value => {
console.log('ObserverB'+value)
})
五、RepetirAsunto
ReplaySubject
Guarde todos los datos enviados. ReplaySubject
Registre Observable
múltiples valores durante la ejecución y reprodúzcalos a un nuevo observador. Al crear ReplaySubject
, puede especificar cuántos valores reproducir y proporcionar los valores más recientes al nuevo observador.
// 规定回放的次数为2.即当有新的订阅者的时候,
// 会查看历史发送数据,并把最近的2条数据发送给新的订阅者
const replaySubject = new ReplaySubject(2);
// 这个订阅者会接收到replaySubject的所有数据
replaySubject.subscribe(value => {
console.log(`replaySubjectA: ${
value}`)
})
replaySubject.next(1);
replaySubject.next(2);
replaySubject.next(3);
// 这个订阅者会接收之前的两条数据以及之后的所有数据
replaySubject.subscribe(value => {
console.log(`replaySubjectB: ${
value}`)
})
6. Operador
Los operadores son Observable
métodos de tipos, como .map(...)
, .filter(...)
, .merge(...)
, etc. Un operador es esencialmente una función pura (pure function)
. Los operadores son RxJs
la parte más útil de la biblioteca. Los operadores se dividen en operadores de instancia y operadores estáticos. Los operadores de instancia son Observable
métodos de instancias y los operadores estáticos son Observable
métodos estáticos de clases. El operador estático más común es el operador de creación, que se utiliza para crear Observable对象
.
1.de
of
Se utiliza para crear parámetros simples Observable
y emitir los parámetros dados.
const observableOf = of(1, '第二个元素', {
name: '第三个元素'});
observableOf.subscribe(
value => console.log(value)
)
2.de
Crea uno a partir de una matriz, un objeto similar a una matriz, Promise
un objeto iterador o un objeto similar a un objeto .Observable
Observable
const arr = [1, 2, 3];
// from创建一个Observable对象,依次将数组中的元素发射出去
const observable1 = from(arr)
observable1.subscribe((value) => {
console.log(`数字数组元素:${
value}`)
})
const promiseArr = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];
// 依次将数组中的promise对象发射出去
const observable2 = from(promiseArr)
observable2.subscribe((value) => {
// value是每一个promise对象元素
value.then((value) => {
console.log(`Promise.then接收到的数据:${
value}`)
})
})
from
Los métodos pueden Promise
convertir objetos en Observable
objetos. Los datos enviados por promise
el objeto serán recibidos por la función de devolución de llamada del observador.resolve
next
// from方法可以将Promise对象转换为Observable对象
const promise = new Promise(resolve => {
setTimeout(() => {
resolve('promise resolved')
}, 1000)
})
const obs1 = from(promise)
obs1.subscribe(value => console.log(value))
3.desdeEvento
público estático fromEvent (objetivo: EventTargetLike, eventName: cadena, opciones: EventListenerOptions, selector: SelectorMethodSignature): Observable
Parámetro uno: DOM
elemento o NodeList
Parámetro dos: nombre del evento click
, mousedown
etc.
Parámetro tres: valor opcional, parámetros pasados a la función de escucha de eventos
Parámetro cuatro: valor opcional, resultado del procesamiento de la función, parámetros de recepción de la función de procesamiento de eventos, se debe devolver un valor único
// 第三个参数是配置对象,once:true表示只执行一次
// 第四个参数是点击事件执行完毕的回调函数
const observable = fromEvent(document, 'click',{
once:true},(event)=>{
console.log(event)
console.log('执行了');
});
observable.subscribe((event) => {
// 如果fromEvent中传递了第四个参数,即点击事件执行完毕的回调函数,
// 那么这里的回调函数不会接收到任何参数
// 如果fromEvent中没有传递第四个参数,这里会接受到一个event对象
console.log(event)
console.log('document被点击');
})
4.mapa
La
función de mapa es similar al método de la matriz map
.
// 返回一个操作方法,接受一个observable对象作为参数
// 对observable对象发出的每一个值进行处理,返回一个新的observable对象
const operatorFunction = map((v: number) => v * v);
const result = operatorFunction(of(1, 2, 3));
result.subscribe(x => console.log(x));
map
Método de simulación
// 模拟map方法
// 1.接受一个函数作为参数
// 2.返回值也是一个函数
// 3.返回的这个函数,需要接受一个observable对象作为参数,
// 4.返回值是一个新的observable对象,并且发出的数据是对observable参数发出的数据进行fn处理
function myMap(fn: (arg0: any) => void) {
return function (observable: any) {
return Observable.create((observer: any) => {
observable.subscribe((value: any) => {
observer.next(fn(value));
})
})
}
}
5.forkUnirse
forkJoin
es similar a Promise.all
un método. Suponiendo que hay varios Observable
objetos que envían datos, forkJoin
puede esperar a que todos Observable
los objetos envíen datos y devuelvan un Observable
objeto. Los métodos asincrónicos mediante forkJoin
composición se ejecutan en paralelo.
// 模拟axios请求
const getUsername =function (){
return new Promise(resolve => {
setTimeout(() => {
resolve('张三')
}, 1000)
})
}
const getAge =function (){
return new Promise(resolve => {
setTimeout(() => {
resolve(78)
}, 2000)
})
}
// forkJoin()接受一个对象,对象指向的是Observable对象发出的数据
// forkJoin()返回一个Observable对象。
// subscribe里面直接传递回调函数就表示对发出的数据执行这个函数
forkJoin({
username: from(getUsername()),
age: from(getAge())
}).subscribe(console.log)
6.arrancar
pluck
Si Observable
los datos emitidos por el objeto son un objeto, puede usarlo pluck
para obtener las propiedades correspondientes. Acepta una cadena como parámetro, recorre los datos emitidos y obtiene los atributos correspondientes.
const observable = new Observable(subscriber => {
subscriber.next({
name: 'Brian'})
subscriber.next({
name: 'Joe'})
subscriber.next({
name: 'Sue'})
})
const pluckFn = pluck('name')
pluckFn(observable).subscribe(console.log)
7.intervalo
Envíe una cantidad incremental de datos cada período de tiempo. Acepta un intervalo de tiempo como argumento.
interval(1000).subscribe((value) => {
console.log(value);
})
Enviar datos cada 1000ms
8.tubería()
En RxJS
la biblioteca, pipe
es un método de programación funcional que se utiliza para unir varios operadores para transformar el Observable
flujo de datos. Debido a que RxJS
hay muchas transformaciones de datos y operadores presentes en , y anidar estas operaciones directamente dificultaría la lectura y el mantenimiento del código, puede usar pipe
métodos para combinar operadores en una canalización. Similar a lodash
la combinación de funciones en, el siguiente código equivale a of(1,2,3)
ejecutar map
el procesamiento primero, luego ejecutar filter
el procesamiento y finalmente devolver uno nuevo procesado Observable
.
of(1,2,3)
.pipe(
map(x => x * 2),
filter(x => x > 3)
).subscribe(console.log)
9.cambiarMapa
switchMap
emite el resultado de aplicar la función de proyección a cada elemento emitido Observable
por la fuente y solo recibe el último valor interno proyectado.Observable
Observable
let observable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
})
const newObservable = observable.pipe(switchMap(value => {
return of(value + '被switchMap了');
}))
newObservable.subscribe(console.log);
10.tomar
Recibe un número como argumento y devuelve los primeros del flujo de datos.
ngOnInit(): void {
of(1,2,3)
.pipe(
take(2)
).subscribe(console.log)
}
11.tomar mientras
Acepta una función condicional como parámetro y devuelve un flujo de datos que satisface la condición. Busque de adelante hacia atrás y deténgase directamente si no se cumplen las condiciones.
of(1,100,3)
.pipe(
takeWhile(value => value < 10)
).subscribe(console.log)
12. tomar hasta
Acepta un objeto observable como parámetro. Cuando el objeto observable emite datos, la fuente de datos principal dejará de enviar datos.
// 创建一个异步发出数据的Observable
const observable= new Observable(subscriber => {
setTimeout(() => {
subscriber.next(1)
},5000)
})
// 使用Interval创建一个Observable
// 当oberable发出数据时,使用takeUntil停止
interval(1000)
.pipe(takeUntil(observable))
.subscribe(console.log)
La consola dejará de imprimir después de imprimir 4 números.
13.tiempo del acelerador
Acelerador. Cuando los eventos observables envían datos al exterior con alta frecuencia, para evitar activar devoluciones de llamadas de suscripción con frecuencia, utilice throttleTime
operadores para pasar un intervalo de tiempo como parámetro para garantizar que los datos solo se envíen una vez dentro del intervalo de tiempo especificado. Si se hace clic con frecuencia en el siguiente código en la página, solo se activará la función de devolución de llamada del suscriptor una vez en 1 segundo.
fromEvent(document, 'click')
.pipe(throttleTime(1000))
.subscribe(() => console.log('Clicked!'));
14.tiempo de rebote
Anti vibración. Acepta un intervalo de tiempo como parámetro y solo responde a los últimos datos enviados en este intervalo de tiempo. Si el evento se activa nuevamente dentro de este intervalo, la función de devolución de llamada del suscriptor no se activará. La función de devolución de llamada del suscriptor se activará solo si el evento se activa solo una vez dentro de un cierto intervalo de tiempo. Se hace clic con frecuencia en el siguiente código en la página. La función de devolución de llamada del suscriptor solo se activará cuando se haga clic en la página solo una vez en 1 segundo.
fromEvent(document, 'click')
.pipe(debounceTime(1000))
.subscribe(() => console.log('Clicked!'));
La limitación es adecuada para escenarios en los que los usuarios hacen clic, porque al hacer clic esperan resultados inmediatos. Para evitar clics repetidos, se agrega limitación. Anti-vibración es adecuado para escenarios de entrada del usuario, ya que retrasa la ejecución de la función de devolución de llamada porque es posible que el evento de entrada aún no haya finalizado. Por lo tanto, la limitación es adecuada para escenarios en los que una operación surte efecto y la antivibración es adecuada para escenarios en los que los resultados se obtienen mediante múltiples operaciones.
15.distinto hasta el cambio
distintivoUntilChanged
no necesita recibir parámetros. Compruebe si los datos enviados esta vez son los mismos que los datos enviados la última vez. Si son iguales se omiten, si son diferentes se pasan al suscriptor.
of(1, 1, 2, 2,3)
.pipe(distinctUntilChanged())
.subscribe(console.log);
7. Caso 1: Arrastrar elementos con el mouse
En plantilla de componente
<style>
#box{
width: 100px;
height: 100px;
background-color: pink;
position: absolute;
left: 0;
top: 0;
}
</style>
<!--使用#标识需要在组件类中获取的html元素-->
<div id="box" #box></div>
En clase de componente
// 实现元素拖拽
// 1.鼠标按下的时候,计算鼠标相对于box的位置,即鼠标相对于视口的位置-box相对于视口的偏移量
// 2.为document增加mousemove事件,并计算鼠标相对于视口的位置:clientX和clientY
// 3.鼠标移动的时候,计算鼠标相对于视口的位置:clientX和clientY
// 4.设置box的偏移量:鼠标相对于视口的位置-鼠标相对于box的位置
// 5.鼠标抬起的时候,移除mousemove事件
// 使用ViewChild获取dom元素
@ViewChild('box') box: ElementRef | undefined;
ngAfterViewInit() {
// 移动事件
const moveObservable = fromEvent(document, 'mousemove')
.pipe(takeUntil(fromEvent(document, 'mouseup')))
// this.box.nativeElement为dom元素。?为可选链,防止报错
// fromEvent为dom元素绑定事件并将事件转为Observable
fromEvent(this.box?.nativeElement, 'mousedown').pipe(
// 处理数据的操作都放在操作符中
map(event => ({
// 获取鼠标相对于box的位置
distanceX: (event as MouseEvent).clientX - this.box?.nativeElement.offsetLeft,
distanceY: (event as MouseEvent).clientY - this.box?.nativeElement.offsetTop
})),
// 流式操作,这里获取的是map处理后的结果
// 使用对象结构赋值直接获取distanceX和distanceY
switchMap(({
distanceX, distanceY}) =>
fromEvent(document, 'mousemove').pipe(
// 当鼠标抬起时停止移动事件数据的发送
takeUntil(fromEvent(document, 'mouseup')),
map(moveEvent => ({
left: (moveEvent as MouseEvent).clientX - distanceX,
top: (moveEvent as MouseEvent).clientY - distanceY
})
)
)))
// 使用解构赋值直接获取left和top
.subscribe(({
left, top}) => {
this.box!.nativeElement.style.left = left + 'px';
this.box!.nativeElement.style.top = top + 'px';
})
}
8. Caso 2: Búsqueda
En plantilla de componente
<input type="text" placeholder="请输入搜索内容" #search>
En clase de componente
@ViewChild('search') search: ElementRef | undefined;
ngAfterViewInit() {
fromEvent(this.search?.nativeElement, 'input')
.pipe(
debounceTime(1000), // 防抖。适用于输入框,防止用户输入过快
map((event: any) => event.target.value), // 获取输入的内容
distinctUntilChanged(), // 过滤掉重复的值。如果用户删除之后快速输入,和上次的值一样,就阻止触发请求
switchMap((value: string) =>
this.response(value) // switchMap应该返回Observable,但是这里返回Promise也正常运行了,应该是能自动转成Obervable
)
)
.subscribe((res: any) => {
console.log(res)
}
)
}
// 模拟一个请求
private response(data: any) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: data,
status: 200
})
}, 1000)
})
}
9. Caso 3: Solicitud en serie
Hacer clic en un botón requiere enviar dos solicitudes en secuencia. Los parámetros de la segunda solicitud deben depender del valor de retorno de la primera solicitud.
Plantilla de componente
<button #btn>点击获取数据</button>
Clase de componente
@ViewChild('btn') btn: ElementRef | undefined;
ngAfterViewInit() {
fromEvent(this.btn?.nativeElement, 'click')
.pipe(
concatMap(event=>this.response('Hello')),
pluck('data'),
concatMap((data: any) => this.response(data + ' Angular'))
)
.subscribe((res: any) => {
console.log(res)
}
)
}
// 模拟一个请求
private response(data: any) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: data,
status: 200
})
}, 1000)
})
}
Después de hacer clic, obtienes el resultado de la segunda solicitud.