一.项目基础构建
1.vite构建vue
npm init vite@latest
2.src下创建router文件夹,router文件夹创建index.js文件
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
2.src下创建views文件夹,views文件夹创建About.vue、Home.vue两个路由文件
3.安装插件AutoImport(自动导入 Vue 相关函数,如:ref, reactive, toRef 等),并设置可以让@来代替src使用。
npm i unplugin-auto-import -D
vite.config.js写法如下
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import AutoImport from "unplugin-auto-import/vite"; //自动导入 Vue 相关函数,如:ref, reactive, toRef 等
import { resolve } from 'path';
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router']
})
],
define: {
'process.env': {},
},
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
});
二.常用方法介绍
ref:响应式数据,在使用时候需要:str.value
(可以使用volar插件自动添加.value)
let str = reactive('1');
reactive:响应式数据,不需要.value,只能写对象或者数组
let str = reactive(['a','b','c']);
toRefs:结构响应式数据
let obj = reactive({
name: '张三',
age: 20
})
let { name, age } = toRefs(obj)
const btn = () => {
name.value = '李四'
}
computed:计算属性
// 第1种写法
let changeStr = computed(() => {
return str.value;
})
// 第2种写法
let changeStr2 = computed({
get() {
return str.value;
},
set(val) {
str.value = val
}
})
watch:监听事件
// (1)监听第一个
let str = ref('这是第一个数据');
watch(str, (newVal, oldVal) => {
console.log(newVal, oldVal);
})
// (2)同时监听多个(用得不多)-------------------------
let str2 = ref('这是第二个数据');
watch([str, num], (newVal, oldVal) => {
console.log(newVal, oldVal);
})
// (3)初始化监听-------------------------------------
let str3 = ref('这是第三个数据');
watch(num, (newVal, oldVal) => {
console.log(newVal, oldVal);
}, {
immediate: true
})
// (4)监听对象--------------------------------------
watch(obj, (newVal) => {
console.log(newVal);
}, {
immediate: true
})
// (5)监听具体的某一个key值,并且深度监听-----------
let obj = reactive({
a: 1,
b: 2,
m: {
c: 3
}
})
const btn = () => {
obj.a = 'xxx';
}
watch(() => obj.a, (newVal, oldVal) => {
console.log(newVal, oldVal);
}, {
immediate: true,
deep: true
})
// (6)立即执行监听函数------------------------------
let str4 = ref('这是第四个数据');
watchEffect(() => {
console.log(str4.value);
})
// (7)监听路由--------------------------------------
let router = userRouter();
watch(() => router.currentRoute.value, (newVal) => {
console.log(newVal);
}, {
immediate: true
})
生命周期钩子
<template>
首页Home.vue
</template>
<script setup>
import axios from 'axios';
let str = ref("这是数据")
// 请求接口
onBeforeMount(() => {
axios({
url: "http://testapi.xuexiluxian.cn/api/slider/getSliders"
}).then(res => {
console.log(res);
})
})
// 生命周期钩子
// onBeforeMount
// onMounted
// onBeforeUpdate
// onUpdated
// onBeforeUnmount
// onUnmounted
// onErrorCaptured
// onRenderTracked
// onRenderTriggered
// onActivated
// onDeactivated
// 获取dom
onMounted(() => {
console.log("获取dom", str.value);
})
// 修改前
onBeforeUpdate(() => {
console.log("修改前");
})
// 修改后
onUpdated(() => {
console.log("修改后");
})
// 销毁前
onBeforeUnmount(() => {
console.log("销毁前");
})
// 销毁后
onUnmounted(() => {
console.log("销毁后");
})
</script>
路由
// 1. tag属性去除了
<router-link to='/about' tag='div'>跳转到关于我们</router-link>
// 2. 写法问题
let router = useRouter(); ===> this.$router
let route = useRoute(); ===> this.$route
// 3. 导航守卫
全局路由守卫
beforeEach(to, from, next) 全局前置守卫,路由跳转前触发
beforeResolve(to, from, next) 全局解析守卫 在所有组件内守卫和异步路由组件被解析之后触发
afterEach(to, from) 全局后置守卫,路由跳转完成后触发
路由独享守卫
beforeEnter(to,from,next) 路由对象单个路由配置 ,单个路由进入前触发
组件路由守卫
beforeRouteEnter(to,from,next) 在组件生命周期beforeCreate阶段触发
beforeRouteUpdadte(to,from,next) 当前路由改变时触发
beforeRouteLeave(to,from,next) 导航离开该组件的对应路由时触发
组件间的传值
一、父传子
// 父组件
<template>
<div>
<List :msg='msg'></List>
</div>
</template>
<script setup>
import List from '../components/List.vue'
let msg = ref('这是父传过去的数据');
</script>
//子组件:通过defineProps接收
<template>
<div>
这是子组件 ==> {{ msg }}
</div>
</template>
<script setup>
import {defineProps} from 'vue'
defineProps({
msg:{
type:String,
default:'1111'
}
})
</script>
// 二、子传父
// 父组件:
<template>
<div>
<List @fn='changeHome'></List>
</div>
</template>
<script setup>
import List from '../components/List.vue'
const changeHome = (n)=>{
console.log( n.value );
}
</script>
// 子组件:
<template>
<div>
这是子组件 ==> {{ num }}
<button @click='changeNum'>按钮</button>
</div>
</template>
<script setup>
import { ref , defineEmits } from 'vue'
let num = ref(200);
const emit = defineEmits(['fn'])
const changeNum = ()=>{
emit('fn',num)
}
</script>
// 三、兄弟组件
// 步骤:
// 1》下载使用:mitt,类似于bus,npm install mitt -S
// 2》新建目录文件:plugins/Bus.js
import mitt from 'mitt';
const emitter = mitt()
export default emitter;
// A组件:
<template>
<div>
<h1>A组件</h1>
<button @click='btn'>按钮</button>
</div>
</template>
<script setup>
import emitter from '../plugins/Bus.js'
import { ref } from 'vue'
let str = ref('这是A组件的数据');
const btn = ()=>{
emitter.emit('fn',str);
}
</script>
// B组件:
<template>
<div>
<h1>B组件</h1>
{{ s }}
</div>
</template>
<script setup>
import emitter from '../plugins/Bus.js'
import { ref , onBeforeMount } from 'vue'
let s = ref('');
onBeforeMount(()=>{
emitter.on('fn',e=>{
s.value = e.value;
})
})
</script>
插槽
// 匿名插槽
//父:
<A>
这是xxxxx数据
这是yyyyy数据
</A>
//子
<header>
<div>头部</div>
<slot></slot>
</header>
<footer>
<div>底部</div>
<slot></slot>
</footer>
// 具名插槽
//父:
<A>
<template v-slot:xxx>//***简写:<template #xxx>
这是xxxxx数据
</template>
<template v-slot:yyy>
这是yyyyy数据
</template>
</A>
//子:
<header>
<div>头部</div>
<slot name='xxx'></slot>
<slot name='yyy'></slot>
</header>
<footer>
<div>底部</div>
<slot name='xxx'></slot>
</footer>
// 作用域插槽
//父:
<template #header='{user}'>
<div>{{ user.age }}</div>
</template>
//子:
<div>
<slot name="header" :user="user"></slot>
</div>
data(){
return{
user:{
name:"张三",
age:18
}
}
}
//父:
<template v-slot='{data}'>// 简写:<template #default='{data}'>
{{ data.name }} --> {{ data.age }}
</template>
//子:
<div v-for='item in list' :key='item.id'>
<slot :data='item'></slot>
</div>
//动态插槽:
//说了就是通过数据进行切换
//父:
<template #[xxx]>
这是xxxxx数据
</template>
<script setup>
let xxx = ref('xxx');
</script>
Teleport:传送
// A组件
<template>
<h1>子组件</h1>
<teleport to=".main">
<div>这是A.vue的传送</div>
</teleport>
</template>
<script setup>
</script>
// 父组件
<template>
首页
<div class="main">
传送到这里:
</div>
<A />
</template>
<script setup>
import A from '@/components/A.vue'
</script>
动态组件 <component :is="动态去切换组件"></component>
<template>
<div>
<ul>
<li v-for="(item, index) in tabList" :key="index" @click='change(index)'>
{{ item.name }}
</li>
</ul>
<component :is='currentComponent.com' />
</div>
</template>
<script setup>
import A from '../components/A.vue'
import B from '../components/B.vue'
import C from '../components/C.vue'
let tabList = reactive([
{ name: 'A准备好的面试题', com: markRaw(A) },
{ name: 'B贮备好的简历', com: markRaw(B) },
{ name: 'C准备好的项目', com: markRaw(C) }
])
let currentComponent = reactive({
com: tabList[0].com
})
const change = (idx) => {
currentComponent.com = tabList[idx].com
}
</script>
异步组件
***提升性能
npm i @vueuse/core
使用场景1:
组件按需引入:当用户访问到了具体组件再去加载该组件
<template>
<div ref='target'>
<C v-if='targetIsVisible'></C>
</div>
</template>
<script setup>
import { useIntersectionObserver } from '@vueuse/core'
const C = defineAsyncComponent(() =>
import('../components/C.vue')
)
const target = ref(null);
const targetIsVisible = ref(false);
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }]) => {
if (isIntersecting) {
targetIsVisible.value = isIntersecting
}
},
)
</script>
使用场景2
A组件是异步的如axios或promise等等,需要用Suspense
<Suspense>
<template #default>//加载出数据后
<A></A>
</template>
<template #fallback>//没加载出数据时
加载中...
</template>
</Suspense>
<script setup>
const A = defineAsyncComponent(() =>
import('../components/A.vue')
)
</script>
----------------------------------------------------------------------------------------------------------------------
<div ref='target'>
<Suspense v-if='targetIsVisible'>
<template #default>//加载出数据后
<C></C>
</template>
<template #fallback>//没加载出数据时
加载中...
</template>
</Suspense>
</div>
打包分包处理(按需引入)
npm run build打包完成后,异步组件有单独的js文件,是从主体js分包出来的
Mixin : 混入
来分发 Vue 组件中的可复用功能
mixin.js
import { ref } from 'vue'
export default function(){
let num = ref(1);
let fav = ref(false);
let favBtn = ()=>{
num.value += 1;
fav.value = true;
setTimeout(()=>{
fav.value = false;
},2000)
}
return {
num,
fav,
favBtn
}
}
<template>
<div>
<h1>A组件</h1>
{{ num }}
<button @click='favBtn'>
{{ fav ? '收藏中...' : '收藏' }}
</button>
</div>
</template>
<script setup>
import mixin from '../mixins/mixin.js'
let { num, fav, favBtn } = mixin();
</script>
Provide / Inject ==> 依赖注入 传值
提供:
<script setup>
provide('changeNum', num );
</script>
注入:
<template>
<div>
<h1>B组件</h1>
{{ bNum }}
</div>
</template>
<script setup>
const bNum = inject('changeNum');
</script>
Vuex
let store=useStore();
state:
let num = computed( ()=> store.state.num );
getters:
let total = computed( ()=> store.getters.total );
mutations:
store.commit('xxx')
actions:
store.dispatch( 'xxx' )
modules: 和之前的版本使用一样
Vuex持久化存储【插件】
npm i vuex-persistedstate -S
import { createStore } from 'vuex';
import persistedState from 'vuex-persistedstate'
export default createStore({
state: {
num: 10,
sum: 10,
str: '这是store数据'
},
getters: {
total(state) {
return state.num + state.sum;
}
},
mutations: {
changeNum(state, val) {
state.num = val
}
},
actions: {},
modules: {
user
},
plugins: [persistedState({
key: 'keyName', //浏览器中的名字
paths: ['user'] //需要存储起来的参数模块
})]
})
Pinia
Vuex和Pinia的区别
大致总结:
支持选项式api和组合式api写法
1.pinia没有mutations,只有:state、getters、actions
2.pinia分模块不需要modules(之前vuex分模块需要modules)、TypeScript支持很好、自动化代码拆分
3.pinia体积更小(性能更好)
pinia可以直接修改state数据
一、安装使用Pinia
1.1 安装下载
yarn add pinia
# or with npm
npm install pinia
1.2 main.js引入
import { createPinia } from 'pinia'
app.use(createPinia())
1.3 根目录新建store/index.js中写入
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
state: () => {
return {
counter: 0,
}
},
getters:{},
actions:{}
})
1.4 组件使用
<script setup>
import { useStore } from '../store'
const store = useStore();
</script>
二、State
2.1 Pinia定义state数据
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
state: () => {
return {
counter: 0,
name: 'Eduardo',
isAdmin: true,
}
},
getters:{},
actions:{}
})
2.2 组件使用pinia的state数据
<template>
<div>
<h1>A组件</h1>
{{ name }}
</div>
</template>
<script setup>
import { useStore } from '../store'
const store = useStore();
let { name } = store;
</script>
2.3 组件修改pinia的state数据
本身pinia可以直接修改state数据,无需像vuex一样通过mutations才可以修改,但是上面写的let { name } = store;这种解构是不可以的,所以要换解构的方式。
<template>
<div>
<h1>A组件</h1>
{{ name }}
<button @click='btn'>按钮</button>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useStore } from '../store'
const store = useStore();
let { name } = storeToRefs(store);
const btn = ()=>{
name.value = '123';
}
</script>
如果state数据需要批量更新
store/index.js
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
state: () => {
return {
counter: 0,
name: 'Eduardo',
arr:['a','b','c']
}
},
getters:{},
actions:{}
})
组件代码
<template>
<div>
<h1>A组件</h1>
{{ name }}
{{ counter }}
{{ arr }}
<button @click='btn'>按钮</button>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useStore } from '../store'
const store = useStore();
let { name,counter,arr } = storeToRefs(store);
const btn = ()=>{
//批量更新
store.$patch(state=>{
state.counter++;
state.arr.push(4);
state.name = '456';
})
}
</script>
***使用$patch进行批量更新
三、actions
actions就比较简单了,写入方法,比如我们可以让state中的某一个值+=,而且传入参数
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
state: () => {
return {
counter: 0
}
},
getters:{},
actions:{
changeCounter( val ){
this.counter += val;
}
}
})
<template>
<div>
<h1>A组件</h1>
{{ counter }}
<button @click='add'>加10</button>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useStore } from '../store'
const store = useStore();
let { counter } = storeToRefs(store);
const add = ()=>{
store.changeCounter(10);
}
</script>
四、getters
getters和vuex的getters几乎类似,也是有缓存的机制
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
state: () => {
return {
counter: 0,
}
},
getters:{
counterPar( ){
console.log(111);
return this.counter + 100;
}
},
actions:{}
})
<template>
<div>
{{ counterPar }}
{{ counterPar }}
{{ counterPar }}
<h1>A组件</h1>
{{ counter }}
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useStore } from '../store'
const store = useStore();
let { counter, counterPar } = storeToRefs(store);
</script>
设置代理和axios二次封装,api解耦
一、设置代理
文件:vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import AutoImport from "unplugin-auto-import/vite"; //自动导入 Vue 相关函数,如:ref, reactive, toRef 等
import { resolve } from 'path';
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router']
})
],
server: {
proxy: {
'/api': 'http://testapi.xuexiluxian.cn'
}
},
define: {
'process.env': {},
},
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
});
二、axios二次封装
文件:新建utils/request.js
import axios from 'axios';
//1. 创建axios对象
const service = axios.create();
//2. 请求拦截器
service.interceptors.request.use(config => {
return config;
}, error => {
Promise.reject(error);
});
//3. 响应拦截器
service.interceptors.response.use(response => {
//判断code码
return response.data;
},error => {
return Promise.reject(error);
});
export default service;
文件:新建api/https.js
import request from '../utils/request'
export function mostNew(data) {
return request({
url: '/api/course/mostNew',
method: "post",
data
})
}
页面调用
<template>
<div>
A组件
</div>
</template>
<script setup>
import { getSliders } from '../api/http'
onBeforeMount(() => {
getSliders().then(res => {
console.log(666, res.data.list);
})
})
</script>
<style lang="scss" scoped></style>
使用element-plus
vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
import AutoImport from "unplugin-auto-import/vite";
// @代替src
import { resolve } from 'path';
// 按需引入element-plus
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router'],
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
server: {
proxy: {
'/api': 'http://testapi.xuexiluxian.cn'
}
},
define: {
'process.env': {},
},
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
});