从命令式到响应式(六)

news/2025/2/12 19:19:50

从这个系列的第一章开始到第五章,基于rxjs的响应式编程的基础知识基本上就介绍完了,当然有很多知识点没有提到,比如 Scheduler, behaviorSubject,replaySubject等,不是他们不重要,而是碍于时间、精力等原因没办法一一详细介绍。从这章开始将把响应式放在angular的大环境中,看如何在实际项目中去使用,当然这些都是个人在使用中的一些经验,如有不妥,欢迎指正。

另外本章开始的示例代码可能只是一些片段,或思路,正式要跑起来需要各位自己将代码放入正确的环境中。

angular中响应式接口无处不在

既然 angular 中内置了rxjs,必须有好多地方都能找到响应式的影子,客官请看:

ActivatedRoute - 经常用它来获取路由上的信息,比如传递的参数等。

export interface ActivatedRoute {url: Observable<UrlSegment[];>params: Observable<Params>;queryParams: Observable<Params>;fragment: Observable<string>;data: Observable<Data>;get paramMap: Observable<ParamMap>;get queryParamMap: Observable<ParamMap>;toString(): string;
}

AbstractControl - FormControl的基类,尤其响应式表单中,你一定见过它。

export abstract class AbstractControl {get valueChanges: Observable<any>;get statusChanges: Observable<any>;
}

Http - 这个更不用说,使用Http通信的项目离了它简直了没法干活。

export class Http {get(url: string, options?: RequestOptionsArgs): Observable<Response>;post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response>;head(url: string, options?: RequestOptionsArgs): Observable<Response>;
}

EventEmitter - 组件向外传递数据时,你一定用过吧?

export class EventEmitter<T> extends Subject {subscribe(generatorOrNext?: any, error?: any, complete?: any): any;
}

没有发现Observable?仔细看,它继承自Subject,那Subject呢?接着看:

export declare class Subject<T> extends Observable<T> implements ISubscription {...省略
}

Subject 最终还得继承自 Observable。当然还有很多其它的,总而言之请记住响应式的世界里everything is Observable,不管是输入还是输出。

搭建响应式的组件

输入和输出是编程中两个无处不在的东西,只要涉及到交互的东西,都可以把它抽象成输入和输出。

最明显的,当我们使用 @Input 和 @Output 无疑是在和输入和输出打交道,除此之外呢。如果我们把定义component的 Class看作一部分,那么它给template 传递的数据也可以认为是一种输出,而它从各service获取的数据也可以当作一种数据输入。基于这种想法,我们可以认为一个组件就是连接数据和模板的桥梁,它最主要的功能就是获取服务中的数据作为输入输出给模板,当然也可以获取模板中产生的数据作为输入输出给服务。于是我们可以抽象出这样一个组件:

export abstract class BaseComponent {abstract subscription$$: Subscription; // 用于在组件销毁时取消不得不手动订阅的一些流。abstract launch(option?: any): void;  // 给服务输出数据abstract initialModel(option?: any): void; // 从服务中获取数据输入
}
  • initialModel 所有组件中要用到的数据都在这个方法中获得,再分发给数据的使用者。
  • launch 所有组件中需要向服务传递的数据都会在这个方法中向外传递。
  • subscription$$ 在实际项目中,无法避免会手动订阅一些流,其中的某一些流可能需要我们手动释放,这个变量可以全权负责,而且它的初始化基本上会被固定在 launch 方法中。

假设我们需要实现一个带有图片验证码的登录功能,我们来实现它的数据交互。

@Component({...
})
export class LoginComponent extends BaseComponent implements OnInit, OnDestroy {subscription$$: Subscription;randomCode: Observable<string>; // 随机码,在页面上展示给用户generateCode$: Subject<boolean> = new Subject(); // 和服务交互,通知服务我们需要一个随机码。// 这里我们没有定义这个接口,你可以想像它就是登录表单的值,例如: { username: string; password: string; randomCode: string}; 它的功能就是发出登录请求的数据。login$: Subject<LoginFormValue> = new Subject();constructor(private auth: AuthService) { } // 这个服务随后实现ngOnInit() {this.initialModel();this.launch();this.goTo(); // 初始化时就调用跳转函数。}initialModel() {this.randomCode = this.auth.getRandomCode();}launch() {this.subscription = this.auth.login(this.login$).add(this.auth.generateRandomCode(this.generateCode$));}goTo() {this.subscription.add(this.auth.isLoginSuccess().subscribe(success => {// 跳转逻辑等等。}))}ngOnDestroy {this.subscription$$.unsubscribe();}
}

通过阅读以上代码,我们的核心关注点只需要放在 initialModel 和 launch 两个方法上,一个告诉我们获取了哪些数据,一个告诉我们输出了哪此数据。另外你会发现,登录动作和获取验证码的动作在组件初始化时就已经告诉了服务,这种命令式的是完全不同的两种风格,在命令式的风格中我们都是在等到用户点击登录按钮时才去调用login函数,发起登录动作。下面来看服务代码:

