Skip to content

createStore

createStore 允许你创建一个公开 API 实用程序的 vanilla 存储。

¥createStore lets you create a vanilla store that exposes API utilities.

js
const someStore = createStore(stateCreatorFn)

类型

¥Types

签名

¥Signature

ts
createStore<T>()(stateCreatorFn: StateCreator<T, [], []>): StoreApi<T>

参考

¥Reference

createStore(stateCreatorFn)

参数

¥Parameters

  • stateCreatorFn:以 set 函数、get 函数和 store 作为参数的函数。通常,你将返回一个包含要公开的方法的对象。

    ¥stateCreatorFn: A function that takes set function, get function and store as arguments. Usually, you will return an object with the methods you want to expose.

返回

¥Returns

createStore 返回一个公开 API 实用程序 setStategetStategetInitialStatesubscribe 的原始存储。

¥createStore returns a vanilla store that exposes API utilities, setState, getState, getInitialState and subscribe.

用法

¥Usage

根据先前状态更新状态

¥Updating state based on previous state

此示例展示了如何在操作中支持更新程序功能。

¥This example shows how you can support updater functions within actions.

tsx
import { createStore } from 'zustand/vanilla'

type AgeStoreState = { age: number }

type AgeStoreActions = {
  setAge: (
    nextAge:
      | AgeStoreState['age']
      | ((currentAge: AgeStoreState['age']) => AgeStoreState['age']),
  ) => void
}

type AgeStore = AgeStoreState & AgeStoreActions

const ageStore = createStore<AgeStore>()((set) => ({
  age: 42,
  setAge: (nextAge) =>
    set((state) => ({
      age: typeof nextAge === 'function' ? nextAge(state.age) : nextAge,
    })),
}))

function increment() {
  ageStore.getState().setAge((currentAge) => currentAge + 1)
}

const $yourAgeHeading = document.getElementById(
  'your-age',
) as HTMLHeadingElement
const $incrementBy3Button = document.getElementById(
  'increment-by-3',
) as HTMLButtonElement
const $incrementBy1Button = document.getElementById(
  'increment-by-1',
) as HTMLButtonElement

$incrementBy3Button.addEventListener('click', () => {
  increment()
  increment()
  increment()
})

$incrementBy1Button.addEventListener('click', () => {
  increment()
})

const render: Parameters<typeof ageStore.subscribe>[0] = (state) => {
  $yourAgeHeading.innerHTML = `Your age: ${state.age}`
}

render(ageStore.getInitialState(), ageStore.getInitialState())

ageStore.subscribe(render)

这是 html 代码

¥Here's the html code

html
<h1 id="your-age"></h1>
<button id="increment-by-3" type="button">+3</button>
<button id="increment-by-1" type="button">+1</button>

更新状态中的原始内容

¥Updating Primitives in State

状态可以保存任何类型的 JavaScript 值。当你想要更新内置原始值(如数字、字符串、布尔值等)时,你应该直接分配新值以确保正确应用更新,并避免意外行为。

¥State can hold any kind of JavaScript value. When you want to update built-in primitive values like numbers, strings, booleans, etc. you should directly assign new values to ensure updates are applied correctly, and avoid unexpected behaviors.

默认情况下,`set` 函数执行浅合并。如果你需要用新状态完全替换状态,请使用设置为 `true` 的 `replace` 参数

¥[!NOTE] By default, set function performs a shallow merge. If you need to completely replace the state with a new one, use the replace parameter set to true

ts
import { createStore } from 'zustand/vanilla'

type XStore = number

const xStore = createStore<XStore>()(() => 0)

const $dotContainer = document.getElementById('dot-container') as HTMLDivElement
const $dot = document.getElementById('dot') as HTMLDivElement

$dotContainer.addEventListener('pointermove', (event) => {
  xStore.setState(event.clientX, true)
})

const render: Parameters<typeof xStore.subscribe>[0] = (x) => {
  $dot.style.transform = `translate(${x}px, 0)`
}

render(xStore.getInitialState(), xStore.getInitialState())

xStore.subscribe(render)

这是 html 代码

¥Here's the html code

html
<div
  id="dot-container"
  style="position: relative; width: 100vw; height: 100vh;"
