Nuxtjs基本使用
起步
安装
npx nuxi@latest init <project-name>
后面跟着提示走就行
最后yarn run dev 启动项目访问localhost:3000
即可
路由组件
app.vue
为项目根组件
<nuxt-page />
为路由显示入口
将app.vue更改内容如下
<template>
<div>
<h1>根组件页面</h1>
<nuxt-page/>
</div>
</template>
**根目录下创建pages/about.vue组件,对应的路由地址就是/about
,内容如下
<template>
<div class="">
我是about.vue组件
</div>
</template>
访问localhost:3000
此时会404,因为这时候的这个路径会去找pages/index.vue
了,找不到就404,访问localhost:3000/about
效果如下:
父子路由(页面嵌套,二级路由显示)
父级页面
在pages下创建roles.vue 对应路由地址为/roles 需要重启
<template>
<div>
<h1>父页面</h1>
<!-- 渲染子组件 -->
<nuxt-page/>
</div>
</template>
子级页面
在pages下创建roles文件夹里面存放子级页面
注意:子级页面文件夹名称和父级文件名一一对应
- about.vue
- myInio.vue
- setting.vue
<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根组件进行演示跳转了,内容如下
<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值为动态的
// 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.vue
和son.vue
子组件son.vue
<template>
<div>
我是子组件
</div>
</template>
父组件father.vue
<template>
我是父组件
<SON></SON>
</template>
<script setup>
import SON from './son.vue' // 引入子组件
</script>
全局组件
在根目录下创建components
文件夹,新建aaa.vue
在根目录下创建components/user
文件夹,新建bbb.vue
<template>
我是全局组件
</template>
全局组件可以在项目任意地方直接使用,无需手动导入
如下使用示例任意页面直接用即可
<template>
<aaa></aaa>
<user-bbb></user-bbb>
// <UserBbb></UserBbb> 这样驼峰命名也可以
</template>
布局处理
在根目录components下面新建AppHearder.vue
组件,内容如下:
<template>
<div>
头部布局
</div>
</template>
在根目录components下面新建AppAside.vue
组件,内容如下:
<template>
<div>
侧边栏组件
</div>
</template>
在根目录components下面新建AppFooter.vue
组件,内容如下:
<template>
<div>
底部组件
</div>
</template>
在根组件app.vue
作为整体布局组件,内容改造如下
<template>
<AppHearder />
<AppAside />
<!-- 在这里nuxt-page写主题内容 -->
<nuxt-page />
<AppFooter />
</template>
SEO配置
全局配置
在nuxt.config.ts
里面新增app节点,内容如下:
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节点更详细的参数如下
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代码如下:
<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信息
我们也可以使用原生方式写,这里把原生标签都做成了组件供我们使用,如下页面展示
<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进行动态标题设置,这个就代表子页面如果有标题则在子页面标题后面拼写一个萧寂
否则就只显示萧寂
<template>
<!-- 在这里写主题内容 -->
<nuxt-page />
</template>
<script setup lang="ts">
useHead({
titleTemplate: (titleChunk) => {
// 判断子页面是否有标题,动态展示标题
return titleChunk ? `${titleChunk} - 萧寂` : '萧寂';
}
})
</script>
例如某个子页面标题如下
<template>这是首页</template>
<script lang="ts" setup>
useHead({
title: '我是'
})
</script>
此时页面展示效果如下
静态资源的访问
在nuxtjs中,静态资源文件夹共有两种assets
和public
例如在根目录下的assets
和public
文件夹下面都新建一个img文件夹,有个vue.jpg
则两个文件夹内的vue.jpg访问路径分别如下
assets
: ~/assets/img/vue.jpg
public
: /img/vue.jpg
环境变量配置
配置文件配置
找到nuxt.config.ts
,里面新增如下
export default defineNuxtConfig({
runtimeConfig: {
// 这里的所有数据可以被服务端所访问到,不能被客户端访问
apiSecret: '123',
public: {
// public节点下面的数据可以被服务端和客户端访问
apiBase: '/api'
}
}
})
上面数据的使用方法如下
任意一个页面文件内加上下面代码
<template>这是首页</template>
<script lang="ts" setup>
const runtimeConfig = useRuntimeConfig();
console.log(runtimeConfig.apiSecret);
console.log(runtimeConfig.public.apiBase);
</script>
客户端打印如下:
服务器打印如下:
.env文件配置
根目录下新建.env文件,内容如下(也是创建跟上面一样的两个变量)
这个访问方式跟上面一样,这个优先级大于上面配置里面的同级变量优先级
NUXT_API_SECRET = 123
NUXT_PUBLIC_APIBASE = '/api'
数据获取
这里大家可以自行做个后端接口查看axios和官方内置请求的区别
自行做接口在后端进行打印数据,最好做个累加操作
可以发现axios发请求,每次服务端会打印两次数据
因为axios在服务端被请求了一次,在客户端又被请求了一次,因此axios每次请求相当于请求了两次数据
因此这里推荐大家使用官方内置的hooks函数进行请求
useFetch
使用方式如下页面代码,默认git请求
<template>这是首页</template>
<script lang="ts" setup>
useFetch("https://fts.jd.com/area/get?fid=-1").then((res) => {
console.log(res);
});
</script>
详细使用方法
<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
<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调用后可以取消上一次的请求
<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
改造如下所示
<template>
<!-- 在这里写主题内容 -->
<nuxt-link to="/">用户首页</nuxt-link>
<nuxt-link to="/about">关于页面</nuxt-link>
<nuxt-page />
</template>
我们在用户首页做发请求数据,我们先定位到about页面 用户首页代码如下
<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发请求
<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基本也没啥区别非要看的话自己官网扒拉扒拉
请求拦截器响应拦截器
<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
文件,内容如下
export default defineEventHandler((event) => {
// return的对象就是返回给前端的数据
return {
hello: 'world'
}
})
任意页面访问
<script setup lang="ts">
const { data } = await useFetch('/api/hello')
</script>
<template>
<pre>{{ data }}</pre>
</template>
这时发现页面上面就显示了我们请求得到的return的数据
返回字符串
export default defineEventHandler((event) => {
// 下面这种方法返回到前端的字符串
event.node.res.end('我是返回给前端的数据')
})
不要api前缀的
根目录下server/routes/hello.ts
在routes文件夹下面的文件不用带有api前缀,如下
export default defineEventHandler(() => 'Hello World!')
页面访问
`<script setup lang="ts">
const { data } = await useFetch("/hello");
</script>
<template>
<pre>{{ data }}</pre>
</template>
动态参数
在server/api/hello/[name].ts
加入以下内容,name就是动态参数
export default defineEventHandler((event) => {
const name = event.context.params.name; // 获取到动态参数
// 解码 name 以防乱码
const decodedName = decodeURIComponent(name);
return decodedName;
});
页面访问
<script setup lang="ts">
const { data } = await useFetch("/api/hello/萧寂");
</script>
<template>
<pre>{{ data }}</pre>
</template>
查询字符串参数
根目录下创建server/api/hello.ts
文件,内容如下
export default defineEventHandler((event) => {
const query = getQuery(event); // 获取到动态参数
return {
a:query.name,
b:query.age
};
});
页面访问
<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
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// 可以打印看看body就是参数列表
return { body }
})
页面访问
<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
文件内加入以下代码
export default defineEventHandler(() => '未匹配到正确的接口')
页面访问一个不存在的接口,如下
<script setup lang="ts">
const { data } = await useFetch("/api/xxxxxx");
</script>
<template>
{{ data }}
</template>
代理请求思路(跨域解决)
在服务器端请求别人的接口出现跨域问题时,我们可以在项目里面写个后端接口这个接口访问别人的跨域的接口,然后将数据返回回来,我们在前端再次请求自己写的这个接口就不会有问题了,因为跨域发生在浏览器端,后端不存在跨域,可以利用这个小漏洞实现跨域解决
状态管理
方式一
在项目根目录下创建composables
文件夹,里面随便创建一个xxx.ts
export const useCounter = () => useState("counter", () => 0); // 初始数据为0
页面访问和修改数据
<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)
下面是个人总结的使用步骤
安装
yarn add @pinia/nuxt
在nuxt.config.ts
配置如下
// Nuxt 3
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
})
在根目录下创建store/index.ts,内容如下:
// 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里面
<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>
区分服务端和客户端
process.server // 为true则是服务端,false则为客户端
process.client // 为true则是客户端,false则为服务端
自动导入
无需手动导入的方法
- coomponent文件夹里面的组件无需手动导入,直接在代码里面写标签引入即可
- 状态管理的composables文件夹下面的状态管理的函数导出后使用时无序导入,直接使用即可
- utils文件夹下面的导出的函数也无需手动导入即可使用
- 上述讲到的四种获取数据的函数无需手动导入即可使用
- use开头的hooks函数等无需手动导入即可使用
- vue相关的方法,如ref,computed以及生命周期钩子等等无需手动导入即可使用
- 如vue-route路由这种工具也无需手动导入
非要想自动导入,可以使用下面的方法
<script setup lang="ts">
import { ref, computed } from '#imports'
const count = ref(1)
const double = computed(() => count.value * 2)
</script>
关闭自动导入,在nuxt.config.ts
进行如下配置
export default defineNuxtConfig({
imports: {
autoImport: false
}
})
除了上述文件,单独配置某个文件期望它自动导入的方法(自定义的文件)
例如这里让pinia的store目录被自动导入,就可以按照如下设置
imports: {
// Auto-import pinia stores defined in `~/stores`
dirs: ['stores']
}
打包
打包
yarn run build
执行打包后的项目
node .output/server/index.mj
预渲染SSG
yarn run generate
执行上述命令后根目录会生成一个.output文件,运行里面的public/index.html即可看到生成的静态页面(默认动态参数的文件不参与SSG预渲染,可以进行配置,这里我就不写了)
使用element-plus
安装
yarn add @element-plus/nuxt -D
配置
export default defineNuxtConfig({
modules: [
'@element-plus/nuxt'
],
elementPlus: { /** Options */ }
})
使用
<template>
<el-button @click="ElMessage('hello')">button</el-button>
<ElButton :icon="ElIconEditPen" type="success">button</ElButton>
<LazyElButton type="warning">lazy button</LazyElButton>
</template>