@Injectable()
export class AuthService {login$: BehaviorSubject = new BehaviorSubject();constructor(private http: Http) { }login(data: Observable<LoginFormValue>): Subscription {// url: 请求的url; Response: angular 定义的http响应接口。return this.http.post(url).map((res: Response) => {// 假设登录成功会后台会返回token,这里我们利用 BehaviorSubject 来保存这个 token;const body = res.json();return body.data.token;}).subscribe(this.login$);}generateRandomCode(signal: Observable<boolean>): Observable<string> {return this.http.get(url).map((res: Response) => {// 假设数据保存在random 字段下const body = res.json();return body.data.random;});}isLoginSuccess(): Observable<boolean> {return this.login$.mapTo(true);}
}

基于开始说到的思路,我们基本搭建好了一个完全基于响应式风格的登录组件的骨架,可以说基本的套路出来了,暂时先到这里。各位可以先想一下可以扩展哪些功能,比如实现30秒换一次验证码,用户点击时立即更换验证码等,下次继续。

图片描述


https://dhexx.cn/news/show-4234366.html

相关文章

Maven--使用import scope解决maven继承(单)问题

测试环境 maven 3.3.9 想必大家在做SpringBoot应用的时候&#xff0c;都会有如下代码&#xff1a; <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.REL…

SpringCloud--构建Eureka注册发现中心

一、Eureka简介 Eureka是Netflix开源的服务发现组件&#xff0c;本身是一个基于REST的服务。它包含Server和Client两部分。SpringCloud将它集成在子项目SpringCloud Netflix中&#xff0c;从而实现微服务的注册与发现。 二、EureKa原理 Eureka包含两个组件&#xff1a;Eureka …

基于Redis实现简单的分布式锁

2019独角兽企业重金招聘Python工程师标准>>> 分布式锁要点 不同的节点访问到的应该是同一把锁。锁的基本特新不变&#xff0c;需要有加锁和释放两大操作。一个最重要的&#xff0c;不像JVM的锁&#xff0c;在单机上&#xff0c;可以使用try-catch-finally来进行锁的…

SpringBoot线上部署踩坑

SpringBOOT项目在线上部署时&#xff0c;要么在命令行里添加–spring.profiles.activeprod来指定环境&#xff0c;要么通过maven 打包时动态传参激活–spring.profiles.activeprod,之前对于SpringBOOT的项目部署都是采用命令行方式激活环境分支&#xff0c;现项目集成部署环境&…

MySQL基操---深入浅出增量断点备份与日志管理

-----------------------------日志-----------------------------------MySQL的日志类型有以下几种&#xff1a;1. 错误日志&#xff08;error&#xff09;,MySQL服务实例启动、运行或者停止等相关信息。 2. 普通查询日志&#xff08;general&#xff09;,MySQL服务实例运行…

第三章_Redis的十大数据类型

which 10 官网&#xff1a;https://redis.io/docs/data-types/ 一图说明 提前声明 这里说的数据类型是value的数据类型&#xff0c;key的类型都是字符串 十大数据类型分别是 redis字符串&#xff08;String&#xff09; String&#xff08;字符串&#xff09;。 string是…

Docker系列(四)Docker 网络模式及配置

四种网络模式摘自 Docker 网络详解及 pipework 源码解读与实践 一、Docker网络模式 docker run 创建 Docker 容器时&#xff0c;可以用 –net 选项指定容器的网络模式&#xff0c;Docker 有以下 4 种网络模式&#xff1a; host 模式&#xff0c;使用 –nethost 指定。contai…

笔试题之Java基础部分【简】【一】

2019独角兽企业重金招聘Python工程师标准>>> 1.length、length()和size() length针对数组&#xff0c;数组的长度用length length()针对字符串String&#xff0c;字符串的长度用length() size()针对泛型集合&#xff0c;有多少元素用size()2.jdk中哪些类是不能继承的…

Hadoop 多次namenode -format造成datanode进程异常

之前在启动hadoop集群时&#xff0c;使用jps命令没有查看到datanode&#xff0c;但第一次启动集群时有datanode&#xff0c;查看datanode的相关日志&#xff0c;报Java IO异常“java.io.IOException”&#xff0c;异常信息里显示的是&#xff0c;namenode和datanode的clusterID…

Spark系列--SparkCore(六)RDD分区详解

转载自&#xff1a;https://blog.csdn.net/jiangsanfeng1111/article/details/78191891 一、分区个数规则 spark.default.parallelism&#xff1a;&#xff08;默认的并发数&#xff09; 2 当配置文件spark-default.conf中没有显示的配置&#xff0c;则按照如下规则取值&…