>
  <div
    id="dot"
    style="position: absolute; background-color: red; border-radius: 50%; left: -10px; top: -10px; width: 20px; height: 20px;"
  ></div>
</div>

更新状态中的对象

¥Updating Objects in State

对象在 JavaScript 中是可变的,但当你将它们存储在状态中时,你应该将它们视为不可变的。相反,当你想要更新对象时,你需要创建一个新对象(或复制现有对象),然后设置状态以使用新对象。

¥Objects are mutable in JavaScript, but you should treat them as immutable when you store them in state. Instead, when you want to update an object, you need to create a new one (or make a copy of an existing one), and then set the state to use the new object.

默认情况下,set 函数执行浅合并。对于大多数只需要修改特定属性的更新,默认浅合并是首选,因为它更有效率。要用新状态完全替换旧状态,请谨慎使用设置为 truereplace 参数,因为它会丢弃状态内任何现有的嵌套数据。

¥By default, set function performs a shallow merge. For most updates where you only need to modify specific properties, the default shallow merge is preferred as it's more efficient. To completely replace the state with a new one, use the replace parameter set to true with caution, as it discards any existing nested data within the state.

ts
import { createStore } from 'zustand/vanilla'

type PositionStoreState = { position: { x: number; y: number } }

type PositionStoreActions = {
  setPosition: (nextPosition: PositionStoreState['position']) => void
}

type PositionStore = PositionStoreState & PositionStoreActions

const positionStore = createStore<PositionStore>()((set) => ({
  position: { x: 0, y: 0 },
  setPosition: (position) => set({ position }),
}))

const $dotContainer = document.getElementById('dot-container') as HTMLDivElement
const $dot = document.getElementById('dot') as HTMLDivElement

$dotContainer.addEventListener('pointermove', (event) => {
  positionStore.getState().setPosition({
    x: event.clientX,
    y: event.clientY,
  })
})

const render: Parameters<typeof positionStore.subscribe>[0] = (state) => {
  $dot.style.transform = `translate(${state.position.x}px, ${state.position.y}px)`
}

render(positionStore.getInitialState(), positionStore.getInitialState())

positionStore.subscribe(render)

这是 html 代码

¥Here's the html code

html
<div
  id="dot-container"
  style="position: relative; width: 100vw; height: 100vh;"
>
  <div
    id="dot"
    style="position: absolute; background-color: red; border-radius: 50%; left: -10px; top: -10px; width: 20px; height: 20px;"
  ></div>
</div>

更新状态中的数组

¥Updating Arrays in State

数组在 JavaScript 中是可变的,但当你将它们存储在状态中时,你应该将它们视为不可变的。与对象一样,当你想要更新存储在状态中的数组时,你需要创建一个新数组(或复制现有数组),然后设置状态以使用新数组。

¥Arrays are mutable in JavaScript, but you should treat them as immutable when you store them in state. Just like with objects, when you want to update an array stored in state, you need to create a new one (or make a copy of an existing one), and then set state to use the new array.

默认情况下,set 函数执行浅合并。要更新数组值,我们应该分配新值以确保正确应用更新,并避免意外行为。要用新状态完全替换旧状态,请将 replace 参数设置为 true

¥By default, set function performs a shallow merge. To update array values we should assign new values to ensure updates are applied correctly, and avoid unexpected behaviors. To completely replace the state with a new one, use the replace parameter set to true.

我们应该更喜欢不可变的操作,例如:`[...array]`、`concat(...)`、`filter(...)`、`slice(...)`、`map(...)`、`toSpliced(...)`、`toSorted(...)` 和 `toReversed(...)`,并避免可变操作,如 `array[arrayIndex] = ...`、`push(...)`、`unshift(...)`、`pop(...)`、`shift(...)`、`splice(...)`、`reverse(...)` 和 `sort(...)`。

¥[!IMPORTANT] We should prefer immutable operations like: [...array], concat(...), filter(...), slice(...), map(...), toSpliced(...), toSorted(...), and toReversed(...), and avoid mutable operations like array[arrayIndex] = ..., push(...), unshift(...), pop(...), shift(...), splice(...), reverse(...), and sort(...).

ts
import { createStore } from 'zustand/vanilla'

type PositionStore = [number, number]

