资讯专栏INFORMATION COLUMN

从命令式到响应式(六)

sanyang / 1452人阅读

摘要:从这个系列的第一章开始到第五章,基于的响应式编程的基础知识基本上就介绍完了,当然有很多知识点没有提到,比如,等,不是他们不重要,而是碍于时间精力等原因没办法一一详细介绍。

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

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

angular中响应式接口无处不在

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

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

export interface ActivatedRoute {
    url: Observable
    params: Observable;
    queryParams: Observable;
    fragment: Observable;
    data: Observable;
    get paramMap: Observable;
    get queryParamMap: Observable;
    toString(): string;
}

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

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

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

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

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

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

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

export declare class Subject extends Observable 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; // 随机码,在页面上展示给用户

    generateCode$: Subject = new Subject(); // 和服务交互,通知服务我们需要一个随机码。

    // 这里我们没有定义这个接口,你可以想像它就是登录表单的值,例如: { username: string; password: string; randomCode: string}; 它的功能就是发出登录请求的数据。
    login$: Subject = 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): 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): Observable {
        return this.http.get(url)
            .map((res: Response) => {
                // 假设数据保存在random 字段下
                const body = res.json();

                return body.data.random;
            });
    }

    isLoginSuccess(): Observable {
        return this.login$.mapTo(true);
    }
}

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97260.html

相关文章

  • Spring Boot 2 快速教程:WebFlux 快速入门(二)

    摘要:响应式编程是基于异步和事件驱动的非阻塞程序,只是垂直通过在内启动少量线程扩展,而不是水平通过集群扩展。三特性常用的生产的特性如下响应式编程模型适用性内嵌容器组件还有对日志消息测试及扩展等支持。 摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 02:WebFlux 快速入门实践 文章工程: JDK...

    gaara 评论0 收藏0
  • vue组件间通信种方(完整版)

    摘要:本文总结了组件间通信的几种方式,如和,以通俗易懂的实例讲述这其中的差别及使用场景,希望对小伙伴有些许帮助。状态改变提交操作方法。 前言 组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:showImg(https://segmentfault.com/img/remote/146000001...

    frontoldman 评论0 收藏0
  • 关于Vue2一些值得推荐的文章 -- 五、月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    sutaking 评论0 收藏0
  • 关于Vue2一些值得推荐的文章 -- 五、月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    khs1994 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<