mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
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:
parent
54d5e0cd71
commit
0ae0293e88
@ -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) => {
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user