20分钟带你入门Nuxt3

Nuxt 是一个使用 Vue.js 做为视图引擎的 Web 框架,可以用它做出 SSR 友好的 Web 程序,也可以使用其中 content 功能来做博客文档(类似于 VitePress 等工具)当然也支持纯静态站点部署。

环境配置

开始之前确保 Node 版本 >= 14.16

如果使用 VSCode 推荐预先安装相关插件:

目前来说是可以工作,但在每个项目里我们都运行了两个语言服务实例:一个来自 Volar,一个来自 VSCode 的内置服务。在一些项目可能会出现一些问题;

可以通过开启 Volar 的托管模式(Takeover)使用一个 TypeScript 语言服务实例同时为 .vue.ts 文件提供支持;具体配置参考如下:

Nuxt 安装

pnpm dlx nuxi init nuxt-app
复制代码
pnpm install --shamefully-hoist
复制代码
pnpm run dev
复制代码

执行完上述操作后,http://localhost:3000 此页面应能顺利打开。 nuxt-welcome.png

项目结构

Api 学习开始之前,先看一下项目的结构。Nuxt 项目遵循了一套约定;这些约定与目录又息息相关。

.
├── .nuxt           Nuxt 框架帮助生成的文件夹这其实就是当前运行的项目
├── node_modeules
├── .gitignore
├── app.vue         项目的入口文件
├── nuxt.config.ts  项目的配置文件
├── package.json
├── pnpm-lock.yaml
├── README.md
└── tsconfig.json
复制代码

除了以上的一些目录,Nuxt 还允许你创建或者生成一些其他的目录或者文件,使应用程序的功能更加丰富。

打包产出 .output/

.output/:执行 pnpm run build 后生成的打包结果。

静态资源 assets/

assets:用来存放一些静态资源;比如图片、样式等;在 .vue 的模板中你可以这样去引入 assets 下的资源

<img src="~/assets/test.jpg" />
复制代码
import '~/assets/test.js';
复制代码

样式的引入需要注意,在普通的 style 标签中可以去引入各种资源的样式包括 scss less 但如果你使用了 <style lang="scss"></style> 类似的标签则需要去下载预处理器和对应的 loader

<style>
import '~/assets/test.css';
import '~/assets/test.less';
import '~/assets/test.scss';
</style>
复制代码

scss 的使用:pnpm install sass sass-loader -D

<style lang="scss">
@import url('~/assets/test.scss');
</style>
复制代码

less 的使用:pnpm install less less-loader -D

<style lang="less">
@import '~/assets/test.less';
</style>
复制代码

静态资源 public/

public 多用于去存储一些项目的公共文件,比如 favicon.icorobots.txt 等;

但其实也不是不可以使用图片等其他资源,public 下的资源不会经过打包处理,在部署之后全部都在项目的根目录下可以直接访问;例如 http://www.xsq.com/test.jpg

如果需要在项目中去使用 public 下的资源则可以以 / 开始去引入内容;例如 <img src="/test.jpg" />

注意 如果你真的考虑这样使用,记得写完后重启项目验证是否成功,因为 public 并不在 nuxt 的动态解析范围中,可能会导致项目失去了关于这部分的热更新功能。

组件 components/

此章节开始之前希望你能先将 app.vue 中的示例 <NuxtWelcome /> 组件删除掉,还原一个干净的项目,方便后续的操作。

