浅谈——关于Nuxt3+Vue3的基础使用

794 阅读5分钟

nuxt.config.ts的配置

1. 配置应用标题和图标

export default ({
  app: {
    head: {
      title: '我是nuxt应用', // 应用的默认标题
      charset: "utf-8", // 字符集
      htmlAttrs: {
        lang: "zh-CN", // HTML的语言属性为“zh-CN”(中文)
      },
      // 修改页签图标
      link: [
        {
          rel: 'icon',
          type: 'image/x-icon',
          href: '/favicon.png', // 该图片文件的放置在根目录下的public下
        },
      ],
    },
  },
  ...
});

应用的头部配置需要放置在app:{}中,

2. 配置渲染方式

服务端渲染
export default ({
 ssr: true
  ...
});

客户端渲染
export default ({
 ssr: false
  ...
});

混合渲染
export default ({
 // ssr: false 这里可以不用写
   routeRules: {
    '/': { ssr: true }, // 开启服务端渲染
    '/route1': { ssr: false }, // 仅客户端渲染
    '/route2': { ssr: false }, // 仅客户端渲染
    '/route1/*': { ssr: false },// 仅客户端渲染
    '/route2/*': { ssr: false },// 仅客户端渲染
  },
  ...
});

3. 配置端口

export default ({
 devServer: {
  port: 8080, //配置端口为8080
},
 server: {
  port: 3000,
  host: '0.0.0.0', //指定应用在生产环境中可被访问的主机地址。设置为 '0.0.0.0' 表示应用将接受来自任何 IP 地址的请求。
  timing: false, //用于启用或禁用服务器端请求计时,这对于调试和性能监控可能有用。
},
...
});

server 配置是针对生产环境的,而 devServer 配置是针对开发环境的。

4. 配置开发代理--网关配置

nuxt3的代理需要在nitro里面进行配置

export default ({
  nitro: {
    devProxy: {
      "/api": {
        target: "http://xxx.xxx.x.xxx:xxxx", // 后端的接口地址
        changeOrigin: true,
        prependPath: true,
      },
    },
    routeRules: {
      '/api/**': {
        proxy: 'http://xxx.xxx.x.xxx:xxxx/**'
      }
    }
  },
  ...
});

