Nuxt.js useState #

一、useState概述 #

useState 是 Nuxt.js 提供的内置组合式函数,用于创建跨组件共享的响应式状态。它基于 Vue 的响应式系统,并支持 SSR。

1.1 特点 #

  • 跨组件共享:状态在组件间共享
  • SSR友好:支持服务端渲染
  • 类型安全:完整的 TypeScript 支持
  • 简单易用:无需额外配置

1.2 与其他方案对比 #

方案 复杂度 适用场景
useState 简单状态共享
Pinia 复杂状态管理
Vuex 大型应用(不推荐)

二、基本用法 #

2.1 创建状态 #

vue
<script setup lang="ts">
const count = useState('count', () => 0)
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="count++">增加</button>
  </div>
</template>

2.2 唯一键 #

每个状态需要一个唯一的键:

vue
<script setup lang="ts">
const user = useState('user', () => null)
const cart = useState('cart', () => [])
const settings = useState('settings', () => ({
  theme: 'light',
  language: 'zh-CN'
}))
</script>

2.3 初始值函数 #

vue
<script setup lang="ts">
const items = useState('items', () => {
  return Array.from({ length: 10 }, (_, i) => ({
    id: i + 1,
    name: `Item ${i + 1}`
  }))
})
</script>

三、类型定义 #

3.1 基本类型 #

vue
<script setup lang="ts">
const count = useState<number>('count', () => 0)
const message = useState<string>('message', () => 'Hello')
const isActive = useState<boolean>('isActive', () => false)
</script>

3.2 对象类型 #

vue
<script setup lang="ts">
interface User {
  id: number
  name: string
  email: string
}

const user = useState<User>('user', () => ({
  id: 0,
  name: '',
  email: ''
}))
</script>

3.3 数组类型 #

vue
<script setup lang="ts">
interface CartItem {
  id: number
  name: string
  price: number
  quantity: number
}

const cart = useState<CartItem[]>('cart', () => [])
</script>

四、组合式函数封装 #

4.1 封装状态逻辑 #

composables/useCounter.ts

typescript
export const useCounter = () => {
  const count = useState('counter-count', () => 0)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = 0
  
  return {
    count,
    increment,
    decrement,
    reset
  }
}

使用:

vue
<script setup lang="ts">
const { count, increment, decrement, reset } = useCounter()
</script>

4.2 购物车状态 #

composables/useCart.ts

typescript
interface CartItem {
  id: number
  name: string
  price: number
  quantity: number
}

export const useCart = () => {
  const items = useState<CartItem[]>('cart-items', () => [])
  
  const totalItems = computed(() => 
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )
  
  const totalPrice = computed(() => 
    items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )
  
  const addItem = (product: Omit<CartItem, 'quantity'>) => {
    const existing = items.value.find(item => item.id === product.id)
    
    if (existing) {
      existing.quantity++
    } else {
      items.value.push({ ...product, quantity: 1 })
    }
  }
  
  const removeItem = (id: number) => {
    const index = items.value.findIndex(item => item.id === id)
    if (index > -1) {
      items.value.splice(index, 1)
    }
  }
  
  const updateQuantity = (id: number, quantity: number) => {
    const item = items.value.find(item => item.id === id)
    if (item) {
      item.quantity = Math.max(0, quantity)
      if (item.quantity === 0) {
        removeItem(id)
      }
    }
  }
  
  const clearCart = () => {
    items.value = []
  }
  
  return {
    items,
    totalItems,
    totalPrice,
    addItem,
    removeItem,
    updateQuantity,
    clearCart
  }
}

4.3 用户认证状态 #

composables/useAuth.ts

typescript
interface User {
  id: number
  name: string
  email: string
  role: string
}

export const useAuth = () => {
  const user = useState<User | null>('auth-user', () => null)
  const token = useCookie('auth-token')
  
  const isAuthenticated = computed(() => !!user.value && !!token.value)
  const isAdmin = computed(() => user.value?.role === 'admin')
  
  const login = async (credentials: { email: string; password: string }) => {
    const { data } = await useFetch('/api/auth/login', {
      method: 'POST',
      body: credentials
    })
    
    if (data.value) {
      user.value = data.value.user
      token.value = data.value.token
    }
    
    return data.value
  }
  
  const logout = async () => {
    await useFetch('/api/auth/logout')
    user.value = null
    token.value = null
  }
  
  const fetchUser = async () => {
    if (!token.value) return
    
    const { data } = await useFetch('/api/auth/me')
    user.value = data.value
  }
  
  return {
    user,
    token,
    isAuthenticated,
    isAdmin,
    login,
    logout,
    fetchUser
  }
}

五、SSR注意事项 #

5.1 服务端状态 #

vue
<script setup lang="ts">
const data = useState('server-data', () => {
  if (import.meta.server) {
    return 'Server initialized'
  }
  return 'Client initialized'
})
</script>

5.2 状态水合 #

vue
<script setup lang="ts">
const user = useState('user', () => null)

onMounted(async () => {
  if (!user.value) {
    const { data } = await useFetch('/api/auth/me')
    user.value = data.value
  }
})
</script>

