Skip to content
This repository has been archived by the owner on Jul 24, 2020. It is now read-only.
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: little-saga/rx-hooks
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.2.0
Choose a base ref
...
head repository: little-saga/rx-hooks
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 5 commits
  • 9 files changed
  • 1 contributor

Commits on Apr 2, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5dc61cf View commit details
  2. deps: update

    feichao93 committed Apr 2, 2020
    Copy the full SHA
    d4b281d View commit details
  3. 2.3.0

    feichao93 committed Apr 2, 2020
    Copy the full SHA
    9b4e9df View commit details

Commits on Jul 20, 2020

  1. chore: update tsconfig.json

    feichao93 committed Jul 20, 2020
    Copy the full SHA
    b788ffb View commit details
  2. 2.3.1

    feichao93 committed Jul 20, 2020
    Copy the full SHA
    4acb50d View commit details
Showing with 770 additions and 725 deletions.
  1. +3 −3 package.json
  2. +10 −6 readme.md
  3. +6 −6 src/__tests__/novel.test.ts
  4. +74 −0 src/effect-novel.ts
  5. +12 −4 src/index.ts
  6. +14 −0 src/interfaces.ts
  7. +4 −15 src/{novel.ts → memo-novel.ts}
  8. +1 −1 tsconfig.json
  9. +646 −690 yarn.lock
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@little-saga/rx-hooks",
"version": "2.2.0",
"version": "2.3.1",
"description": "RxJS hooks for React and its friends.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -31,7 +31,7 @@
"@testing-library/react-hooks": "^3.2.1",
"@types/jest": "^24.0.23",
"@types/node": "^12.12.11",
"@types/react": "^16.9.11",
"@types/react": "^16.9.31",
"coveralls": "^3.0.8",
"cross-env": "^6.0.3",
"jest": "^24.9.0",
@@ -42,7 +42,7 @@
"typescript": "^3.7.2"
},
"dependencies": {
"immer": "3 || 4 || 5",
"immer": "3 || 4 || 5 || 6",
"react": "^16.12.0",
"rxjs": "^6.5.3"
},
16 changes: 10 additions & 6 deletions readme.md
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ https://zhuanlan.zhihu.com/p/92248348

## `Novel`

@little-saga/rx-hooks,Novel 是一种满足特定接口的函数。开发者将自定义的逻辑封装为相应的 novel,然后调用 useNovel 函数,使 novel 运行在一个 React 组件的生命周期内(基于 React hooks 机制)。
@little-saga/rx-hooks,Novel 是一种满足特定接口的函数。开发者将自定义的逻辑封装为相应的 novel,然后调用 useMemoNovel 函数,使 novel 运行在一个 React 组件的生命周期内(基于 React hooks 机制)。

Novel 内的逻辑一般采用 RxJS 编程,@little-saga/rx-hooks 不对 novel 内部的逻辑进行限制,开发者可以选用自己熟悉的 RxJS 开发方式。而 Novel 的输入/输出(即函数的参数和返回值)则需要满足下面描述的要求。