nuxt自动导入声明在 components 下的所有 .vue 文件当做项目中的组件,但是会有一套它自己的规则。

  • 一级目录下的 component 例如:components/TheHeader.vue,此种情况的使用方式为<TheHeader />

  • 多级目录下的 components 例如 components/base/foo/button.vue,此种情况的使用方式为 <BaseFooButton />

  • 动态组件(<componet :is="dynamicComponent" />)的使用方式大致分成两种:

    1. 配合 resolveComponent 方法。(推荐使用)
    <template>
      <component :is="BaseDynamic" />
      <component :is="Dynamic" />
    </template>
    <script setup>
    // path: components/base/Dynamic.vue
    const BaseDynamic = resolveComponent('BaseDynamic');
    // path: components/Dynamic.vue
    const Dynamic = resolveComponent('Dynamic');
    </script>
    复制代码
    1. 可以选择在 nuxt.config.ts 中去全局的注册它们,使可以应用到全局的任何地方。
    export default defineNuxtConfig {
      components: {
        global: true,
        dirs: ['~/components/global']
      }
    };
    复制代码

    使用时则可以这样 <components :is="GlobalDynamic" />

    注意 此时 /components/global 下的所有组件都会被全局注册,但需要注意的是 global/ 下的组件不能和外部存在同名组件。

  • 动态导入 个人理解与 Vue 中的 defineAsyncComponent 类似 Vue 异步组件。使用方法如下

<template>
  <!-- 对于定义并没有影响,只需要将需要加载的组件前面加上 Lazy 即可实现 -->
  <LazyBaseDynamic v-if="isShow" />
  <div v-else>loading...</div>
</template>
<script setup lang="ts">
const isShow = ref<boolean>(false);

setTimeout(() => {
  isShow.value = true;
}, 2000);
</script>
复制代码
  • client/server 组件,目前来看我并没有想到实际的使用场景,就暂时不说了,大伙有兴趣可以去看一下。也希望能得到评论区的最佳实践。 ClientOnly 组件

可组合式(hooks) composables/

Vue 的开发过程中,对于一些逻辑的复用大多数人都会选择封装 hooks 来解决问题,nuxt 中推荐将 hooks 都存储在 composables/ 中进行逻辑复用。

// composables/useCounter.ts
export default function useCounter() {
  return 1 + 1;
}
复制代码
<!-- app.vue -->
<template>
  <div>{{ counter }}</div>
</template>
<script setup lang="ts">
const counter = useCounter();
</script>
复制代码

默认情况下 composables 只会扫描第一层文件并对其加上自动导入的功能。如果想要更深层的引用有如下两种方案:

  1. index.ts 再加一层默认导出,即可在全局内使用。(推荐)

    // composables/index.ts
    export { useFoo } from './nested/useFoo';
    export { useBar } from './nested/useBar';
    复制代码
  2. nuxt.config.ts 配置它可扫描的文件夹深度。

    export default defineNuxtConfig({
      imports: {
        dirs: ['composables/**']
      }
    });
    复制代码

文档 content/

类似于 Vue 生态下 VitePressVuePress 。但是目前没有提供主题需要自己去实现。

关于 Nuxt3content 部分个人认为很完美,但不是更完美。不像 Nuxt2 提供了一套成熟的主题,Nuxt3 的文档样式都要自己去定制。有兴趣可以去深入的了解下。

Nuxt3 content 文档Content Nuxt 文档

页面 pages/ (路由)

Nuxt 提供一个基于文件的路由,即会根据 pages/ 下的目录结构生成一套程序的导航机制,当然在文件的命名方面也要遵循它的机制。

在开始之前希望先了解两个 Nuxt 的内置组件 <NuxtPage /><NuxtLink /> 对标 Vue 中的 <RouterLink /><RouterView />

先将 app.vue 中的内容修改为 <NuxtPage /> 作为整个项目的页面的入口;

再创建 pages/index.vue 随意输入内容。index.vue 则是 http://localhost:3000/ 中的 / 地址。

此时你还要注意两点内容:

  1. pages/ 下的文件默认支持 jsx 或者 h 渲染函数以及 .vue 模板语法的。
  2. pages/ 下的文件尽可能都保持只有一个根节点,保证页面路由可以正常切换。

此时整个项目的路由就已经全部被 pages/ 所接管,就可以愉快的开始学习后面内容了。

静态路由 (入门)

-| pages/
---| index.vue
---| users
---| profile
复制代码

以上路由路由的访问方式为 http://localhost:3000/usershttp://localhost:3000/profile

