Hành viSubject vs quan sát?


690

Tôi đang xem xét các mẫu RxJ của Angular và tôi không hiểu sự khác biệt giữa a BehaviorSubjectvà an Observable.

Theo hiểu biết của tôi, a BehaviorSubjectlà một giá trị có thể thay đổi theo thời gian (có thể được đăng ký và người đăng ký có thể nhận được kết quả cập nhật). Đây dường như là cùng một mục đích của một Observable.

Khi nào bạn sẽ sử dụng Observablevs a BehaviorSubject? Có lợi ích gì khi sử dụng BehaviorSubjecthơn một Observablehoặc ngược lại?

Câu trả lời:


968

BehaviorSubject là một loại chủ đề, một chủ đề là một loại đặc biệt có thể quan sát được để bạn có thể đăng ký tin nhắn như bất kỳ thứ gì khác có thể quan sát được. Các tính năng độc đáo của BehaviorSubject là:

  • Nó cần một giá trị ban đầu vì nó phải luôn trả về giá trị khi đăng ký ngay cả khi nó chưa nhận được next()
  • Khi đăng ký, nó trả về giá trị cuối cùng của chủ đề. Một kích hoạt quan sát thường xuyên chỉ kích hoạt khi nó nhận được mộtonnext
  • tại bất kỳ thời điểm nào, bạn có thể truy xuất giá trị cuối cùng của đối tượng trong mã không thể quan sát được bằng getValue()phương thức.

Các tính năng độc đáo của một chủ đề so với một quan sát là:

  • Đây là một người quan sát ngoài việc có thể quan sát được, do đó bạn cũng có thể gửi các giá trị cho một chủ đề ngoài việc đăng ký vào nó.

Ngoài ra, bạn có thể nhận được một quan sát từ chủ thể hành vi bằng asObservable()phương thức trên BehaviorSubject.

Có thể quan sát là Chung và BehaviorSubjectvề mặt kỹ thuật là một loại phụ của Có thể quan sát được vì BehaviorSubject là một loại có thể quan sát được với các phẩm chất cụ thể.

Ví dụ với BehaviorSubject :

// Behavior Subject

// a is an initial value. if there is a subscription 
// after this, it would get "a" value immediately
let bSubject = new BehaviorSubject("a"); 

bSubject.next("b");

bSubject.subscribe(value => {
  console.log("Subscription got", value); // Subscription got b, 
                                          // ^ This would not happen 
                                          // for a generic observable 
                                          // or generic subject by default
});

bSubject.next("c"); // Subscription got c
bSubject.next("d"); // Subscription got d

Ví dụ 2 với chủ đề thông thường:

// Regular Subject

let subject = new Subject(); 

subject.next("b");

subject.subscribe(value => {
  console.log("Subscription got", value); // Subscription wont get 
                                          // anything at this point
});

subject.next("c"); // Subscription got c
subject.next("d"); // Subscription got d

Một quan sát có thể được tạo ra từ cả hai SubjectBehaviorSubjectsử dụng subject.asObservable().

Sự khác biệt duy nhất là bạn không thể gửi các giá trị cho một next()phương thức có thể quan sát được .

Trong các dịch vụ Angular, tôi sẽ sử dụng BehaviorSubjectcho dịch vụ dữ liệu vì dịch vụ góc thường khởi tạo trước đối tượng thành phần và hành vi đảm bảo rằng thành phần tiêu thụ dịch vụ nhận được dữ liệu cập nhật mới nhất ngay cả khi không có cập nhật mới kể từ khi đăng ký thành phần này vào dữ liệu này.


7
Tôi hơi bối rối với ví dụ 2 của chủ đề thông thường. Tại sao đăng ký sẽ không nhận được bất cứ điều gì thậm chí khó khăn hơn trên dòng thứ hai mà bạn gửi giá trị cho chủ đề bằng cách sử dụng topic.next ("b")?
jmod999

25
@ jmod999 Ví dụ thứ hai là một chủ đề thông thường nhận được giá trị ngay trước khi đăng ký được gọi. Trong các môn học thông thường, đăng ký chỉ được kích hoạt cho các giá trị nhận được sau khi đăng ký được gọi. Vì một được nhận ngay trước khi đăng ký, nó không được gửi đến đăng ký.
Chaianu Bhadoria