@@ -35,7 +35,7 @@ type Novel<I, S extends object, D extends object, E> = (
- novel 函数的参数用来抽象 `React -> RxJS` 的通信过程
- novel 函数被调用时,调用参数为 `input$``state$`
- input\$ 表示 React 当前向 novel 提供的输入,其类型为 `BehaviorSubject<I>`
- 每次组件的 render 方法被执行时,input\$ 中就会发出组件最新提供的值;值对应于 `useNovel(input, initState, novel)` 中的 input 参数
- 每次组件的 render 方法被执行时,input\$ 中就会发出组件最新提供的值;值对应于 `useMemoNovel(input, initState, novel)` 中的 input 参数
- 「input\$ 之于 novel」相当于「props 之于 React 组件」;input\$ 应当被认为是只读的
- state\$ 表示 novel 绑定到 React 组件的当前状态;其背后对应一个 useState hook
- 每次组件的 render 方法被执行时,novel 就可以通过 state\$ 获取到最新的状态
@@ -45,14 +45,14 @@ type Novel<I, S extends object, D extends object, E> = (
- novel 函数的返回值用来抽象 `RxJS -> React` 的通信过程
- novel 的返回值一般为一个对象,对象中各个字段的含义如下
- nextState:用于表示下一个状态的 Observable. 每当该 Observable 发出一个值的时候,对应的 useState hook 的 setState 方法将被调用,组件将重新渲染
- derived: 用于表示缓存/计算状态的 Observable. useNovel 的返回值中会包含该 Observable 的最新的值;该 Observable 发出值的时候不会触发渲染
- derived: 用于表示缓存/计算状态的 Observable. useMemoNovel 的返回值中会包含该 Observable 的最新的值;该 Observable 发出值的时候不会触发渲染
- exports: 对象导出,使得 React 组件可以获取到 novel 内部的对象
- teardown: 清理逻辑,当组件被卸载时,该函数将被调用
- novel 的返回值也可以是一个简单的 Observable,此时该 Observable 的作用与 nextState 字段相同。
## API
### `useNovel(input: I, initState: S, novel: Novel<I, S, D, E>) => [S & D, E]`
### `useMemoNovel(input: I, initState: S, novel: Novel<I, S, D, E>) => [S & D, E]`
泛型参数说明:
@@ -61,6 +61,10 @@ type Novel<I, S extends object, D extends object, E> = (
- `D` novel 返回值中 derived 字段的类型
- `E` novel 返回值中 exports 字段的类型
useNovel 将在一个 React 组件内执行 novel 函数,并将 novel 的输入输出与 React 组件绑定起来。注意 useNovel 是一个 React hooks,调用该函数需要遵循 [hooks rules](https://zh-hans.reactjs.org/docs/hooks-rules.html).
useMemoNovel 将在一个 React 组件内执行 novel 函数,并将 novel 的输入输出与 React 组件绑定起来。注意 useMemoNovel 是一个 React hooks,调用该函数需要遵循 [hooks rules](https://zh-hans.reactjs.org/docs/hooks-rules.html).
useNovel 会返回一个数组,数组的长度固定为 2,第一个元素是 state 与 derived 两个对象的合并结果,第二个元素即为 novel 返回对象的 exports 字段。
useMemoNovel 会返回一个数组,数组的长度固定为 2,第一个元素是 state 与 derived 两个对象的合并结果,第二个元素即为 novel 返回对象的 exports 字段。
### `useMemoNovel`
`useMemoNovel` 相似,但是 novel 函数会在 useEffect 会被调用
12 changes: 6 additions & 6 deletions src/__tests__/novel.test.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { combineLatest, interval, isObservable, NEVER, Observable, Subscription
import { map, startWith, switchMap } from 'rxjs/operators'
import { SubjectProxy } from '../helpers'
import { StateObservable } from '../index'
import { useNovel } from '../novel'
import { useMemoNovel } from '../memo-novel'
import { applyMutatorAsReducer } from '../operators'

describe('simpleCounterNovel', () => {
@@ -45,7 +45,7 @@ describe('simpleCounterNovel', () => {
test('simple inc/dec/reset case', () => {
const { result } = renderHook(
({ initCount }: { initCount: number }) =>
useNovel({ initCount }, { count: 0 }, simpleCounterNovel),
useMemoNovel({ initCount }, { count: 0 }, simpleCounterNovel),
{ initialProps: { initCount: 0 } },
)

@@ -76,7 +76,7 @@ describe('simpleCounterNovel', () => {
test('derived and exports', () => {
const { result } = renderHook(
({ initCount }: { initCount: number }) =>
useNovel({ initCount }, { count: 0 }, simpleCounterNovel),
useMemoNovel({ initCount }, { count: 0 }, simpleCounterNovel),
{ initialProps: { initCount: 0 } },
)

@@ -106,7 +106,7 @@ test('derived$ not emitting a value synchronously should throw', () => {
}
}

const { result } = renderHook(() => useNovel(null, null, flawNovel))
const { result } = renderHook(() => useMemoNovel(null, null, flawNovel))

expect(result.error.message).toMatch('derived$ must synchronously emit a value.')
})
@@ -128,7 +128,7 @@ test('novel directly return nextState$ and novel should unsubscribe when unmount
}

const { result, unmount, waitForNextUpdate } = renderHook(() =>
useNovel(null, { count: 0 }, novel),
useMemoNovel(null, { count: 0 }, novel),
)

expect(result.current[0].count).toBe(0)
@@ -152,7 +152,7 @@ test('novel should call teardown() when unmount', () => {
}
}

const { unmount } = renderHook(() => useNovel(null, null, novel))
const { unmount } = renderHook(() => useMemoNovel(null, null, novel))

expect(teardown).not.toBeCalled()
unmount()
74 changes: 74 additions & 0 deletions src/effect-novel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { isObservable, Subject, Subscription } from 'rxjs'
import { Novel } from './interfaces'
import StateObservable from './StateObservable'

export function useEffectNovel<I, S extends object, D extends object, E>(
input: I,
initialState: S,
novel: Novel<I, S, D, E>,
) {
const [state, setState] = useState(initialState)
const input$ = useMemo(() => new Subject<I>(), [])
const state$ = useMemo(() => new Subject<S>(), [])

// 每次渲染之后更新 input$
const mount = useRef(true)
useEffect(() => {
// 跳过第一次渲染
if (mount.current) {
mount.current = false
return
}
input$.next(input)
})

const derivedValueRef = useRef<D>(null)
const exportsRef = useRef<E>(null)
const subscription = useMemo(() => new Subscription(), [])

// 用 useEffect 来执行 novel 与订阅 derived$,注意这一个操作是「异步」的
// derived/nextState 的初始值会被丢弃
useEffect(() => {
const output = novel(
new StateObservable(input$, input),
new StateObservable(state$, initialState),
)
if (output == null) {
return
}

if (isObservable(output)) {
subscription.add(
output.subscribe(value => {
state$.next(value)
setState(value)
}),
)
} else {
subscription.add(
output.nextState?.subscribe(value => {
state$.next(value)
setState(value)
}),
)
if (output.derived) {
subscription.add(
output.derived.subscribe(value => {
derivedValueRef.current = value
}),
)
}
subscription.add(output.teardown)
exportsRef.current = output.exports
}
}, [])

useEffect(() => {
return () => {
subscription.unsubscribe()
}
}, [])

return [{ ...state, ...derivedValueRef.current }, exportsRef.current] as const
}
16 changes: 12 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
export * from './equal-fns'
export * from './helpers'
export * from './novel'
export * from './operators'
export { deepEqual, shallowEqual } from './equal-fns'
export { SubjectProxy } from './helpers'
export { combineLatestFromObject, applyMutatorAsReducer, log, ofType } from './operators'
export { default as StateObservable } from './StateObservable'
export { Novel} from './interfaces'
export { useMemoNovel } from './memo-novel'
export { useEffectNovel } from './effect-novel'
export { NO_VALUE } from './memo-novel'

import { useMemoNovel } from './memo-novel'

/** @deprecated Use {useMemoNovel} instead */
export const useNovel = useMemoNovel
14 changes: 14 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Observable } from 'rxjs'
import StateObservable from './StateObservable'

export type Novel<I, S extends object, D extends object, E> = (
input$: StateObservable<I>,
state$: StateObservable<S>,
) =>
| Observable<S>
| {
nextState?: Observable<S>
derived?: Observable<D>
exports?: E
teardown?(): void
}
19 changes: 4 additions & 15 deletions src/novel.ts → src/memo-novel.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { isObservable, Observable, Subject, Subscription } from 'rxjs'
import { isObservable, Subject, Subscription } from 'rxjs'
import { Novel } from './interfaces'
import StateObservable from './StateObservable'

const NO_VALUE = Symbol('no-value')
export const NO_VALUE = Symbol('no-value')

export type Novel<I, S extends object, D extends object, E> = (
input$: StateObservable<I>,
state$: StateObservable<S>,
) =>
| Observable<S>
| {
nextState?: Observable<S>
derived?: Observable<D>
exports?: E
teardown?(): void
}

export function useNovel<I, S extends object, D extends object, E>(
export function useMemoNovel<I, S extends object, D extends object, E>(
input: I,
initialState: S,
novel: Novel<I, S, D, E>,
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
"target": "es2019",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true
"sourceMap": false
},
"include": ["src"]
}
Loading