Skip to content

Commit

Permalink
feat(createGlobalState): 支持 watchState
Browse files Browse the repository at this point in the history
  • Loading branch information
fjc0k committed Jun 19, 2020
1 parent c7a75e2 commit 2412f3a
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 24 deletions.
39 changes: 36 additions & 3 deletions src/react/createGlobalState.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { act, renderHook } from '@testing-library/react-hooks'
import { createGlobalState } from './createGlobalState'
import { createGlobalState, CreateGlobalStateResult } from './createGlobalState'

describe('createGlobalState', () => {
describe('有初始值', () => {
const useGlobalName = createGlobalState('')
let useGlobalName: CreateGlobalStateResult<string>

beforeEach(() => {
useGlobalName = createGlobalState('')
})

test('getState 正常', () => {
expect(useGlobalName.getState()).toBe('')
Expand All @@ -14,6 +18,17 @@ describe('createGlobalState', () => {
expect(useGlobalName.getState()).toBe('Jay')
})

test('watchState 正常', () => {
const cb = jest.fn()
const off = useGlobalName.watchState(cb)
useGlobalName.setState('Jay')
expect(cb).toBeCalled().toBeCalledTimes(1).toBeCalledWith('Jay', '')
useGlobalName.setState('Jay2')
expect(cb).toBeCalled().toBeCalledTimes(2).toBeCalledWith('Jay2', 'Jay')
off()
expect(cb).toBeCalled().toBeCalledTimes(2).toBeCalledWith('Jay2', 'Jay')
})

test('跨组件状态共享正常', () => {
const { result: result1 } = renderHook(() => useGlobalName())
const { result: result2 } = renderHook(() => useGlobalName())
Expand Down Expand Up @@ -43,7 +58,11 @@ describe('createGlobalState', () => {
})

describe('无初始值', () => {
const useGlobalName = createGlobalState<string>()
let useGlobalName: CreateGlobalStateResult<string | undefined>

beforeEach(() => {
useGlobalName = createGlobalState()
})

test('getState 正常', () => {
expect(useGlobalName.getState()).toBe(undefined)
Expand All @@ -54,6 +73,20 @@ describe('createGlobalState', () => {
expect(useGlobalName.getState()).toBe('Jay')
})

test('watchState 正常', () => {
const cb = jest.fn()
const off = useGlobalName.watchState(cb)
useGlobalName.setState('Jay')
expect(cb)
.toBeCalled()
.toBeCalledTimes(1)
.toBeCalledWith('Jay', undefined)
useGlobalName.setState('Jay2')
expect(cb).toBeCalled().toBeCalledTimes(2).toBeCalledWith('Jay2', 'Jay')
off()
expect(cb).toBeCalled().toBeCalledTimes(2).toBeCalledWith('Jay2', 'Jay')
})

test('跨组件状态共享正常', () => {
const { result: result1 } = renderHook(() => useGlobalName())
const { result: result2 } = renderHook(() => useGlobalName())
Expand Down
53 changes: 32 additions & 21 deletions src/react/createGlobalState.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
import { EventBus } from '../utils'

export interface CreateGlobalStateResult<S> {
(): readonly [S, Dispatch<SetStateAction<S>>]
getState(): S
setState(nextState: SetStateAction<S>): void
watchState(callback: (nextState: S, prevState: S) => any): () => void
}

export function createGlobalState<S>(): CreateGlobalStateResult<S | undefined>
Expand All @@ -15,33 +17,42 @@ export function createGlobalState<S>(
export function createGlobalState<S>(
initialState?: S,
): CreateGlobalStateResult<S | undefined> {
let currentState: S | undefined = initialState
const stateSetters: Dispatch<SetStateAction<S | undefined>>[] = []
const getState: () => S | undefined = () => currentState
const setState: Dispatch<SetStateAction<S | undefined>> = nextState => {
if (typeof nextState === 'function') {
nextState = (nextState as any)(currentState)
const bus = new EventBus<{
setGlobalState: (
nextGlobalState: S | undefined,
prevGlobalState: S | undefined,
) => void
}>()
let currentGlobalState: S | undefined = initialState
const getGlobalState: () => S | undefined = () => currentGlobalState
const setGlobalState: Dispatch<SetStateAction<
S | undefined
>> = nextGlobalState => {
if (typeof nextGlobalState === 'function') {
nextGlobalState = (nextGlobalState as any)(currentGlobalState)
}
currentState = nextState as any
for (const setter of stateSetters) {
setter(nextState)
if (nextGlobalState !== currentGlobalState) {
bus.emit('setGlobalState', nextGlobalState as any, currentGlobalState)
currentGlobalState = nextGlobalState as any
}
}
const watchGlobalState: (
callback: (
nextGlobalState: S | undefined,
prevGlobalState: S | undefined,
) => any,
) => () => void = callback => {
return bus.on('setGlobalState', callback)
}
const useGlobalState: CreateGlobalStateResult<S | undefined> = (() => {
const [globalState, setGlobalState] = useState(currentState)
const [state, setState] = useState(currentGlobalState)
useEffect(() => {
stateSetters.push(setGlobalState)
return () => {
const i = stateSetters.indexOf(setGlobalState)
/* istanbul ignore else */
if (i !== -1) {
stateSetters.splice(i, 1)
}
}
return bus.on('setGlobalState', setState)
}, [])
return [globalState, setState] as const
return [state, setGlobalState] as const
}) as any
useGlobalState.getState = getState
useGlobalState.setState = setState
useGlobalState.getState = getGlobalState
useGlobalState.setState = setGlobalState
useGlobalState.watchState = watchGlobalState
return useGlobalState
}

0 comments on commit 2412f3a

Please sign in to comment.