Một lưu ý về giải pháp tuyệt vời đó, nếu bạn sử dụng nó trong một hàm và trả về nó, sau đó trả về một quan sát được. Tôi đã gặp một số vấn đề với việc trả lại một chủ đề và nó khiến các nhà phát triển khác nhầm lẫn rằng chỉ biết Đài quan sát là gì
sam

8
Tôi đã có một cuộc phỏng vấn Angular 4 vào thứ Tư. Vì tôi vẫn đang học nền tảng mới, anh ấy đã vấp tôi bằng cách hỏi tôi một câu như "Chuyện gì sẽ xảy ra nếu tôi đăng ký vào một mô-đun có thể quan sát được trong một mô-đun chưa được tải lười biếng?" Tôi không chắc, nhưng anh ấy nói với tôi rằng câu trả lời là sử dụng BSubject - CHÍNH XÁC cách ông Bhadoria giải thích nó ở trên. Câu trả lời là sử dụng BSubject vì nó luôn trả về giá trị mới nhất (ít nhất đó là cách tôi nhớ nhận xét cuối cùng của người phỏng vấn về điều đó).
bob.mazzo

1
@ bob.mazzo Tại sao tôi cần sử dụng BSubject cho trường hợp đó? - Nếu tôi đăng ký với Người quan sát đó, tôi sẽ nhận được bất cứ thứ gì vì người quan sát đã không được khởi tạo để nó không thể đẩy dữ liệu đến người quan sát và nếu tôi sử dụng BSubject, tôi sẽ không nhận được bất cứ điều gì vì lý do tương tự. Trong cả hai trường hợp, người đăng ký đã giành được Namt nhận được bất cứ điều gì vì nằm trong một mô-đun đã không được khởi tạo. Tôi có đúng không
Rafael Reyes

183

Có thể quan sát: Kết quả khác nhau cho mỗi Người quan sát

Một sự khác biệt rất rất quan trọng. Vì Observable chỉ là một hàm, nó không có bất kỳ trạng thái nào, vì vậy với mỗi Observer mới, nó sẽ thực thi mã tạo có thể quan sát được nhiều lần. Kết quả này trong:

Mã được chạy cho mỗi người quan sát. Nếu đó là cuộc gọi HTTP, nó sẽ được gọi cho mỗi người quan sát

Điều này gây ra lỗi lớn và không hiệu quả

BehaviorSubject (hoặc Chủ đề) lưu trữ chi tiết người quan sát, chỉ chạy mã một lần và đưa ra kết quả cho tất cả người quan sát.

Ví dụ:

JSBin: http://jsbin.com/qowulet/edit?js,console

// --- Observable ---
let randomNumGenerator1 = Rx.Observable.create(observer => {
   observer.next(Math.random());
});

let observer1 = randomNumGenerator1
      .subscribe(num => console.log('observer 1: '+ num));

let observer2 = randomNumGenerator1
      .subscribe(num => console.log('observer 2: '+ num));


// ------ BehaviorSubject/ Subject

let randomNumGenerator2 = new Rx.BehaviorSubject(0);
randomNumGenerator2.next(Math.random());

let observer1Subject = randomNumGenerator2
      .subscribe(num=> console.log('observer subject 1: '+ num));
      
let observer2Subject = randomNumGenerator2
      .subscribe(num=> console.log('observer subject 2: '+ num));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.3/Rx.min.js"></script>

Đầu ra:

"observer 1: 0.7184075243594013"
"observer 2: 0.41271850211336103"
"observer subject 1: 0.8034263165479893"
"observer subject 2: 0.8034263165479893"

Quan sát cách sử dụng Observable.createđã tạo ra đầu ra khác nhau cho mỗi người quan sát, nhưng BehaviorSubjectđưa ra cùng một đầu ra cho tất cả các nhà quan sát. Điều này quan trọng.


Khác biệt tóm tắt.

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃         Observable                  ┃     BehaviorSubject/Subject         ┃      
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ 
┃ Is just a function, no state        ┃ Has state. Stores data in memory    ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Code run for each observer          ┃ Same code run                       ┃
┃                                     ┃ only once for all observers         ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Creates only Observable             ┃Can create and also listen Observable┃
┃ ( data producer alone )             ┃ ( data producer and consumer )      ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Usage: Simple Observable with only  ┃ Usage:                              ┃
┃ one Obeserver.                      ┃ * Store data and modify frequently  ┃
┃                                     ┃ * Multiple observers listen to data ┃
┃                                     ┃ * Proxy between Observable  and     ┃
┃                                     ┃   Observer                          ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

