mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
feat: deprecate equalityFn and add createWithEqualityFn (#1945)
* feat: deprecate equalityFn and add createWithEqualityFn * add link to deprecation message
This commit is contained in:
parent
6d9c0cff0d
commit
c6900a4562
10
package.json
10
package.json
@ -65,6 +65,15 @@
|
|||||||
"module": "./esm/shallow.js",
|
"module": "./esm/shallow.js",
|
||||||
"default": "./shallow.js"
|
"default": "./shallow.js"
|
||||||
},
|
},
|
||||||
|
"./traditional": {
|
||||||
|
"types": "./traditional.d.ts",
|
||||||
|
"import": {
|
||||||
|
"types": "./esm/traditional.d.mts",
|
||||||
|
"default": "./esm/traditional.mjs"
|
||||||
|
},
|
||||||
|
"module": "./esm/traditional.js",
|
||||||
|
"default": "./traditional.js"
|
||||||
|
},
|
||||||
"./context": {
|
"./context": {
|
||||||
"types": "./context.d.ts",
|
"types": "./context.d.ts",
|
||||||
"import": {
|
"import": {
|
||||||
@ -84,6 +93,7 @@
|
|||||||
"build:middleware": "rollup -c --config-middleware",
|
"build:middleware": "rollup -c --config-middleware",
|
||||||
"build:middleware:immer": "rollup -c --config-middleware_immer",
|
"build:middleware:immer": "rollup -c --config-middleware_immer",
|
||||||
"build:shallow": "rollup -c --config-shallow",
|
"build:shallow": "rollup -c --config-shallow",
|
||||||
|
"build:traditional": "rollup -c --config-traditional",
|
||||||
"build:context": "rollup -c --config-context",
|
"build:context": "rollup -c --config-context",
|
||||||
"postbuild": "yarn patch-d-ts && yarn copy && yarn patch-esm-ts",
|
"postbuild": "yarn patch-d-ts && yarn copy && yarn patch-esm-ts",
|
||||||
"prettier": "prettier \"*.{js,json,md}\" \"{examples,src,tests,docs}/**/*.{js,jsx,ts,tsx,md,mdx}\" --write",
|
"prettier": "prettier \"*.{js,json,md}\" \"{examples,src,tests,docs}/**/*.{js,jsx,ts,tsx,md,mdx}\" --write",
|
||||||
|
|||||||
@ -6,8 +6,9 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { useStore } from 'zustand'
|
|
||||||
import type { StoreApi } from 'zustand'
|
import type { StoreApi } from 'zustand'
|
||||||
|
// eslint-disable-next-line import/extensions
|
||||||
|
import { useStoreWithEqualityFn } from 'zustand/traditional'
|
||||||
|
|
||||||
type UseContextStore<S extends StoreApi<unknown>> = {
|
type UseContextStore<S extends StoreApi<unknown>> = {
|
||||||
(): ExtractState<S>
|
(): ExtractState<S>
|
||||||
@ -62,7 +63,7 @@ function createContext<S extends StoreApi<unknown>>() {
|
|||||||
'Seems like you have not used zustand provider as an ancestor.'
|
'Seems like you have not used zustand provider as an ancestor.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return useStore(
|
return useStoreWithEqualityFn(
|
||||||
store,
|
store,
|
||||||
selector as (state: ExtractState<S>) => StateSlice,
|
selector as (state: ExtractState<S>) => StateSlice,
|
||||||
equalityFn
|
equalityFn
|
||||||
|
|||||||
22
src/react.ts
22
src/react.ts
@ -27,10 +27,19 @@ export function useStore<S extends WithReact<StoreApi<unknown>>>(
|
|||||||
api: S
|
api: S
|
||||||
): ExtractState<S>
|
): ExtractState<S>
|
||||||
|
|
||||||
|
export function useStore<S extends WithReact<StoreApi<unknown>>, U>(
|
||||||
|
api: S,
|
||||||
|
selector: (state: ExtractState<S>) => U
|
||||||
|
): U
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `useStoreWithEqualityFn` from 'zustand/traditional'
|
||||||
|
* https://github.com/pmndrs/zustand/discussions/1937
|
||||||
|
*/
|
||||||
export function useStore<S extends WithReact<StoreApi<unknown>>, U>(
|
export function useStore<S extends WithReact<StoreApi<unknown>>, U>(
|
||||||
api: S,
|
api: S,
|
||||||
selector: (state: ExtractState<S>) => U,
|
selector: (state: ExtractState<S>) => U,
|
||||||
equalityFn?: (a: U, b: U) => boolean
|
equalityFn: (a: U, b: U) => boolean
|
||||||
): U
|
): U
|
||||||
|
|
||||||
export function useStore<TState, StateSlice>(
|
export function useStore<TState, StateSlice>(
|
||||||
@ -38,6 +47,11 @@ export function useStore<TState, StateSlice>(
|
|||||||
selector: (state: TState) => StateSlice = api.getState as any,
|
selector: (state: TState) => StateSlice = api.getState as any,
|
||||||
equalityFn?: (a: StateSlice, b: StateSlice) => boolean
|
equalityFn?: (a: StateSlice, b: StateSlice) => boolean
|
||||||
) {
|
) {
|
||||||
|
if (import.meta.env?.MODE !== 'production' && equalityFn) {
|
||||||
|
console.warn(
|
||||||
|
"[DEPRECATED] Use `createWithEqualityFn` from 'zustand/traditional'. https://github.com/pmndrs/zustand/discussions/1937"
|
||||||
|
)
|
||||||
|
}
|
||||||
const slice = useSyncExternalStoreWithSelector(
|
const slice = useSyncExternalStoreWithSelector(
|
||||||
api.subscribe,
|
api.subscribe,
|
||||||
api.getState,
|
api.getState,
|
||||||
@ -51,9 +65,13 @@ export function useStore<TState, StateSlice>(
|
|||||||
|
|
||||||
export type UseBoundStore<S extends WithReact<ReadonlyStoreApi<unknown>>> = {
|
export type UseBoundStore<S extends WithReact<ReadonlyStoreApi<unknown>>> = {
|
||||||
(): ExtractState<S>
|
(): ExtractState<S>
|
||||||
|
<U>(selector: (state: ExtractState<S>) => U): U
|
||||||
|
/**
|
||||||
|
* @deprecated Use `createWithEqualityFn` from 'zustand/traditional'
|
||||||
|
*/
|
||||||
<U>(
|
<U>(
|
||||||
selector: (state: ExtractState<S>) => U,
|
selector: (state: ExtractState<S>) => U,
|
||||||
equals?: (a: U, b: U) => boolean
|
equalityFn: (a: U, b: U) => boolean
|
||||||
): U
|
): U
|
||||||
} & S
|
} & S
|
||||||
|
|
||||||
|
|||||||
98
src/traditional.ts
Normal file
98
src/traditional.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { useDebugValue } from 'react'
|
||||||
|
// import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'
|
||||||
|
// This doesn't work in ESM, because use-sync-external-store only exposes CJS.
|
||||||
|
// See: https://github.com/pmndrs/valtio/issues/452
|
||||||
|
// The following is a workaround until ESM is supported.
|
||||||
|
// eslint-disable-next-line import/extensions
|
||||||
|
import useSyncExternalStoreExports from 'use-sync-external-store/shim/with-selector'
|
||||||
|
import { createStore } from './vanilla.ts'
|
||||||
|
import type {
|
||||||
|
Mutate,
|
||||||
|
StateCreator,
|
||||||
|
StoreApi,
|
||||||
|
StoreMutatorIdentifier,
|
||||||
|
} from './vanilla.ts'
|
||||||
|
|
||||||
|
const { useSyncExternalStoreWithSelector } = useSyncExternalStoreExports
|
||||||
|
|
||||||
|
type ExtractState<S> = S extends { getState: () => infer T } ? T : never
|
||||||
|
|
||||||
|
type ReadonlyStoreApi<T> = Pick<StoreApi<T>, 'getState' | 'subscribe'>
|
||||||
|
|
||||||
|
type WithReact<S extends ReadonlyStoreApi<unknown>> = S & {
|
||||||
|
getServerState?: () => ExtractState<S>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useStoreWithEqualityFn<S extends WithReact<StoreApi<unknown>>>(
|
||||||
|
api: S
|
||||||
|
): ExtractState<S>
|
||||||
|
|
||||||
|
export function useStoreWithEqualityFn<
|
||||||
|
S extends WithReact<StoreApi<unknown>>,
|
||||||
|
U
|
||||||
|
>(
|
||||||
|
api: S,
|
||||||
|
selector: (state: ExtractState<S>) => U,
|
||||||
|
equalityFn?: (a: U, b: U) => boolean
|
||||||
|
): U
|
||||||
|
|
||||||
|
export function useStoreWithEqualityFn<TState, StateSlice>(
|
||||||
|
api: WithReact<StoreApi<TState>>,
|
||||||
|
selector: (state: TState) => StateSlice = api.getState as any,
|
||||||
|
equalityFn?: (a: StateSlice, b: StateSlice) => boolean
|
||||||
|
) {
|
||||||
|
const slice = useSyncExternalStoreWithSelector(
|
||||||
|
api.subscribe,
|
||||||
|
api.getState,
|
||||||
|
api.getServerState || api.getState,
|
||||||
|
selector,
|
||||||
|
equalityFn
|
||||||
|
)
|
||||||
|
useDebugValue(slice)
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UseBoundStoreWithEqualityFn<
|
||||||
|
S extends WithReact<ReadonlyStoreApi<unknown>>
|
||||||
|
> = {
|
||||||
|
(): ExtractState<S>
|
||||||
|
<U>(
|
||||||
|
selector: (state: ExtractState<S>) => U,
|
||||||
|
equalityFn?: (a: U, b: U) => boolean
|
||||||
|
): U
|
||||||
|
} & S
|
||||||
|
|
||||||
|
type CreateWithEqualityFn = {
|
||||||
|
<T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(
|
||||||
|
initializer: StateCreator<T, [], Mos>,
|
||||||
|
defaultEqualityFn: <U>(a: U, b: U) => boolean
|
||||||
|
): UseBoundStoreWithEqualityFn<Mutate<StoreApi<T>, Mos>>
|
||||||
|
<T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>(
|
||||||
|
initializer: StateCreator<T, [], Mos>,
|
||||||
|
defaultEqualityFn: <U>(a: U, b: U) => boolean
|
||||||
|
) => UseBoundStoreWithEqualityFn<Mutate<StoreApi<T>, Mos>>
|
||||||
|
}
|
||||||
|
|
||||||
|
const createWithEqualityFnImpl = <T>(
|
||||||
|
createState: StateCreator<T, [], []>,
|
||||||
|
defaultEqualityFn?: <U>(a: U, b: U) => boolean
|
||||||
|
) => {
|
||||||
|
const api = createStore(createState)
|
||||||
|
|
||||||
|
const useBoundStoreWithEqualityFn: any = (
|
||||||
|
selector?: any,
|
||||||
|
equalityFn = defaultEqualityFn
|
||||||
|
) => useStoreWithEqualityFn(api, selector, equalityFn)
|
||||||
|
|
||||||
|
Object.assign(useBoundStoreWithEqualityFn, api)
|
||||||
|
|
||||||
|
return useBoundStoreWithEqualityFn
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createWithEqualityFn = (<T>(
|
||||||
|
createState: StateCreator<T, [], []> | undefined,
|
||||||
|
defaultEqualityFn?: <U>(a: U, b: U) => boolean
|
||||||
|
) =>
|
||||||
|
createState
|
||||||
|
? createWithEqualityFnImpl(createState, defaultEqualityFn)
|
||||||
|
: createWithEqualityFnImpl) as CreateWithEqualityFn
|
||||||
@ -11,6 +11,7 @@ import ReactDOM from 'react-dom'
|
|||||||
import { afterEach, expect, it, vi } from 'vitest'
|
import { afterEach, expect, it, vi } from 'vitest'
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import type { StoreApi } from 'zustand'
|
import type { StoreApi } from 'zustand'
|
||||||
|
import { createWithEqualityFn } from 'zustand/traditional'
|
||||||
|
|
||||||
const consoleError = console.error
|
const consoleError = console.error
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -89,7 +90,10 @@ it('uses the store with selectors', async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('uses the store with a selector and equality checker', async () => {
|
it('uses the store with a selector and equality checker', async () => {
|
||||||
const useBoundStore = create(() => ({ item: { value: 0 } }))
|
const useBoundStore = createWithEqualityFn(
|
||||||
|
() => ({ item: { value: 0 } }),
|
||||||
|
Object.is
|
||||||
|
)
|
||||||
const { setState } = useBoundStore
|
const { setState } = useBoundStore
|
||||||
let renderCount = 0
|
let renderCount = 0
|
||||||
|
|
||||||
@ -214,7 +218,10 @@ it('can update the selector', async () => {
|
|||||||
it('can update the equality checker', async () => {
|
it('can update the equality checker', async () => {
|
||||||
type State = { value: number }
|
type State = { value: number }
|
||||||
type Props = { equalityFn: (a: State, b: State) => boolean }
|
type Props = { equalityFn: (a: State, b: State) => boolean }
|
||||||
const useBoundStore = create<State>(() => ({ value: 0 }))
|
const useBoundStore = createWithEqualityFn<State>(
|
||||||
|
() => ({ value: 0 }),
|
||||||
|
Object.is
|
||||||
|
)
|
||||||
const { setState } = useBoundStore
|
const { setState } = useBoundStore
|
||||||
const selector = (s: State) => s
|
const selector = (s: State) => s
|
||||||
|
|
||||||
@ -258,7 +265,10 @@ it('can call useBoundStore with progressively more arguments', async () => {
|
|||||||
equalityFn?: (a: number, b: number) => boolean
|
equalityFn?: (a: number, b: number) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const useBoundStore = create<State>(() => ({ value: 0 }))
|
const useBoundStore = createWithEqualityFn<State>(
|
||||||
|
() => ({ value: 0 }),
|
||||||
|
Object.is
|
||||||
|
)
|
||||||
const { setState } = useBoundStore
|
const { setState } = useBoundStore
|
||||||
|
|
||||||
let renderCount = 0
|
let renderCount = 0
|
||||||
@ -357,7 +367,7 @@ it('can throw an error in equality checker', async () => {
|
|||||||
type State = { value: string | number }
|
type State = { value: string | number }
|
||||||
|
|
||||||
const initialState: State = { value: 'foo' }
|
const initialState: State = { value: 'foo' }
|
||||||
const useBoundStore = create(() => initialState)
|
const useBoundStore = createWithEqualityFn(() => initialState, Object.is)
|
||||||
const { setState } = useBoundStore
|
const { setState } = useBoundStore
|
||||||
const selector = (s: State) => s
|
const selector = (s: State) => s
|
||||||
const equalityFn = (a: State, b: State) =>
|
const equalityFn = (a: State, b: State) =>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user