Last active
September 29, 2024 16:08
-
-
Save arkatsy/7ff5b6cd95fe94b5e480972a0d116aeb to your computer and use it in GitHub Desktop.
How zustand works internally
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useSyncExternalStore } from "react"; | |
// For more on the useSyncExternalStore hook, see https://react.dev/reference/react/useSyncExternalStore | |
// The code is almost identical to the source code of zustand, without types and some features stripped out. | |
// Check the links to see the references in the source code. | |
// The links are referencing the v5 of the library. If you plan on reading the source code yourself v5 is the best way to start. | |
// The current v4 version contains lot of deprecated code and extra stuff that makes it hard to reason about if you're new to this. | |
// https://github.com/pmndrs/zustand/blob/fe47d3e6c6671dbfb9856fda52cb5a3a855d97a6/src/vanilla.ts#L57-L94 | |
function createStore(createState) { | |
let listeners = new Set(); | |
let state; | |
let initialState; | |
const setState = (partial) => { | |
const nextState = typeof partial === "function" ? partial(state) : partial; | |
if (!Object.is(nextState, state)) { | |
const previousState = state; | |
state = Object.assign({}, state, nextState); | |
listeners.forEach((listener) => listener(state, previousState)); | |
} | |
}; | |
const getState = () => state; | |
const getInitialState = () => initialState; | |
const subscribe = (listener) => { | |
listeners.add(listener); | |
return () => listeners.delete(listener); | |
}; | |
const api = { setState, getState, getInitialState, subscribe }; | |
initialState = state = createState(setState, getState, api); | |
return api; | |
} | |
// https://github.com/pmndrs/zustand/blob/fe47d3e6c6671dbfb9856fda52cb5a3a855d97a6/src/react.ts#L21 | |
const identity = (state) => state; | |
// https://github.com/pmndrs/zustand/blob/fe47d3e6c6671dbfb9856fda52cb5a3a855d97a6/src/react.ts#L29-L40 | |
function useStore(api, selector = identity) { | |
const slice = useSyncExternalStore( | |
api.subscribe, | |
() => selector(api.getState()), | |
() => selector(api.getInitialState()) | |
); | |
return slice; | |
} | |
// https://github.com/pmndrs/zustand/blob/fe47d3e6c6671dbfb9856fda52cb5a3a855d97a6/src/react.ts#L56-L64 | |
function create(createState) { | |
const api = createStore(createState); | |
const useBoundStore = (selector) => useStore(api, selector); | |
Object.assign(useBoundStore, api); | |
return useBoundStore; | |
} | |
// =================================== | |
// Usage | |
const useCountStore = create((set) => ({ | |
count: 0, | |
increment: () => set((state) => ({ count: state.count + 1 })), | |
decrement: () => set((state) => ({ count: state.count - 1 })), | |
})); | |
function App() { | |
return ( | |
<div> | |
<Counter1 /> | |
<Counter2 /> | |
</div> | |
); | |
} | |
function Counter1() { | |
const { count, increment, decrement } = useCountStore(); | |
return ( | |
<div> | |
<h2>Counter1</h2> | |
<div>{count}</div> | |
<button onClick={decrement}>-</button> | |
<button onClick={increment}>+</button> | |
</div> | |
); | |
} | |
function Counter2() { | |
const { count, increment, decrement } = useCountStore(); | |
return ( | |
<div> | |
<h2>Counter2</h2> | |
<div>{count}</div> | |
<button onClick={decrement}>-</button> | |
<button onClick={increment}>+</button> | |
</div> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment