Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | 10x 1x 17x 17x 24x 24x 22x 22x 25x 17x 21x 7x 21x 20x 20x 20x 19x 10x 2x 2x 21x 21x 17x 17x 32x 32x 32x 13x 11x 4x 28x 28x 28x 28x 28x 28x 28x 17x 17x 17x | import { useEffect, useLayoutEffect, useReducer, useRef } from 'react'
export type State = Record<string | number | symbol, any>
export type StateListener<T> = (state: T) => void
export type StateSelector<T extends State, U> = (state: T) => U
export type PartialState<T extends State> =
| Partial<T>
| ((state: T) => Partial<T>)
export type EqualityChecker<T> = (state: T, newState: any) => boolean
export interface SubscribeOptions<T extends State, U> {
selector?: StateSelector<T, U>
equalityFn?: EqualityChecker<U>
currentSlice?: U
subscribeError?: Error
}
export type StateCreator<T extends State> = (
set: SetState<T>,
get: GetState<T>,
api: StoreApi<T>
) => T
export type SetState<T extends State> = (partial: PartialState<T>) => void
export type GetState<T extends State> = () => T
export type Subscribe<T extends State> = <U>(
listener: StateListener<U | void>,
options?: SubscribeOptions<T, U>
) => () => void
export type Destroy = () => void
export type UseStore<T extends State> = <U>(
selector?: StateSelector<T, U>,
equalityFn?: EqualityChecker<U>
) => U
export interface StoreApi<T extends State> {
setState: SetState<T>
getState: GetState<T>
subscribe: Subscribe<T>
destroy: Destroy
}
const forceUpdateReducer = (state: boolean) => !state
// For server-side rendering: https://github.com/react-spring/zustand/pull/34
const useIsoLayoutEffect =
typeof window === 'undefined' ? useEffect : useLayoutEffect
export default function create<TState extends State>(
// Use TState for createState signature when available.
// e.g. create<MyState>(set => ...
createState: keyof TState extends never
? StateCreator<State>
: StateCreator<TState>
): [UseStore<TState>, StoreApi<TState>] {
const listeners: Set<StateListener<void>> = new Set()
const setState: SetState<TState> = partial => {
const partialState =
typeof partial === 'function' ? partial(state) : partial
if (partialState !== state) {
state = Object.assign({}, state, partialState)
listeners.forEach(listener => listener())
}
}
const getState: GetState<TState> = () => state
const subscribe: Subscribe<TState> = <StateSlice>(
listener: StateListener<StateSlice | void>,
options: SubscribeOptions<TState, StateSlice> = {}
) => {
if (!('currentSlice' in options)) {
options.currentSlice = (options.selector || getState)(state)
}
const listenerFn = () => {
// Destructure in the listener to get current values. We rely on this
// because options is mutated in useStore.
const { selector = getState, equalityFn = Object.is } = options
// Selector or equality function could throw but we don't want to stop
// the listener from being called.
// https://github.com/react-spring/zustand/pull/37
try {
const newStateSlice = selector(state)
if (!equalityFn(options.currentSlice as StateSlice, newStateSlice)) {
listener((options.currentSlice = newStateSlice))
}
} catch (error) {
options.subscribeError = error
listener()
}
}
listeners.add(listenerFn)
return () => void listeners.delete(listenerFn)
}
const destroy: Destroy = () => listeners.clear()
const useStore = <StateSlice>(
selector: StateSelector<TState, StateSlice> = getState,
equalityFn: EqualityChecker<StateSlice> = Object.is
): StateSlice => {
const isInitial = useRef(true)
const options = useRef(
// isInitial prevents the selector from being called every render.
isInitial.current && {
selector,
equalityFn,
currentSlice: ((isInitial.current = false), selector(state)),
}
).current as SubscribeOptions<TState, StateSlice>
// Update state slice if selector has changed or subscriber errored.
if (selector !== options.selector || options.subscribeError) {
const newStateSlice = selector(state)
if (!equalityFn(options.currentSlice as StateSlice, newStateSlice)) {
options.currentSlice = newStateSlice
}
}
useIsoLayoutEffect(() => {
options.selector = selector
options.equalityFn = equalityFn
options.subscribeError = undefined
})
const forceUpdate = useReducer(forceUpdateReducer, false)[1]
useIsoLayoutEffect(() => subscribe(forceUpdate, options), [])
return options.currentSlice as StateSlice
}
const api = { setState, getState, subscribe, destroy }
let state = createState(setState, getState, api)
return [useStore, api]
}
|