主题
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 takesset
function,get
function andstore
as arguments. Usually, you will return an object with the methods you want to expose.
返回
¥Returns
createStore
返回一个公开 API 实用程序 setState
、getState
、getInitialState
和 subscribe
的原始存储。
¥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
函数执行浅合并。对于大多数只需要修改特定属性的更新,默认浅合并是首选,因为它更有效率。要用新状态完全替换旧状态,请谨慎使用设置为 true
的 replace
参数,因为它会丢弃状态内任何现有的嵌套数据。
¥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)