const positionStore = createStore<PositionStore>()(() => [0, 0])

const $dotContainer = document.getElementById('dot-container') as HTMLDivElement
const $dot = document.getElementById('dot') as HTMLDivElement

$dotContainer.addEventListener('pointermove', (event) => {
  positionStore.setState([event.clientX, event.clientY], true)
})

const render: Parameters<typeof positionStore.subscribe>[0] = ([x, y]) => {
  $dot.style.transform = `translate(${x}px, ${y}px)`
}

render(positionStore.getInitialState(), positionStore.getInitialState())

positionStore.subscribe(render)

这是 html 代码

¥Here's the html code

html
<div
  id="dot-container"
  style="position: relative; width: 100vw; height: 100vh;"
>
  <div
    id="dot"
    style="position: absolute; background-color: red; border-radius: 50%; left: -10px; top: -10px; width: 20px; height: 20px;"
  ></div>
</div>

订阅状态更新

¥Subscribing to state updates

通过订阅状态更新,你可以注册一个回调,该回调在存储的状态更新时触发。我们可以使用 subscribe 进行外部状态管理。

¥By subscribing to state updates, you register a callback that fires whenever the store's state updates. We can use subscribe for external state management.

ts
import { createStore } from 'zustand/vanilla'

type PositionStoreState = { position: { x: number; y: number } }

type PositionStoreActions = {
  setPosition: (nextPosition: PositionStoreState['position']) => void
}

type PositionStore = PositionStoreState & PositionStoreActions

const positionStore = createStore<PositionStore>()((set) => ({
  position: { x: 0, y: 0 },
  setPosition: (position) => set({ position }),
}))

const $dot = document.getElementById('dot') as HTMLDivElement

$dot.addEventListener('mouseenter', (event) => {
  const parent = event.currentTarget.parentElement
  const parentWidth = parent.clientWidth
  const parentHeight = parent.clientHeight

  positionStore.getState().setPosition({
    x: Math.ceil(Math.random() * parentWidth),
    y: Math.ceil(Math.random() * parentHeight),
  })
})

const render: Parameters<typeof positionStore.subscribe>[0] = (state) => {
  $dot.style.transform = `translate(${state.position.x}px, ${state.position.y}px)`
}

render(positionStore.getInitialState(), positionStore.getInitialState())

positionStore.subscribe(render)

const logger: Parameters<typeof positionStore.subscribe>[0] = (state) => {
  console.log('new position', { position: state.position })
}

positionStore.subscribe(logger)

这是 html 代码

¥Here's the html code

html
<div
  id="dot-container"
  style="position: relative; width: 100vw; height: 100vh;"
>
  <div
    id="dot"
    style="position: absolute; background-color: red; border-radius: 50%; left: -10px; top: -10px; width: 20px; height: 20px;"
  ></div>
</div>

故障排除

¥Troubleshooting

我已经更新了状态,但屏幕没有更新

¥I’ve updated the state, but the screen doesn’t update

在前面的例子中,position 对象始终从当前光标位置重新创建。但是通常,你会希望将现有数据作为你正在创建的新对象的一部分。例如,你可能只想更新表单中的一个字段,但保留所有其他字段的先前值。

¥In the previous example, the position object is always created fresh from the current cursor position. But often, you will want to include existing data as a part of the new object you’re creating. For example, you may want to update only one field in a form, but keep the previous values for all other fields.

这些输入字段不起作用,因为 oninput 处理程序会改变状态:

¥These input fields don’t work because the oninput handlers mutate the state:

ts
import { createStore } from 'zustand/vanilla'

type PersonStoreState = {
  person: { firstName: string; lastName: string; email: string }
}

type PersonStoreActions = {
  setPerson: (nextPerson: PersonStoreState['person']) => void
}

type PersonStore = PersonStoreState & PersonStoreActions

const personStore = createStore<PersonStore>()((set) => ({
  person: {
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bhepworth@sculpture.com',
  },
  setPerson: (person) => set({ person }),
}))

const $firstNameInput = document.getElementById(
  'first-name',
) as HTMLInputElement
const $lastNameInput = document.getElementById('last-name') as HTMLInputElement
const $emailInput = document.getElementById('email') as HTMLInputElement
const $result = document.getElementById('result') as HTMLDivElement