对应的跳转方式为

<!--路由默认的名称为文件名-->
<NuxtLink to="users">UsersPage</NuxtLink>
<NuxtLink :to="{ name: 'users' }">UsersPage</NuxtLink>
复制代码

动态路由

-| pages/
---| index.vue
---| users-[group].vue
复制代码

以上路由的访问方式为 http://localhost:3000/users-xxxx,但是动态路由的跳转还是老老实实的使用路径模式的方案吧。

访问参数的方式分为两种:

  1. 模板中直接使用 $route 对象,<div>{{ $route.params.group }}</div>
  2. script 中使用 useRoute
    const route = useRoute(); 
    console.log(route.params.group);
    复制代码

嵌套路由

-| pages/
---| parent/
------| child.vue
---| parent.vue
复制代码

以上路由的访问方式为 http://localhost:3000/parenthttp://localhost:3000/parent/child

务必确认在 parent.vue 中存在 <NuxtPage /> 保证子路由可以正常渲染

<!-- parent.vue -->
<template>
  <div>
    this is parent Page
    <NuxtLink :to="{ path: '/parent/child' }">go to child</NuxtLink>
    <NuxtPage />
  </div>
</template>
<!-- child.vue -->
<template>
  <div>this is child page <button @click="$router.back()">go back</button></div>
</template>
复制代码

如果遇到动态路由导致的路由参数失效无法触发路由刷新的问题,则可以使用给路由加 key 来解决此问题。

  1. 模板方式 <NuxtPage :page-key="$route.fullPath" />
  2. script 方式则通过 definePageMeta 解决。
    definePageMeta({
      key: route => route.fullPath
    });
    复制代码

404 页面

全局的 404 页面可以声明 page/404.vue,来作为全局的 404 页面。

通用路由

通用路由的声明方式为 [...slug].vue 任何没有被导航命中的页面都会进入此页面,优先级低于 404.vue

[...slug].vue 的规则取决于它声明的位置。假设 pages/[...slug].vue 则是以全局为准。但如果你将它声明在某个页面下,比如 pages/parent/[...slug].vue 则通用页面只在 parent/ 下生效。

编程式导航

日常的导航方式你仍可以使用 $router.push() 等方法。但 Nuxt 提供了一个兼容性更好的方法 navigateTo() API 文档

const goUsers = async () => {
  await navigateTo({ path: '/user', query: { msg: 'fromparent/child' } });
};
复制代码

路由模式

Nuxt 默认的路由模式为 history 可以通过配置修改为 hash 模式,但是此模式不支持 ssr

export default defineNuxtConfig({
  ssr: false,
  router: {
    options: {
      hashMode: true
    }
  }
});
复制代码

页面元数据

可以针对通用配置做出一些针对这个页面的调整。API 文档

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

definePageMeta({
  key: route.fullPath
});
</script>
复制代码

其余属性先混个眼熟,后面用到会再次使用。

  • key 防止路由的重复渲染
  • keepalive 是否需要缓存这个页面或者定义新的缓存规则
  • layout 需要用哪些布局,或者不适用布局
  • layoutTransition 设置过渡的名称(修改过渡效果)或者不适用过渡
  • pageTransition 设置页面的过渡名称或者不使用页面过渡
  • validate 验证页面,类似于路由导航。这个功能也可以放到中间件中去使用。

布局 layouts/

日常应用程序的开发中,肯定少不了一些通用的布局页面,比如 通用 headerfooter 中间放主体内容 containernuxt 允许你在 layouts/ 中去按照特定的规则生成一些布局页面将页面中那些通用的布局放在这里。

