类型安全的TypeScript+React+RxJS集成思路

#1

发现现在合用RxJS和React变得简单多了,论坛里也有其他用RxJS和React的同学吧,想抛砖引玉讨论下思路。

用了mapped types, 所以需要2.1以后的版本

import * as React from 'react'
import {Observable, Subject} from 'rxjs'
import {ISubscription} from 'rxjs/Subscription'

export abstract class ViewComponent<Props, State, Interactions> extends React.Component<Props, State> {
    protected abstract readonly interactionFilters: InteractionFilters<Interactions> // 单个事件的过滤,例如debounce
    protected abstract viewLogic(): ISubscription[]

    private _interactionSubjects: Subjects<Interactions>
    protected report: EventHandlers<Interactions>
    protected interactions: Observables<Interactions>

    constructor(props: Props) {
        super(props)
        this.setState = this.setState.bind(this) // 无关
    }

    componentWillMount(): void {
        this._interactionSubjects = createSubjects<Interactions>(this.interactionFilters)
        this.report = generateHandlers<Interactions>(this._interactionSubjects)
        this.interactions = filterInteractions<Interactions>(this._interactionSubjects, this.interactionFilters)
        this._jobs = this.viewLogic()
    }

    componentWillUnmount() {
        this._jobs.forEach(job => job.unsubscribe())
    }

    private _jobs: ISubscription[]
}

type EventHandlers<Interactions> = {[Name in keyof Interactions]: (event: Interactions[Name])=> void}
type Subjects<Interactions> = {[Key in keyof Interactions]: Subject<Interactions[Key]>}
type Observables<Interactions> = {[Name in keyof Interactions]: Observable<Interactions[Name]>}
export type InteractionFilters<Interactions> = {
    [Key in keyof Interactions]: (input: Observable<Interactions[Key]>)=> Observable<Interactions[Key]>
}

export let KeepOrigin = <T>(input: Observable<T>): Observable<T> => input

let createSubjects = <Interactions>(shape: {[Key in keyof Interactions]: any}): Subjects<Interactions> => {
    let result: Subjects<Interactions> = {} as any
    Object.keys(shape).forEach((key: keyof Interactions) => result[key] = new Subject<Interactions[typeof key]>())
    return result
}
let generateHandlers = <Interactions>(subjects: Subjects<Interactions>): EventHandlers<Interactions> => {
    let result: EventHandlers<Interactions> = {} as any
    Object.keys(subjects).forEach((key: keyof Interactions) =>
        result[key] = (event: Interactions[typeof key]) => subjects[key].next(event))
    return result
}
let filterInteractions = <Interactions>(subjects: Subjects<Interactions>,
                                        filters: InteractionFilters<Interactions>): Observables<Interactions> => {
    let result: Observables<Interactions> = {} as any
    Object.keys(subjects).forEach((key: keyof Interactions) => result[key] = filters[key](subjects[key]))
    return result
}


用法:

class Example extends ViewComponent<Props, State, Interactions> {
    interactionFilters: InteractionFilters<Interactions> = {
        clicked: KeepOrigin
    }

    protected viewLogic(): ISubscription[] {
        let {interactions} = this
        return [interactions.clicked.subscribe(event=> console.log(event))]
    }

    render(){
        let {report} = this
        return <div onClick={report.clicked}>
        </div>
    }
}
type Interactions = {clicked: any}
type Props = {}
type State = {}

好处是把回调转成Observable的同时,可以获得IDE的正确代码提示和对事件的类型检查

this.interactions里面是根据Interactions和InteractionFilters自动生成的很多Observable,这里是{clicked: Observable<any>}

之前看到Cyclejs的方案感觉也可以用在React上,也看到过友人的方案,各位是怎么连接RxJS和React的呢?

1 Like
#2

继承看得有点晕

#3

好麻烦的写法。。我也晕了

#4

子类覆盖父类的那两个protected abstract,之后就可以用剩下的那两个protected(report和interactions)
其中report是入口,interactions是出口,把report里面的回调函数传给DOM,interactions里面就可以subscribe了
总之只是把onClick一类事件转成Observable了,感觉还蛮适合在组件内处理事件的场合