Skip to content

第6章:数据获取与状态管理

1. 三种数据获取方式(新手循序渐进)

1.1 方式1:useAsyncData(核心,用于异步获取数据,支持 SSR)

基本用法

vue
<template>
  <div>
    <h1>Posts</h1>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else>
      <ul>
        <li v-for="post in posts" :key="post.id">
          {{ post.title }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
const { data: posts, pending, error } = useAsyncData('posts', () => {
  return $fetch('/api/posts')
})
</script>

带参数的使用

vue
<template>
  <div>
    <h1>Post Details</h1>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else>
      <h2>{{ post.title }}</h2>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>

<script setup>
const route = useRoute()
const { data: post, pending, error } = useAsyncData(`post-${route.params.id}`, () => {
  return $fetch(`/api/posts/${route.params.id}`)
})
</script>

带选项的使用

vue
<template>
  <div>
    <h1>Posts</h1>
    <!-- 内容 -->
  </div>
</template>

<script setup>
const { data: posts, pending, error, refresh } = useAsyncData('posts', () => {
  return $fetch('/api/posts')
}, {
  server: true, // 在服务端获取数据
  lazy: false, // 非懒加载
  refreshInterval: 10000, // 每10秒刷新一次
  initialCache: false, // 不使用初始缓存
  watch: [/* 依赖项 */] // 依赖项变化时重新获取数据
})

// 手动刷新数据
function handleRefresh() {
  refresh()
}
</script>

1.2 方式2:useFetch(简化版 useAsyncData,直接请求接口)

基本用法

vue
<template>
  <div>
    <h1>Posts</h1>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else>
      <ul>
        <li v-for="post in data" :key="post.id">
          {{ post.title }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
const { data, pending, error } = useFetch('/api/posts')
</script>

带参数的使用

vue
<template>
  <div>
    <h1>Post Details</h1>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else>
      <h2>{{ data.title }}</h2>
      <p>{{ data.body }}</p>
    </div>
  </div>
</template>

<script setup>
const route = useRoute()
const { data, pending, error } = useFetch(`/api/posts/${route.params.id}`)
</script>

带选项的使用

vue
<template>
  <div>
    <h1>Posts</h1>
    <!-- 内容 -->
  </div>
</template>

<script setup>
const { data, pending, error, refresh } = useFetch('/api/posts', {
  method: 'get',
  params: { page: 1, limit: 10 },
  server: true,
  lazy: false
})
</script>

1.3 方式3:useLazyFetch / useLazyAsyncData(懒加载数据)

基本用法

vue
<template>
  <div>
    <h1>Posts</h1>
    <div v-if="!data">Loading...</div>
    <div v-else>
      <ul>
        <li v-for="post in data" :key="post.id">
          {{ post.title }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
const { data } = useLazyFetch('/api/posts')
</script>

useLazyAsyncData

vue
<template>
  <div>
    <h1>Posts</h1>
    <div v-if="!posts">Loading...</div>
    <div v-else>
      <ul>
        <li v-for="post in posts" :key="post.id">
          {{ post.title }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
const { data: posts } = useLazyAsyncData('posts', () => {
  return $fetch('/api/posts')
})
</script>

2. 数据请求封装(统一请求实例、请求拦截器、响应拦截器)

2.1 创建请求实例

创建 composables/useApi.ts

typescript
// composables/useApi.ts
import { $fetch, type FetchOptions } from 'ofetch'

const baseURL = import.meta.env.VITE_API_BASE_URL || '/api'

const api = $fetch.create({
  baseURL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
})

export function useApi() {
  return {
    get: <T>(url: string, options?: FetchOptions) => {
      return api<T>(url, { ...options, method: 'get' })
    },
    post: <T>(url: string, body?: any, options?: FetchOptions) => {
      return api<T>(url, { ...options, method: 'post', body })
    },
    put: <T>(url: string, body?: any, options?: FetchOptions) => {
      return api<T>(url, { ...options, method: 'put', body })
    },
    delete: <T>(url: string, options?: FetchOptions) => {
      return api<T>(url, { ...options, method: 'delete' })
    }
  }
}

2.2 请求拦截器

修改 composables/useApi.ts

typescript
// composables/useApi.ts
import { $fetch, type FetchOptions } from 'ofetch'

const baseURL = import.meta.env.VITE_API_BASE_URL || '/api'

const api = $fetch.create({
  baseURL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  },
  onRequest({ options }) {
    // 添加 token
    const token = useCookie('token').value
    if (token) {
      options.headers = {
        ...options.headers,
        Authorization: `Bearer ${token}`
      }
    }
  }
})

// 其余代码不变

2.3 响应拦截器

修改 composables/useApi.ts

typescript
// composables/useApi.ts
import { $fetch, type FetchOptions } from 'ofetch'

const baseURL = import.meta.env.VITE_API_BASE_URL || '/api'

const api = $fetch.create({
  baseURL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  },
  onRequest({ options }) {
    // 添加 token
    const token = useCookie('token').value
    if (token) {
      options.headers = {
        ...options.headers,
        Authorization: `Bearer ${token}`
      }
    }
  },
  onResponse({ response }) {
    // 统一处理响应
    return response._data
  },
  onResponseError({ response }) {
    // 统一处理错误
    if (response.status === 401) {
      // 未授权,跳转到登录页
      navigateTo('/login')
    }
    throw response._data
  }
})

// 其余代码不变

2.4 使用封装的请求

示例

vue
<template>
  <div>
    <h1>Posts</h1>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else>
      <ul>
        <li v-for="post in posts" :key="post.id">
          {{ post.title }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
const { get } = useApi()
const { data: posts, pending, error } = useAsyncData('posts', () => {
  return get('/posts')
})
</script>

3. 状态管理(Nuxt 内置状态管理)

3.1 简单状态共享(useState 核心用法,响应式、服务端兼容)

基本用法

vue
<template>
  <div>
    <h1>Counter: {{ count }}</h1>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script setup>
const count = useState('count', () => 0)

function increment() {
  count.value++
}

function decrement() {
  count.value--
}
</script>

在多个组件中共享

vue
<!-- ComponentA.vue -->
<template>
  <div>
    <h1>Component A</h1>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
const count = useState('count', () => 0)

function increment() {
  count.value++
}
</script>

<!-- ComponentB.vue -->
<template>
  <div>
    <h1>Component B</h1>
    <p>Count: {{ count }}</p>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script setup>
const count = useState('count', () => 0)

function decrement() {
  count.value--
}
</script>

3.2 复杂状态管理(Pinia 集成 Nuxt,实战场景)

3.2.1 安装 Pinia

bash
# 使用 npm
npm install pinia @pinia/nuxt

# 使用 pnpm
pnpm add pinia @pinia/nuxt

# 使用 yarn
yarn add pinia @pinia/nuxt

3.2.2 配置 Pinia

nuxt.config.ts 中添加模块

typescript
export default defineNuxtConfig({
  modules: [
    '@pinia/nuxt'
  ]
})

3.2.3 创建 Store

创建 stores/counter.ts

typescript
// stores/counter.ts
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Nuxt'
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    reset() {
      this.count = 0
    }
  }
})