注意: nuxt 的布局页面中必须要有一个根元素。

  • 默认布局 layouts/default.vuedefault 文件为默认的布局,不需要任何引用声明后即可使用。配合 NuxtLayout 来使用布局。

    <!-- default.vue -->
    <template>
      <div>
        <header>this is default header</header>
        <slot></slot>
        <!-- 插槽的使用方式与 vue 一致 -->
        <slot name="aside"></slot>
        <footer>this is default footer</footer>
      </div>
    </template>
    <!-- app.vue 任何页面都可使用,这里只是为了示例 -->
    <NuxtLayout> 
      this is index page
      <template #aside>
        this is aside container
      </template>
    </NuxtLayout>
    复制代码
  • 其他布局 layout/xxx.vue;非 default 命名的文件即为其他布局,在使用层面上面也基本一致,不过需要给 NuxtLayout 执行一个 name 来表明当前使用的哪个布局;或者也可以使用 definePageMeta 声明一个 layout注意 layout 如果为 false 则布局不会生效

    <NuxtLayout name="custom"> 使用 custom 布局的页面 </NuxtLayout>
    复制代码
    definePageMeta({ layout: 'custom' });
    复制代码
  • 动态更改布局。通过 setPageLayout 方法来切换布局。 setPageLayout('custom');

中间件 middleware/

在日常的使用的过程中称之为 路由拦截 应该更加合适。中间件的声明共有三种模式。

  • 在页面中声明中间件 definePageMeta({ middleware: (to, from) => {} });,此种中间件只会在当前页面导航被触发之前执行。

  • middleware/ 中创建拦截文件,命名规则为 kebab-case 例如 some-middleware

    // middleware/auth.ts
    export default function auth(to, from) {
      console.log(to, from)
    };
    // pages/user-detail.vue  
    // 此种拦截方式在没有 return 值的情况下,如果在同一页面刷新会触发错误,不晓得是什么原因。
    definePageMeta({ middleware: ['auth'] });
    复制代码
  • 同样在 middleware/ 中创建文件,拦截文件最后加 .global 后缀,此拦截器不需要引用即会在全局的页面导航被触发之前执行。 global 的拦截优先于以上两种拦截方式。

对比 vue-router 的拦截而言,中间件缺少了 next 第三个参数。需要 next 完成的事情则交给返回值来做。在 nuxt 中还提供了几个导航的钩子。

return abortNavigation():停止当前导航;return abortNavigation(error):拒绝当前导航并出现错误;return navigateTo():导航到指定页面;

```ts
// 伪代码
export default function auth(to, from) {
  if (!to.params.id) {
    return abortNavigation()
  } else {
    // get user token by id
    if (getAuthToken(to.params.id)) {
      return navigateTo('/home')
    } else {
      return abortNavigation('get user token error')
    }
  }
}
```
复制代码

插件 plugins/

可以理解为 nuxt 注册组件库的地方。在这之前可能先需要了解一下配置相关的知识点,毕竟在 nuxt 认为约定大于一切。

  1. plugins/ 中文件在 Vue 程序加载时一同被加载(可以直接使用无需在进行配置),可以使用文件名 .server .client 让其只在某一个环境下加载。
  2. plugins/ 只有顶层的文件或者子目录下的 index 文件会被自动注册。
  3. 注册插件的方法为 defineNuxtApp 接受的唯一参数是 nuxtApp。(Vue.use 方法类似)
  4. 通过给文件名加序号的方式决定插件注册的顺序。后注册的有能力访问前注册的插件。
    plugins/
     | - 1.myPlugin.ts
     | - 2.myOtherPlugin.ts
    复制代码
  5. 在插件中可以使用 composables/ 下的 hooks。但是这个 hook 不要涉及到 vue 的生命周期否则将无法运行。
  6. 可以提供一些方法到 nuxt 实例上面方便后续的使用。比如 vue 程序中的 $message 组件。
    import type { NuxtApp } from '#app';
    import { useSum } from '~~/composables/useSum';
    
    export default defineNuxtPlugin((nuxtApp: NuxtApp) => {
      return {
        provide: {
          sum: (a: number, b: number) => useSum(a, b)
        }
      };
    });
    复制代码
    <template>
      {{ $sum(1, 3) }}
    </template>
    <script setup lang="ts">
    const { $sum } = useNuxtApp();
    console.log($sum(2, 4));
    </script>
    复制代码
    如果像上面这样使用可能会失去一些类型提示。对应的你在 index.d.ts 中配置类型声明。
    declare module '#app' {
      interface NuxtApp {
        $sum(a: number, b: number): void;
      }
    }
    
    declare module '@vue/runtime-core' {
      interface ComponentCustomProperties {
        $sum(a: number, b: number): void;
      }
    }
    复制代码
  7. vue 插件注册需要依赖 nuxtApp.vueApp.use 方法。同样也支持一些指令,组件的注册
    import type { NuxtApp } from '#app';
    
    export default defineNuxtPlugin((nuxtApp: NuxtApp) => {
      nuxtApp.vueApp.use(xxx); // 等价于 vue.use 
      nuxtApp.vueApp.directive('focus', {}); // 等价于 vue.directive
    })
    复制代码

