mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
[v5]: follow React "standard" way with breaking behavioral change (#2395)
* [v5]: follow React "standard" way with breaking behavioral change * add test
This commit is contained in:
parent
b19acdfab7
commit
f87eec594d
22
src/react.ts
22
src/react.ts
@ -12,29 +12,13 @@ import type {
|
||||
StoreMutatorIdentifier,
|
||||
} from './vanilla.ts'
|
||||
|
||||
const { useDebugValue, useMemo, useSyncExternalStore } = ReactExports
|
||||
const { useDebugValue, useSyncExternalStore } = ReactExports
|
||||
|
||||
type ExtractState<S> = S extends { getState: () => infer T } ? T : never
|
||||
|
||||
type ReadonlyStoreApi<T> = Pick<StoreApi<T>, 'getState' | 'subscribe'>
|
||||
|
||||
const identity = <T>(arg: T): T => arg
|
||||
|
||||
const useMemoSelector = <TState, StateSlice>(
|
||||
getState: () => TState,
|
||||
selector: (state: TState) => StateSlice,
|
||||
) =>
|
||||
useMemo(() => {
|
||||
let prev: readonly [TState, StateSlice] | undefined
|
||||
return () => {
|
||||
const state = getState()
|
||||
if (!prev || !Object.is(prev[0], state)) {
|
||||
prev = [state, selector(state)]
|
||||
}
|
||||
return prev[1]
|
||||
}
|
||||
}, [getState, selector])
|
||||
|
||||
export function useStore<S extends StoreApi<unknown>>(api: S): ExtractState<S>
|
||||
|
||||
export function useStore<S extends StoreApi<unknown>, U>(
|
||||
@ -48,8 +32,8 @@ export function useStore<TState, StateSlice>(
|
||||
) {
|
||||
const slice = useSyncExternalStore(
|
||||
api.subscribe,
|
||||
useMemoSelector(api.getState, selector),
|
||||
useMemoSelector(api.getInitialState, selector),
|
||||
() => selector(api.getState()),
|
||||
() => selector(api.getInitialState()),
|
||||
)
|
||||
useDebugValue(slice)
|
||||
return slice
|
||||
|
||||
@ -472,9 +472,47 @@ it('can set the store without merging', () => {
|
||||
expect(getState()).toEqual({ b: 2 })
|
||||
})
|
||||
|
||||
it('only calls selectors when necessary', async () => {
|
||||
it('only calls selectors when necessary with static selector', async () => {
|
||||
type State = { a: number; b: number }
|
||||
const useBoundStore = create<State>(() => ({ a: 0, b: 0 }))
|
||||
const useBoundStore = createWithEqualityFn<State>(() => ({ a: 0, b: 0 }))
|
||||
const { setState } = useBoundStore
|
||||
let staticSelectorCallCount = 0
|
||||
|
||||
function staticSelector(s: State) {
|
||||
staticSelectorCallCount++
|
||||
return s.a
|
||||
}
|
||||
|
||||
function Component() {
|
||||
useBoundStore(staticSelector)
|
||||
return (
|
||||
<>
|
||||
<div>static: {staticSelectorCallCount}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const { rerender, findByText } = render(
|
||||
<>
|
||||
<Component />
|
||||
</>,
|
||||
)
|
||||
await findByText('static: 1')
|
||||
|
||||
rerender(
|
||||
<>
|
||||
<Component />
|
||||
</>,
|
||||
)
|
||||
await findByText('static: 1')
|
||||
|
||||
act(() => setState({ a: 1, b: 1 }))
|
||||
await findByText('static: 2')
|
||||
})
|
||||
|
||||
it('only calls selectors when necessary (traditional)', async () => {
|
||||
type State = { a: number; b: number }
|
||||
const useBoundStore = createWithEqualityFn<State>(() => ({ a: 0, b: 0 }))
|
||||
const { setState } = useBoundStore
|
||||
let inlineSelectorCallCount = 0
|
||||
let staticSelectorCallCount = 0
|
||||
|
||||
@ -19,11 +19,9 @@ const useBearStore = create<BearStoreState & BearStoreAction>((set) => ({
|
||||
}))
|
||||
|
||||
function Counter() {
|
||||
const { bears, increasePopulation } = useBearStore(
|
||||
({ bears, increasePopulation }) => ({
|
||||
bears,
|
||||
increasePopulation,
|
||||
}),
|
||||
const bears = useBearStore(({ bears }) => bears)
|
||||
const increasePopulation = useBearStore(
|
||||
({ increasePopulation }) => increasePopulation,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user