主题
create ⚛️
create
允许你创建一个附加了 API 实用程序的 React Hook。
¥create
lets you create a React Hook with API utilities attached.
js
const useSomeStore = create(stateCreatorFn)
类型
¥Types
签名
¥Signature
ts
create<T>()(stateCreatorFn: StateCreator<T, [], []>): UseBoundStore<StoreApi<T>>
参考
¥Reference
create(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
create
返回一个附加了 API 实用程序 setState
、getState
、getInitialState
和 subscribe
的 React Hook。它允许你使用选择器函数返回基于当前状态的数据。它应该将选择器函数作为其唯一参数。
¥create
returns a React Hook with API utilities, setState
, getState
, getInitialState
and subscribe
, attached. It lets you return data that is based on current state, using a selector function. It should take a selector function as its only argument.
用法
¥Usage
根据先前状态更新状态
¥Updating state based on previous state
要根据先前的状态更新状态,我们应该使用更新程序函数。阅读有关 此处 的更多信息。
¥To update a state based on previous state we should use updater functions. Read more about that here.
此示例展示了如何在操作中支持更新程序功能。
¥This example shows how you can support updater functions within actions.
tsx
import { create } from 'zustand'
type AgeStoreState = { age: number }
type AgeStoreActions = {
setAge: (
nextAge:
| AgeStoreState['age']
| ((currentAge: AgeStoreState['age']) => AgeStoreState['age']),
) => void
}
type AgeStore = AgeStoreState & AgeStoreActions
const useAgeStore = create<AgeStore>()((set) => ({
age: 42,
setAge: (nextAge) => {
set((state) => ({
age: typeof nextAge === 'function' ? nextAge(state.age) : nextAge,
}))
},
}))
export default function App() {
const age = useAgeStore((state) => state.age)
const setAge = useAgeStore((state) => state.setAge)
function increment() {
setAge((currentAge) => currentAge + 1)
}
return (
<>
<h1>Your age: {age}</h1>
<button
onClick={() => {
increment()
increment()
increment()
}}
>
+3
</button>
<button
onClick={() => {
increment()
}}
>
+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
tsx
import { create } from 'zustand'
type XStore = number
const useXStore = create<XStore>()(() => 0)
export default function MovingDot() {
const x = useXStore()
const setX = (nextX: number) => {
useXStore.setState(nextX, true)
}
const position = { y: 0, x }
return (
<div
onPointerMove={(e) => {
setX(e.clientX)
}}
style={{
position: 'relative',
width: '100vw',
height: '100vh',
}}
>
<div
style={{
position: 'absolute',
backgroundColor: 'red',
borderRadius: '50%',
transform: `translate(${position.x}px, ${position.y}px)`,
left: -10,
top: -10,
width: 20,
height: 20,
}}
/>
</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.
tsx
import { create } from 'zustand'
type PositionStoreState = { position: { x: number; y: number } }
type PositionStoreActions = {
setPosition: (nextPosition: PositionStoreState['position']) => void
}
type PositionStore = PositionStoreState & PositionStoreActions
const usePositionStore = create<PositionStore>()((set) => ({
position: { x: 0, y: 0 },
setPosition: (nextPosition) => set(nextPosition),
}))
export default function MovingDot() {
const position = usePositionStore((state) => state.position)
const setPosition = usePositionStore((state) => state.setPosition)
return (
<div
onPointerMove={(e) => {
setPosition({
x: e.clientX,
y: e.clientY,
})
}}
style={{
position: 'relative',
width: '100vw',
height: '100vh',
}}
>
<div
style={{
position: 'absolute',
backgroundColor: 'red',
borderRadius: '50%',
transform: `translate(${position.x}px, ${position.y}px)`,
left: -10,
top: -10,
width: 20,
height: 20,
}}
/>
</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(...)
.
tsx
import { create } from 'zustand'
type PositionStore = [number, number]
const usePositionStore = create<PositionStore>()(() => [0, 0])
export default function MovingDot() {
const [x, y] = usePositionStore()
const setPosition: typeof usePositionStore.setState = (nextPosition) => {
usePositionStore.setState(nextPosition, true)
}
const position = { x, y }
return (
<div
onPointerMove={(e) => {
setPosition([e.clientX, e.clientY])
}}
style={{
position: 'relative',
width: '100vw',
height: '100vh',
}}
>
<div
style={{
position: 'absolute',
backgroundColor: 'red',
borderRadius: '50%',
transform: `translate(${position.x}px, ${position.y}px)`,
left: -10,
top: -10,
width: 20,
height: 20,
}}
/>
</div>
)
}
不使用存储操作更新状态
¥Updating state with no store actions
在模块中定义操作级别,外部存储有几个优点,例如:它不需要钩子来调用操作,并且它有助于代码拆分。
¥Defining actions at module level, external to the store have a few advantages like: it doesn't require a hook to call an action, and it facilitates code splitting.
建议的方式是将操作和状态放在存储中(让你的操作与你的状态放在一起)。
¥[!NOTE] The recommended way is to colocate actions and states within the store (let your actions be located together with your state).
tsx
import { create } from 'zustand'
const usePositionStore = create<{
x: number
y: number
}>()(() => ({ x: 0, y: 0 }))
const setPosition: typeof usePositionStore.setState = (nextPosition) => {
usePositionStore.setState(nextPosition)
}
export default function MovingDot() {
const position = usePositionStore()
return (
<div
style={{
position: 'relative',
width: '100vw',
height: '100vh',
}}
>
<div
style={{
position: 'absolute',
backgroundColor: 'red',
borderRadius: '50%',
transform: `translate(${position.x}px, ${position.y}px)`,
left: -10,
top: -10,
width: 20,
height: 20,
}}
onMouseEnter={(event) => {
const parent = event.currentTarget.parentElement
const parentWidth = parent.clientWidth
const parentHeight = parent.clientHeight
setPosition({
x: Math.ceil(Math.random() * parentWidth),
y: Math.ceil(Math.random() * parentHeight),
})
}}
/>
</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.
tsx
import { useEffect } from 'react'
import { create } from 'zustand'
type PositionStoreState = { position: { x: number; y: number } }
type PositionStoreActions = {
setPosition: (nextPosition: PositionStoreState['position']) => void
}
type PositionStore = PositionStoreState & PositionStoreActions
const usePositionStore = create<PositionStore>()((set) => ({
position: { x: 0, y: 0 },
setPosition: (nextPosition) => set(nextPosition),
}))
export default function MovingDot() {
const position = usePositionStore((state) => state.position)
const setPosition = usePositionStore((state) => state.setPosition)
useEffect(() => {
const unsubscribePositionStore = usePositionStore.subscribe(
({ position }) => {
console.log('new position', { position })
},
)
return () => {
unsubscribePositionStore()
}
}, [])
return (
<div
style={{
position: 'relative',
width: '100vw',
height: '100vh',
}}
>
<div
style={{
position: 'absolute',
backgroundColor: 'red',
borderRadius: '50%',
transform: `translate(${position.x}px, ${position.y}px)`,
left: -10,
top: -10,
width: 20,
height: 20,
}}
onMouseEnter={(event) => {
const parent = event.currentTarget.parentElement
const parentWidth = parent.clientWidth
const parentHeight = parent.clientHeight
setPosition({
x: Math.ceil(Math.random() * parentWidth),
y: Math.ceil(Math.random() * parentHeight),
})
}}
/>
</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.
这些输入字段不起作用,因为 onChange
处理程序会改变状态:
¥These input fields don’t work because the onChange
handlers mutate the state:
tsx
import { create } from 'zustand'
type PersonStoreState = {
firstName: string
lastName: string
email: string
}
type PersonStoreActions = {
setPerson: (nextPerson: Partial<PersonStoreState>) => void
}
type PersonStore = PersonStoreState & PersonStoreActions
const usePersonStore = create<PersonStore>()((set) => ({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com',
setPerson: (nextPerson) => set(nextPerson),
}))
export default function Form() {
const person = usePersonStore((state) => state)
const setPerson = usePersonStore((state) => state.setPerson)
function handleFirstNameChange(e: ChangeEvent<HTMLInputElement>) {
person.firstName = e.target.value
}
function handleLastNameChange(e: ChangeEvent<HTMLInputElement>) {
person.lastName = e.target.value
}
function handleEmailChange(e: ChangeEvent<HTMLInputElement>) {
person.email = e.target.value
}
return (
<>
<label style={{ display: 'block' }}>
First name:
<input value={person.firstName} onChange={handleFirstNameChange} />
</label>
<label style={{ display: 'block' }}>
Last name:
<input value={person.lastName} onChange={handleLastNameChange} />
</label>
<label style={{ display: 'block' }}>
Email:
<input value={person.email} onChange={handleEmailChange} />
</label>
<p>
{person.firstName} {person.lastName} ({person.email})
</p>
</>
)
}
例如,此行会从过去的渲染中改变状态:
¥For example, this line mutates the state from a past render:
tsx
person.firstName = e.target.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
setPerson({ ...person, 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!
tsx
import { create } from 'zustand'
type PersonStoreState = {
person: { firstName: string; lastName: string; email: string }
}
type PersonStoreActions = {
setPerson: (nextPerson: PersonStoreState['person']) => void
}
type PersonStore = PersonStoreState & PersonStoreActions
const usePersonStore = create<PersonStore>()((set) => ({
person: {
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com',
},
setPerson: (nextPerson) => set(nextPerson),
}))
export default function Form() {
const person = usePersonStore((state) => state.person)
const setPerson = usePersonStore((state) => state.setPerson)
function handleFirstNameChange(e: ChangeEvent<HTMLInputElement>) {
setPerson({ ...person, firstName: e.target.value })
}
function handleLastNameChange(e: ChangeEvent<HTMLInputElement>) {
setPerson({ ...person, lastName: e.target.value })
}
function handleEmailChange(e: ChangeEvent<HTMLInputElement>) {
setPerson({ ...person, email: e.target.value })
}
return (
<>
<label style={{ display: 'block' }}>
First name:
<input value={person.firstName} onChange={handleFirstNameChange} />
</label>
<label style={{ display: 'block' }}>
Last name:
<input value={person.lastName} onChange={handleLastNameChange} />
</label>
<label style={{ display: 'block' }}>
Email:
<input value={person.email} onChange={handleEmailChange} />
</label>
<p>
{person.firstName} {person.lastName} ({person.email})
</p>
</>
)
}