服务端 server/

SSR 并不是一个单纯的前端框架,拥有的一定的后端服务能力,Nuxt 也是支持这一点,允许你在服务端去代理 api 注册路由以及中间件。你甚至可以将后端返回的一些不利于处理的数据,放到 server/ 中进行数据的转换最终把完整的数据返回给前端,让前端只需要做纯渲染的逻辑不涉及任何数据处理逻辑。

有兴趣的小伙伴可以去了解一下 server 能力

应用程序配置文件 app.config.ts

此文件用于定义一些通用的可以被公开的配置,比如主题,模式等内容。于此对应的还有一个 runtimeConfignuxt.config.ts 中进行配置。可以在里面配置一些私密性的信息,比如环境变量还有 key

// app.config.ts
export default defineAppConfig({
  theme: {
    primaryColor: '#ababab'
  }
});

// app.vue 访问时同时支持服务端和客户端
const appConfig = useAppConfig();
console.log(appConfig.theme);

// index.d.ts 如果想要完整的类型提示,需要配置额外的类型文件 
declare module '@nuxt/schema' {
  interface AppConfigInput {
    /** Theme configuration */
    theme?: {
      /** Primary app color */
      primaryColor?: string
    }
  }
}
export {}
复制代码

项目配置文件 nuxt.config.ts

在前面的介绍中或多或少的也在这个文件中加了一些配置,但这也只是冰山一角,这里也不可能对它进行一个完整的示例或者说讲解。着重看一下 runtimeConfig 运行时配置。后续如果有其他用得到的地方再进行文档查阅。nuxt.config.ts

运行时配置整体分两个部分 public 客户端访问,以及默认的服务端访问。nuxt 默认支持 dotenv 这意味着可以在项目的根目录去根据环境声明一些 env 文件; 了解更多

export default defineNuxtConfig({
  runtimeConfig: {
    // 只能在服务端访问
    apiSecret: process.env.API_SECRET 
    public: {
      // 可以在客户端访问
      apiBase: process.env.BASE_URL
    }
  }
})
复制代码

访问 runtimeConfig 方式则是通过 useRuntimeConfig()

整体的目录规范

.
├── .nuxt           Nuxt 框架帮助生成的文件夹这其实就是当前运行的项目
├── .output         Nuxt 打包后的文件目录
├── assets          静态文件的存储位置 (参与打包)
├── components      组件目录
├── composables     通过 js 逻辑目录 (hooks)
├── content         静态文档目录
├── layouts         布局
├── middleware      中间件 比如 auth 权限认证
├── node_modeules
├── pages           页面
├── plugins         插件
├── public          项目根静态资源 (不参与打包)
├── server          服务端
├── .gitignore
├── .nuxtignore     构建阶段忽略配置
├── app.config.ts   定义运行时的应用程序配置,比如换肤功能
├── app.vue         项目的入口文件
├── nuxt.config.ts  项目的配置文件
├── package.json    
├── pnpm-lock.yaml
├── README.md
└── tsconfig.json
复制代码

自动导入 AutoImport

