mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
breaking(middleware/devtools): remove deprecations and warnings (#892)
* imaginary code that uses uSES * revert backward compatibility code as this is not going to be v4 * use use-sync-external-store * revert to react 17 * handle error by our own * v4.0.0-alpha.2 * fix&refactor a bit * update uSES experimental package * remove error propagation hack * update size snapshot * update uSES and add dts * split react.ts and no export wild * split useStore impl * context to follow the new api, export wild again * v4.0.0-alpha.3 * add missing await * update uSES * update uSES * uses uSES extra! * v4.0.0-alpha.3 * fix(types): Rename from UseStore to UseBoundStore * breaking(types): drop deprecated UseStore type * breaking(core): drop v2 hook compatibility * breaking(middleware): drop deprecated persist options * breaking(core): drop deprecated store.subscribe with selector * update uSES * fix update uSES * v4.0.0-alpha.5 * combine subscribe type * intentional undefined type * add useDebugValue * update uSES * update uSES types * breaking(middleware): make persist options.removeItem required * update uSES * v4.0.0-alpha.6 * fix(readme): remove memoization section which is no longer valid with uSES * feat(readme): add new createStore/useStore usage * update useSES * update uSES and deps * v4.0.0-alpha.7 * update uSES * update uSES * shave bytes * update uSES * breaking(middleware/devtools): use official devtools extension types * type object.create * avoid emitting @redux-devtools/extension * fix type with any * refactor * fix yarn lock * temporary fix #829 * v4.0.0-beta.2 * fix lint * lock date-fns version * lock testing-library/react alpha version * devtools: remove deprecations and warnings * fix tests * use `__DEV__` * revert irrelevant v4 changes * correct merge * fix test * fix `any` * fix `any` again Co-authored-by: daishi <daishi@axlight.com>
This commit is contained in:
parent
f65a25acd5
commit
943af082ae
@ -18,15 +18,7 @@ type Message = {
|
||||
type Write<T extends object, U extends object> = Omit<T, keyof U> & U
|
||||
type Cast<T, U> = T extends U ? T : U
|
||||
|
||||
type WithDevtools<S> = Write<Cast<S, object>, StoreSetStateWithAction<S>> & {
|
||||
/**
|
||||
* @deprecated `devtools` property on the store is deprecated
|
||||
* it will be removed in the next major.
|
||||
* You shouldn't interact with the extension directly. But in case you still want to
|
||||
* you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly
|
||||
*/
|
||||
devtools?: DevtoolsType
|
||||
}
|
||||
type WithDevtools<S> = Write<Cast<S, object>, StoreSetStateWithAction<S>>
|
||||
|
||||
type StoreSetStateWithAction<S> = S extends { getState: () => infer T }
|
||||
? S & { setState: NamedSet<Cast<T, object>> }
|
||||
@ -50,23 +42,6 @@ interface DevtoolsOptions {
|
||||
}
|
||||
}
|
||||
|
||||
type DevtoolsType = {
|
||||
/**
|
||||
* @deprecated along with `api.devtools`, `api.devtools.prefix` is deprecated.
|
||||
* We no longer prefix the actions/names, because the `name` option already
|
||||
* creates a separate instance of devtools for each store.
|
||||
*/
|
||||
prefix: string
|
||||
subscribe: (dispatch: any) => () => void
|
||||
unsubscribe: () => void
|
||||
send: {
|
||||
(action: string | { type: unknown }, state: any): void
|
||||
(action: null, liftedState: any): void
|
||||
}
|
||||
init: (state: any) => void
|
||||
error: (payload: any) => void
|
||||
}
|
||||
|
||||
export type NamedSet<T extends State> = {
|
||||
<
|
||||
K1 extends keyof T,
|
||||
@ -85,13 +60,6 @@ export type NamedSet<T extends State> = {
|
||||
*/
|
||||
export type StoreApiWithDevtools<T extends State> = StoreApi<T> & {
|
||||
setState: NamedSet<T>
|
||||
/**
|
||||
* @deprecated `devtools` property on the store is deprecated
|
||||
* it will be removed in the next major.
|
||||
* You shouldn't interact with the extension directly. But in case you still want to
|
||||
* you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly
|
||||
*/
|
||||
devtools?: DevtoolsType
|
||||
}
|
||||
|
||||
export function devtools<
|
||||
@ -104,11 +72,7 @@ export function devtools<
|
||||
): (
|
||||
set: CustomSetState,
|
||||
get: CustomGetState,
|
||||
api: CustomStoreApi &
|
||||
StoreApiWithDevtools<S> & {
|
||||
dispatch?: unknown
|
||||
dispatchFromDevtools?: boolean
|
||||
}
|
||||
api: CustomStoreApi & StoreApiWithDevtools<S>
|
||||
) => S
|
||||
/**
|
||||
* @deprecated Passing `name` as directly will be not allowed in next major.
|
||||
@ -125,11 +89,7 @@ export function devtools<
|
||||
): (
|
||||
set: CustomSetState,
|
||||
get: CustomGetState,
|
||||
api: CustomStoreApi &
|
||||
StoreApiWithDevtools<S> & {
|
||||
dispatch?: unknown
|
||||
dispatchFromDevtools?: boolean
|
||||
}
|
||||
api: CustomStoreApi & StoreApiWithDevtools<S>
|
||||
) => S
|
||||
export function devtools<
|
||||
S extends State,
|
||||
@ -142,11 +102,7 @@ export function devtools<
|
||||
): (
|
||||
set: CustomSetState,
|
||||
get: CustomGetState,
|
||||
api: CustomStoreApi &
|
||||
StoreApiWithDevtools<S> & {
|
||||
dispatch?: unknown
|
||||
dispatchFromDevtools?: boolean
|
||||
}
|
||||
api: CustomStoreApi & StoreApiWithDevtools<S>
|
||||
) => S
|
||||
export function devtools<
|
||||
S extends State,
|
||||
@ -160,20 +116,8 @@ export function devtools<
|
||||
return (
|
||||
set: CustomSetState,
|
||||
get: CustomGetState,
|
||||
api: CustomStoreApi &
|
||||
StoreApiWithDevtools<S> & {
|
||||
dispatch?: unknown
|
||||
dispatchFromDevtools?: boolean
|
||||
}
|
||||
api: CustomStoreApi & StoreApiWithDevtools<S>
|
||||
): S => {
|
||||
let didWarnAboutNameDeprecation = false
|
||||
if (typeof options === 'string' && !didWarnAboutNameDeprecation) {
|
||||
console.warn(
|
||||
'[zustand devtools middleware]: passing `name` as directly will be not allowed in next major' +
|
||||
'pass the `name` in an object `{ name: ... }` instead'
|
||||
)
|
||||
didWarnAboutNameDeprecation = true
|
||||
}
|
||||
const devtoolsOptions =
|
||||
options === undefined
|
||||
? {}
|
||||
@ -197,71 +141,7 @@ export function devtools<
|
||||
return fn(set, get, api)
|
||||
}
|
||||
|
||||
let extension = (Object.create as <T>(t: T) => T)(
|
||||
extensionConnector.connect(devtoolsOptions)
|
||||
)
|
||||
// We're using `Object.defineProperty` to set `prefix`, so if extensionConnector.connect
|
||||
// returns the same reference we'd get cannot redefine property prefix error
|
||||
// hence we `Object.create` to make a new reference
|
||||
|
||||
let didWarnAboutDevtools = false
|
||||
Object.defineProperty(api, 'devtools', {
|
||||
get: () => {
|
||||
if (!didWarnAboutDevtools) {
|
||||
console.warn(
|
||||
'[zustand devtools middleware] `devtools` property on the store is deprecated ' +
|
||||
'it will be removed in the next major.\n' +
|
||||
"You shouldn't interact with the extension directly. But in case you still want to " +
|
||||
'you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly'
|
||||
)
|
||||
didWarnAboutDevtools = true
|
||||
}
|
||||
return extension
|
||||
},
|
||||
set: (value) => {
|
||||
if (!didWarnAboutDevtools) {
|
||||
console.warn(
|
||||
'[zustand devtools middleware] `api.devtools` is deprecated, ' +
|
||||
'it will be removed in the next major.\n' +
|
||||
"You shouldn't interact with the extension directly. But in case you still want to " +
|
||||
'you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly'
|
||||
)
|
||||
didWarnAboutDevtools = true
|
||||
}
|
||||
extension = value
|
||||
},
|
||||
})
|
||||
|
||||
let didWarnAboutPrefix = false
|
||||
Object.defineProperty(extension, 'prefix', {
|
||||
get: () => {
|
||||
if (!didWarnAboutPrefix) {
|
||||
console.warn(
|
||||
'[zustand devtools middleware] along with `api.devtools`, `api.devtools.prefix` is deprecated.\n' +
|
||||
'We no longer prefix the actions/names' +
|
||||
devtoolsOptions.name ===
|
||||
undefined
|
||||
? ', pass the `name` option to create a separate instance of devtools for each store.'
|
||||
: ', because the `name` option already creates a separate instance of devtools for each store.'
|
||||
)
|
||||
didWarnAboutPrefix = true
|
||||
}
|
||||
return ''
|
||||
},
|
||||
set: () => {
|
||||
if (!didWarnAboutPrefix) {
|
||||
console.warn(
|
||||
'[zustand devtools middleware] along with `api.devtools`, `api.devtools.prefix` is deprecated.\n' +
|
||||
'We no longer prefix the actions/names' +
|
||||
devtoolsOptions.name ===
|
||||
undefined
|
||||
? ', pass the `name` option to create a separate instance of devtools for each store.'
|
||||
: ', because the `name` option already creates a separate instance of devtools for each store.'
|
||||
)
|
||||
didWarnAboutPrefix = true
|
||||
}
|
||||
},
|
||||
})
|
||||
const extension = extensionConnector.connect(devtoolsOptions)
|
||||
|
||||
let isRecording = true
|
||||
;(api.setState as NamedSet<S>) = (state, replace, nameOrAction) => {
|
||||
@ -286,11 +166,18 @@ export function devtools<
|
||||
const initialState = fn(api.setState, get, api)
|
||||
extension.init(initialState)
|
||||
|
||||
if (api.dispatchFromDevtools && typeof api.dispatch === 'function') {
|
||||
if (
|
||||
(api as any).dispatchFromDevtools &&
|
||||
typeof (api as any).dispatch === 'function'
|
||||
) {
|
||||
let didWarnAboutReservedActionType = false
|
||||
const originalDispatch = api.dispatch
|
||||
api.dispatch = (...a: any[]) => {
|
||||
if (a[0].type === '__setState' && !didWarnAboutReservedActionType) {
|
||||
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.'
|
||||
@ -325,9 +212,9 @@ export function devtools<
|
||||
return
|
||||
}
|
||||
|
||||
if (!api.dispatchFromDevtools) return
|
||||
if (typeof api.dispatch !== 'function') return
|
||||
;(api.dispatch as any)(action)
|
||||
if (!(api as any).dispatchFromDevtools) return
|
||||
if (typeof (api as any).dispatch !== 'function') return
|
||||
;(api as any).dispatch(action)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { devtools, redux } from 'zustand/middleware'
|
||||
import create from 'zustand/vanilla'
|
||||
import create, { StoreApi } from 'zustand/vanilla'
|
||||
|
||||
let extensionSubscriber: ((message: any) => void) | undefined
|
||||
const extension = {
|
||||
@ -125,7 +125,7 @@ describe('when it receives an message of type...', () => {
|
||||
it('does nothing even if there is `api.dispatch`', () => {
|
||||
const initialState = { count: 0 }
|
||||
const api = create(devtools(() => initialState))
|
||||
api.dispatch = jest.fn()
|
||||
;(api as any).dispatch = jest.fn()
|
||||
const setState = jest.spyOn(api, 'setState')
|
||||
|
||||
;(extensionSubscriber as (message: any) => void)({
|
||||
@ -135,14 +135,14 @@ describe('when it receives an message of type...', () => {
|
||||
|
||||
expect(api.getState()).toBe(initialState)
|
||||
expect(setState).not.toBeCalled()
|
||||
expect(api.dispatch).not.toBeCalled()
|
||||
expect((api as any).dispatch).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('dispatches with `api.dispatch` when `api.dispatchFromDevtools` is set to true', () => {
|
||||
const initialState = { count: 0 }
|
||||
const api = create(devtools(() => initialState))
|
||||
api.dispatch = jest.fn()
|
||||
api.dispatchFromDevtools = true
|
||||
;(api as any).dispatch = jest.fn()
|
||||
;(api as any).dispatchFromDevtools = true
|
||||
const setState = jest.spyOn(api, 'setState')
|
||||
|
||||
;(extensionSubscriber as (message: any) => void)({
|
||||
@ -152,14 +152,16 @@ describe('when it receives an message of type...', () => {
|
||||
|
||||
expect(api.getState()).toBe(initialState)
|
||||
expect(setState).not.toBeCalled()
|
||||
expect(api.dispatch).toHaveBeenLastCalledWith({ type: 'INCREMENT' })
|
||||
expect((api as any).dispatch).toHaveBeenLastCalledWith({
|
||||
type: 'INCREMENT',
|
||||
})
|
||||
})
|
||||
|
||||
it('does not throw for unsupported payload', () => {
|
||||
const initialState = { count: 0 }
|
||||
const api = create(devtools(() => initialState))
|
||||
api.dispatch = jest.fn()
|
||||
api.dispatchFromDevtools = true
|
||||
;(api as any).dispatch = jest.fn()
|
||||
;(api as any).dispatchFromDevtools = true
|
||||
const setState = jest.spyOn(api, 'setState')
|
||||
const originalConsoleError = console.error
|
||||
console.error = jest.fn()
|
||||
@ -195,7 +197,7 @@ describe('when it receives an message of type...', () => {
|
||||
|
||||
expect(api.getState()).toBe(initialState)
|
||||
expect(setState).not.toBeCalled()
|
||||
expect(api.dispatch).not.toBeCalled()
|
||||
expect((api as any).dispatch).not.toBeCalled()
|
||||
|
||||
console.error = originalConsoleError
|
||||
})
|
||||
@ -436,43 +438,50 @@ describe('when it receives an message of type...', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('works with redux middleware', () => {
|
||||
const api = create(
|
||||
devtools(
|
||||
redux(
|
||||
({ count }, { type }: { type: 'INCREMENT' | 'DECREMENT' }) => ({
|
||||
count: count + (type === 'INCREMENT' ? 1 : -1),
|
||||
}),
|
||||
{ count: 0 }
|
||||
describe('with redux middleware', () => {
|
||||
let api: StoreApi<{ count: number }>
|
||||
|
||||
it('works as expected', () => {
|
||||
api = create(
|
||||
devtools(
|
||||
redux(
|
||||
(
|
||||
{ count },
|
||||
{ type }: { type: 'INCREMENT' } | { type: 'DECREMENT' }
|
||||
) => ({
|
||||
count: count + (type === 'INCREMENT' ? 1 : -1),
|
||||
}),
|
||||
{ count: 0 }
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
;(api as any).dispatch({ type: 'INCREMENT' })
|
||||
;(api as any).dispatch({ type: 'INCREMENT' })
|
||||
;(extensionSubscriber as (message: any) => void)({
|
||||
type: 'ACTION',
|
||||
payload: JSON.stringify({ type: 'DECREMENT' }),
|
||||
})
|
||||
|
||||
api.dispatch({ type: 'INCREMENT' })
|
||||
api.dispatch({ type: 'INCREMENT' })
|
||||
;(extensionSubscriber as (message: any) => void)({
|
||||
type: 'ACTION',
|
||||
payload: JSON.stringify({ type: 'DECREMENT' }),
|
||||
expect(extension.init.mock.calls).toMatchObject([[{ count: 0 }]])
|
||||
expect(extension.send.mock.calls).toMatchObject([
|
||||
[{ type: 'INCREMENT' }, { count: 1 }],
|
||||
[{ type: 'INCREMENT' }, { count: 2 }],
|
||||
[{ type: 'DECREMENT' }, { count: 1 }],
|
||||
])
|
||||
expect(api.getState()).toMatchObject({ count: 1 })
|
||||
})
|
||||
|
||||
expect(extension.init.mock.calls).toMatchObject([[{ count: 0 }]])
|
||||
expect(extension.send.mock.calls).toMatchObject([
|
||||
[{ type: 'INCREMENT' }, { count: 1 }],
|
||||
[{ type: 'INCREMENT' }, { count: 2 }],
|
||||
[{ type: 'DECREMENT' }, { count: 1 }],
|
||||
])
|
||||
expect(api.getState()).toMatchObject({ count: 1 })
|
||||
it('[DEV-ONLY] warns about misusage', () => {
|
||||
const originalConsoleWarn = console.warn
|
||||
console.warn = jest.fn()
|
||||
;(api as any).dispatch({ type: '__setState' as any })
|
||||
expect(console.warn).toHaveBeenLastCalledWith(
|
||||
'[zustand devtools middleware] "__setState" action type is reserved ' +
|
||||
'to set state from the devtools. Avoid using it.'
|
||||
)
|
||||
|
||||
const originalConsoleWarn = console.warn
|
||||
console.warn = jest.fn()
|
||||
|
||||
api.dispatch({ type: '__setState' as any })
|
||||
expect(console.warn).toHaveBeenLastCalledWith(
|
||||
'[zustand devtools middleware] "__setState" action type is reserved ' +
|
||||
'to set state from the devtools. Avoid using it.'
|
||||
)
|
||||
|
||||
console.warn = originalConsoleWarn
|
||||
console.warn = originalConsoleWarn
|
||||
})
|
||||
})
|
||||
|
||||
it('works in non-browser env', () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user