function handleFirstNameChange(event: Event) {
  personStore.getState().person.firstName = (event.target as any).value
}

function handleLastNameChange(event: Event) {
  personStore.getState().person.lastName = (event.target as any).value
}

function handleEmailChange(event: Event) {
  personStore.getState().person.email = (event.target as any).value
}

$firstNameInput.addEventListener('input', handleFirstNameChange)
$lastNameInput.addEventListener('input', handleLastNameChange)
$emailInput.addEventListener('input', handleEmailChange)

const render: Parameters<typeof personStore.subscribe>[0] = (state) => {
  $firstNameInput.value = state.person.firstName
  $lastNameInput.value = state.person.lastName
  $emailInput.value = state.person.email

  $result.innerHTML = `${state.person.firstName} ${state.person.lastName} (${state.person.email})`
}

render(personStore.getInitialState(), personStore.getInitialState())

personStore.subscribe(render)

这是 html 代码

¥Here's the html code

html
<label style="display: block">
  First name:
  <input id="first-name" />
</label>
<label style="display: block">
  Last name:
  <input id="last-name" />
</label>
<label style="display: block">
  Email:
  <input id="email" />
</label>
<p id="result"></p>

例如,此行会从过去的渲染中改变状态:

¥For example, this line mutates the state from a past render:

ts
personStore.getState().firstName = (e.target as any).value

获取所需行为的可靠方法是创建一个新对象并将其传递给 setPerson。但在这里你还想将现有数据复制到其中,因为只有一个字段发生了变化:

¥The reliable way to get the behavior you’re looking for is to create a new object and pass it to setPerson. But here you want to also copy the existing data into it because only one of the fields has changed:

ts
personStore.getState().setPerson({
  firstName: e.target.value, // New first name from the input
})

由于 `set` 函数默认执行浅合并,因此我们不需要单独复制每个属性。

¥[!NOTE] We don’t need to copy every property separately due to set function performing shallow merge by default.

现在表单可以工作了!

¥Now the form works!

请注意你没有为每个输入字段声明单独的状态变量。对于大型表单,将所有数据分组在一个对象中非常方便 - 只要你正确更新它!

¥Notice how you didn’t declare a separate state variable for each input field. For large forms, keeping all data grouped in an object is very convenient—as long as you update it correctly!

ts
import { createStore } from 'zustand/vanilla'

type PersonStoreState = {
  person: { firstName: string; lastName: string; email: string }
}

type PersonStoreActions = {
  setPerson: (nextPerson: PersonStoreState['person']) => void
}

type PersonStore = PersonStoreState & PersonStoreActions

const personStore = createStore<PersonStore>()((set) => ({
  person: {
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bhepworth@sculpture.com',
  },
  setPerson: (person) => set({ person }),
}))

const $firstNameInput = document.getElementById(
  'first-name',
) as HTMLInputElement
const $lastNameInput = document.getElementById('last-name') as HTMLInputElement
const $emailInput = document.getElementById('email') as HTMLInputElement
const $result = document.getElementById('result') as HTMLDivElement

function handleFirstNameChange(event: Event) {
  personStore.getState().setPerson({
    ...personStore.getState().person,
    firstName: (event.target as any).value,
  })
}

function handleLastNameChange(event: Event) {
  personStore.getState().setPerson({
    ...personStore.getState().person,
    lastName: (event.target as any).value,
  })
}

function handleEmailChange(event: Event) {
  personStore.getState().setPerson({
    ...personStore.getState().person,
    email: (event.target as any).value,
  })
}

$firstNameInput.addEventListener('input', handleFirstNameChange)
$lastNameInput.addEventListener('input', handleLastNameChange)
$emailInput.addEventListener('input', handleEmailChange)

const render: Parameters<typeof personStore.subscribe>[0] = (state) => {
  $firstNameInput.value = state.person.firstName
  $lastNameInput.value = state.person.lastName
  $emailInput.value = state.person.email

  $result.innerHTML = `${state.person.firstName} ${state.person.lastName} (${state.person.email})`
}

render(personStore.getInitialState(), personStore.getInitialState())

personStore.subscribe(render)

Zustand v5.0 中文网 - 粤ICP备13048890号