在日常使用 nuxt 的开发过程中,大多数情况下是不需要手动导入内容的,nuxt 会帮助我们进行一些自动导入。使其在开发的过程中可以直接使用。

虽然这个功能很好,但并不能解决开发过程中潜在的问题,比如我们需要某些类型的时候,需要手动的导入以获得更好的类型提示。

import type { NuxtApp } from '#app';
复制代码

此时你可能会疑问 #app 是什么,从哪里来? 可以从 .nuxt/tsconfig.json 中找到答案。包括我们用到的所有方法,都可以在 .nuxt/ 下的那些类型声明文件中找到合适的引用。

16672281208139.jpg

如果你觉得 nuxt 的自动导入会让你觉得方法难以溯源,你也可以关闭自动导入的功能;

export default defineNuxtConfig({
  imports: {
    autoImport: false
  }
})
复制代码

常见的一些导入方式有以下几种

import { useAsyncData } from '#app'; // nuxt 内部的一些方法及类型
import { ref, reactive } from '#imports'; // vue 内部的方法及类型
import { AsyncDynamic } from '#components'; // components 目录下的组件
import { useHead } from '#head'; // nuxt 头部的一些内容
// ~ 别名 .nuxt/tsconfig.json 中可以查询所有别名
import { useFoo } from '~/compoables'; // 导入 compoables 组合式函数
复制代码

异步数据获取

整体来看 nuxt 中获取异步数据的方式有四种方式,可以被分为两组

  • useFetch useLazyFetch
    const {
      data: Ref<DataT>,
      pending: Ref<boolean>,
      refresh: (force?: boolean) => Promise<void>,
      error?: any
    } = useFetch(url: string, options?)
    复制代码
  • useAsyncData useLazyAsyncData
    const {
      data: Ref<DataT>,// 返回的数据结果
      pending: Ref<boolean>,// 是否在请求状态中
      refresh: (force?: boolean) => Promise<void>,// 强制刷新数据
      error?: any // 请求失败返回的错误信息
    } = useAsyncData(
      key: string, // 唯一键,确保相同的请求数据的获取和去重
      fn: () => Object,// 一个返回数值的异步函数
      options?: { lazy: boolean, server: boolean }
      // options.lazy,是否在加载路由后才请求该异步方法,默认为false
      // options.server,是否在服务端请求数据,默认为true
      // options.default,异步请求前设置数据data默认值的工厂函数(对lazy:true选项特别有用)
      // options.transform,更改fn返回结果的函数
      // options.pick,只从数组中指定的key进行缓存
    )
    复制代码

需要注意的是这几种获取数据的方式,只能在 setup 或者 生命周期的过程中去使用。

以 聚合数据 免费 api 为例

const url = 'http://apis.juhe.cn/simpleWeather/query?city=上海&key=5051878bb2230acc96a28f53944219a0';

const { data, pending, refresh, error } = await useFetch(url);
const { data, pending, refresh, error } = useLazyFetch(url);

const { data, pending, refresh, error } = useAsyncData('count', () => $fetch(url));
const { data, pending, refresh, error } = useLazyAsyncData('count', () =>$fetch(url));
复制代码
  1. 包含 lazy 的数据获取方式不会阻止导航,也需要去额外处理数据为空的情况。
  2. 数据刷新可以使用返回的 refresh 方法 refresh(),默认的情况下 refresh 将取消正在进行的请求,可以通过设置 dedupe 来取消这种情况 refresh({ dedupe: true })
  3. 如果页面中存在多个请求时进行刷新可以使用 refreshNuxtData 方法,他接收一个 key 也就是 AsyncData 系列钩子的第一个参数(fetchkey 是默认生成的),可以不传递即刷新所有的接口请求。
  4. 与刷新请求对应的还有一个 clearNuxtData 方法同样接收一个 key。此方法会清除所有异步获取到数据包括状态等信息 clearNuxtData()
  5. 多数情况下接口返回的数据不一定都是我们需要的,可以通过 fetch 的第二个参数,或者 asyncData 的第三个参数指定一个 options 通过 pick 只提取我们想要的字段数据。
    const { data, pending, refresh, error } = await useFetch(url, { pick: ['result', 'code' ] });
    复制代码