5.3 避免内存泄漏 #

vue
<script setup lang="ts">
const route = useRoute()

const pageData = useState(`page-${route.path}`, () => null)

onUnmounted(() => {
  clearNuxtState(`page-${route.path}`)
})
</script>

六、状态持久化 #

6.1 使用Cookie #

vue
<script setup lang="ts">
const theme = useCookie('theme', {
  default: () => 'light',
  watch: true
})

const toggleTheme = () => {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}
</script>

6.2 使用localStorage #

composables/useLocalStorage.ts

typescript
export const useLocalStorage = <T>(key: string, defaultValue: T) => {
  const data = ref<T>(defaultValue)
  
  if (import.meta.client) {
    const stored = localStorage.getItem(key)
    if (stored) {
      data.value = JSON.parse(stored)
    }
    
    watch(data, (newValue) => {
      localStorage.setItem(key, JSON.stringify(newValue))
    }, { deep: true })
  }
  
  return data
}

使用:

vue
<script setup lang="ts">
const preferences = useLocalStorage('preferences', {
  theme: 'light',
  fontSize: 14
})
</script>

七、状态调试 #

7.1 开发模式日志 #

typescript
export const useDebugState = <T>(key: string, initialValue: T) => {
  const state = useState(key, () => initialValue)
  
  if (process.dev) {
    watch(state, (newValue, oldValue) => {
      console.log(`[${key}] State changed:`, { oldValue, newValue })
    }, { deep: true })
  }
  
  return state
}

7.2 DevTools集成 #

Nuxt DevTools 可以查看 useState 的状态。

八、最佳实践 #

8.1 命名规范 #

typescript
const user = useState('auth-user')
const cart = useState('cart-items')
const settings = useState('app-settings')

8.2 封装组合式函数 #

typescript
export const useFeature = () => {
  const state = useState('feature-state', () => ({
    data: null,
    loading: false,
    error: null
  }))
  
  const actions = {
    fetch: async () => {
      state.value.loading = true
      try {
        const { data } = await useFetch('/api/feature')
        state.value.data = data.value
      } catch (error) {
        state.value.error = error
      } finally {
        state.value.loading = false
      }
    }
  }
  
  return {
    state: readonly(state),
    ...actions
  }
}

8.3 避免过度使用 #

  • 简单的父子通信使用 Props/Events
  • 复杂的状态管理使用 Pinia
  • useState 适合中等复杂度的状态共享

九、完整示例 #

9.1 主题切换 #

composables/useTheme.ts

typescript
export const useTheme = () => {
  const theme = useCookie<'light' | 'dark'>('theme', {
    default: () => 'light',
    watch: true
  })
  
  const isDark = computed(() => theme.value === 'dark')
  
  const toggle = () => {
    theme.value = isDark.value ? 'light' : 'dark'
  }
  
  const setTheme = (newTheme: 'light' | 'dark') => {
    theme.value = newTheme
  }
  
  watchEffect(() => {
    if (import.meta.client) {
      document.documentElement.classList.toggle('dark', isDark.value)
    }
  })
  
  return {
    theme,
    isDark,
    toggle,
    setTheme
  }
}

9.2 全局通知 #

composables/useNotification.ts

typescript
interface Notification {
  id: number
  type: 'success' | 'error' | 'warning' | 'info'
  message: string
  duration?: number
}

export const useNotification = () => {
  const notifications = useState<Notification[]>('notifications', () => [])
  
  let idCounter = 0
  
  const add = (notification: Omit<Notification, 'id'>) => {
    const id = ++idCounter
    const newNotification: Notification = {
      id,
      duration: 5000,
      ...notification
    }
    
    notifications.value.push(newNotification)
    
    if (newNotification.duration && newNotification.duration > 0) {
      setTimeout(() => {
        remove(id)
      }, newNotification.duration)
    }
    
    return id
  }
  
  const remove = (id: number) => {
    const index = notifications.value.findIndex(n => n.id === id)
    if (index > -1) {
      notifications.value.splice(index, 1)
    }
  }
  
  const clear = () => {
    notifications.value = []
  }
  
  const success = (message: string) => add({ type: 'success', message })
  const error = (message: string) => add({ type: 'error', message })
  const warning = (message: string) => add({ type: 'warning', message })
  const info = (message: string) => add({ type: 'info', message })
  
  return {
    notifications,
    add,
    remove,
    clear,
    success,
    error,
    warning,
    info
  }
}

使用:

vue
<script setup lang="ts">
const { success, error } = useNotification()

const handleSave = async () => {
  try {
    await saveData()
    success('保存成功!')
  } catch (e) {
    error('保存失败,请重试')
  }
}
</script>

十、总结 #

本章介绍了 Nuxt.js 的 useState

  • 基本用法和类型定义
  • 封装组合式函数
  • SSR 注意事项
  • 状态持久化
  • 最佳实践

useState 适合简单到中等复杂度的状态管理,下一章我们将学习 Pinia 状态管理。

最后更新:2026-03-28