mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
Fix TS4023 and other minor changes (#940)
* fix(ts4023): use type aliases instead of interfaces * refactor(types): immer, devtools - make immer types more like devtools for better quickinfo - make immer and devtools store names consistent with StoreMiddleware naming convention (eg StorePersist, StoreRedux, etc) * format(docs): add a linebreak after <summary>
This commit is contained in:
parent
0ae0293e88
commit
72de1aed5d
@ -21,6 +21,8 @@ const useStore = create<BearState>()((set) => ({
|
||||
<details>
|
||||
<summary>Why can't we just simply infer the type from initial state?</summary>
|
||||
|
||||
<br/>
|
||||
|
||||
**TLDR**: Because state generic `T` is invariant.
|
||||
|
||||
Consider this minimal version `create`...
|
||||
@ -69,6 +71,8 @@ const useStore = create<BearState>()((set) => ({
|
||||
|
||||
<details>
|
||||
<summary>Why that currying `()(...)`?</summary>
|
||||
|
||||
<br/>
|
||||
|
||||
**TLDR**: It's a workaround for [microsoft/TypeScript#10571](https://github.com/microsoft/TypeScript/issues/10571).
|
||||
|
||||
@ -118,6 +122,8 @@ const useStore = create(combine({ bears: 0 }, (set) => ({
|
||||
<details>
|
||||
<summary>But be a little careful...</summary>
|
||||
|
||||
<br/>
|
||||
|
||||
We achieve the inference by lying a little in the types of `set`, `get` and `store` that you receive as parameters. The lie is that they're typed in a way as if the state is the first parameter only when in fact the state is the shallow-merge (`{ ...a, ...b }`) of both first parameter and the second parameter's return. So for example `get` from the second parameter has type `() => { bears: number }` and that's a lie as it should be `() => { bears: number, increase: (by: number) => void }`. And `useStore` still has the correct type, ie for example `useStore.getState` is typed as `() => { bears: number, increase: (by: number) => void }`.
|
||||
|
||||
It's not a lie lie because `{ bears: number }` is still a subtype `{ bears: number, increase: (by: number) => void }`, so in most cases there won't be a problem. Just you have to be careful while using replace. For eg `set({ bears: 0 }, true)` would compile but will be unsound as it'll delete the `increase` function. (If you set from "outside" ie `useStore.setState({ bears: 0 }, true)` then it won't compile because the "outside" store knows that `increase` is missing.) Another instance where you should be careful you're doing `Object.keys`, `Object.keys(get())` will return `["bears", "increase"]` and not `["bears"]` (the return type of `get` can make you fall for this).
|
||||
|
||||
@ -538,6 +538,8 @@ A more complete TypeScript guide is [here](https://github.com/pmndrs/zustand/blo
|
||||
<details>
|
||||
<summary>Calling actions outside a React event handler in pre React 18</summary>
|
||||
|
||||
<br/>
|
||||
|
||||
Because React handles `setState` synchronously if it's called outside an event handler. Updating the state outside an event handler will force react to update the components synchronously, therefore adding the risk of encountering the zombie-child effect.
|
||||
In order to fix this, the action needs to be wrapped in `unstable_batchedUpdates`
|
||||
|
||||
|
||||
@ -44,9 +44,9 @@ type TakeTwo<T> = T extends []
|
||||
? [A0?, A1?]
|
||||
: never
|
||||
|
||||
type WithDevtools<S> = Write<Cast<S, object>, StoreSetStateWithAction<S>>
|
||||
type WithDevtools<S> = Write<Cast<S, object>, StoreDevtools<S>>
|
||||
|
||||
type StoreSetStateWithAction<S> = S extends {
|
||||
type StoreDevtools<S> = S extends {
|
||||
setState: (...a: infer A) => infer Sr
|
||||
}
|
||||
? {
|
||||
|
||||
@ -18,6 +18,7 @@ declare module '../vanilla' {
|
||||
}
|
||||
|
||||
type Write<T extends object, U extends object> = Omit<T, keyof U> & U
|
||||
type Cast<T, U> = T extends U ? T : U
|
||||
type SkipTwo<T> = T extends []
|
||||
? []
|
||||
: T extends [unknown]
|
||||
@ -32,22 +33,21 @@ type SkipTwo<T> = T extends []
|
||||
? A
|
||||
: never
|
||||
|
||||
type WithImmer<S> = S extends {
|
||||
type WithImmer<S> = Write<Cast<S, object>, StoreImmer<S>>
|
||||
|
||||
type StoreImmer<S> = S extends {
|
||||
getState: () => infer T
|
||||
setState: infer SetState
|
||||
}
|
||||
? Write<
|
||||
S,
|
||||
SetState extends (...a: infer A) => infer Sr
|
||||
? {
|
||||
setState(
|
||||
nextStateOrUpdater: T | Partial<T> | ((state: Draft<T>) => void),
|
||||
shouldReplace?: boolean | undefined,
|
||||
...a: SkipTwo<A>
|
||||
): Sr
|
||||
}
|
||||
: never
|
||||
>
|
||||
? SetState extends (...a: infer A) => infer Sr
|
||||
? {
|
||||
setState(
|
||||
nextStateOrUpdater: T | Partial<T> | ((state: Draft<T>) => void),
|
||||
shouldReplace?: boolean | undefined,
|
||||
...a: SkipTwo<A>
|
||||
): Sr
|
||||
}
|
||||
: never
|
||||
: never
|
||||
|
||||
type PopArgument<T extends (...a: never[]) => unknown> = T extends (
|
||||
|
||||
@ -4,15 +4,15 @@ import { NamedSet } from './devtools'
|
||||
type Write<T extends object, U extends object> = Omit<T, keyof U> & U
|
||||
type Cast<T, U> = T extends U ? T : U
|
||||
|
||||
interface Action {
|
||||
type Action = {
|
||||
type: unknown
|
||||
}
|
||||
|
||||
interface ReduxState<A extends Action> {
|
||||
type ReduxState<A extends Action> = {
|
||||
dispatch: StoreRedux<A>['dispatch']
|
||||
}
|
||||
|
||||
interface StoreRedux<A extends Action> {
|
||||
type StoreRedux<A extends Action> = {
|
||||
dispatch: (a: A) => A
|
||||
dispatchFromDevtools: true
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ declare module '../vanilla' {
|
||||
}
|
||||
}
|
||||
|
||||
interface StoreSubscribeWithSelector<T extends State> {
|
||||
type StoreSubscribeWithSelector<T extends State> = {
|
||||
subscribe: {
|
||||
(listener: (selectedState: T, previousSelectedState: T) => void): () => void
|
||||
<U>(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user