可能遇到的问题:

  • 在使用 refresh 或者 refreshNuxtData 时可能会出现跨域的问题,问题出现的原因是 默认的情况下请求是在服务端发送的,刷新时不是。如果在发送请求时第二个或者第三个参数 { server: false } 同样也会碰到这个问题。

常规认知中可能配置一下 proxy 即可解决此类问题,但是目前来看 nuxt 暂时不支持此种操作。不过社区中提供了一些其他的解决方案 github

搜索引擎优化

常见的 SEO 优化方式主要就是设置 head 中的标签方便爬虫的读取。在 nuxt 中更改这些数据的方式,整体可以分为三种。

  1. 全局设置在 nuxt.config.ts 中设置,当应用程序的页面中没有设置数据时,以配置文件中的为主

    export default defineNuxtConfig({
      app: {
        // head 中的标签基本都可以设置
        head: {
          title: 'global title',
          meta: [
            { name: 'description', content: 'global description' }
          ]
        }
      }
    })
    复制代码
  2. useHead 单页面设置,设置了此种方式可以设置当前页面的 head 信息

    useHead({
      title: 'user title',
      meta: [{ name: 'description', content: 'this is ueser description' }]
    });
    复制代码
  3. 组件设置,除了 hook 的方式也 nuxt 也提供了很多组件可以直接在模板中去编写相关内容。<Title><Base><Script><NoScript><Style><Meta><Link><Body>

    <Head>
      <Title>home title</Title>
      <Meta name="description" content="this is home desc"></Meta>
    </Head>
    复制代码

状态管理

nuxt 中官方推荐的方式是使用其内部提供的 useState 方法来进行状态的管理。

// composables/states.ts
export const useCounter = () => useState<number>('counter', () => 0);
export const useColor = () => useState<string>('color', () => 'pink');
复制代码
<!--app.vue-->
<script setup>
const color = useColor() // Same as useState('color')
</script>

<template>
  <p>Current color: {{ color }}</p>
</template>
复制代码

过渡动画

页面过渡动画

nuxt.config.ts 中设置动画过渡的类名。

export default defineNuxtConfig({
  app: {
    // name: 页面转换的类名前缀,默认是 page 
    // mode: default out-in in-out
    pageTransition: { name: 'page', mode: 'out-in' }
  }
})
复制代码

随后在项目的 app.vue 中增加动画 css,即可实现路由切换的动画效果。

.page-enter-active,
.page-leave-active {
  transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
  opacity: 0;
  filter: blur(1rem);
}
复制代码

如果需要为某一个页面单独的设置过渡动画,也可以使用 definePageMeta 设置这个页面的动画前缀

<template>
  this is profile page
  <NuxtLink to="/"> go to index </NuxtLink>
</template>
<script setup lang="ts">
definePageMeta({
  pageTranstion: {
    name: 'profile',
    mode: 'out-in'
  }
});
</script>
<style lang="scss">
.profile-enter-active,
.profile-leave-active {
  transition: all 0.4s;
}
.profile-enter-from,
.profile-leave-to {
   transition: translate(50px, 0);
}
</style>
复制代码

布局过渡动画

布局动画的整体核心和 page 的动画基本一致,默认类名会以 layout 开头。通过可以通过全局配置文件或者 definePageMeta 来进行设置。

类名对应为 layout-enter-active, layout-leave-active, layout-enter-from, layout-leave-to

export default defineNuxtConfig({
  app: {
    layoutTranstion: {
      name: 'layout',
      mode: 'out-in'
    }
  }
})
复制代码

如果需要为某个单独的 layout 增加动画则可以使用以下语法

definePageMeta({
  layout: 'orange',
  layoutTransition: {
    name: 'side-in'
  }
})
复制代码