3
bất cứ ai đến từ KnockoutJS's ko.observable()ngay lập tức sẽ thấy nhiều điểm tương đồng Rx.BehaviorSubjecthơn so vớiRx.Observable
Simon_Weaver

@Skeptor Observable: phương thức đăng ký sẽ luôn kích hoạt phương thức onNext được liên kết với người quan sát và mang lại giá trị trả về. BehaviourSubject / Chủ đề: Sẽ luôn trả về giá trị mới nhất trong luồng. ở đây, việc gán phương thức với chủ đề sẽ không kích hoạt phương thức onNext của Trình quan sát của nó cho đến khi tìm thấy giá trị mới nhất trong luồng.
Mohan Ram

62

Có thể quan sát và cả hai đối tượng đều có thể quan sát được có nghĩa là người quan sát có thể theo dõi chúng. nhưng cả hai đều có một số đặc điểm độc đáo. Hơn nữa, có tổng số 3 loại đối tượng, mỗi đối tượng lại có những đặc điểm riêng. chúng ta hãy cố gắng để hiểu từng người trong số họ.

bạn có thể tìm thấy ví dụ thực tế ở đây trên stackblitz . (Bạn cần kiểm tra bàn điều khiển để xem đầu ra thực tế)

nhập mô tả hình ảnh ở đây

Observables

Họ lạnh lùng: Mã được thực thi khi họ có ít nhất một người quan sát.

Tạo bản sao dữ liệu: Observable tạo bản sao dữ liệu cho mỗi người quan sát.

Uni-directional: Người quan sát không thể gán giá trị cho có thể quan sát được (nguồn gốc / bản gốc).

Subject

Chúng rất hấp dẫn: mã được thực thi và giá trị được phát ngay cả khi không có người quan sát.

Chia sẻ dữ liệu: Cùng một dữ liệu được chia sẻ giữa tất cả các nhà quan sát.

bi-directional: Người quan sát có thể gán giá trị cho có thể quan sát (gốc / chủ).

Nếu đang sử dụng chủ đề thì bạn bỏ lỡ tất cả các giá trị được phát trước khi tạo người quan sát. Vì vậy, đến đây Phát lại chủ đề

ReplaySubject

Chúng rất hấp dẫn: mã được thực thi và giá trị được phát ngay cả khi không có người quan sát.

Chia sẻ dữ liệu: Cùng một dữ liệu được chia sẻ giữa tất cả các nhà quan sát.

bi-directional: Người quan sát có thể gán giá trị cho có thể quan sát (gốc / chủ). thêm

Phát lại luồng tin nhắn: Bất kể khi bạn đăng ký chủ đề phát lại, bạn sẽ nhận được tất cả các tin nhắn được phát.

Trong chủ đề và phát lại chủ đề, bạn không thể đặt giá trị ban đầu thành có thể quan sát được. Vì vậy, đây là chủ đề hành vi

BehaviorSubject

Chúng rất hấp dẫn: mã được thực thi và giá trị được phát ngay cả khi không có người quan sát.

Chia sẻ dữ liệu: Cùng một dữ liệu được chia sẻ giữa tất cả các nhà quan sát.

bi-directional: Người quan sát có thể gán giá trị cho có thể quan sát (gốc / chủ). thêm

Phát lại luồng tin nhắn: Bất kể khi bạn đăng ký chủ đề phát lại, bạn sẽ nhận được tất cả các tin nhắn được phát.

Bạn có thể đặt giá trị ban đầu: Bạn có thể khởi tạo giá trị quan sát được với giá trị mặc định.


3
Có thể đáng nói là một ReplaySubjectlịch sử và có thể phát / phát một chuỗi các giá trị (cũ). Chỉ khi bộ đệm được đặt thành 1, nó mới hoạt động tương tự như a BehaviorSubject.
Héo

28

Đối tượng quan sát đại diện cho một bộ sưu tập dựa trên đẩy.

Các giao diện Observer và Observable cung cấp một cơ chế tổng quát cho thông báo dựa trên đẩy, còn được gọi là mẫu thiết kế quan sát viên. Đối tượng quan sát đại diện cho đối tượng gửi thông báo (nhà cung cấp); đối tượng Observer đại diện cho lớp nhận chúng (người quan sát).

