mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
chore: run prettier for docs (#1007)
This commit is contained in:
parent
e85bd2b899
commit
2b698fb249
2
.github/workflows/lint-and-type.yml
vendored
2
.github/workflows/lint-and-type.yml
vendored
@ -18,6 +18,8 @@ jobs:
|
||||
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: cd examples && yarn install --frozen-lockfile --check-files
|
||||
- name: Prettier
|
||||
run: yarn prettier:ci
|
||||
- name: Lint
|
||||
run: yarn eslint:ci
|
||||
- name: Type
|
||||
|
||||
1
.husky/.gitignore
vendored
1
.husky/.gitignore
vendored
@ -1 +0,0 @@
|
||||
_
|
||||
@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
||||
@ -5,7 +5,7 @@
|
||||
When using TypeScript you just have to make a tiny change that instead of writing `create(...)` you'll have to write `create<T>()(...)` where `T` would be type of the state so as to annotate it. Example...
|
||||
|
||||
```ts
|
||||
import create from "zustand"
|
||||
import create from 'zustand'
|
||||
|
||||
interface BearState {
|
||||
bears: number
|
||||
@ -23,50 +23,51 @@ const useStore = create<BearState>()((set) => ({
|
||||
|
||||
<br/>
|
||||
|
||||
**TLDR**: Because state generic `T` is invariant.
|
||||
|
||||
Consider this minimal version `create`...
|
||||
**TLDR**: Because state generic `T` is invariant.
|
||||
|
||||
```ts
|
||||
declare const create: <T>(f: (get: () => T) => T) => T
|
||||
Consider this minimal version `create`...
|
||||
|
||||
const x = create((get) => ({
|
||||
foo: 0,
|
||||
bar: () => get()
|
||||
}))
|
||||
// `x` is inferred as `unknown` instead of
|
||||
// interface X {
|
||||
// foo: number,
|
||||
// bar: () => X
|
||||
// }
|
||||
```
|
||||
```ts
|
||||
declare const create: <T>(f: (get: () => T) => T) => T
|
||||
|
||||
Here if you look at the type of `f` in `create` ie `(get: () => T) => T` it "gives" `T` as it returns `T` but then it also "takes" `T` via `get` so where does `T` come from TypeScript thinks... It's a like that chicken or egg problem. At the end TypeScript gives up and infers `T` as `unknown`.
|
||||
const x = create((get) => ({
|
||||
foo: 0,
|
||||
bar: () => get(),
|
||||
}))
|
||||
// `x` is inferred as `unknown` instead of
|
||||
// interface X {
|
||||
// foo: number,
|
||||
// bar: () => X
|
||||
// }
|
||||
```
|
||||
|
||||
So as long as the generic to be inferred is invariant TypeScript won't be able to infer it. Another simple example would be this...
|
||||
Here if you look at the type of `f` in `create` ie `(get: () => T) => T` it "gives" `T` as it returns `T` but then it also "takes" `T` via `get` so where does `T` come from TypeScript thinks... It's a like that chicken or egg problem. At the end TypeScript gives up and infers `T` as `unknown`.
|
||||
|
||||
```ts
|
||||
declare const createFoo: <T>(f: (t: T) => T) => T
|
||||
const x = createFoo(_ => "hello")
|
||||
```
|
||||
So as long as the generic to be inferred is invariant TypeScript won't be able to infer it. Another simple example would be this...
|
||||
|
||||
Here again `x` is `unknown` instead of `string`.
|
||||
|
||||
Now one can argue it's impossible to write an implementation for `createFoo`, and that's true. But then it's also impossible to write Zustand's `create`... Wait but Zustand exists? So what do I mean by that?
|
||||
```ts
|
||||
declare const createFoo: <T>(f: (t: T) => T) => T
|
||||
const x = createFoo((_) => 'hello')
|
||||
```
|
||||
|
||||
The thing is Zustand is lying in it's type, the simplest way to prove it by showing unsoundness. Consider this example...
|
||||
Here again `x` is `unknown` instead of `string`.
|
||||
|
||||
```ts
|
||||
import create from "zustand/vanilla"
|
||||
Now one can argue it's impossible to write an implementation for `createFoo`, and that's true. But then it's also impossible to write Zustand's `create`... Wait but Zustand exists? So what do I mean by that?
|
||||
|
||||
const useStore = create<{ foo: number }>()((_, get) => ({
|
||||
foo: get().foo,
|
||||
}))
|
||||
```
|
||||
The thing is Zustand is lying in it's type, the simplest way to prove it by showing unsoundness. Consider this example...
|
||||
|
||||
This code compiles, but guess what happens when you run it? You'll get an exception "Uncaught TypeError: Cannot read properties of undefined (reading 'foo') because after all `get` would return `undefined` before the initial state is created (hence kids don't call `get` when creating the initial state). But the types tell that get is `() => { foo: number }` which is exactly the lie I was taking about, `get` is that eventually but first it's `() => undefined`.
|
||||
```ts
|
||||
import create from 'zustand/vanilla'
|
||||
|
||||
const useStore = create<{ foo: number }>()((_, get) => ({
|
||||
foo: get().foo,
|
||||
}))
|
||||
```
|
||||
|
||||
This code compiles, but guess what happens when you run it? You'll get an exception "Uncaught TypeError: Cannot read properties of undefined (reading 'foo') because after all `get` would return `undefined` before the initial state is created (hence kids don't call `get` when creating the initial state). But the types tell that get is `() => { foo: number }` which is exactly the lie I was taking about, `get` is that eventually but first it's `() => undefined`.
|
||||
|
||||
Okay we're quite deep in the rabbit hole haha, long story short zustand has a bit crazy runtime behavior that can't be typed in a sound way and inferrable way. We could make it inferrable with the right TypeScript features that don't exist today. And hey that tiny bit of unsoundness is not a problem.
|
||||
|
||||
Okay we're quite deep in the rabbit hole haha, long story short zustand has a bit crazy runtime behavior that can't be typed in a sound way and inferrable way. We could make it inferrable with the right TypeScript features that don't exist today. And hey that tiny bit of unsoundness is not a problem.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@ -76,36 +77,42 @@ const useStore = create<BearState>()((set) => ({
|
||||
|
||||
**TLDR**: It's a workaround for [microsoft/TypeScript#10571](https://github.com/microsoft/TypeScript/issues/10571).
|
||||
|
||||
Imagine you have a scenario like this...
|
||||
Imagine you have a scenario like this...
|
||||
|
||||
```ts
|
||||
declare const withError: <T, E>(p: Promise<T>) =>
|
||||
Promise<[error: undefined, value: T] | [error: E, value: undefined]>
|
||||
declare const doSomething: () => Promise<string>
|
||||
```ts
|
||||
declare const withError: <T, E>(
|
||||
p: Promise<T>
|
||||
) => Promise<[error: undefined, value: T] | [error: E, value: undefined]>
|
||||
declare const doSomething: () => Promise<string>
|
||||
|
||||
const main = async () => {
|
||||
let [error, value] = await withError(doSomething())
|
||||
}
|
||||
```
|
||||
const main = async () => {
|
||||
let [error, value] = await withError(doSomething())
|
||||
}
|
||||
```
|
||||
|
||||
Here `T` is inferred as `string` and `E` is inferred as `unknown`. Now for some reason you want to annotate `E` as `Foo` because you're certain what shape of error `doSomething()` would throw. But too bad you can't do that, you can either pass all generics or none. So now along with annotating `E` as `Foo` you'll also have to annotate `T` as `string` which gets inferred anyway. So what to do? What you do is make a curried version of `withError` that does nothing in runtime, it's purpose is to just allow you annotate `E`...
|
||||
Here `T` is inferred as `string` and `E` is inferred as `unknown`. Now for some reason you want to annotate `E` as `Foo` because you're certain what shape of error `doSomething()` would throw. But too bad you can't do that, you can either pass all generics or none. So now along with annotating `E` as `Foo` you'll also have to annotate `T` as `string` which gets inferred anyway. So what to do? What you do is make a curried version of `withError` that does nothing in runtime, it's purpose is to just allow you annotate `E`...
|
||||
|
||||
```ts
|
||||
declare const withError: {
|
||||
<E>(): <T>(p: Promise<T>) =>
|
||||
Promise<[error: undefined, value: T] | [error: E, value: undefined]>
|
||||
<T, E>(p: Promise<T>):
|
||||
Promise<[error: undefined, value: T] | [error: E, value: undefined]>
|
||||
}
|
||||
declare const doSomething: () => Promise<string>
|
||||
interface Foo { bar: string }
|
||||
```ts
|
||||
declare const withError: {
|
||||
<E>(): <T>(
|
||||
p: Promise<T>
|
||||
) => Promise<[error: undefined, value: T] | [error: E, value: undefined]>
|
||||
<T, E>(p: Promise<T>): Promise<
|
||||
[error: undefined, value: T] | [error: E, value: undefined]
|
||||
>
|
||||
}
|
||||
declare const doSomething: () => Promise<string>
|
||||
interface Foo {
|
||||
bar: string
|
||||
}
|
||||
|
||||
const main = async () => {
|
||||
let [error, value] = await withError<Foo>()(doSomething())
|
||||
}
|
||||
```
|
||||
const main = async () => {
|
||||
let [error, value] = await withError<Foo>()(doSomething())
|
||||
}
|
||||
```
|
||||
|
||||
And now `T` gets inferred and you get to annotate `E` too. Zustand has the same use case we want to annotate the state (the first type parameter) but allow the rest type parameters to get inferred.
|
||||
|
||||
And now `T` gets inferred and you get to annotate `E` too. Zustand has the same use case we want to annotate the state (the first type parameter) but allow the rest type parameters to get inferred.
|
||||
</details>
|
||||
|
||||
Alternatively you can also use `combine` which infers the state instead of you having to type it...
|
||||
@ -124,11 +131,12 @@ const useStore = create(combine({ bears: 0 }, (set) => ({
|
||||
|
||||
<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 }`.
|
||||
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).
|
||||
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).
|
||||
|
||||
So `combine` trades-off a little type-safety for the convenience of not having to write a type for state. Hence you should use `combine` accordingly, usually it's not a big deal and it's okay to use it.
|
||||
|
||||
So `combine` trades-off a little type-safety for the convenience of not having to write a type for state. Hence you should use `combine` accordingly, usually it's not a big deal and it's okay to use it.
|
||||
</details>
|
||||
|
||||
Also note that we're not using the curried version when using `combine` because `combine` "creates" the state. When using a middleware that creates the state, it's not necessary to use the curried version because the state now can be inferred. Another middleware that creates state is `redux`. So when using `combine`, `redux` or any other custom middleware that creates the state, it's not recommended to use the curried version.
|
||||
@ -138,37 +146,43 @@ Also note that we're not using the curried version when using `combine` because
|
||||
You don't have to do anything special to use middlewares in TypeScript.
|
||||
|
||||
```ts
|
||||
import create from "zustand"
|
||||
import { devtools, persist } from "zustand/middleware"
|
||||
import create from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
|
||||
interface BearState {
|
||||
bears: number
|
||||
increase: (by: number) => void
|
||||
}
|
||||
|
||||
const useStore = create<BearState>()(devtools(persist((set) => ({
|
||||
bears: 0,
|
||||
increase: (by) => set((state) => ({ bears: state.bears + by })),
|
||||
}))))
|
||||
const useStore = create<BearState>()(
|
||||
devtools(
|
||||
persist((set) => ({
|
||||
bears: 0,
|
||||
increase: (by) => set((state) => ({ bears: state.bears + by })),
|
||||
}))
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Just make sure you're using them immediately inside `create` so as to make the contextual inference work. Doing something even remotely fancy like the following `myMiddlewares` would require more advanced types.
|
||||
|
||||
```ts
|
||||
import create from "zustand"
|
||||
import { devtools, persist } from "zustand/middleware"
|
||||
import create from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
|
||||
const myMiddlewares = f => devtools(persist(f))
|
||||
const myMiddlewares = (f) => devtools(persist(f))
|
||||
|
||||
interface BearState {
|
||||
bears: number
|
||||
increase: (by: number) => void
|
||||
}
|
||||
|
||||
const useStore = create<BearState>()(myMiddlewares((set) => ({
|
||||
bears: 0,
|
||||
increase: (by) => set((state) => ({ bears: state.bears + by })),
|
||||
})))
|
||||
const useStore = create<BearState>()(
|
||||
myMiddlewares((set) => ({
|
||||
bears: 0,
|
||||
increase: (by) => set((state) => ({ bears: state.bears + by })),
|
||||
}))
|
||||
)
|
||||
```
|
||||
|
||||
## Authoring middlewares and advanced usage
|
||||
@ -176,14 +190,14 @@ const useStore = create<BearState>()(myMiddlewares((set) => ({
|
||||
Imagine you had to write this hypothetical middleware...
|
||||
|
||||
```ts
|
||||
import create from "zustand"
|
||||
import create from 'zustand'
|
||||
|
||||
const foo = (f, bar) => (set, get, store) => {
|
||||
store.foo = bar
|
||||
return f(set, get, store);
|
||||
return f(set, get, store)
|
||||
}
|
||||
|
||||
const useStore = create(foo(() => ({ bears: 0 }), "hello"))
|
||||
const useStore = create(foo(() => ({ bears: 0 }), 'hello'))
|
||||
console.log(useStore.foo.toUpperCase())
|
||||
```
|
||||
|
||||
@ -198,24 +212,21 @@ If you're eager to know what the answer is to this particular problem then it's
|
||||
### Middleware that does not change the store type
|
||||
|
||||
```ts
|
||||
import create, { State, StateCreator, StoreMutatorIdentifier } from "zustand"
|
||||
import create, { State, StateCreator, StoreMutatorIdentifier } from 'zustand'
|
||||
|
||||
type Logger =
|
||||
< T extends State
|
||||
, Mps extends [StoreMutatorIdentifier, unknown][] = []
|
||||
, Mcs extends [StoreMutatorIdentifier, unknown][] = []
|
||||
>
|
||||
( f: StateCreator<T, Mps, Mcs>
|
||||
, name?: string
|
||||
) =>
|
||||
StateCreator<T, Mps, Mcs>
|
||||
type Logger = <
|
||||
T extends State,
|
||||
Mps extends [StoreMutatorIdentifier, unknown][] = [],
|
||||
Mcs extends [StoreMutatorIdentifier, unknown][] = []
|
||||
>(
|
||||
f: StateCreator<T, Mps, Mcs>,
|
||||
name?: string
|
||||
) => StateCreator<T, Mps, Mcs>
|
||||
|
||||
type LoggerImpl =
|
||||
<T extends State>
|
||||
( f: PopArgument<StateCreator<T, [], []>>
|
||||
, name?: string
|
||||
) =>
|
||||
PopArgument<StateCreator<T, [], []>>
|
||||
type LoggerImpl = <T extends State>(
|
||||
f: PopArgument<StateCreator<T, [], []>>,
|
||||
name?: string
|
||||
) => PopArgument<StateCreator<T, [], []>>
|
||||
|
||||
const loggerImpl: LoggerImpl = (f, name) => (set, get, store) => {
|
||||
type T = ReturnType<typeof f>
|
||||
@ -230,34 +241,45 @@ const loggerImpl: LoggerImpl = (f, name) => (set, get, store) => {
|
||||
|
||||
export const logger = loggerImpl as unknown as Logger
|
||||
|
||||
type PopArgument<T extends (...a: never[]) => unknown> =
|
||||
T extends (...a: [...infer A, infer _]) => infer R
|
||||
? (...a: A) => R
|
||||
: never
|
||||
type PopArgument<T extends (...a: never[]) => unknown> = T extends (
|
||||
...a: [...infer A, infer _]
|
||||
) => infer R
|
||||
? (...a: A) => R
|
||||
: never
|
||||
|
||||
// ---
|
||||
|
||||
const useStore = create<BearState>()(logger((set) => ({
|
||||
bears: 0,
|
||||
increase: (by) => set((state) => ({ bears: state.bears + by })),
|
||||
}), "bear-store"))
|
||||
const useStore = create<BearState>()(
|
||||
logger(
|
||||
(set) => ({
|
||||
bears: 0,
|
||||
increase: (by) => set((state) => ({ bears: state.bears + by })),
|
||||
}),
|
||||
'bear-store'
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Middleware that changes the store type
|
||||
|
||||
```ts
|
||||
import create, { State, StateCreator, StoreMutatorIdentifier, Mutate, StoreApi } from "zustand"
|
||||
import create, {
|
||||
State,
|
||||
StateCreator,
|
||||
StoreMutatorIdentifier,
|
||||
Mutate,
|
||||
StoreApi,
|
||||
} from 'zustand'
|
||||
|
||||
type Foo =
|
||||
< T extends State
|
||||
, A
|
||||
, Mps extends [StoreMutatorIdentifier, unknown][] = []
|
||||
, Mcs extends [StoreMutatorIdentifier, unknown][] = []
|
||||
>
|
||||
( f: StateCreator<T, [...Mps, ['foo', A]], Mcs>
|
||||
, bar: A
|
||||
) =>
|
||||
StateCreator<T, Mps, [['foo', A], ...Mcs]>
|
||||
type Foo = <
|
||||
T extends State,
|
||||
A,
|
||||
Mps extends [StoreMutatorIdentifier, unknown][] = [],
|
||||
Mcs extends [StoreMutatorIdentifier, unknown][] = []
|
||||
>(
|
||||
f: StateCreator<T, [...Mps, ['foo', A]], Mcs>,
|
||||
bar: A
|
||||
) => StateCreator<T, Mps, [['foo', A], ...Mcs]>
|
||||
|
||||
declare module 'zustand' {
|
||||
interface StoreMutators<S, A> {
|
||||
@ -265,11 +287,10 @@ declare module 'zustand' {
|
||||
}
|
||||
}
|
||||
|
||||
type FooImpl =
|
||||
<T extends State, A>
|
||||
( f: PopArgument<StateCreator<T, [], []>>
|
||||
, bar: A
|
||||
) => PopArgument<StateCreator<T, [], []>>
|
||||
type FooImpl = <T extends State, A>(
|
||||
f: PopArgument<StateCreator<T, [], []>>,
|
||||
bar: A
|
||||
) => PopArgument<StateCreator<T, [], []>>
|
||||
|
||||
const fooImpl: FooImpl = (f, bar) => (set, get, _store) => {
|
||||
type T = ReturnType<typeof f>
|
||||
@ -282,20 +303,19 @@ const fooImpl: FooImpl = (f, bar) => (set, get, _store) => {
|
||||
|
||||
export const foo = fooImpl as unknown as Foo
|
||||
|
||||
type PopArgument<T extends (...a: never[]) => unknown> =
|
||||
T extends (...a: [...infer A, infer _]) => infer R
|
||||
? (...a: A) => R
|
||||
: never
|
||||
type PopArgument<T extends (...a: never[]) => unknown> = T extends (
|
||||
...a: [...infer A, infer _]
|
||||
) => infer R
|
||||
? (...a: A) => R
|
||||
: never
|
||||
|
||||
type Write<T extends object, U extends object> =
|
||||
Omit<T, keyof U> & U
|
||||
type Write<T extends object, U extends object> = Omit<T, keyof U> & U
|
||||
|
||||
type Cast<T, U> =
|
||||
T extends U ? T : U;
|
||||
type Cast<T, U> = T extends U ? T : U
|
||||
|
||||
// ---
|
||||
|
||||
const useStore = create(foo(() => ({ bears: 0 }), "hello"))
|
||||
const useStore = create(foo(() => ({ bears: 0 }), 'hello'))
|
||||
console.log(useStore.foo.toUpperCase())
|
||||
```
|
||||
|
||||
@ -326,7 +346,7 @@ const useStore = create<
|
||||
### Independent slices pattern
|
||||
|
||||
```ts
|
||||
import create, { StateCreator } from "zustand"
|
||||
import create, { StateCreator } from 'zustand'
|
||||
|
||||
interface BearSlice {
|
||||
bears: number
|
||||
@ -334,7 +354,7 @@ interface BearSlice {
|
||||
}
|
||||
const createBearSlice: StateCreator<BearSlice, [], []> = (set) => ({
|
||||
bears: 0,
|
||||
addBear: () => set((state) => ({ bears: state.bears + 1 }))
|
||||
addBear: () => set((state) => ({ bears: state.bears + 1 })),
|
||||
})
|
||||
|
||||
interface FishSlice {
|
||||
@ -343,12 +363,12 @@ interface FishSlice {
|
||||
}
|
||||
const createFishSlice: StateCreator<FishSlice, [], []> = (set) => ({
|
||||
fishes: 0,
|
||||
addFish: () => set((state) => ({ fishes: state.fishes + 1 }))
|
||||
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
|
||||
})
|
||||
|
||||
const useStore = create<BearSlice & FishSlice>()((...a) => ({
|
||||
...createBearSlice(...a),
|
||||
...createFishSlice(...a)
|
||||
...createFishSlice(...a),
|
||||
}))
|
||||
```
|
||||
|
||||
@ -359,31 +379,41 @@ Also you can even write `StateCreator<MySlice>` instead of `StateCreator<MySlice
|
||||
### Interdependent slices pattern
|
||||
|
||||
```ts
|
||||
import create, { StateCreator } from "zustand"
|
||||
import create, { StateCreator } from 'zustand'
|
||||
|
||||
interface BearSlice {
|
||||
bears: number
|
||||
addBear: () => void
|
||||
eatFish: () => void
|
||||
}
|
||||
const createBearSlice: StateCreator<BearSlice & FishSlice, [], [], BearSlice> = (set) => ({
|
||||
const createBearSlice: StateCreator<
|
||||
BearSlice & FishSlice,
|
||||
[],
|
||||
[],
|
||||
BearSlice
|
||||
> = (set) => ({
|
||||
bears: 0,
|
||||
addBear: () => set((state) => ({ bears: state.bears + 1 })),
|
||||
eatFish: () => set((state) => ({ fishes: state.fishes - 1 }))
|
||||
eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
|
||||
})
|
||||
|
||||
interface FishSlice {
|
||||
fishes: number
|
||||
addFish: () => void
|
||||
}
|
||||
const createFishSlice: StateCreator<BearSlice & FishSlice, [], [], FishSlice> = (set) => ({
|
||||
const createFishSlice: StateCreator<
|
||||
BearSlice & FishSlice,
|
||||
[],
|
||||
[],
|
||||
FishSlice
|
||||
> = (set) => ({
|
||||
fishes: 0,
|
||||
addFish: () => set((state) => ({ fishes: state.fishes + 1 }))
|
||||
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
|
||||
})
|
||||
|
||||
const useStore = create<BearSlice & FishSlice>()((...a) => ({
|
||||
...createBearSlice(...a),
|
||||
...createFishSlice(...a)
|
||||
...createFishSlice(...a),
|
||||
}))
|
||||
```
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ If you're not passing any type parameters to `useStore` then there is no migrati
|
||||
|
||||
## `UseBoundStore` (from `zustand` and `zustand/react`)
|
||||
|
||||
### Change
|
||||
### Change
|
||||
|
||||
```diff
|
||||
- type UseBoundStore<
|
||||
|
||||
11
package.json
11
package.json
@ -68,6 +68,8 @@
|
||||
"build:shallow": "rollup -c --config-shallow",
|
||||
"build:context": "rollup -c --config-context",
|
||||
"postbuild": "yarn copy",
|
||||
"prettier": "prettier '*.{js,json,md}' '{src,tests,docs}/**/*.{ts,tsx,md,mdx}' --write",
|
||||
"prettier:ci": "prettier '*.{js,json,md}' '{src,tests,docs}/**/*.{ts,tsx,md,mdx}' --list-different",
|
||||
"eslint": "eslint --fix '*.{js,json}' '{src,tests}/**/*.{ts,tsx}'",
|
||||
"eslint:ci": "eslint '*.{js,json}' '{src,tests}/**/*.{ts,tsx}'",
|
||||
"pretest": "tsc --noEmit",
|
||||
@ -75,7 +77,7 @@
|
||||
"test:ci": "jest",
|
||||
"test:dev": "jest --watch --no-coverage",
|
||||
"test:coverage:watch": "jest --watch",
|
||||
"copy": "shx cp -r dist/src/* dist/esm && shx cp -r dist/src/* dist && shx rm -rf dist/src && shx rm -rf dist/{src,tests} && downlevel-dts dist dist/ts3.4 && shx cp package.json readme.md LICENSE dist && json -I -f dist/package.json -e \"this.private=false; this.devDependencies=undefined; this.optionalDependencies=undefined; this.scripts=undefined; this.prettier=undefined; this.jest=undefined; this['lint-staged']=undefined;\""
|
||||
"copy": "shx cp -r dist/src/* dist/esm && shx cp -r dist/src/* dist && shx rm -rf dist/src && shx rm -rf dist/{src,tests} && downlevel-dts dist dist/ts3.4 && shx cp package.json readme.md LICENSE dist && json -I -f dist/package.json -e \"this.private=false; this.devDependencies=undefined; this.optionalDependencies=undefined; this.scripts=undefined; this.prettier=undefined; this.jest=undefined;\""
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
@ -88,11 +90,6 @@
|
||||
"tabWidth": 2,
|
||||
"printWidth": 80
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,tsx}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/pmndrs/zustand.git"
|
||||
@ -183,12 +180,10 @@
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.5.0",
|
||||
"husky": "^7.0.4",
|
||||
"immer": "^9.0.12",
|
||||
"jest": "^28.0.3",
|
||||
"jest-environment-jsdom": "^28.0.2",
|
||||
"json": "^11.0.0",
|
||||
"lint-staged": "^12.4.1",
|
||||
"prettier": "^2.6.2",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
|
||||
340
readme.md
340
readme.md
@ -20,15 +20,15 @@ npm install zustand # or yarn add zustand
|
||||
|
||||
## First create a store
|
||||
|
||||
Your store is a hook! You can put anything in it: primitives, objects, functions. The `set` function *merges* state.
|
||||
Your store is a hook! You can put anything in it: primitives, objects, functions. The `set` function _merges_ state.
|
||||
|
||||
```jsx
|
||||
import create from 'zustand'
|
||||
|
||||
const useStore = create(set => ({
|
||||
const useStore = create((set) => ({
|
||||
bears: 0,
|
||||
increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
|
||||
removeAllBears: () => set({ bears: 0 })
|
||||
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
|
||||
removeAllBears: () => set({ bears: 0 }),
|
||||
}))
|
||||
```
|
||||
|
||||
@ -38,28 +38,28 @@ Use the hook anywhere, no providers needed. Select your state and the component
|
||||
|
||||
```jsx
|
||||
function BearCounter() {
|
||||
const bears = useStore(state => state.bears)
|
||||
const bears = useStore((state) => state.bears)
|
||||
return <h1>{bears} around here ...</h1>
|
||||
}
|
||||
|
||||
function Controls() {
|
||||
const increasePopulation = useStore(state => state.increasePopulation)
|
||||
const increasePopulation = useStore((state) => state.increasePopulation)
|
||||
return <button onClick={increasePopulation}>one up</button>
|
||||
}
|
||||
```
|
||||
|
||||
### Why zustand over redux?
|
||||
|
||||
* Simple and un-opinionated
|
||||
* Makes hooks the primary means of consuming state
|
||||
* Doesn't wrap your app in context providers
|
||||
* [Can inform components transiently (without causing render)](#transient-updates-for-often-occurring-state-changes)
|
||||
- Simple and un-opinionated
|
||||
- Makes hooks the primary means of consuming state
|
||||
- Doesn't wrap your app in context providers
|
||||
- [Can inform components transiently (without causing render)](#transient-updates-for-often-occurring-state-changes)
|
||||
|
||||
### Why zustand over context?
|
||||
|
||||
* Less boilerplate
|
||||
* Renders components only on changes
|
||||
* Centralized, action-based state management
|
||||
- Less boilerplate
|
||||
- Renders components only on changes
|
||||
- Centralized, action-based state management
|
||||
|
||||
---
|
||||
|
||||
@ -78,8 +78,8 @@ const state = useStore()
|
||||
It detects changes with strict-equality (old === new) by default, this is efficient for atomic state picks.
|
||||
|
||||
```jsx
|
||||
const nuts = useStore(state => state.nuts)
|
||||
const honey = useStore(state => state.honey)
|
||||
const nuts = useStore((state) => state.nuts)
|
||||
const honey = useStore((state) => state.honey)
|
||||
```
|
||||
|
||||
If you want to construct a single object with multiple state-picks inside, similar to redux's mapStateToProps, you can tell zustand that you want the object to be diffed shallowly by passing the `shallow` equality function.
|
||||
@ -88,20 +88,23 @@ If you want to construct a single object with multiple state-picks inside, simil
|
||||
import shallow from 'zustand/shallow'
|
||||
|
||||
// Object pick, re-renders the component when either state.nuts or state.honey change
|
||||
const { nuts, honey } = useStore(state => ({ nuts: state.nuts, honey: state.honey }), shallow)
|
||||
const { nuts, honey } = useStore(
|
||||
(state) => ({ nuts: state.nuts, honey: state.honey }),
|
||||
shallow
|
||||
)
|
||||
|
||||
// Array pick, re-renders the component when either state.nuts or state.honey change
|
||||
const [nuts, honey] = useStore(state => [state.nuts, state.honey], shallow)
|
||||
const [nuts, honey] = useStore((state) => [state.nuts, state.honey], shallow)
|
||||
|
||||
// Mapped picks, re-renders the component when state.treats changes in order, count or keys
|
||||
const treats = useStore(state => Object.keys(state.treats), shallow)
|
||||
const treats = useStore((state) => Object.keys(state.treats), shallow)
|
||||
```
|
||||
|
||||
For more control over re-rendering, you may provide any custom equality function.
|
||||
|
||||
```jsx
|
||||
const treats = useStore(
|
||||
state => state.treats,
|
||||
(state) => state.treats,
|
||||
(oldTreats, newTreats) => compare(oldTreats, newTreats)
|
||||
)
|
||||
```
|
||||
@ -111,13 +114,13 @@ const treats = useStore(
|
||||
The `set` function has a second argument, `false` by default. Instead of merging, it will replace the state model. Be careful not to wipe out parts you rely on, like actions.
|
||||
|
||||
```jsx
|
||||
import omit from "lodash-es/omit"
|
||||
import omit from 'lodash-es/omit'
|
||||
|
||||
const useStore = create(set => ({
|
||||
const useStore = create((set) => ({
|
||||
salmon: 1,
|
||||
tuna: 2,
|
||||
deleteEverything: () => set({ }, true), // clears the entire store, actions included
|
||||
deleteTuna: () => set(state => omit(state, ['tuna']), true)
|
||||
deleteEverything: () => set({}, true), // clears the entire store, actions included
|
||||
deleteTuna: () => set((state) => omit(state, ['tuna']), true),
|
||||
}))
|
||||
```
|
||||
|
||||
@ -126,12 +129,12 @@ const useStore = create(set => ({
|
||||
Just call `set` when you're ready, zustand doesn't care if your actions are async or not.
|
||||
|
||||
```jsx
|
||||
const useStore = create(set => ({
|
||||
const useStore = create((set) => ({
|
||||
fishies: {},
|
||||
fetch: async pond => {
|
||||
fetch: async (pond) => {
|
||||
const response = await fetch(pond)
|
||||
set({ fishies: await response.json() })
|
||||
}
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
@ -178,22 +181,34 @@ If you need to subscribe with selector,
|
||||
`subscribeWithSelector` middleware will help.
|
||||
|
||||
With this middleware `subscribe` accepts an additional signature:
|
||||
|
||||
```ts
|
||||
subscribe(selector, callback, options?: { equalityFn, fireImmediately }): Unsubscribe
|
||||
```
|
||||
|
||||
```js
|
||||
import { subscribeWithSelector } from 'zustand/middleware'
|
||||
const useStore = create(subscribeWithSelector(() => ({ paw: true, snout: true, fur: true })))
|
||||
const useStore = create(
|
||||
subscribeWithSelector(() => ({ paw: true, snout: true, fur: true }))
|
||||
)
|
||||
|
||||
// Listening to selected changes, in this case when "paw" changes
|
||||
const unsub2 = useStore.subscribe(state => state.paw, console.log)
|
||||
const unsub2 = useStore.subscribe((state) => state.paw, console.log)
|
||||
// Subscribe also exposes the previous value
|
||||
const unsub3 = useStore.subscribe(state => state.paw, (paw, previousPaw) => console.log(paw, previousPaw))
|
||||
const unsub3 = useStore.subscribe(
|
||||
(state) => state.paw,
|
||||
(paw, previousPaw) => console.log(paw, previousPaw)
|
||||
)
|
||||
// Subscribe also supports an optional equality function
|
||||
const unsub4 = useStore.subscribe(state => [state.paw, state.fur], console.log, { equalityFn: shallow })
|
||||
const unsub4 = useStore.subscribe(
|
||||
(state) => [state.paw, state.fur],
|
||||
console.log,
|
||||
{ equalityFn: shallow }
|
||||
)
|
||||
// Subscribe and fire immediately
|
||||
const unsub5 = useStore.subscribe(state => state.paw, console.log, { fireImmediately: true })
|
||||
const unsub5 = useStore.subscribe((state) => state.paw, console.log, {
|
||||
fireImmediately: true,
|
||||
})
|
||||
```
|
||||
|
||||
## Using zustand without React
|
||||
@ -241,15 +256,18 @@ Reducing nested structures is tiresome. Have you tried [immer](https://github.co
|
||||
```jsx
|
||||
import produce from 'immer'
|
||||
|
||||
const useStore = create(set => ({
|
||||
lush: { forest: { contains: { a: "bear" } } },
|
||||
clearForest: () => set(produce(state => {
|
||||
state.lush.forest.contains = null
|
||||
}))
|
||||
const useStore = create((set) => ({
|
||||
lush: { forest: { contains: { a: 'bear' } } },
|
||||
clearForest: () =>
|
||||
set(
|
||||
produce((state) => {
|
||||
state.lush.forest.contains = null
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
const clearForest = useStore(state => state.clearForest)
|
||||
clearForest();
|
||||
const clearForest = useStore((state) => state.clearForest)
|
||||
clearForest()
|
||||
```
|
||||
|
||||
[Alternatively, there are some other solutions.](https://github.com/pmndrs/zustand/wiki/Updating-nested-state-object-values)
|
||||
@ -260,16 +278,23 @@ You can functionally compose your store any way you like.
|
||||
|
||||
```jsx
|
||||
// Log every time state is changed
|
||||
const log = (config) => (set, get, api) => config((...args) => {
|
||||
console.log(" applying", args)
|
||||
set(...args)
|
||||
console.log(" new state", get())
|
||||
}, get, api)
|
||||
const log = (config) => (set, get, api) =>
|
||||
config(
|
||||
(...args) => {
|
||||
console.log(' applying', args)
|
||||
set(...args)
|
||||
console.log(' new state', get())
|
||||
},
|
||||
get,
|
||||
api
|
||||
)
|
||||
|
||||
const useStore = create(log((set) => ({
|
||||
bees: false,
|
||||
setBees: (input) => set({ bees: input }),
|
||||
})))
|
||||
const useStore = create(
|
||||
log((set) => ({
|
||||
bees: false,
|
||||
setBees: (input) => set({ bees: input }),
|
||||
}))
|
||||
)
|
||||
```
|
||||
|
||||
## Persist middleware
|
||||
@ -277,19 +302,21 @@ const useStore = create(log((set) => ({
|
||||
You can persist your store's data using any kind of storage.
|
||||
|
||||
```jsx
|
||||
import create from "zustand"
|
||||
import { persist } from "zustand/middleware"
|
||||
import create from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
const useStore = create(persist(
|
||||
(set, get) => ({
|
||||
fishes: 0,
|
||||
addAFish: () => set({ fishes: get().fishes + 1 })
|
||||
}),
|
||||
{
|
||||
name: "food-storage", // unique name
|
||||
getStorage: () => sessionStorage, // (optional) by default, 'localStorage' is used
|
||||
}
|
||||
))
|
||||
const useStore = create(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
fishes: 0,
|
||||
addAFish: () => set({ fishes: get().fishes + 1 }),
|
||||
}),
|
||||
{
|
||||
name: 'food-storage', // unique name
|
||||
getStorage: () => sessionStorage, // (optional) by default, 'localStorage' is used
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
[See the full documentation for this middleware.](https://github.com/pmndrs/zustand/wiki/Persisting-the-store's-data)
|
||||
@ -299,33 +326,40 @@ const useStore = create(persist(
|
||||
Immer is available as middleware too.
|
||||
|
||||
```jsx
|
||||
import create from "zustand"
|
||||
import { immer } from "zustand/middleware/immer"
|
||||
import create from 'zustand'
|
||||
import { immer } from 'zustand/middleware/immer'
|
||||
|
||||
const useStore = create(immer((set) => ({
|
||||
bees: 0,
|
||||
addBees: (by) => set((state) => { state.bees += by }),
|
||||
})))
|
||||
const useStore = create(
|
||||
immer((set) => ({
|
||||
bees: 0,
|
||||
addBees: (by) =>
|
||||
set((state) => {
|
||||
state.bees += by
|
||||
}),
|
||||
}))
|
||||
)
|
||||
```
|
||||
|
||||
## Can't live without redux-like reducers and action types?
|
||||
|
||||
```jsx
|
||||
const types = { increase: "INCREASE", decrease: "DECREASE" }
|
||||
const types = { increase: 'INCREASE', decrease: 'DECREASE' }
|
||||
|
||||
const reducer = (state, { type, by = 1 }) => {
|
||||
switch (type) {
|
||||
case types.increase: return { grumpiness: state.grumpiness + by }
|
||||
case types.decrease: return { grumpiness: state.grumpiness - by }
|
||||
case types.increase:
|
||||
return { grumpiness: state.grumpiness + by }
|
||||
case types.decrease:
|
||||
return { grumpiness: state.grumpiness - by }
|
||||
}
|
||||
}
|
||||
|
||||
const useStore = create(set => ({
|
||||
const useStore = create((set) => ({
|
||||
grumpiness: 0,
|
||||
dispatch: args => set(state => reducer(state, args)),
|
||||
dispatch: (args) => set((state) => reducer(state, args)),
|
||||
}))
|
||||
|
||||
const dispatch = useStore(state => state.dispatch)
|
||||
const dispatch = useStore((state) => state.dispatch)
|
||||
dispatch({ type: types.increase, by: 2 })
|
||||
```
|
||||
|
||||
@ -348,15 +382,15 @@ const useStore = create(devtools(store))
|
||||
const useStore = create(devtools(redux(reducer, initialState)))
|
||||
```
|
||||
|
||||
devtools takes the store function as its first argument, optionally you can name the store or configure [serialize](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#serialize) options with a second argument.
|
||||
|
||||
devtools takes the store function as its first argument, optionally you can name the store or configure [serialize](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#serialize) options with a second argument.
|
||||
|
||||
Name store: `devtools(store, {name: "MyStore"})`, which will create a separate instance named "MyStore" in the devtools.
|
||||
|
||||
Serialize options: `devtools(store, { serialize: { options: true } })`.
|
||||
|
||||
|
||||
#### Logging Actions
|
||||
|
||||
devtools will only log actions from each separated store unlike in a typical *combined reducers* redux store. See an approach to combining stores https://github.com/pmndrs/zustand/issues/163
|
||||
devtools will only log actions from each separated store unlike in a typical _combined reducers_ redux store. See an approach to combining stores https://github.com/pmndrs/zustand/issues/163
|
||||
|
||||
You can log a specific action type for each `set` function by passing a third parameter:
|
||||
|
||||
@ -366,12 +400,12 @@ const createBearSlice = (set, get) => ({
|
||||
set(
|
||||
(prev) => ({ fishes: prev.fishes > 1 ? prev.fishes - 1 : 0 }),
|
||||
false,
|
||||
"bear/eatFish"
|
||||
'bear/eatFish'
|
||||
),
|
||||
})
|
||||
```
|
||||
|
||||
If an action type is not provided, it is defaulted to "anonymous". You can customize this default value by providing an `anonymousActionType` parameter:
|
||||
If an action type is not provided, it is defaulted to "anonymous". You can customize this default value by providing an `anonymousActionType` parameter:
|
||||
|
||||
```jsx
|
||||
devtools(..., { anonymousActionType: 'unknown', ... })
|
||||
@ -431,81 +465,82 @@ const Component = () => {
|
||||
<details>
|
||||
<summary>createContext usage in real components</summary>
|
||||
|
||||
```jsx
|
||||
import create from "zustand";
|
||||
import createContext from "zustand/context";
|
||||
```jsx
|
||||
import create from "zustand";
|
||||
import createContext from "zustand/context";
|
||||
|
||||
// Best practice: You can move the below createContext() and createStore to a separate file(store.js) and import the Provider, useStore here/wherever you need.
|
||||
// Best practice: You can move the below createContext() and createStore to a separate file(store.js) and import the Provider, useStore here/wherever you need.
|
||||
|
||||
const { Provider, useStore } = createContext();
|
||||
const { Provider, useStore } = createContext();
|
||||
|
||||
const createStore = () =>
|
||||
create((set) => ({
|
||||
bears: 0,
|
||||
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
|
||||
removeAllBears: () => set({ bears: 0 })
|
||||
}));
|
||||
const createStore = () =>
|
||||
create((set) => ({
|
||||
bears: 0,
|
||||
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
|
||||
removeAllBears: () => set({ bears: 0 })
|
||||
}));
|
||||
|
||||
const Button = () => {
|
||||
return (
|
||||
{/** store() - This will create a store for each time using the Button component instead of using one store for all components **/}
|
||||
<Provider createStore={createStore}>
|
||||
<ButtonChild />
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
const Button = () => {
|
||||
return (
|
||||
{/** store() - This will create a store for each time using the Button component instead of using one store for all components **/}
|
||||
<Provider createStore={createStore}>
|
||||
<ButtonChild />
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const ButtonChild = () => {
|
||||
const state = useStore();
|
||||
return (
|
||||
<div>
|
||||
{state.bears}
|
||||
<button
|
||||
onClick={() => {
|
||||
state.increasePopulation();
|
||||
}}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const ButtonChild = () => {
|
||||
const state = useStore();
|
||||
return (
|
||||
<div>
|
||||
{state.bears}
|
||||
<button
|
||||
onClick={() => {
|
||||
state.increasePopulation();
|
||||
}}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<Button />
|
||||
<Button />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<Button />
|
||||
<Button />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>createContext usage with initialization from props</summary>
|
||||
|
||||
```tsx
|
||||
import create from "zustand";
|
||||
import createContext from "zustand/context";
|
||||
```tsx
|
||||
import create from 'zustand'
|
||||
import createContext from 'zustand/context'
|
||||
|
||||
const { Provider, useStore } = createContext();
|
||||
const { Provider, useStore } = createContext()
|
||||
|
||||
export default function App({ initialBears }) {
|
||||
return (
|
||||
<Provider
|
||||
createStore={() =>
|
||||
create((set) => ({
|
||||
bears: initialBears,
|
||||
increase: () => set((state) => ({ bears: state.bears + 1 })),
|
||||
}))
|
||||
}>
|
||||
<Button />
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
export default function App({ initialBears }) {
|
||||
return (
|
||||
<Provider
|
||||
createStore={() =>
|
||||
create((set) => ({
|
||||
bears: initialBears,
|
||||
increase: () => set((state) => ({ bears: state.bears + 1 })),
|
||||
}))
|
||||
}
|
||||
>
|
||||
<Button />
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
## TypeScript Usage
|
||||
@ -513,28 +548,31 @@ const Component = () => {
|
||||
Basic typescript usage doesn't require anything special except for writing `create<State>()(...)` instead of `create(...)`...
|
||||
|
||||
```ts
|
||||
import create from "zustand"
|
||||
import { devtools, persist } from "zustand/middleware"
|
||||
import create from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
|
||||
interface BearState {
|
||||
bears: number
|
||||
increase: (by: number) => void
|
||||
}
|
||||
|
||||
const useStore = create<BearState>()(devtools(persist((set) => ({
|
||||
bears: 0,
|
||||
increase: (by) => set((state) => ({ bears: state.bears + by })),
|
||||
}))))
|
||||
const useStore = create<BearState>()(
|
||||
devtools(
|
||||
persist((set) => ({
|
||||
bears: 0,
|
||||
increase: (by) => set((state) => ({ bears: state.bears + by })),
|
||||
}))
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
A more complete TypeScript guide is [here](https://github.com/pmndrs/zustand/blob/main/docs/typescript.md).
|
||||
|
||||
## Best practices
|
||||
|
||||
* You may wonder how to organize your code for better maintenance: [Splitting the store into seperate slices](https://github.com/pmndrs/zustand/wiki/Splitting-the-store-into-separate-slices).
|
||||
|
||||
* Recommended usage for this unopinionated library: [Flux inspired practice](https://github.com/pmndrs/zustand/wiki/Flux-inspired-practice).
|
||||
|
||||
|
||||
- You may wonder how to organize your code for better maintenance: [Splitting the store into seperate slices](https://github.com/pmndrs/zustand/wiki/Splitting-the-store-into-separate-slices).
|
||||
- Recommended usage for this unopinionated library: [Flux inspired practice](https://github.com/pmndrs/zustand/wiki/Flux-inspired-practice).
|
||||
|
||||
<details>
|
||||
<summary>Calling actions outside a React event handler in pre React 18</summary>
|
||||
|
||||
@ -548,7 +586,7 @@ import { unstable_batchedUpdates } from 'react-dom' // or 'react-native'
|
||||
|
||||
const useStore = create((set) => ({
|
||||
fishes: 0,
|
||||
increaseFishes: () => set((prev) => ({ fishes: prev.fishes + 1 }))
|
||||
increaseFishes: () => set((prev) => ({ fishes: prev.fishes + 1 })),
|
||||
}))
|
||||
|
||||
const nonReactCallback = () => {
|
||||
|
||||
251
yarn.lock
251
yarn.lock
@ -1769,14 +1769,6 @@ agent-base@6:
|
||||
dependencies:
|
||||
debug "4"
|
||||
|
||||
aggregate-error@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
|
||||
integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
|
||||
dependencies:
|
||||
clean-stack "^2.0.0"
|
||||
indent-string "^4.0.0"
|
||||
|
||||
ajv@^6.10.0, ajv@^6.12.4:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
@ -1787,7 +1779,7 @@ ajv@^6.10.0, ajv@^6.12.4:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
|
||||
ansi-escapes@^4.2.1:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
|
||||
integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
|
||||
@ -1799,11 +1791,6 @@ ansi-regex@^5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-regex@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
|
||||
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
@ -1823,11 +1810,6 @@ ansi-styles@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
ansi-styles@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3"
|
||||
integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==
|
||||
|
||||
anymatch@^3.0.3:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
@ -1889,11 +1871,6 @@ array.prototype.flatmap@^1.2.5:
|
||||
es-abstract "^1.19.2"
|
||||
es-shim-unscopables "^1.0.0"
|
||||
|
||||
astral-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
|
||||
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
@ -2103,34 +2080,6 @@ cjs-module-lexer@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
|
||||
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
|
||||
|
||||
cli-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
|
||||
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
|
||||
dependencies:
|
||||
restore-cursor "^3.1.0"
|
||||
|
||||
cli-truncate@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
|
||||
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
|
||||
dependencies:
|
||||
slice-ansi "^3.0.0"
|
||||
string-width "^4.2.0"
|
||||
|
||||
cli-truncate@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389"
|
||||
integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==
|
||||
dependencies:
|
||||
slice-ansi "^5.0.0"
|
||||
string-width "^5.0.0"
|
||||
|
||||
cliui@^7.0.2:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
|
||||
@ -2174,11 +2123,6 @@ color-name@~1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
colorette@^2.0.16:
|
||||
version "2.0.16"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da"
|
||||
integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
@ -2191,11 +2135,6 @@ commander@^2.20.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
@ -2386,11 +2325,6 @@ downlevel-dts@^0.10.0:
|
||||
shelljs "^0.8.3"
|
||||
typescript next
|
||||
|
||||
eastasianwidth@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||
|
||||
electron-to-chromium@^1.4.118:
|
||||
version "1.4.127"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.127.tgz#4ef19d5d920abe2676d938f4170729b44f7f423a"
|
||||
@ -2406,11 +2340,6 @@ emoji-regex@^8.0.0:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
emoji-regex@^9.2.2:
|
||||
version "9.2.2"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||
|
||||
error-ex@^1.3.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
||||
@ -2834,7 +2763,7 @@ esutils@^2.0.2:
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||
|
||||
execa@^5.0.0, execa@^5.1.1:
|
||||
execa@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
|
||||
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
|
||||
@ -3153,11 +3082,6 @@ human-signals@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||
|
||||
husky@^7.0.4:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535"
|
||||
integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||
@ -3196,11 +3120,6 @@ imurmurhash@^0.1.4:
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||
|
||||
indent-string@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
||||
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
@ -3277,11 +3196,6 @@ is-fullwidth-code-point@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
|
||||
is-fullwidth-code-point@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
|
||||
integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==
|
||||
|
||||
is-generator-fn@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
|
||||
@ -3953,50 +3867,11 @@ levn@~0.3.0:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
lilconfig@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
|
||||
integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==
|
||||
|
||||
lines-and-columns@^1.1.6:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
||||
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
||||
|
||||
lint-staged@^12.4.1:
|
||||
version "12.4.1"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.4.1.tgz#63fa27bfc8a33515f6902f63f6670864f1fb233c"
|
||||
integrity sha512-PTXgzpflrQ+pODQTG116QNB+Q6uUTDg5B5HqGvNhoQSGt8Qy+MA/6zSnR8n38+sxP5TapzeQGTvoKni0KRS8Vg==
|
||||
dependencies:
|
||||
cli-truncate "^3.1.0"
|
||||
colorette "^2.0.16"
|
||||
commander "^8.3.0"
|
||||
debug "^4.3.3"
|
||||
execa "^5.1.1"
|
||||
lilconfig "2.0.4"
|
||||
listr2 "^4.0.1"
|
||||
micromatch "^4.0.4"
|
||||
normalize-path "^3.0.0"
|
||||
object-inspect "^1.12.0"
|
||||
pidtree "^0.5.0"
|
||||
string-argv "^0.3.1"
|
||||
supports-color "^9.2.1"
|
||||
yaml "^1.10.2"
|
||||
|
||||
listr2@^4.0.1:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/listr2/-/listr2-4.0.5.tgz#9dcc50221583e8b4c71c43f9c7dfd0ef546b75d5"
|
||||
integrity sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==
|
||||
dependencies:
|
||||
cli-truncate "^2.1.0"
|
||||
colorette "^2.0.16"
|
||||
log-update "^4.0.0"
|
||||
p-map "^4.0.0"
|
||||
rfdc "^1.3.0"
|
||||
rxjs "^7.5.5"
|
||||
through "^2.3.8"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
locate-path@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||
@ -4032,16 +3907,6 @@ lodash@^4.17.21:
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
log-update@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
|
||||
integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
|
||||
dependencies:
|
||||
ansi-escapes "^4.3.0"
|
||||
cli-cursor "^3.1.0"
|
||||
slice-ansi "^4.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
@ -4243,7 +4108,7 @@ once@^1.3.0:
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
onetime@^5.1.0, onetime@^5.1.2:
|
||||
onetime@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
|
||||
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
|
||||
@ -4302,13 +4167,6 @@ p-locate@^4.1.0:
|
||||
dependencies:
|
||||
p-limit "^2.2.0"
|
||||
|
||||
p-map@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
|
||||
integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
|
||||
dependencies:
|
||||
aggregate-error "^3.0.0"
|
||||
|
||||
p-try@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
|
||||
@ -4381,11 +4239,6 @@ picomatch@^2.0.4, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1:
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
pidtree@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.5.0.tgz#ad5fbc1de78b8a5f99d6fbdd4f6e4eee21d1aca1"
|
||||
integrity sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==
|
||||
|
||||
pirates@^4.0.4:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
|
||||
@ -4628,24 +4481,11 @@ resolve@^2.0.0-next.3:
|
||||
is-core-module "^2.2.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
restore-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
|
||||
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
|
||||
dependencies:
|
||||
onetime "^5.1.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
reusify@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||
|
||||
rfdc@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
|
||||
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
|
||||
|
||||
rimraf@^3.0.0, rimraf@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||
@ -4695,13 +4535,6 @@ rxjs@^6.6.3:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
rxjs@^7.5.5:
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f"
|
||||
integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-buffer@^5.1.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
@ -4793,7 +4626,7 @@ side-channel@^1.0.4:
|
||||
get-intrinsic "^1.0.2"
|
||||
object-inspect "^1.9.0"
|
||||
|
||||
signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||
signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
@ -4808,32 +4641,6 @@ slash@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||
|
||||
slice-ansi@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
|
||||
integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
astral-regex "^2.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
|
||||
slice-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
|
||||
integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
astral-regex "^2.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
|
||||
slice-ansi@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a"
|
||||
integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==
|
||||
dependencies:
|
||||
ansi-styles "^6.0.0"
|
||||
is-fullwidth-code-point "^4.0.0"
|
||||
|
||||
source-map-support@0.5.13:
|
||||
version "0.5.13"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
|
||||
@ -4889,11 +4696,6 @@ stack-utils@^2.0.3:
|
||||
dependencies:
|
||||
escape-string-regexp "^2.0.0"
|
||||
|
||||
string-argv@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
|
||||
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
|
||||
|
||||
string-length@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
|
||||
@ -4911,15 +4713,6 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^5.0.0:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
|
||||
dependencies:
|
||||
eastasianwidth "^0.2.0"
|
||||
emoji-regex "^9.2.2"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
string.prototype.matchall@^4.0.6:
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d"
|
||||
@ -4957,13 +4750,6 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
|
||||
integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==
|
||||
dependencies:
|
||||
ansi-regex "^6.0.1"
|
||||
|
||||
strip-bom@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
|
||||
@ -5005,11 +4791,6 @@ supports-color@^8.0.0, supports-color@^8.1.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^9.2.1:
|
||||
version "9.2.2"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.2.tgz#502acaf82f2b7ee78eb7c83dcac0f89694e5a7bb"
|
||||
integrity sha512-XC6g/Kgux+rJXmwokjm9ECpD6k/smUoS5LKlUCcsYr4IY3rW0XyAympon2RmxGrlnZURMpg5T18gWDP9CsHXFA==
|
||||
|
||||
supports-hyperlinks@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb"
|
||||
@ -5065,11 +4846,6 @@ throat@^6.0.1:
|
||||
resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375"
|
||||
integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==
|
||||
|
||||
through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
|
||||
tmpl@1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
|
||||
@ -5130,11 +4906,6 @@ tslib@^1.8.1, tslib@^1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.1.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||
@ -5336,15 +5107,6 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
|
||||
wrap-ansi@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
@ -5392,11 +5154,6 @@ yallist@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yaml@^1.10.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||
|
||||
yargs-parser@^20.2.2:
|
||||
version "20.2.9"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user