layoutTransitionpageTransition 同样接受 false 以表示该页面或者全局禁用动画,这取决于你声明的位置。

如果 css 不能满足你的需求,也可以使用它所提供的钩子设置动画。

<script lang="ts" setup>
definePageMeta({
  pageTransition: {
    name: 'custom-flip',
    mode: 'out-in',
    onBeforeEnter: (el) => {
      console.log('Before enter...')
    },
    onEnter: (el, done) => {},
    onAfterEnter: (el) => {}
  }
})
</script>
复制代码

错误捕获

nuxt 提供三个钩子来捕获 vue 声明周期中的错误。分别为 vueApp.config.errorHanlderonErrorCaptured 以及 nuxtApp.hook('vue:error', cb) 经过测试这只能侦听到生命周期中的错误。并不包含 setup 语法中的任意错误。

// plugins/index.ts
import type { NuxtApp } from '#app';

export default defineNuxtPlugin((nuxtApp: NuxtApp) => {
  // err: 错误信息
  // context: vue 实例
  nuxtApp.vueApp.config.errorHandler = (err, context) => {
    console.log(err, context);
  }
  // err: 错误信息
  // instance: vue 实例
  // info: 具体报错的钩子名称
  nuxtApp.hook('vue:error', (err, instance, info) => {
    console.log(err, instance, info);
  });
});
复制代码
<!-- app.vue -->
<script setup lang="ts">
// 该方式与 vue:error 参数一致
onErrorCaptured((err, instance, info) => {
  console.log(err, instance, info)
})
</script>
复制代码

触发的优先级为 onErrorCaptued -> errorHandler -> hook(vue:error, cb)

同样还提供了一个捕获 nuxt 错误的钩子 nuxtApp.hook('app:error', cb)

import type { NuxtApp } from '#app';
export default defineNuxtPlugin((nuxtApp: NuxtApp) => {
  nuxtApp.hook('app:error', err => {
    console.log(err);
  });
});

复制代码

错误页面

可以通过声明全局的错误页面来捕获那些程序中的致命错误。 error.vue 应当声明在页面的根目录下,与 app.vue 平级。

error 页面中可以使用 clearError 方法重定向到安全的页面。

<template>
  <button @click="handleError">Clear errors</button>
</template>

<script setup>
const props = defineProps({
  error: Object
})
const handleError = () => clearError({ redirect: '/' })
</script>
复制代码

clearError 相对应还提供了两个抛出错误的方法。createErrorshowError

  • crateError 创建一个带有元信息的错误对象,在服务端会抛出致命错误,客户端则通过配置 fatal 是否为 true 来进行决定。
    if (!data.value) {
      throw createError({ statusCode: 404, message: '找不到页面', statusMessage: 'Not Fount' });
    }
    复制代码
  • showError 直接抛出一个致命错误。使其进入错误页面
    if (!data.value) {
      showError({ statusCode: 404, message: '找不到页面', statusMessage: 'Not Fount' });
    }
    复制代码

loading bar 定制

nuxt 提供了一个 <NuxtLoadingIndicator /> 组件 可以根据此组件扩展 loading 效果

<script lang="ts" setup>
const show = ref(true)
</script>

<template>
  <NuxtLayout>
    <NuxtLoadingIndicator
      :height="0"
    >
      <van-overlay :show="show" @click="show = false">
        <div class="wrapper" @click.stop>
          <van-loading type="spinner" color="#1989fa" />
        </div>
      </van-overlay>
    </NuxtLoadingIndicator>
    <NuxtPage />
  </NuxtLayout>
</template>

<style lang="scss">
.wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}
</style>

复制代码

结语

Nuxt3 基于 Vue 而衍生出来的 SSR 框架,相信熟悉 Vue3 的小伙伴都不会很难。

更多的社区插件集成方案参考:nuxt.com/modules

移动端 nuxt 项目最佳实践参考:

猜你喜欢

转载自juejin.im/post/7186596767591301177