Lớp Chủ đề kế thừa cả Người quan sát và Người quan sát, theo nghĩa là nó vừa là người quan sát vừa là người quan sát. Bạn có thể sử dụng một chủ đề để đăng ký tất cả các quan sát viên, sau đó đăng ký chủ đề vào nguồn dữ liệu phụ trợ

var subject = new Rx.Subject();

var subscription = subject.subscribe(
    function (x) { console.log('onNext: ' + x); },
    function (e) { console.log('onError: ' + e.message); },
    function () { console.log('onCompleted'); });

subject.onNext(1);
// => onNext: 1

subject.onNext(2);
// => onNext: 2

subject.onCompleted();
// => onCompleted

subscription.dispose();

Thông tin khác về https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/subjects.md


sự khác biệt giữa scribe.dispose () và scribe.unubscribe () là gì?
choopage - Jek Bao

4
@choopage không có sự khác biệt. cách thứ hai là cách mới
Royi Namir

Nếu hủy đăng ký trước khi chủ đề được xử lý, nếu không, đăng ký sẽ trở thành rác vì nó đăng ký một giá trị null.
Sophie Zhang

20

Một điều tôi không thấy trong các ví dụ là khi bạn truyền BehaviorSubject thành Observable qua asObservable, nó thừa hưởng hành vi trả về giá trị cuối cùng khi đăng ký.

Đó là một mẹo nhỏ, vì thông thường các thư viện sẽ hiển thị các trường là có thể quan sát được (tức là các thông số trong ActivatedRoute trong Angular2), nhưng có thể sử dụng Chủ đề hoặc Hành vi phía sau hậu trường. Những gì họ sử dụng sẽ ảnh hưởng đến hành vi đăng ký.

Xem tại đây http://jsbin.com/ziquxapubo/edit?html,js,console

let A = new Rx.Subject();
let B = new Rx.BehaviorSubject(0);

A.next(1);
B.next(1);

A.asObservable().subscribe(n => console.log('A', n));
B.asObservable().subscribe(n => console.log('B', n));

A.next(2);
B.next(2);

11

Một quan sát cho phép bạn chỉ đăng ký trong khi một chủ đề cho phép bạn cả xuất bản và đăng ký.

Vì vậy, một chủ đề cho phép các dịch vụ của bạn được sử dụng làm nhà xuất bản và người đăng ký.

Đến bây giờ, tôi không giỏi lắm Observablenên tôi chỉ chia sẻ một ví dụ về Subject.

Hãy hiểu rõ hơn với một ví dụ CLI góc . Chạy các lệnh dưới đây:

npm install -g @angular/cli

ng new angular2-subject

cd angular2-subject

ng serve

Thay thế nội dung của app.component.htmlbằng:

<div *ngIf="message">
  {{message}}
</div>

<app-home>
</app-home>

Chạy lệnh ng g c components/homeđể tạo thành phần nhà. Thay thế nội dung của home.component.htmlbằng:

<input type="text" placeholder="Enter message" #message>
<button type="button" (click)="setMessage(message)" >Send message</button>

#messagelà biến cục bộ ở đây. Thêm một tài sản message: string; cho app.component.tslớp của.

Chạy lệnh này ng g s service/message. Điều này sẽ tạo ra một dịch vụ tại src\app\service\message.service.ts. Cung cấp dịch vụ này cho ứng dụng .

Nhập Subjectvào MessageService. Thêm một chủ đề quá. Mã cuối cùng sẽ trông như thế này:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class MessageService {

  public message = new Subject<string>();

  setMessage(value: string) {
    this.message.next(value); //it is publishing this value to all the subscribers that have already subscribed to this message
  }
}

Bây giờ, đưa dịch vụ này vào home.component.tsvà chuyển một thể hiện của nó cho hàm tạo. Làm điều này cho app.component.tsquá. Sử dụng ví dụ dịch vụ này để truyền giá trị của #messagehàm dịch vụ setMessage:

import { Component } from '@angular/core';
import { MessageService } from '../../service/message.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent {

  constructor(public messageService:MessageService) { }

  setMessage(event) {
    console.log(event.value);
    this.messageService.setMessage(event.value);
  }
}

Bên trong app.component.ts, đăng ký và hủy đăng ký (để tránh rò rỉ bộ nhớ) vào Subject:

import { Component, OnDestroy } from '@angular/core';
import { MessageService } from './service/message.service';
import { Subscription } from 'rxjs/Subscription';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {

  message: string;
  subscription: Subscription;

  constructor(public messageService: MessageService) { }

  ngOnInit() {
    this.subscription = this.messageService.message.subscribe(
      (message) => {
        this.message = message;
      }
    );
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Đó là nó.

Bây giờ, bất kỳ giá trị nhập vào bên trong #messagecủa home.component.htmlsẽ được in để {{message}}bên trongapp.component.html


Tại sao hình ảnh khổng lồ? Nếu nó không liên quan trực tiếp đến câu trả lời của bạn, thì có vẻ như bỏ phiếu.
ruffin

@ruffin Đây chỉ là một câu trả lời trung bình với số phiếu trung bình, hãy xem hồ sơ của tôi. Không chắc chắn bỏ phiếu: D
xameeramir

1
Tôi đã đưa cho bạn một upvote trước đó, nhưng bạn đã né tránh câu hỏi tại sao hình ảnh ở đó. Nó không liên quan trực tiếp đến câu trả lời của bạn. Không quan trọng bạn có nhiều đại diện hay không - nếu hình ảnh không trực tiếp và cụ thể là làm sáng tỏ, tôi yêu cầu bạn xóa nó . / nhún vai
ruffin

1
@ruffin Nếu nó đi ngược lại sự đồng ý của cộng đồng, thì chắc chắn nó không nên ở đó!
xameeramir

4

app.component.ts

behaviourService.setName("behaviour");

behavioriour.service.ts

private name = new BehaviorSubject("");
getName = this.name.asObservable();`

constructor() {}

setName(data) {
    this.name.next(data);
}

custom.component.ts

behaviourService.subscribe(response=>{
    console.log(response);    //output: behaviour
});

1

BehaviorSubject vs Observable : RxJS có người quan sát và quan sát được, Rxjs cung cấp nhiều lớp để sử dụng với các luồng dữ liệu và một trong số đó là BehaviorSubject.

Đài quan sát : Đài quan sát là bộ sưu tập lười biếng của nhiều giá trị theo thời gian.

BehaviorSubject : Chủ đề yêu cầu giá trị ban đầu và phát ra giá trị hiện tại của nó cho những người đăng ký mới.

 // RxJS v6+
import { BehaviorSubject } from 'rxjs';

const subject = new BehaviorSubject(123);

//two new subscribers will get initial value => output: 123, 123
subject.subscribe(console.log);
subject.subscribe(console.log);

//two subscribers will get new value => output: 456, 456
subject.next(456);

//new subscriber will get latest value (456) => output: 456
subject.subscribe(console.log);

//all three subscribers will get new value => output: 789, 789, 789
subject.next(789);

// output: 123, 123, 456, 456, 456, 789, 789, 789

1

Hãy nghĩ về Đài quan sát như một đường ống có nước chảy trong đó, đôi khi nước chảy và đôi khi không. Trong một số trường hợp, bạn thực sự có thể cần một đường ống luôn có nước trong đó, bạn có thể làm điều này bằng cách tạo ra một đường ống đặc biệt luôn chứa nước dù nó nhỏ đến mức nào, hãy gọi ống này là BehaviorSubject đặc biệt , nếu bạn tình cờ một nhà cung cấp nước trong cộng đồng của bạn, bạn có thể ngủ yên vào ban đêm khi biết rằng đường ống mới được lắp đặt của bạn chỉ hoạt động.

Về mặt kỹ thuật: bạn có thể gặp phải các trường hợp sử dụng trong đó một Observable phải luôn có giá trị trong đó, có lẽ bạn muốn nắm bắt giá trị của văn bản đầu vào theo thời gian, sau đó bạn có thể tạo một thể hiện của BehaviorSubject để đảm bảo loại hành vi này, giả sử:


const firstNameChanges = new BehaviorSubject("<empty>");

// pass value changes.
firstNameChanges.next("Jon");
firstNameChanges.next("Arya");

Sau đó, bạn có thể sử dụng "giá trị" để thay đổi mẫu theo thời gian.


firstNameChanges.value;

Điều này rất hữu ích khi bạn kết hợp các Đài quan sát sau đó, bằng cách xem loại luồng của bạn là BehaviorSubject, sau đó bạn có thể đảm bảo rằng luồng đó ít nhất phát ra hoặc báo hiệu chỉ một lần ít nhất .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.