From 2d37e65e53a2f68f19f1a587762a0fb5aaa8b539 Mon Sep 17 00:00:00 2001 From: "feichao.sfc" Date: Thu, 28 Nov 2019 01:54:06 +0800 Subject: [PATCH] =?UTF-8?q?version=202.2.0;=20=E6=96=B0=E5=A2=9E=20StateOb?= =?UTF-8?q?servable.ts;=20=E4=BD=BF=E7=94=A8=20StateObservable=20=E4=BD=9C?= =?UTF-8?q?=E4=B8=BA=20novel=20=E5=85=A5=E5=8F=82=E7=9A=84=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/StateObservable.ts | 30 +++++++++++++++++ src/__tests__/novel.test.ts | 5 +-- src/index.ts | 1 + src/novel.ts | 65 +++++++++++++++++++------------------ 5 files changed, 69 insertions(+), 34 deletions(-) create mode 100644 src/StateObservable.ts diff --git a/package.json b/package.json index 236b819..f0d8bc4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@little-saga/rx-hooks", - "version": "2.1.0", + "version": "2.2.0", "description": "RxJS hooks for React and its friends.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/StateObservable.ts b/src/StateObservable.ts new file mode 100644 index 0000000..d39b185 --- /dev/null +++ b/src/StateObservable.ts @@ -0,0 +1,30 @@ +import { Observable, Subject } from 'rxjs' + +// StateObservable from redux-observable +// https://github.com/redux-observable/redux-observable +export default class StateObservable extends Observable { + value: S + private __notifier = new Subject() + + constructor(input$: Observable, initialState: S) { + super(subscriber => { + const subscription = this.__notifier.subscribe(subscriber) + if (subscription && !subscription.closed) { + subscriber.next(this.value) + } + return subscription + }) + + this.value = initialState + input$.subscribe(value => { + // We only want to update state$ if it has actually changed since + // redux requires reducers use immutability patterns. + // This is basically what distinctUntilChanged() does but it's so simple + // we don't need to pull that code in + if (value !== this.value) { + this.value = value + this.__notifier.next(value) + } + }) + } +} diff --git a/src/__tests__/novel.test.ts b/src/__tests__/novel.test.ts index 21660d1..a5800a4 100644 --- a/src/__tests__/novel.test.ts +++ b/src/__tests__/novel.test.ts @@ -2,13 +2,14 @@ import { act, renderHook } from '@testing-library/react-hooks' import { combineLatest, interval, isObservable, NEVER, Observable, Subscription } from 'rxjs' import { map, startWith, switchMap } from 'rxjs/operators' import { SubjectProxy } from '../helpers' +import { StateObservable } from '../index' import { useNovel } from '../novel' import { applyMutatorAsReducer } from '../operators' describe('simpleCounterNovel', () => { function simpleCounterNovel( - input$: Observable<{ initCount: number }>, - state$: Observable<{ count: number }>, + input$: StateObservable<{ initCount: number }>, + state$: StateObservable<{ count: number }>, ) { const subscription = new Subscription() const actionProxy$ = new SubjectProxy(subscription) diff --git a/src/index.ts b/src/index.ts index 5a69812..c2bd43e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,4 @@ export * from './equal-fns' export * from './helpers' export * from './novel' export * from './operators' +export { default as StateObservable } from './StateObservable' diff --git a/src/novel.ts b/src/novel.ts index f3e2d29..da2c5c5 100644 --- a/src/novel.ts +++ b/src/novel.ts @@ -1,11 +1,12 @@ import { useEffect, useMemo, useRef, useState } from 'react' -import { BehaviorSubject, isObservable, Observable, Subscription } from 'rxjs' +import { isObservable, Observable, Subject, Subscription } from 'rxjs' +import StateObservable from './StateObservable' const NO_VALUE = Symbol('no-value') export type Novel = ( - input$: Observable, - state$: Observable, + input$: StateObservable, + state$: StateObservable, ) => | Observable | { @@ -21,8 +22,8 @@ export function useNovel( novel: Novel, ) { const [state, setState] = useState(initialState) - const input$ = useMemo(() => new BehaviorSubject(input), []) - const state$ = useMemo(() => new BehaviorSubject(state), []) + const input$ = useMemo(() => new Subject(), []) + const state$ = useMemo(() => new Subject(), []) // 每次渲染之后更新 input$ const mount = useRef(true) @@ -37,38 +38,43 @@ export function useNovel( const derivedValueRef = useRef(null) const exportsRef = useRef(null) - const ref = useRef<{ - deriveSub: Subscription - stateSub: Subscription - teardown?(): void - }>({} as any) + const subscription = useMemo(() => new Subscription(), []) // 用 useMemo 来同步地执行 novel 与订阅 derived$,防止 derived 的初始值丢失 useMemo(() => { - const output = novel(input$.asObservable(), state$.asObservable()) + const output = novel( + new StateObservable(input$, input), + new StateObservable(state$, initialState), + ) if (output == null) { return } if (isObservable(output)) { - ref.current.stateSub = output.subscribe(value => { - state$.next(value) - setState(value) - }) + subscription.add( + output.subscribe(value => { + state$.next(value) + setState(value) + }), + ) } else { - ref.current.stateSub = output.nextState?.subscribe(value => { - state$.next(value) - setState(value) - }) + subscription.add( + output.nextState?.subscribe(value => { + state$.next(value) + setState(value) + }), + ) if (output.derived) { let syncEmittedValue: D | typeof NO_VALUE = NO_VALUE - ref.current.deriveSub = output.derived.subscribe(value => { - derivedValueRef.current = value - /* istanbul ignore else */ - if (process.env.NODE_ENV !== 'production') { - syncEmittedValue = value - } - }) + subscription.add( + output.derived.subscribe(value => { + derivedValueRef.current = value + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + syncEmittedValue = value + } + }), + ) /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { if (syncEmittedValue === NO_VALUE) { @@ -76,17 +82,14 @@ export function useNovel( } } } - ref.current.teardown = output.teardown + subscription.add(output.teardown) exportsRef.current = output.exports } }, []) useEffect(() => { return () => { - state$.complete() - ref.current.teardown?.() - ref.current.deriveSub?.unsubscribe() - ref.current.stateSub?.unsubscribe() + subscription.unsubscribe() } }, [])