Skip to content
Nuxtjs基本使用

起步

官方文档

官方目录结构

安装

js
npx nuxi@latest init <project-name>

后面跟着提示走就行

最后yarn run dev 启动项目访问localhost:3000即可

路由组件

app.vue为项目根组件

<nuxt-page />为路由显示入口

将app.vue更改内容如下

js
<template>
  <div>
    <h1>根组件页面</h1>
    <nuxt-page/>
  </div>
</template>

**根目录下创建pages/about.vue组件,对应的路由地址就是/about,内容如下

js
<template>
    <div class="">
        我是about.vue组件
    </div>
</template>

访问localhost:3000此时会404,因为这时候的这个路径会去找pages/index.vue了,找不到就404,访问localhost:3000/about效果如下:

在这里插入图片描述

父子路由(页面嵌套,二级路由显示)

父级页面

在pages下创建roles.vue 对应路由地址为/roles 需要重启

html
<template>
  <div>
    <h1>父页面</h1>
    <!-- 渲染子组件 -->
    <nuxt-page/>  
  </div>
</template>

子级页面

在pages下创建roles文件夹里面存放子级页面

注意:子级页面文件夹名称和父级文件名一一对应

  • about.vue
  • myInio.vue
  • setting.vue
ts
<template>
    我是about页面
</template>

<template>
    我是myInIo页面
</template>

<template>
    我是setting页面
</template>

分别访问

  • localhost:3000/roles
  • localhost:3000/roles/about
  • localhost:3000/roles/myInIo
  • localhost:3000/roles/setting

效果图

在这里插入图片描述

导航跳转

基础演示

在app.vue根组件进行演示跳转了,内容如下

js
<template>
  <div>
    <h1>根组件页面</h1>
    <nuxt-link to="/">首页</nuxt-link>
    <nuxt-link to="/about">about页面</nuxt-link>
    <nuxt-link to="/roles/setting">设置页面</nuxt-link>
    <nuxt-link to="/course/22">课程页面</nuxt-link>
    <nuxt-page />
  </div>
</template>

在pages下新建course目录,新建[id].vue文件,这里用的就是动态路由的形式,id值为动态的

js
// route.params.id这个就是id的值,也就是文件名
<template>课程id为{{ route.params.id }}</template>

<script setup>
import { useRoute } from "vue-router";

const route = useRoute();
console.log(route);
</script>

组件使用

在pages下面新建father.vueson.vue

子组件son.vue

js
<template>
    <div>
        我是子组件
    </div>
</template>

父组件father.vue

js
<template>
    我是父组件
    <SON></SON>
</template>

<script setup>
import SON from './son.vue'  // 引入子组件
</script>

全局组件

在根目录下创建components文件夹,新建aaa.vue 在根目录下创建components/user文件夹,新建bbb.vue

js
<template>
    我是全局组件
</template>

全局组件可以在项目任意地方直接使用,无需手动导入

如下使用示例任意页面直接用即可

js
<template>
    <aaa></aaa>
    <user-bbb></user-bbb>
    // <UserBbb></UserBbb>  这样驼峰命名也可以
</template>

布局处理

在根目录components下面新建AppHearder.vue组件,内容如下:

js
<template>
    <div>
  	  头部布局
    </div>
</template>

在根目录components下面新建AppAside.vue组件,内容如下:

js
<template>
    <div>
        侧边栏组件
    </div>
</template>

在根目录components下面新建AppFooter.vue组件,内容如下:

js
<template>
    <div>
        底部组件
    </div>
</template>

在根组件app.vue作为整体布局组件,内容改造如下

js
<template>
  <AppHearder />
  <AppAside />
  <!-- 在这里nuxt-page写主题内容 -->
  <nuxt-page />
  <AppFooter />
</template>

SEO配置

全局配置

nuxt.config.ts里面新增app节点,内容如下:

js
export default defineNuxtConfig({
  ...
  app: {
    head: {
      title: "网页标题",
      meta: [
        {
          name: "keywords",
          content: "关键词1,关键词2",
        },
        {
          name: "description",
          content: "描述",
        },
      ],
      charset: "utf-8",
      viewport: "width=device-width, initial-scale=1",
    },
  },
});

这样就将seo相关的信息放到网页上了

在这里插入图片描述关于app节点更详细的参数如下

js
app: {
  head: {
    meta: [
      // <meta name="viewport" content="width=device-width, initial-scale=1">
      { name: 'viewport', content: 'width=device-width, initial-scale=1' }
    ],
    script: [
      // <script src="https://myawesome-lib.js"></script>
      { src: 'https://awesome-lib.js' }
    ],
    link: [
      // <link rel="stylesheet" href="https://myawesome-lib.css">
      { rel: 'stylesheet', href: 'https://awesome-lib.css' }
    ],
    // please note that this is an area that is likely to change
    style: [
      // <style type="text/css">:root { color: red }</style>
      { children: ':root { color: red }', type: 'text/css' }
    ],
    noscript: [
      // <noscript>JavaScript is required</noscript>
      { children: 'JavaScript is required' }
    ]
  }
}

上面直接在nuxt.config.ts写的不允许使用响应式数据,如果要加全局的响应式数据可以使用useHead在app.vue进行配置

例如app.vue代码如下:

js
<script setup lang="ts">
useHead({
  title: 'My App',
  meta: [
    { name: 'description', content: 'My amazing site.' }
  ],
  bodyAttrs: {
    class: 'test'
  },
  script: [ { innerHTML: 'console.log(\'Hello world\')' } ]
})
</script>

则会覆盖我们上面的nuxt.config.ts配置的同名的app节点的属性

同理,每个页面都可以使用useHead单独设置当前页面的标题描述等SEO信息

我们也可以使用原生方式写,这里把原生标签都做成了组件供我们使用,如下页面展示

js
<script setup lang="ts">
const title = ref('Hello World')
</script>

<template>
  <div>
    <Head>
      <Title>{{ title }}</Title>
      <Meta name="description" :content="title" />
      <Style type="text/css" children="body { background-color: green; }" ></Style>
    </Head>
    <h1>{{ title }}</h1>
  </div>
</template>

动态标题

app.vue使用useHead的titleTemplate进行动态标题设置,这个就代表子页面如果有标题则在子页面标题后面拼写一个萧寂 否则就只显示萧寂

js
<template>
  <!-- 在这里写主题内容 -->
  <nuxt-page />
</template>
<script setup lang="ts">
useHead({
  titleTemplate: (titleChunk) => {
  	// 判断子页面是否有标题,动态展示标题
    return titleChunk ? `${titleChunk} - 萧寂` : '萧寂';
  }
})
</script>

例如某个子页面标题如下

js
<template>这是首页</template>
<script lang="ts" setup>
useHead({
  title: '我是'
})
</script>

此时页面展示效果如下

在这里插入图片描述

静态资源的访问

在nuxtjs中,静态资源文件夹共有两种assetspublic

例如在根目录下的assetspublic文件夹下面都新建一个img文件夹,有个vue.jpg

则两个文件夹内的vue.jpg访问路径分别如下

assets: ~/assets/img/vue.jpg

public: /img/vue.jpg

相关的官网文档

环境变量配置

配置文件配置

找到nuxt.config.ts,里面新增如下

js
export default defineNuxtConfig({
  runtimeConfig: {
    // 这里的所有数据可以被服务端所访问到,不能被客户端访问
    apiSecret: '123',
    public: {
      // public节点下面的数据可以被服务端和客户端访问
      apiBase: '/api'
    }
  }
})

上面数据的使用方法如下

任意一个页面文件内加上下面代码

js
<template>这是首页</template>
<script lang="ts" setup>
const runtimeConfig = useRuntimeConfig();
console.log(runtimeConfig.apiSecret);
console.log(runtimeConfig.public.apiBase);
</script>

客户端打印如下:

在这里插入图片描述服务器打印如下:

在这里插入图片描述

.env文件配置

根目录下新建.env文件,内容如下(也是创建跟上面一样的两个变量)

这个访问方式跟上面一样,这个优先级大于上面配置里面的同级变量优先级

js
NUXT_API_SECRET = 123 
NUXT_PUBLIC_APIBASE = '/api'

数据获取

这里大家可以自行做个后端接口查看axios和官方内置请求的区别

自行做接口在后端进行打印数据,最好做个累加操作

可以发现axios发请求,每次服务端会打印两次数据

因为axios在服务端被请求了一次,在客户端又被请求了一次,因此axios每次请求相当于请求了两次数据

因此这里推荐大家使用官方内置的hooks函数进行请求

useFetch

使用方式如下页面代码,默认git请求

js
<template>这是首页</template>
<script lang="ts" setup>
useFetch("https://fts.jd.com/area/get?fid=-1").then((res) => {
  console.log(res);
});
</script>

详细使用方法

js
<template>这是首页</template>
<script lang="ts" setup>
useFetch("/posts", {
  method: "GET", // 请求方式
  params: { id: 3 }, // 传递参数
  baseURL: "https://jsonplaceholder.typicode.com", // 请求地址
}).then((res) => {
  console.log(res);
});
</script>

useAsyncData

跟上面使用方法没区别,就多了一个说明而已,相当于一个唯一的key

js
<template>这是首页</template>
<script lang="ts" setup>
useAsyncData("获取文章", () =>
  $fetch("/posts", {
    method: "GET", // 请求方式
    baseURL: "https://jsonplaceholder.typicode.com", // 请求地址
  })
).then((res) => {
  console.log(res);
});
</script>

刷新数据

如果要手动获取或刷新数据,请使用 组合项提供的 or 函数。execute refresh

例如下面案例:点击按钮重新请求数据(或者做分页时页码加1后重新刷新数据)

refresh调用后可以取消上一次的请求

js
<script setup lang="ts">
const { data, error, execute, refresh, pending } = await useFetch('/api/users')
</script>

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="() => refresh()">Refresh data</button>
  </div>
</template>

返回值共有

  • data:请求的结果
  • pending:一个布尔值,指示是否仍在获取数据
  • refresh/execute:可用于刷新处理程序函数返回的数据的函数
  • error: 如果数据获取失败,则返回错误对象

useLazyFetch

app.vue改造如下所示

js
<template>
  <!-- 在这里写主题内容 -->
  <nuxt-link to="/">用户首页</nuxt-link>
  <nuxt-link to="/about">关于页面</nuxt-link>
  <nuxt-page />
</template>

我们在用户首页做发请求数据,我们先定位到about页面 用户首页代码如下

js
<template>这是首页,数据为{{ data }}</template>
<script lang="ts" setup>
const { data } = await useFetch("/posts", {
  method: "GET", // 请求方式
  params: { id: 3 }, // 传递参数
  baseURL: "https://jsonplaceholder.typicode.com", // 请求地址
});
</script>

将浏览器网速调整到慢速3G,可以发现,数据获取不到是跳转不到首页显示不了内容的,也就是必须有数据才能跳转过来

更换为useLazyFetch发请求

js
<template>这是首页,数据为{{ data }}</template>
<script lang="ts" setup>
const { data } = await useLazyFetch("/posts", {
  method: "GET", // 请求方式
  params: { id: 3 }, // 传递参数
  baseURL: "https://jsonplaceholder.typicode.com", // 请求地址
});
</script>

发现当数据未获取到时也会先跳转到这个页面,先显示上面的文字,等数据加载到才显示数据,也就是无数据也能正常跳转页面

useLazyAsyncData

与useAsyncData的区别就如同useFetch和useLazyFetch的区别一样

useAsyncData与useFetch又区别不大

所以useLazyAsyncData与useLazyFetch基本也没啥区别非要看的话自己官网扒拉扒拉

请求拦截器响应拦截器

js
<template>这是首页,数据为{{ data }}</template>
<script setup>
const { data } = await useLazyFetch("/posts", {
  method: "GET", // 请求方式
  params: { id: 3 }, // 传递参数
  baseURL: "https://jsonplaceholder.typicode.com", // 请求地址
  onRequest({ request, options }) {
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.authorization = "123456";
    console.log("请求前", options);
  },
  onResponse({ request, response, options }) {
    console.log("请求后", options);
  },
  onRequestError(error) {
    console.log("请求错误", error);
  },
  onResponseError(error) {
    console.log("响应错误", error);
  },
});
</script>

cookie和token处理思路

参考此视频

服务器接口编写

带有api前缀的

简单创建和访问

创建

根目录下创建server/api/hello.ts文件,内容如下

js
export default defineEventHandler((event) => {
  // return的对象就是返回给前端的数据
  return {
    hello: 'world'
  }
})

任意页面访问

