Когда использовать asObservable() в rxjs?
Мне интересно, какая польза от asObservable
:
Согласно документам:
Наблюдаемая последовательность, которая скрывает идентичность исходной последовательности.
Но зачем вам нужно скрывать последовательность?
4 ответа
Когда вы не хотите пропускать "сторону наблюдателя" Subject
из вашего API. (В основном для предотвращения негерметичной абстракции).
var myAPI = {
getData: () => {
var subject = new Subject();
var source = new SomeWeirdDataSource();
source.onMessage = (data) => subject.next({ type: 'message', data });
source.onOtherMessage = (data) => subject.next({ type: 'othermessage', data });
return subject.asObservable();
}
};
Теперь, когда кто-то получает наблюдаемый результат от myAPI.getData()
они не могут next
значения в результате:
var result = myAPI.getData();
result.next('LOL hax!'); // throws an error because `next` doesn't exist
А Subject
может действовать как observer
и observable
.
An Obervable
имеет 2 метода.
- подписываться
- отказаться от подписки
Всякий раз, когда вы подписываетесь наobservable
вы получите observer
в котором есть методы next, error и complete.
Вам нужно будет скрыть последовательность, потому что вы не хотите, чтобы источник потока был общедоступным в каждом компоненте. Вы можете обратиться к@BenLesh
Например, для того же.
PS: Когда я впервые столкнулся с реактивным Javascript, я не мог понять asObservable
. Потому что мне нужно было четко понять основы, а затем перейти кasObservable
.:)
(Только Typescript) Используйте типы вместо asObservable()
Мне нравится то, что Алекс Вайда говорит об использовании типов вместо этого, поэтому я собираюсь добавить дополнительную информацию для пояснения.
Если вы используете asObservable(), вы используете следующий код.
/**
* Creates a new Observable with this Subject as the source. You can do this
* to create customize Observer-side logic of the Subject and conceal it from
* code that uses the Observable.
* @return {Observable} Observable that the Subject casts to
*/
asObservable(): Observable<T> {
const observable = new Observable<T>();
(<any>observable).source = this;
return observable;
}
Это полезно для Javascript, но не требуется в Typescript. Я объясню почему ниже.
Пример
export class ExampleViewModel {
// I don't want the outside codeworld to be able to set this INPUT
// so I'm going to make it private. This means it's scoped to this class
// and only this class can set it.
private _exampleData = new BehaviorSubject<ExampleData>(null);
// I do however want the outside codeworld to be able to listen to
// this data source as an OUTPUT. Therefore, I make it public so that
// any code that has reference to this class can listen to this data
// source, but can't write to it because of a type cast.
// So I write this
public exampleData$ = this._exampleData as Observable<ExampleData>;
// and not this
// public exampleData$ = this._exampleData.asObservable();
}
Оба делают одно и то же, но не добавляют в вашу программу дополнительные вызовы кода или выделение памяти.
❌
this._exampleData.asObservable();
❌
Требует дополнительного выделения памяти и вычислений во время выполнения.
✅
this._exampleData as Observable<ExampleData>;
✅
Обрабатывается системой типов и НЕ добавляет дополнительный код или выделение памяти во время выполнения.
Вывод
Если ваши коллеги попытаются это сделать,
referenceToExampleViewModel.exampleData$.next(new ExampleData());
, то он будет перехвачен во время компиляции и система типов не позволит, потому что
exampleData$
был брошен на
Observable<ExampleData>
и больше не тип
BehaviorSubject<ExampleData>
, но они смогут слушать(
.subscribe()
) к этому источнику данных или расширить его (
.pipe()
).
Это полезно, когда вы хотите, чтобы конкретный сервис или класс устанавливали источник информации, и когда вы не хотите, чтобы люди устанавливали этот источник информации волей-неволей из случайных мест в вашей кодовой базе.
В дополнение к этому ответу я хотел бы упомянуть, что, на мой взгляд, это зависит от используемого языка.
Для нетипизированных (или слабо типизированных) языков, таких как JavaScript, может иметь смысл скрыть исходный объект от вызывающего, создав объект делегата, как это делает метод. Хотя, если подумать, это не помешает звонящему сделать
observable.source.next(...)
. Таким образом, этот метод не предотвращает утечку Subject API, но действительно делает его более скрытым от вызывающего.
С другой стороны, для строго типизированных языков, таких как TypeScript, метод
asObservable()
не имеет особого смысла (если таковой имеется). Статически типизированные языки решают проблему утечки API, просто используя систему типов (например, интерфейсы). Например, если ваш
getData()
метод определяется как возвращающий
Observable<T>
тогда вы можете смело вернуть оригинал
Subject
, и вызывающий получит ошибку компиляции при попытке вызвать
getData().next()
в теме.
Подумайте об этом модифицированном примере:
let myAPI: { getData: () => Observable<any> }
myAPI = {
getData: () => {
const subject = new Subject()
// ... stuff ...
return subject
}
}
myAPI.getData().next() // <--- error TS2339: Property 'next' does not exist on type 'Observable<any>'
Конечно, поскольку все это компилируется в JavaScript, в конце концов, могут быть случаи, когда вы захотите создать делегата. Но я хочу сказать, что место для этих случаев намного меньше, чем при использовании ванильного JavaScript , и, вероятно, в большинстве случаев вам не нужен этот метод.