最近自己的一个项目使用了 RxJS 进行状态管理, 需要在组件中订阅流来获取全局状态, 但是在组件卸载的时候需要手动取消订阅流.
这样写起来有点繁琐,所以考虑用装饰器简化下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import * as React from "react"; import { Subscription } from "rxjs"; import { action$, state$ } from "../store/store";
class Artist extends React.Component { sub: Subscription; handleClick() { setInterval(() => { action$.next((state: any) => state); }, 1000); } componentDidMount() { this.sub = state$.subscribe(v => { console.log("artist", v); }); } componentWillUnmount() { console.log("artist unsubscribe"); this.sub.unsubscribe(); } render() { return ( <div> <button onClick={() => this.handleClick()}>Click Me</button> </div> ); } }
export default Artist;
|
我们先实现一个组件卸载的时候取消订阅所有流的装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Subscription } from "rxjs";
export function unsubscribeAll(target: any) { const originHook = target.prototype.componentWillUnmount; target.prototype.componentWillUnmount = function() { for (let prop in this) { const val = this[prop]; if (val && val instanceof Subscription) { val.unsubscribe(); } } originHook && typeof originHook === "function" && originHook.apply(this, arguments); }; }
|
使用方法
1 2 3 4
| @unsubscribeAll class Artist extends React.Component { }
|
但是有时候我并不想取消所有的流,只是想取消订阅指定的流呢? 我们可以这样实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
export function unsubscribe(...list: Array<string>) { return function(target: any) { const originHook = target.prototype.componentWillUnmount; target.prototype.componentWillUnmount = function() { for (let prop in this) { const val = this[prop]; if (list.indexOf(prop) > -1 && val && val instanceof Subscription) { val.unsubscribe(); } } originHook && typeof originHook === "function" && originHook.apply(this, arguments); }; }; }
|
使用方法
1 2 3 4
| @unsubscribe("subscription1", "subscription2") class Artist extends React.Component { }
|
RxJS 取消订阅并非只有unsubscribe
, takeUtil
和take
等也是可以的, 例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import * as React from "react"; import { Subscription, Subject } from "rxjs"; import { action$, state$ } from "../store/store"; import { takeUntil } from "rxjs/operators";
class Artist extends React.Component { sub: Subscription; unmount$ = new Subject(); handleClick() { setInterval(() => { action$.next((state: any) => state); }, 1000); } componentDidMount() { this.sub = state$.pipe(takeUntil(this.unmount$)).subscribe(v => { console.log("artist", v); }); } componentWillUnmount() { this.unmount$.next(); } render() { return ( <div> <button onClick={() => this.handleClick()}>Click Me</button> </div> ); } }
export default Artist;
|
其实这样代码量还是不少,但是在订阅比较多的情况下共用一个 unmount$ 来取消其他相关订阅还是减少了不少代码量, 相比原始方案还是有优势, 相比装饰器就没太大的优势了。
其他
对 TypeScript 的类型还是不够熟练, 写成 AnyScript 了, 捂脸。
参考链接
Automagically Unsubscribe in Angular
RxJS: Don’t Unsubscribe
写于 2018-07-10 晚