注意routeRules后面还有/**,这是必不可少的,devProxy 是客户端渲染的,而 routeRules 是针对的服务端渲染的。

5. 多环境开发配置

假设我们有多个环境,如dev、sit环境,这里采用的是通过命令行传参,定义构建的是哪个环境,并在项目里接收参数设置环境变量。

在根目录下新建一个文件,这里叫作build.js,进行参数的接收

// build.js

// 解析命令行参数

//这里是获取构建命令 --env=环境,注意,我们通过process.env.npm_config_argv获取的对象是字符串的形式,所以需要通过JSON.parse进行转换
const envArg = JSON.parse(process.env.npm_config_argv)?.original[1];  

//sit
const env = envArg ? envArg.split('=')[1] : 'dev';  //

// 设置环境变量
process.env.NUXT_ENV_MODE = env;

// 接下来,调用 Nuxt 构建命令或其他需要的脚本
console.log(`正在以 ${env} 模式构建...`);

在nuxt.config.ts文件引用,并且修改nitro routeRules的proxy

import './build.js';
...

function getEnv(env: any) {
  let api = '';
    switch (env) {
    case 'dev':
      api = 'http://dev-api/**';
      break;

    case sit:
      api = 'http://sit-api/**';
      break;
   
  }
  
  return api;
}
...

export default ({
  nitro: {
    routeRules: {
      '/api/**': {
        proxy: getEnv(process.env.NUXT_ENV_MODE)
      }
    }
  },
  ...
});

规定构建命令传参方式(“--env=环境变量”是我们的参数):

//构建命令 终端执行
yarn build --env=环境变量

//例子——构建sit环境
yarn build --env=sit  

这时候执行构建命令,便可以构建出相应的环境了

更多关于nuxt.config.ts的配置可参考:ezdoc.cn/docs/nuxtjs…

插件的安装并全局引入

ant-design-vue

除了使用yarnnpm等命令安装ant-design之外,需要在根目录创建一个plugins文件夹(有就不用创建了)创建一个文件(这里用的是ts)antd.ts

// plugins/antd.ts
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.less';
import { defineNuxtPlugin } from 'nuxt/app';

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(Antd);
});

nuxt.config.ts配置

export default ({
  plugins: [
    '@/plugins/antd.ts',
    '@/plugins/echarts.ts',
    { src: '@/plugins/pdf.ts', ssr: false, mode: client }
  ],
  ...
});

类似这样的依赖库需要在plugins文件夹下创建文件全局引入,并在nuxt.config.ts,但类似less、typescript等插件或扩展类的直接使用命令安装相应的版本就可以了。

接口的封装

在nuxt中,我们可以使用useAsyncData或useFetch进行接口的调用,在这里我采用了二者的接口,使用useFetch进行接口的调用,useAsyncData对useFetch获取的数据进行处理。

1. useFetch的封装

import { type UseFetchOptions } from "nuxt/app";

export interface IResultData {
  data?: any;
  code?: number;
  message?: string;
  success?: boolean;
}

// 封装请求函数
async function request(
  url: string,
  option?: UseFetchOptions<IResultData>,
): Promise<Partial<IResultData>> {
  if (process.client) {
    await nextTick(); //解决刷新页面useFetch无返回
  }

  const { data } = await useFetch<IResultData>(url, {
    onRequest({ options }) {
      // 设置请求头
      options.headers = {....};
      return options;
    },
    // 请求拦截error
    onRequestError({ response }) {
      throw new Error(response);
    },

    // 响应拦截
    onResponse({ response }) {
      const { code, message } = response._data;
      if (code !== SUCCESS_CODE) {
        throw new Error(message);
      }
      return response._data;

    },
    ...option
  }

  );
  return data.value || {};
}


// 封装常用方法
export function get(url: string, params?: any, options?: UseFetchOptions<IResultData>) {
  return request(url, { method: "GET", params, ...options });
}

export function post(url: string, params: any, options?: UseFetchOptions<IResultData>) {
  return request(url, { method: "POST", body: params, ...options });
}

export function put(url: string, params: any, options?: UseFetchOptions<IResultData>) {
  return request(url, { method: "PUT", body: params, ...options });
}

export function del(url: string, params: any, options?: UseFetchOptions<IResultData>) {
  return request(url, { method: "DELETE", body: params, ...options });
}

2. useAsyncData的使用

useAsyncData('get-data', async () => {
    const results = await Promise.allSettled([api1(), api1()]);
    results?.forEach((result, index) => {
        if (result.status === 'fulfilled') {
            switch (index) {
                case 0:
                   const data1 = result.value.data;
                   break;
                case 1:
                   const data2 = result.value.data;
                   break;
            }
        } else {
            console.error(`Request ${index} failed:`, result.reason);
        }
    });
});

当需要执行并行和独立的异步操作并收集所有结果时,Promise.allSettled() 就是不错的选择,即使一些异步操作可能失败。

如果不需要执行多个接口:

useAsyncData('get-data-2', async () => {
    const res = await api()
    console.log(res)
});

注意:如果在同一个页面中使用useAsyncData执行多个相同的接口(但参数不一样)会导致最终的数据都会被最后调用的那个接口的数据覆盖。解决办法,不使用useAsyncData执行,采用常见的项目的接口调用方法。

路由生成

nuxt的项目会对在pages目录下的文件自动生成路由,不需要自行配置,除components文件夹外;components文件夹下的文件不会被nuxt处理。

Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。假设 pages 的目录结构如下:

pages/
--| custompage/
-----| index.vue
--| index.vue

那么,Nuxt.js 自动生成的路由配置如下:

router: {
  routes: [
    {
      name'index',
      path'/',
      component'pages/index.vue'
    },
    {
      name'custompage',
      path'/custompage',
      component'pages/custompage/index.vue'
    }
  ]
}

layout布局

在根目录下的layouts文件夹下的存储的是页面的布局,命名为default的文件会成为每一个页面级的默认布局。例子:

<template>
  <CHeader />
    <div>
      <slot></slot>
    </div>
  <CFooter />
</template>

<script lang="ts">
import CHeader from '@/components/common/CHeader.vue';
import CFooter from '@/components/common/CFooter.vue';
export default defineComponent({
	name: 'layout',
	components: { CHeader, CFooter},
});
</script>

在app.vue使用

<template>
    <div id="app">
        <NuxtLayout>
            <NuxtPage></NuxtPage>
        </NuxtLayout>
    </div>
</template>

如果某个页面不需要使用这个布局

<script lang="ts">
export default defineComponent({
    name: 'xxxx',
    setup() {
        //ui布局
        definePageMeta({
            layout: false,
        });
    },
});
</script>

使用其他布局则把layouts文件夹下的文件名赋值给definePageMeta的的layout属性就好:

<script lang="ts">
export default defineComponent({
    name: 'xxxx',
    setup() {
        //ui布局
        definePageMeta({
            layout: '其他布局的文件名',
        });
    },
});
</script>

编码常见

1. <ClientOnly>标签

在服务端渲染的页面中,如果存在部分组件需要进行客户端渲染可使用该标签进行包裹,如:

<ClientOnly>
    <我是客户端渲染的组件/>
</ClientOnly>
2. 只能在客户端渲染代码

在Nuxt 3中,有些行为或代码只适用于客户端(浏览器环境),在服务端渲染(SSR)环境中执行时会报错。这通常是因为某些对象或功能在Node.js环境中不存在,但在浏览器中是可用的。以下是一些在服务端渲染中可能导致错误的常见情况:

  1. 访问浏览器特定的全局对象:例如 windowdocumentlocalStoragesessionStorage。这些对象只在浏览器环境中存在,在服务端渲染中使用它们会导致 ReferenceError
  2. 直接操作DOM:任何直接操作DOM的代码,如 document.getElementById 或使用 querySelector,在服务端都会失败,因为在Node.js环境中没有DOM。
  3. 依赖于浏览器API的功能:例如 FileReaderWebSocketCanvas API,这些只在浏览器中定义。
  4. 客户端特定的事件处理:比如监听滚动事件、点击事件等。这些在服务端没有相应的环境来触发它们。
  5. 使用浏览器导航和位置接口:如使用 window.locationhistory API来处理URL和导航。

如果需要进行上述行为,我们可以采取以下措施

  • 使用 process.clientprocess.server 来判断当前代码是在客户端还是服务端执行,并据此调整行为。
  • 将依赖于客户端API的代码放在Vue的生命周期钩子 mounted 中,因为 mounted 只在客户端调用。
  • 对于客户端专用的功能或组件,可以使用动态导入(dynamic import)并结合 ssr: false 来避免在服务端加载它们。
  • 使用<ClientOnly>标签对使用上述代码的组件进行包裹。