mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
fix(docs): comparison (#1239)
* WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * Adding Jotai and Recoil in the comparison section * Minor changes * Add rewording, some typo fixes and redux toolkit examples * Update comparison.md * Minor typo fixes * Minor fixes * Update docs/getting-started/comparison.md Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com> Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>
This commit is contained in:
parent
476a381df8
commit
bf5d4be360
@ -4,11 +4,409 @@ description:
|
||||
nav: 2
|
||||
---
|
||||
|
||||
⚠️ This doc is still under construction. https://github.com/pmndrs/zustand/discussions/1033
|
||||
Zustand is one of many state management libraries for React. On this page we
|
||||
will discuss Zustand in comparison to some of these libraries, including Redux,
|
||||
Valtio, Jotai, and Recoil.
|
||||
|
||||
## Why zustand over react-redux?
|
||||
Each library has its own strengths and weaknesses, and we will compare key
|
||||
differences and similarities between each.
|
||||
|
||||
- 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)](recipes#transient-updates-for-often-occurring-state-changes)
|
||||
## Redux
|
||||
|
||||
### State Model
|
||||
|
||||
There are no big differences between Zustand and Redux. Both are based on
|
||||
immutable state model. Also, Redux needs to wrap you app in context providers.
|
||||
|
||||
```ts
|
||||
import create from 'zustand'
|
||||
|
||||
type State = {
|
||||
count: number
|
||||
}
|
||||
|
||||
type Actions = {
|
||||
increment: (qty: number) => void
|
||||
decrement: (qty: number) => void
|
||||
}
|
||||
|
||||
const useCountStore = create<State & Actions>((set) => ({
|
||||
count: 0,
|
||||
increment: (qty: number) => set((state) => ({ count: state.count + qty })),
|
||||
decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
|
||||
}))
|
||||
```
|
||||
|
||||
```ts
|
||||
import create from 'zustand'
|
||||
|
||||
type State = {
|
||||
count: number
|
||||
}
|
||||
|
||||
type Actions = {
|
||||
increment: (qty: number) => void
|
||||
decrement: (qty: number) => void
|
||||
}
|
||||
|
||||
type Action = {
|
||||
type: keyof Actions
|
||||
qty: number
|
||||
}
|
||||
|
||||
const countReducer = (state: State, action: Action) => {
|
||||
switch (action.type) {
|
||||
case 'increment':
|
||||
return { count: state.count + action.qty }
|
||||
case 'decrement':
|
||||
return { count: state.count - action.qty }
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const useCountStore = create<State & Actions>((set) => ({
|
||||
count: 0,
|
||||
dispatch: (action: Action) => set((state) => countReducer(state, action)),
|
||||
}))
|
||||
```
|
||||
|
||||
```ts
|
||||
import { createStore } from 'redux'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
|
||||
type State = {
|
||||
count: number
|
||||
}
|
||||
|
||||
type Action = {
|
||||
type: 'increment' | 'decrement'
|
||||
qty: number
|
||||
}
|
||||
|
||||
const countReducer = (state: State, action: Action) => {
|
||||
switch (action.type) {
|
||||
case 'increment':
|
||||
return { count: state.count + action.qty }
|
||||
case 'decrement':
|
||||
return { count: state.count - action.qty }
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const countStore = createStore(countReducer)
|
||||
```
|
||||
|
||||
```ts
|
||||
import { createSlice, configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
const countSlice = createSlice({
|
||||
name: 'count',
|
||||
initialState: { value: 0 },
|
||||
reducers: {
|
||||
incremented: (state, qty: number) => {
|
||||
// Redux Toolkit does not mutate the state, it use Immer library behind
|
||||
// scenes allow us to have something called "draft state".
|
||||
state.value += qty
|
||||
},
|
||||
decremented: (state, qty: number) => {
|
||||
state.value -= qty
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const countStore = configureStore({ reducer: countSlice.reducer })
|
||||
```
|
||||
|
||||
### Render Optimization
|
||||
|
||||
When it comes to render optimizations within your app, there are no major
|
||||
differences in approach between Zustand and Redux. In both libraries it is
|
||||
recommended that you manually apply render optimizations by using selectors.
|
||||
|
||||
```ts
|
||||
import create from 'zustand'
|
||||
|
||||
type State = {
|
||||
count: number
|
||||
}
|
||||
|
||||
type Actions = {
|
||||
increment: (qty: number) => void
|
||||
decrement: (qty: number) => void
|
||||
}
|
||||
|
||||
const useCountStore = create<State & Actions>((set) => ({
|
||||
count: 0,
|
||||
increment: (qty: number) => set((state) => ({ count: state.count + qty })),
|
||||
decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
|
||||
}))
|
||||
|
||||
const Component = () => {
|
||||
const count = useCountStore((state) => state.count)
|
||||
const increment = useCountStore((state) => state.increment)
|
||||
const decrement = useCountStore((state) => state.decrement)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
import { createStore } from 'redux'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
|
||||
type State = {
|
||||
count: number
|
||||
}
|
||||
|
||||
type Action = {
|
||||
type: 'increment' | 'decrement'
|
||||
qty: number
|
||||
}
|
||||
|
||||
const countReducer = (state: State, action: Action) => {
|
||||
switch (action.type) {
|
||||
case 'increment':
|
||||
return { count: state.count + action.qty }
|
||||
case 'decrement':
|
||||
return { count: state.count - action.qty }
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const countStore = createStore(countReducer)
|
||||
|
||||
const Component = () => {
|
||||
const count = useSelector((state) => state.count)
|
||||
const dispatch = useDispatch()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
import { useSelector } from 'react-redux'
|
||||
import type { TypedUseSelectorHook } from 'react-redux'
|
||||
import { createSlice, configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
const countSlice = createSlice({
|
||||
name: 'count',
|
||||
initialState: { value: 0 },
|
||||
reducers: {
|
||||
incremented: (state, qty: number) => {
|
||||
// Redux Toolkit does not mutate the state, it use Immer library behind
|
||||
// scenes allow us to have something called "draft state".
|
||||
state.value += qty
|
||||
},
|
||||
decremented: (state, qty: number) => {
|
||||
state.value -= qty
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const countStore = configureStore({ reducer: countSlice.reducer })
|
||||
|
||||
const useAppSelector: TypedUseSelectorHook<typeof countStore.getState> =
|
||||
useSelector
|
||||
|
||||
const useAppDispatch: () => typeof countStore.dispatch = useDispatch
|
||||
|
||||
const Component = () => {
|
||||
const count = useAppSelector((state) => state.count.value)
|
||||
const dispatch = useAppDispatch()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Valtio
|
||||
|
||||
### State Model
|
||||
|
||||
There is a major difference between Zustand and Valtio. Zustand is based on
|
||||
the immutable state model, while Valtio is based on the mutable state model.
|
||||
|
||||
```ts
|
||||
import create from 'zustand'
|
||||
|
||||
const store = create(() => ({ obj: { count: 0 } }))
|
||||
|
||||
store.setState((prev) => ({ obj: { count: prev.obj.count + 1 } })
|
||||
```
|
||||
|
||||
```ts
|
||||
import { proxy } from 'valtio'
|
||||
|
||||
const state = proxy({ obj: { count: 0 } })
|
||||
|
||||
state.obj.count += 1
|
||||
```
|
||||
|
||||
### Render Optimization
|
||||
|
||||
The other difference between Zustand and Valtio is Valtio makes render
|
||||
optimizations through property access. While Zustand it is recommended that you
|
||||
manually apply render optimizations by using selectors.
|
||||
|
||||
```ts
|
||||
import create from 'zustand'
|
||||
|
||||
const useCountStore = create(() => ({
|
||||
count: 0,
|
||||
}))
|
||||
|
||||
const Component = () => {
|
||||
const count = useCountStore((state) => state.count)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
import { proxy, useSnapshot } from 'valtio'
|
||||
|
||||
const state = proxy({
|
||||
count: 0,
|
||||
})
|
||||
|
||||
const Component = () => {
|
||||
const { count } = useSnapshot(state)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Jotai
|
||||
|
||||
### State Model
|
||||
|
||||
There are two major differences between Zustand and Jotai. The first one is
|
||||
Zustand is a single store, while Jotai consists of primitive atoms and allows
|
||||
composing them together. The last one is Zustand store is global in memory, but
|
||||
Jotai atoms are not (are definitions that do not hold values) and that's why
|
||||
you can use it outside React.
|
||||
|
||||
```ts
|
||||
import create from 'zustand'
|
||||
|
||||
type State = {
|
||||
count: number
|
||||
}
|
||||
|
||||
const useCountStore = create<State>((set) => ({
|
||||
count: 0,
|
||||
updateCount: (countCallback: (count: State['count']) => State['count']) =>
|
||||
set((state) => ({ count: countCallback(state.count) })),
|
||||
}))
|
||||
```
|
||||
|
||||
```ts
|
||||
import { atom } from 'jotai'
|
||||
|
||||
const countAtom = atom<number>(0)
|
||||
```
|
||||
|
||||
### Render Optimization
|
||||
|
||||
The other difference between Zustand and Jotai is: Jotai makes render
|
||||
optimizations through atom dependency. But, with Zustand you need to do manual
|
||||
render optimizations through selectors.
|
||||
|
||||
```ts
|
||||
import create from 'zustand'
|
||||
|
||||
type State = {
|
||||
count: number
|
||||
}
|
||||
|
||||
const useCountStore = create<State>((set) => ({
|
||||
count: 0,
|
||||
updateCount: (countCallback: (count: State['count']) => State['count']) =>
|
||||
set((state) => ({ count: countCallback(state.count) })),
|
||||
}))
|
||||
|
||||
const Component = () => {
|
||||
const count = useCountStore((state) => state.count)
|
||||
const updateCount = useCountStore((state) => state.updateCount)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
import { atom, useAtom } from 'jotai'
|
||||
|
||||
const countAtom = atom<number>(0)
|
||||
|
||||
const Component = () => {
|
||||
const [count, updateCount] = useAtom(countAtom)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Recoil
|
||||
|
||||
### State Model
|
||||
|
||||
The major difference is the same as Zustand and Jotai is: Recoil depends on
|
||||
atom string keys instead of atom object referential identities. Also, Recoil
|
||||
needs to wrap your app in a context provider.
|
||||
|
||||
```ts
|
||||
import create from 'zustand'
|
||||
|
||||
type State = {
|
||||
count: number
|
||||
}
|
||||
|
||||
const useCountStore = create<State>((set) => ({
|
||||
count: 0,
|
||||
setCount: (countCallback: (count: State['count']) => State['count']) =>
|
||||
set((state) => ({ count: countCallback(state.count) })),
|
||||
}))
|
||||
```
|
||||
|
||||
```ts
|
||||
import { atom } from 'recoil'
|
||||
|
||||
const count = atom({
|
||||
key: 'count',
|
||||
default: 0,
|
||||
})
|
||||
```
|
||||
|
||||
### Render Optimization
|
||||
|
||||
The other difference between Zustand and Recoil is: Recoil makes render
|
||||
optimizations through atom dependency. But, with Zustand you need to do manual
|
||||
render optimizations through selectors.
|
||||
|
||||
```ts
|
||||
import create from 'zustand'
|
||||
|
||||
type State = {
|
||||
count: number
|
||||
}
|
||||
|
||||
const useCountStore = create<State>((set) => ({
|
||||
count: 0,
|
||||
setCount: (countCallback: (count: State['count']) => State['count']) =>
|
||||
set((state) => ({ count: countCallback(state.count) })),
|
||||
}))
|
||||
|
||||
const Component = () => {
|
||||
const count = useCountStore((state) => state.count)
|
||||
const setCount = useCountStore((state) => state.setCount)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
import { atom, useRecoilState } from 'recoil'
|
||||
|
||||
const countAtom = atom({
|
||||
key: 'count',
|
||||
default: 0,
|
||||
})
|
||||
|
||||
const Component = () => {
|
||||
const [count, setCount] = useRecoilState(countAtom)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user