js
<script setup lang="ts">
const { data } = await useFetch('/api/hello')
</script>

<template>
  <pre>{{ data }}</pre>
</template>

这时发现页面上面就显示了我们请求得到的return的数据

返回字符串

js
export default defineEventHandler((event) => {
  // 下面这种方法返回到前端的字符串
  event.node.res.end('我是返回给前端的数据')
})

不要api前缀的

根目录下server/routes/hello.ts在routes文件夹下面的文件不用带有api前缀,如下

js
export default defineEventHandler(() => 'Hello World!')

页面访问

js
`<script setup lang="ts">
const { data } = await useFetch("/hello");
</script>

<template>
  <pre>{{ data }}</pre>
</template>

动态参数

server/api/hello/[name].ts加入以下内容,name就是动态参数

js
export default defineEventHandler((event) => {
  const name = event.context.params.name; // 获取到动态参数
  // 解码 name 以防乱码
  const decodedName = decodeURIComponent(name);
  return decodedName;
});

页面访问

js
<script setup lang="ts">
const { data } = await useFetch("/api/hello/萧寂");
</script>

<template>
  <pre>{{ data }}</pre>
</template>

查询字符串参数

根目录下创建server/api/hello.ts文件,内容如下

js
export default defineEventHandler((event) => {
  const query = getQuery(event); // 获取到动态参数
  return {
	 a:query.name,
	 b:query.age
   };
});

页面访问

js
<script setup lang="ts">
const { data } = await useFetch("/api/hello?name=张三&age=18");
</script>

<template>
  <pre>{{ data }}</pre>
</template>

创建不同的请求

get,post,put,delete等请求方式创建

例如创建post请求,创建文件不再是hello.ts了,而是hello.post.ts

例如创建get请求,创建文件不再是hello.ts了,而是hello.get.ts(但是不写的话默认也是get请求)

示例代码,获取到请求体所有参数并返回出去

sever/api/submit.post.ts

js
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  // 可以打印看看body就是参数列表
  return { body }
})

页面访问

js
<script setup lang="ts">
const body = await useFetch("/api/submit", {
  method: "post",
  body: { test: 123 },
});
</script>
<template>
  <pre>
      {{ body.data.value.body.test }}
  </pre>
</template>

未匹配到正确的api的接口,这个接口主要返回未匹配到正确的接口

server/api/[...].ts文件内加入以下代码

js
export default defineEventHandler(() => '未匹配到正确的接口')

页面访问一个不存在的接口,如下

js
<script setup lang="ts">
const { data } = await useFetch("/api/xxxxxx");
</script>
<template>
  {{ data }}
</template>

代理请求思路(跨域解决)

在服务器端请求别人的接口出现跨域问题时,我们可以在项目里面写个后端接口这个接口访问别人的跨域的接口,然后将数据返回回来,我们在前端再次请求自己写的这个接口就不会有问题了,因为跨域发生在浏览器端,后端不存在跨域,可以利用这个小漏洞实现跨域解决

状态管理

方式一

在项目根目录下创建composables文件夹,里面随便创建一个xxx.ts

js
export const useCounter = () => useState("counter", () => 0); // 初始数据为0

页面访问和修改数据

js
<template>
  这是首页{{ count }}值
  <button @click="count++">count+1</button>
  <button @click="addCounter">count+2</button>
</template>
<script setup>
const count = useCounter();
// 给count值加2
const addCounter = () => {
  count.value += 2;
};
</script>

所有页面都可以通过useCounter()访问到数据,并且都是全局共享的数据

方式二(使用pinia)

官方的pinia的示例代码官方文档

下面是个人总结的使用步骤

安装

js
yarn add @pinia/nuxt

nuxt.config.ts配置如下

js
// Nuxt 3
export default defineNuxtConfig({
    modules: ['@pinia/nuxt'],
})

在根目录下创建store/index.ts,内容如下:

js
// count1 是 id 须唯一
// options方法
export const useCountStore = defineStore("count1", {
  // 在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们APP 的 state。
  // 在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得Pinia 可以同时支持服务端和客户端。
  state: () => {
    // 类似vue2的 data,data(){return{}} 防止服务端数据污染;----推荐使用箭头函数,为了完整类型推理;
    return {
      // 所有属性将自动推断出他们的类型
      counter: 0,
    };
  },
  persist: {
    // 持久化
    enabled: true,
  },
  getters: {
    // 相当于computed 计算属性
    doubleCount(state) {
      return state.counter * 2;
    },
  },
  actions: {
    // 相当于methods
    addCounter() {
      // this指向对应的store仓库
      this.counter += 1;
    },
  },
});

// count2 是 id 须唯一
// setup方法
export const useCount2Store = defineStore("count2", () => {
  const counter = ref(1); // state
  const gettersCounter = computed(() => {
    return counter.value + 5;
  });
  function addCounter() {
    counter.value += 1;
  }
  return { counter, gettersCounter, addCounter };
});

数据的操作以及展示

在page/index.vue里面

js
<template>
  <div class="container">
    <div>pinia-options</div>
    <div>count1.counter: {{ count1.counter }}</div>
    <div>count1.doubleCount {{ count1.doubleCount }}</div>
    <button @click="count1.addCounter()">count1.counter: addCount+=1</button>
    <button @click="changeCount1()">count1.counter: addCount+=2</button>
    <hr />
    <div>count2.counter: {{ count2.counter }}</div>
    <div>count2.gettersCounter {{ count2.gettersCounter }}</div>
    <button @click="count2.addCounter()">count2.counter: addCount++</button>
    <button @click="changeCount2()">count2.counter: addCount+=5</button>
  </div>
</template>

<script setup lang="ts">
const count1 = useCountStore();
const count2 = useCount2Store();
console.log("count1:", count1);
console.log("count2:", count2);
const changeCount1 = () => {
  // 方式一、需要修改数据时直接修改
  // count1.counter += 2;
  // 方式二、如果需要修改多个,对象里面可以加多个属性,可以使用pinia的$patch方法,推荐使用
  // count1.$patch({
  //   counter: count1.counter += 2
  // })
  // 方式三、对于复杂的方法可以封装actions的函数
  // count1.$patch((state) => {
  //   state.counter = count1.counter += 2;
  // });

  // 方式四、对于复杂的方法可以封装actions的函数
  count1.addCounter();
};
const changeCount2 = () => {
  count2.counter += 2;
};
</script>

<style scoped lang="less"></style>

区分服务端和客户端

js
process.server // 为true则是服务端,false则为客户端
process.client // 为true则是客户端,false则为服务端

自动导入

无需手动导入的方法

  • coomponent文件夹里面的组件无需手动导入,直接在代码里面写标签引入即可
  • 状态管理的composables文件夹下面的状态管理的函数导出后使用时无序导入,直接使用即可
  • utils文件夹下面的导出的函数也无需手动导入即可使用
  • 上述讲到的四种获取数据的函数无需手动导入即可使用
  • use开头的hooks函数等无需手动导入即可使用
  • vue相关的方法,如ref,computed以及生命周期钩子等等无需手动导入即可使用
  • 如vue-route路由这种工具也无需手动导入

非要想自动导入,可以使用下面的方法

js
<script setup lang="ts">
import { ref, computed } from '#imports'
const count = ref(1)
const double = computed(() => count.value * 2)
</script>

关闭自动导入,在nuxt.config.ts进行如下配置

js
export default defineNuxtConfig({
  imports: {
    autoImport: false
  }
})

除了上述文件,单独配置某个文件期望它自动导入的方法(自定义的文件)

例如这里让pinia的store目录被自动导入,就可以按照如下设置

js
imports: {
  // Auto-import pinia stores defined in `~/stores`
  dirs: ['stores']
}

打包

打包

js
yarn run build

执行打包后的项目

js
node .output/server/index.mj

预渲染SSG

js
yarn run generate

执行上述命令后根目录会生成一个.output文件,运行里面的public/index.html即可看到生成的静态页面(默认动态参数的文件不参与SSG预渲染,可以进行配置,这里我就不写了)

使用element-plus

安装

js
yarn add @element-plus/nuxt -D

配置

js
export default defineNuxtConfig({
  modules: [
    '@element-plus/nuxt'
  ],
  elementPlus: { /** Options */ }
})

使用

js
<template>
  <el-button @click="ElMessage('hello')">button</el-button>
  <ElButton :icon="ElIconEditPen" type="success">button</ElButton>
  <LazyElButton type="warning">lazy button</LazyElButton>
</template>