breaking(middleware/devtools): remove checking old string option (#933)

* breaking(middleware/devtools): remove checking old string option

* fix test

* fix test
This commit is contained in:
Daishi Kato 2022-04-29 23:15:23 +09:00 committed by GitHub
parent 54d5e0cd71
commit 0ae0293e88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 134 additions and 137 deletions

View File

@ -58,8 +58,8 @@ type StoreSetStateWithAction<S> = S extends {
interface DevtoolsOptions {
enabled?: boolean
name?: string
anonymousActionType?: string
name?: string
serialize?:
| boolean
| {
@ -81,7 +81,7 @@ type Devtools = <
Mcs extends [StoreMutatorIdentifier, unknown][] = []
>(
initializer: StateCreator<T, [...Mps, ['zustand/devtools', never]], Mcs>,
options?: DevtoolsOptions
devtoolsOptions?: DevtoolsOptions
) => StateCreator<T, Mps, [['zustand/devtools', never], ...Mcs]>
declare module '../vanilla' {
@ -93,7 +93,7 @@ declare module '../vanilla' {
type DevtoolsImpl = <T extends State>(
storeInitializer: PopArgument<StateCreator<T, [], []>>,
options: DevtoolsOptions
devtoolsOptions?: DevtoolsOptions
) => PopArgument<StateCreator<T, [], []>>
type PopArgument<T extends (...a: never[]) => unknown> = T extends (
@ -104,155 +104,152 @@ type PopArgument<T extends (...a: never[]) => unknown> = T extends (
export type NamedSet<T extends State> = WithDevtools<StoreApi<T>>['setState']
const devtoolsImpl: DevtoolsImpl = (fn, options) => (set, get, api) => {
type S = ReturnType<typeof fn>
const devtoolsImpl: DevtoolsImpl =
(fn, devtoolsOptions = {}) =>
(set, get, api) => {
type S = ReturnType<typeof fn>
const devtoolsOptions =
options === undefined
? {}
: typeof options === 'string'
? { name: options }
: options
const { enabled } = devtoolsOptions as { enabled?: boolean }
let extensionConnector: typeof window['__REDUX_DEVTOOLS_EXTENSION__'] | false
try {
extensionConnector =
(enabled ?? __DEV__) && window.__REDUX_DEVTOOLS_EXTENSION__
} catch {
// ignored
}
if (!extensionConnector) {
if (__DEV__ && enabled) {
console.warn(
'[zustand devtools middleware] Please install/enable Redux devtools extension'
)
const { enabled, anonymousActionType, ...options } = devtoolsOptions
let extensionConnector:
| typeof window['__REDUX_DEVTOOLS_EXTENSION__']
| false
try {
extensionConnector =
(enabled ?? __DEV__) && window.__REDUX_DEVTOOLS_EXTENSION__
} catch {
// ignored
}
return fn(set, get, api)
}
const extension = extensionConnector.connect(devtoolsOptions)
let isRecording = true
;(api.setState as NamedSet<S>) = (state, replace, nameOrAction) => {
const r = set(state, replace)
if (!isRecording) return r
extension.send(
nameOrAction === undefined
? { type: devtoolsOptions.anonymousActionType || 'anonymous' }
: typeof nameOrAction === 'string'
? { type: nameOrAction }
: nameOrAction,
get()
)
return r
}
const setStateFromDevtools: SetState<S> = (...a) => {
const originalIsRecording = isRecording
isRecording = false
set(...a)
isRecording = originalIsRecording
}
const initialState = fn(api.setState, get, api)
extension.init(initialState)
if (
(api as any).dispatchFromDevtools &&
typeof (api as any).dispatch === 'function'
) {
let didWarnAboutReservedActionType = false
const originalDispatch = (api as any).dispatch
;(api as any).dispatch = (...a: any[]) => {
if (
__DEV__ &&
a[0].type === '__setState' &&
!didWarnAboutReservedActionType
) {
if (!extensionConnector) {
if (__DEV__ && enabled) {
console.warn(
'[zustand devtools middleware] "__setState" action type is reserved ' +
'to set state from the devtools. Avoid using it.'
'[zustand devtools middleware] Please install/enable Redux devtools extension'
)
didWarnAboutReservedActionType = true
}
;(originalDispatch as any)(...a)
return fn(set, get, api)
}
}
;(
extension as unknown as {
// FIXME https://github.com/reduxjs/redux-devtools/issues/1097
subscribe: (
listener: (message: Message) => void
) => (() => void) | undefined
const extension = extensionConnector.connect(options)
let isRecording = true
;(api.setState as NamedSet<S>) = (state, replace, nameOrAction) => {
const r = set(state, replace)
if (!isRecording) return r
extension.send(
nameOrAction === undefined
? { type: anonymousActionType || 'anonymous' }
: typeof nameOrAction === 'string'
? { type: nameOrAction }
: nameOrAction,
get()
)
return r
}
).subscribe((message: any) => {
switch (message.type) {
case 'ACTION':
if (typeof message.payload !== 'string') {
console.error(
'[zustand devtools middleware] Unsupported action format'
const setStateFromDevtools: SetState<S> = (...a) => {
const originalIsRecording = isRecording
isRecording = false
set(...a)
isRecording = originalIsRecording
}
const initialState = fn(api.setState, get, api)
extension.init(initialState)
if (
(api as any).dispatchFromDevtools &&
typeof (api as any).dispatch === 'function'
) {
let didWarnAboutReservedActionType = false
const originalDispatch = (api as any).dispatch
;(api as any).dispatch = (...a: any[]) => {
if (
__DEV__ &&
a[0].type === '__setState' &&
!didWarnAboutReservedActionType
) {
console.warn(
'[zustand devtools middleware] "__setState" action type is reserved ' +
'to set state from the devtools. Avoid using it.'
)
return
didWarnAboutReservedActionType = true
}
return parseJsonThen<{ type: unknown; state?: PartialState<S> }>(
message.payload,
(action) => {
if (action.type === '__setState') {
setStateFromDevtools(action.state as PartialState<S>)
return
}
;(originalDispatch as any)(...a)
}
}
if (!(api as any).dispatchFromDevtools) return
if (typeof (api as any).dispatch !== 'function') return
;(api as any).dispatch(action)
}
)
case 'DISPATCH':
switch (message.payload.type) {
case 'RESET':
setStateFromDevtools(initialState)
return extension.init(api.getState())
case 'COMMIT':
return extension.init(api.getState())
case 'ROLLBACK':
return parseJsonThen<S>(message.state, (state) => {
setStateFromDevtools(state)
extension.init(api.getState())
})
case 'JUMP_TO_STATE':
case 'JUMP_TO_ACTION':
return parseJsonThen<S>(message.state, (state) => {
setStateFromDevtools(state)
})
case 'IMPORT_STATE': {
const { nextLiftedState } = message.payload
const lastComputedState =
nextLiftedState.computedStates.slice(-1)[0]?.state
if (!lastComputedState) return
setStateFromDevtools(lastComputedState)
extension.send(
null as any, // FIXME no-any
nextLiftedState
;(
extension as unknown as {
// FIXME https://github.com/reduxjs/redux-devtools/issues/1097
subscribe: (
listener: (message: Message) => void
) => (() => void) | undefined
}
).subscribe((message: any) => {
switch (message.type) {
case 'ACTION':
if (typeof message.payload !== 'string') {
console.error(
'[zustand devtools middleware] Unsupported action format'
)
return
}
return parseJsonThen<{ type: unknown; state?: PartialState<S> }>(
message.payload,
(action) => {
if (action.type === '__setState') {
setStateFromDevtools(action.state as PartialState<S>)
return
}
case 'PAUSE_RECORDING':
return (isRecording = !isRecording)
}
return
}
})
if (!(api as any).dispatchFromDevtools) return
if (typeof (api as any).dispatch !== 'function') return
;(api as any).dispatch(action)
}
)
return initialState
}
case 'DISPATCH':
switch (message.payload.type) {
case 'RESET':
setStateFromDevtools(initialState)
return extension.init(api.getState())
case 'COMMIT':
return extension.init(api.getState())
case 'ROLLBACK':
return parseJsonThen<S>(message.state, (state) => {
setStateFromDevtools(state)
extension.init(api.getState())
})
case 'JUMP_TO_STATE':
case 'JUMP_TO_ACTION':
return parseJsonThen<S>(message.state, (state) => {
setStateFromDevtools(state)
})
case 'IMPORT_STATE': {
const { nextLiftedState } = message.payload
const lastComputedState =
nextLiftedState.computedStates.slice(-1)[0]?.state
if (!lastComputedState) return
setStateFromDevtools(lastComputedState)
extension.send(
null as any, // FIXME no-any
nextLiftedState
)
return
}
case 'PAUSE_RECORDING':
return (isRecording = !isRecording)
}
return
}
})
return initialState
}
export const devtools = devtoolsImpl as unknown as Devtools
const parseJsonThen = <T>(stringified: string, f: (parsed: T) => void) => {

View File

@ -26,9 +26,9 @@ beforeEach(() => {
})
it('connects to the extension by passing the options and initializes', () => {
const options = { name: 'test', foo: 'bar', enabled: true }
const options = { name: 'test', foo: 'bar' }
const initialState = { count: 0 }
create(devtools(() => initialState, options))
create(devtools(() => initialState, { enabled: true, ...options }))
expect(extensionConnector.connect).toHaveBeenLastCalledWith(options)
expect(extension.init).toHaveBeenLastCalledWith(initialState)