3.2.4 使用 Store

示例

vue
<template>
  <div>
    <h1>Counter: {{ counter.count }}</h1>
    <p>Double count: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">Increment</button>
    <button @click="counter.decrement">Decrement</button>
    <button @click="counter.reset">Reset</button>
  </div>
</template>

<script setup>
const counter = useCounterStore()
</script>

3.2.5 异步 Action

示例

typescript
// stores/post.ts
import { defineStore } from 'pinia'

export const usePostStore = defineStore('post', {
  state: () => ({
    posts: [],
    loading: false,
    error: null
  }),
  actions: {
    async fetchPosts() {
      this.loading = true
      this.error = null
      try {
        const { get } = useApi()
        this.posts = await get('/posts')
      } catch (error) {
        this.error = error
      } finally {
        this.loading = false
      }
    }
  }
})

使用

vue
<template>
  <div>
    <h1>Posts</h1>
    <button @click="fetchPosts">Fetch Posts</button>
    <div v-if="postStore.loading">Loading...</div>
    <div v-else-if="postStore.error">Error: {{ postStore.error.message }}</div>
    <div v-else>
      <ul>
        <li v-for="post in postStore.posts" :key="post.id">
          {{ post.title }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
const postStore = usePostStore()

function fetchPosts() {
  postStore.fetchPosts()
}
</script>

4. 本地存储持久化(useCookie、localStorage 封装)

4.1 使用 useCookie

基本用法

vue
<template>
  <div>
    <h1>Welcome {{ username }}</h1>
    <input v-model="username" placeholder="Enter your name" />
  </div>
</template>

<script setup>
const username = useCookie('username', {
  default: 'Guest',
  maxAge: 60 * 60 * 24 * 7 // 7天
})
</script>

4.2 使用 localStorage

创建 composables/useLocalStorage.ts

typescript
// composables/useLocalStorage.ts
import { ref, watch } from 'vue'

export function useLocalStorage<T>(key: string, defaultValue: T) {
  const storedValue = localStorage.getItem(key)
  const value = ref<T>(storedValue ? JSON.parse(storedValue) : defaultValue)

  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })

  return value
}

使用

vue
<template>
  <div>
    <h1>Counter: {{ count }}</h1>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script setup>
const count = useLocalStorage('count', 0)

function increment() {
  count.value++
}

function decrement() {
  count.value--
}
</script>

4.3 结合状态管理使用

示例

typescript
// stores/user.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    user: useCookie('user').value || null
  }),
  actions: {
    login(userData) {
      this.user = userData
      useCookie('user', { maxAge: 60 * 60 * 24 * 7 }).value = userData
    },
    logout() {
      this.user = null
      useCookie('user').value = null
    }
  }
})

小结

本章介绍了 Nuxt.js 的数据获取与状态管理,包括三种数据获取方式(useAsyncData、useFetch、useLazyFetch/useLazyAsyncData)、数据请求封装、状态管理(useState 和 Pinia)以及本地存储持久化。通过本章的学习,你应该已经掌握了如何在 Nuxt.js 中获取数据、管理状态,以及如何实现数据的持久化存储。

在接下来的章节中,我们将学习 Nuxt.js 的布局与组件化开发、Composables 与组合式逻辑复用等核心特性,帮助你更深入地理解和使用 Nuxt.js。

© 2026 编程马·菜鸟教程 版权所有