前端提升4:Vue全家桶
2025-03-22 14:44:23一、Vue3项目搭建
- Node.js:是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O 模型,让JavaScript 运行在服务端的开发平台; 版本要求>=12.0.0;
- npm:Nodejs下的包管理器;版本要求 6.x yarn:包管理器;
- yarn 包管理器是 npm 的一个替代方案,由Facebook于2016年10月发布。
- Vite:是一个 web 开发构建工具,使用 Vite 可以快速构建 VUE 项目比webpack打包更加快速
- 快速的冷启动
- 即时的模块热更新
- 真正的按需编译
创建vite3项目:npm init vite,输入项目名称
安装依赖
cd vite-project 进入项目 依赖安装 npm install 或 cnmp i
运行vite项目:npm run dev
访问:http://localhost:5173
包管理工具Yarn的使用
- yarn create vite:输入项目名称
- cd vite-d2
- 安装依赖:yarn
- 项目启动:yarn dev
二、Vue3.2新特性
1. createApp
在 Vue 3 中,改变全局 Vue 行为的 API 现在被移动到了由新的 createApp 方法所创建的应用实例上。
import { createApp } from 'vue'
const app = createApp({})
该实例提供了一个应用上下文,由于 createApp 方法返回应用实例本身,因此可以在其后链式调用其它方法
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp(App);
app.use(store)
app.use(router)
app.mount('#app');
//合并之后的代码:
createApp(App).use(store).use(router).mount('#app')
2. setup函数
- setup函数是vue3中专门为组件提供的新属性。
- 创建组件实例,然后初始化props,紧接着就调用setup函数,会在beforeCreate钩子之前被调用。
- 如果setup返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文。
- 注意:在setup()函数中无法访问到this
<script setup>
import {ref} from 'vue'
定义变量
const msg = 'Hello!';
定义响应式数据
const num = ref(10);
定义函数
function log() {
console.log(msg)
}
</script>
<template>
<div @click="log">{{ msg }}--{{num}}</div>
</template>
3. reactive、ref函数
- reactive()函数接收一个普通对象,返回一个响应式的数据对象
- ref()函数用来根据给定的值创建一个响应式的数据对象,ref()函数调用的返回值是一个对象,这个对象上只包含一个value属性
//1. 按需导入reactive函数
import { reactive } from 'vue'
//2. 在setup函数中调用reactive()函数,创建响应式数据对象
<script setup>
let n = reactive(10); //添加常量出现警告,值不会有响应式特性
//reactive()函数接收一个普通对象,返回一个响应式的数据对象
// const o1 = reactive({id:1});
// const o2 = {id:2}; //非响应式数据
// const lists = reactive([
// {id:1,title:'新闻1',content:'1111新闻 新闻 新闻 新闻 新闻'},
// {id:2,title:'新闻2',content:'2222新闻 新闻 新闻 新闻 新闻'},
// {id:3,title:'新闻3',content:'3333新闻 新闻 新闻 新闻 新闻'},
// {id:4,title:'新闻4',content:'4444新闻 新闻 新闻 新闻 新闻'},
// {id:5,title:'5555',content:'55555555555'},
// {id:6,title:'6666',content:'666666666666'}
// ]);
// function change(){}
const change = ()=>{
n++;
console.log(n)
// o1.id+=1;
// o2.id+=1;
// console.log(o2.id)
// lists.push({id:lists.length+1,title:'6666',content:'66666'})
}
</script>
//3. 在template中访问响应式数据
<template>
<h1>{{ n }}</h1>
<!-- <h1>{{ o1.id }}</h1>
<h1>{{ o2.id }}</h1> -->
<button @click="change()">click</button>
<!-- <ul>
<li v-for="v in lists" :key="v.id">{{ v.title }}</li>
</ul> -->
</template>
<style scoped>
</style>
<script setup>
import { ref } from 'vue'
let n = ref(10); //相当于reactive({value:10});
//在JS中.value,在template省略.value
const o1 = ref({id:1})
const lists = ref([
{id:1,title:'新闻1',content:'1111新闻 新闻 新闻 新闻 新闻'},
{id:2,title:'新闻2',content:'2222新闻 新闻 新闻 新闻 新闻'},
{id:3,title:'新闻3',content:'3333新闻 新闻 新闻 新闻 新闻'},
{id:4,title:'新闻4',content:'4444新闻 新闻 新闻 新闻 新闻'},
{id:5,title:'5555',content:'55555555555'},
{id:6,title:'6666',content:'666666666666'}
]);
// function change(){}
const change = ()=>{
// n.value++;
// console.log(n)
o1.value.id+=1;
// o2.id+=1;
// console.log(o2.id)
lists.value.push({id:lists.value.length+1,title:'6666',content:'66666'})
}
//ref的另一种使用:DOM引用
let myInput = ref();
const action = ()=>{
myInput.value.focus();
myInput.value.style.color='#f60';
console.log(myInput)
}
</script>
<template>
<h1>{{ o1.id }}</h1>
<!-- <h1>{{ o1.id }}</h1>
<h1>{{ o2.id }}</h1> -->
<button @click="change()">click</button>
<ul>
<li v-for="v in lists" :key="v.id">{{ v.title }}</li>
</ul>
<div>
<h1>ref的另一种使用:DOM引用</h1>
<input type="text" placeholder="请输入用户信息" ref="myInput"/>
<button @click="action">input成为焦点</button>
</div>
</template>
<style scoped>
</style>
<script setup>
import { ref,reactive } from 'vue'
import Data from './data' //引入外部数据
let n1 = 10;
let n2 = ref(10);
//let n3 = reactive(10);
let o1 = ref([]);
let o2 = reactive([]);
let o3 = reactive({arr:[]});
const change=()=>{
// console.log(n1);
// console.log(n2);
// console.log(n3);
// console.log(o1);
// console.log(o2);
//将data数据赋值给o1,o2
//o1.value = Data;
////不用这种方式,避免出现全新的对象赋值等问题
//o2 = Data; //这种方式不行,将原始对象替换了o2对象
//解决方式一
o2.push(...Data);
//另一种解决方式
o3.arr = Data;
}
</script>
<template>
<!-- <h1>{{ o1.id }}</h1> -->
<!-- <h1>{{ o1.id }}</h1>
<h1>{{ o2.id }}</h1> -->
<button @click="change()">click</button>
<ul>
<li v-for="v in o3.arr" :key="v.id">{{ v.title }}</li>
</ul>
</template>
<style scoped>
</style>
4. computed计算属性
computed()用来创建计算属性,computed()函数的返回值是一个 ref 的实例
<script setup>
import { ref,reactive ,computed} from 'vue'
let n = ref(10);
const newN = computed(()=>n.value + 100);
//使用场景
const color = reactive([
{name:'红色',check:false},
{name:'蓝色',check:true},
{name:'绿色',check:false},
{name:'灰色',check:true},
{name:'粉色',check:false},
{name:'橙色',check:false},
{name:'黄色',check:false},
{name:'白色',check:false}
])
const city = reactive([
{name:'北京市',check:false},
{name:'上海市',check:true},
{name:'湖南省',check:false},
{name:'湖北省',check:true},
{name:'广东省',check:false}
])
//计算checkbox打勾的个数
// const checkNum = computed(()=>color.filter(v=>v.check).length);
// const checkCityNum = computed(()=>city.filter(v=>v.check).length);
//计算属性传参
const checkNum = computed(()=>data=>data.filter(v=>v.check).length);
</script>
<template>
<input type="text" v-model="n" />
<h1>{{ n }}</h1>
<h1>选择颜色:</h1>
<div>选中的数量:{{checkNum(color)}}</div>
<ul>
<li v-for="(v,i) in color" :key="i">
<input type="checkbox" v-model="v.check" />
<span>{{ v.name }}</span>
</li>
</ul>
<h1>选择城市:</h1>
<div>选中的数量:{{checkNum(city)}}</div>
<ul>
<li v-for="(v,i) in city" :key="i">
<input type="checkbox" v-model="v.check" />
<span>{{ v.name }}</span>
</li>
</ul>
</template>
<style scoped>
</style>
5. watch函数
watch() 函数用来监视某些数据项的变化,从而触发某些特定的操作
<script setup>
import {ref,reactive,watch} from "vue";
//watch语法 watch(监听的数据,副作用函数,配置对象)
// let name = ref('jack');
// let age = ref(20);
let person = reactive({
name:'jack',
age:20,
city:{
address:'beijin'
}
})
function change(){
person.age++
}
//一)事件监听
// watch(name,(newValue,oldValue)=>{
// console.log(`newValue:${newValue} ====== oldValue:${oldValue}`)
// })
//1、初始化没有触发
//2、监听数据变化会触发
// watch(age,(newValue,oldValue)=>{
// console.log(`newValue:${newValue} ====== oldValue:${oldValue}`)
// })
//二)监听多个
// watch([name,age],(newValue,oldValue)=>{
// console.log(`newValue:${newValue} ====== oldValue:${oldValue}`)
// })
//三)监听proxy数据,整个对象
// watch(person,(newValue,oldValue)=>{
// console.log(`newValue:${JSON.stringify(newValue)} ====== oldValue:${JSON.stringify(oldValue)}`)
// //newValue=oldValue,同一个地址
// })
//proxy数据对象中某个属性,需要将第一个参数写成函数
// watch(()=>person.name,(newValue,oldValue)=>{
// console.log(`newValue:${JSON.stringify(newValue)} ====== oldValue:${JSON.stringify(oldValue)}`)
// });
//四)监听多个
// watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
// console.log(`newValue:${JSON.stringify(newValue)} ====== oldValue:${JSON.stringify(oldValue)}`)
// })
//watch语法 watch(监听的数据,副作用函数,配置对象)
//五)配置对象 {deep:true}深度监听
// watch(()=>person.city,(newValue,oldValue)=>{
// console.log(`newValue:${JSON.stringify(newValue)} ====== oldValue:${JSON.stringify(oldValue)}`)
// },{deep:true})
//newValue不等于oldValue
// watch(()=>person.city.address,(newValue,oldValue)=>{
// console.log(`newValue:${JSON.stringify(newValue)} ====== oldValue:${JSON.stringify(oldValue)}`)
// })
//六)初始化触发 {immediate:true}
// watch(()=>person.city.address,(newValue,oldValue)=>{
// console.log(`newValue:${JSON.stringify(newValue)} ====== oldValue:${JSON.stringify(oldValue)}`)
// },{immediate:true})
</script>
<template>
<div>
<div>姓名:<input type="text" v-model="person.name" /></div>
<div>年龄:{{ person.age }}</div>
<div>城市:<input type="text" v-model="person.city.address" /></div>
<button @click="change">click</button>
</div>
</template>
<style scoped>
</style>
清除监听
<script setup>
import {ref,reactive,watchEffect} from "vue";
//watch语法 watch(监听的数据,副作用函数,配置对象)
let tip = ref('偶数');
let n = ref(20);
//添加监听事件,watchEffect会立即执行,不需要手动添加监听的对象,无法获取修改前后的值
watchEffect(()=>{
console.log(tip.value);
console.log(n.value);
})
//清除监听
function change(){
n.value++;
}
</script>
<template>
<div>
<div>数字:{{ n }}</div>
<div>提示:<input type="text" v-model="tip" /></div>
<button @click="change">click</button>
</div>
</template>
<style scoped>
</style>
三、生命周期钩子函数
- setup()创建组件之前执行
- onBeforeMount:DOM即将挂载(组件挂载页面之前)
- onMounted:DOM挂载完毕 (组件挂载页面之后)
- onBeforeUpdate:DOM即将更新
- onUpdated:DOM更新完毕
- onBeforeUnmount:即将销毁(在组件卸载之前执行)
- onUnmounted:销毁完毕(在组件卸载之后执行)
新旧对比
beforeCreate -> use setup()
created -> use setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
使用举例
<template>
<div>
<p>{{num}}</p>
<p>{{type}}</p>
</div>
</template>
<script setup>
import {ref ,onMounted, onUpdated, onUnmounted} from "vue";
const num = ref(1);
const type = ref('奇数');
let timer = null;
function autoPlay(){
num.value +=1;
if(num.value == 5){
num.value = 0;
}
}
function play(){
timer = setInterval(autoPlay,1000)
}
onMounted(()=>{ //挂载完成
play();
})
onUpdated(()=>{
if(num.value % 2 == 0 ){
type.value = '偶数'
}else {
type.value = '奇数'
}
})
onUnmounted(()=>{ //销毁
clearInterval(timer);
})
</script>
四、组件
组件(Component)是 Vue.js 最强大的功能之一,组件可以扩展 HTML 元素,封装可重用的代码。
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为 一个组件树;
vue中的核心之一就是组件,所有页面都是通过组件来管理
<script setup> 范围里的值也能被直接作为自定义组件的标签名使用:
<script setup>
import MyComponent from './MyComponent.vue' //导入组件
</script>
<template>
组件调用
<MyComponent />
</template>
模块中使用组件
<MyComponent></MyComponent> 或 <MyComponent />
组件传参
- 父组件传参到子组件——defineProps
index.vue
<Sub :title="title" :color="'red'" :action="add"/>
<script setup>
import { ref,reactive } from "vue";
import Sub from './sub.vue'
const title= ref('提交');
function add(){
title.value = '添加'
}
</script>
Btn.vue
<button :style="{color:color}" @click="action">{{title}}</button>
<script setup>
//对象的方式
const {title,color,action} = defineProps({
title:{
type:String,
default:()=>'按钮'
},
color:{
type:String,
default:()=>'#333'
},
action:Function
})
</script>
//传一个数组
index.vue
<template>
<div>
<Home :items="treeData"/>
</div>
</template>
sub.vue
<template>
<div class='lists'>
<ul>
<li v-for="(v,i) in items" :key="v.id">
{{v.name}}
</li>
</ul>
</div>
</template>
<script setup>
const {items} = defineProps({
items:{
type:Array, //类型
default:()=>[1,2,3] //默认值
}
})
</script>
- 子组件操作父组件-defineEmits
//父组件
<template>
<div >
<h1>{{msg}}</h1>
<Sub @subClick='myclick'/>
</div>
</template>
<script setup>
import {ref,reactive} from 'vue'
import Sub from './sub.vue'
const msg = ref('消息');
function myclick(v){
msg.value = v;
}
</script>
//子组件
<template>
<div>
<button @click="emitClick()">子传参到父</button>
</div>
</template>
<script setup>
import { ref ,reactive,defineProps,defineEmits} from 'vue'
let sub = ref("我是子组件");
const emit = defineEmits(['subClick']);
const emitClick = ()=>{
emit('subClick',sub.value) //传入参数
}
</script>
动态组件
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="someCondition ? Foo : Bar" />
</template>
Suspense 异步组件
Suspense组件用于在等待某个异步组件解析时显示后备内容。
何时用它
- 在页面加载之前显示加载动画
- 显示占位符内容
- 处理延迟加载的图像
如何使用
插槽包裹异步组件
<Suspense>
<template #default>
<Async/>
</template>
</Suspense>
具名插槽的缩写是在 vue2.6.0 新增,跟 v-on 和 v-bind 一样,v-slot 也有缩写, 替换为字符 #。
例如 v-slot:header 可以被重写为 #header
插槽包裹渲染异步组件之前的内容
<Suspense>
<template #fallback>
<h1>Loading...</h1>
</template>
</Suspense>
如何运用
//父组件中定义
<Suspense>
<template #default>
<List />
</template>
<template #fallback>
<div>loading......</div>
</template>
</Suspense>
//List子组件中的处理
//可以在setup使用顶层 await。结果代码会被编译成 async setup()
<script setup>
const listData = await axios.get('');
</script>
五、插槽
插槽是组件的一块HTML模板,这块模板显示不显示,怎样显示是由父组件来控制的,而插槽在哪里显 示就由子组件来进行控制
index.vue
<template>
<div>
<Child>这是父传入的数据</Child> //在开始和结束Child 标记之间的内容将插入到插槽所在的Child 组件中,替换的方法
</div>
</template>
child.vue
<template>
<div>
<slot></slot>
</div>
</template>
与props的不同
- 通过props属性,父组件只能向子组件传递属性、方法
- 而插槽还可以传递带标签的内容、甚至是组件等,更灵活
index.vue
<template>
<div>
<!-- 匿名插槽 -->
<!-- <Sub>
<ul slot>
<li v-for="(v,i) in arr" :key="i">{{ v }}</li>
</ul>
</Sub> -->
<!-- 具名插槽 -->
<!-- <Sub>
<template #header>
<h1>ABC</h1>
</template>
<template #default>
<ul>
<li>33333</li>
<li>33333</li>
<li>33333</li>
</ul>
</template>
</Sub> -->
<!-- 数据插槽(官方叫作用域插槽)-->
<!-- 作用域插槽跟单个插槽和具名插槽的区别,因为单个插槽和具名插槽不绑定数据,所以父组件提供的模板一般要既包括样式又包括内容, 而作用域插槽,相当于父组件提供一套样式,数据都是子组件的。-->
<!-- <Sub>
<template #header="{user:person}">
<h1>标题:{{ person.title }}</h1>
<h1>作者:{{ person.name }}</h1>
</template>
</Sub> -->
<Sub>
<template #header="{user:person,n}">
<h1>标题:{{ person.title }}</h1>
<h1>作者:{{ person.name }}</h1>
<h1>数据:{{ n}}</h1>
</template>
</Sub>
</div>
</template>
<script setup>
import { ref,onMounted } from 'vue';
import Sub from './sub.vue'
const arr = ref([10,20,30,40]);
// const user = ref({title:'title',name:'jack'});
</script>
sub.vue
<template>
<div>
<!-- 匿名插槽 -->
<!-- <slot>默认内容</slot> -->
hello sub
<!-- 具名插槽 -->
<!-- <slot name="header" ></slot> -->
<button>click</button>
<!-- 数据插槽 -->
<slot name="header" :user="user" :n="n"></slot>
</div>
</template>
<script setup>
import { ref,onMounted } from 'vue';
const user = ref({title:'title',name:'jack'});
const n = ref(10);
</script>
六、 Vue Router 4
vue-router 默认还是安装的 3.x 版本的,我们需要先安装vue-router4:cnpm i vue-router@4 -S
import { createRouter, createWebHashHistory ,createWebHistory} from 'vue-router'
//路由配置
const routes =[
{
path:'/',//初始化
redirect:'/login'
},
{
path:'/login',
name:'login',
// component:()=>import('../components/login.vue')
//单页面多路由区域,在路由中定义多个组件加载
components:{
default:()=>import('../components/login.vue'),
other1:()=>import('../components/other1.vue'),
other2:()=>import('../components/other2.vue'),
}
},
{
path:'/layout', //布局页
name:'layout',
component:()=>import('../components/layout.vue'),
redirect:'/home',
children:[ //子级路由
{
path:'/home',
name:'home',
component:()=>import('../components/home.vue')
},
{
path:'/news',
name:'news',
component:()=>import('../components/news.vue')
},
{
path:'/manage',
name:'manage',
component:()=>import('../components/manage/index.vue'),
children:[
{
// path:'/manage/census', //多级
path:'census',
name:'census',
component:()=>import('../components/manage/census.vue')
},
{
path:'/manage/order',
name:'order',
component:()=>import('../components/manage/order.vue')
},
]
},
]
},
]
//创建路由
//路由模式:
//hash: createWebHashHistory() http://127.0.0.1:8088/#/login
//history:createWebHistory() http://127.0.0.1:8088/login
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
组件中的使用
因为setup中不能访 this,所以提供两个api来获取 router 和 route , useRouter() 和 useRoute()
<script setup>
import {ref,onMounted} from 'vue'
import {useRouter,useRoute} from "vue-router"
const router = useRouter();//router是全局路由的实例,是VueRouter的实例
const route = useRoute(); //route对象表示当前的路由信息,包含了当前 URL 解析得到的信息
onMounted(()=>{
//获取参数ID
console.log(route.params.id);
})
function goBack(){
router.push('/home')
}
</script>
设置路由导航的两种方法
//声明式的常见方式
<router-link to="/home">home</router-link>
路由通过path对象
<router-link :to="{path:'/home'}">首页</router-link>
路由通过name名称
<router-link :to="{name: 'home'}">首页</router-link>
//编程式的常见方式
字符串
router.push('/home')
路由通过path对象
router.push({path:'/home'})
路由通过name名称
router.push({name: 'home'})
路由传参的二种常见方式
//params方式
路由显示
http://localhost:5173/#/list/10
传入参数的方式
<router-link to="/list/10">列表设置</router-link>
路由配置
const routes = [
{path: '/home', component: Home},
{path: '/news', component: News},
{path: '/list/:id', component: List},
//路由中定义http://localhost:5173/#/list/10 需要定义ID
]
获取参数的方式: route.params.id
//query方式
路由显示
http://localhost:5173/#/list?id=100
传入参数的方式
<router-link :to="{path:'/list',query: {id: 99}}">列表设置</router-link>
路由中定义:list?id=10 不需要在路由配置中定义参数
获取参数的方式: route.query.id
传参写法
//声明式的常见传参写法
直接路由带查询参数query,地址栏变成 /home?id=10
<router-link :to="{path: '/home', query: {id: 10 }}">home</router-link>
命名路由带查询参数query,地址栏变成/home?id=10
<router-link :to="{name: 'homename', query: {id: 10 }}">home</router-link>
直接路由带路由参数params,params 不生效,如果提供了 path,params 会被忽略
<router-link :to="{path: '/home', params: { id: 10 }}">home</router-link>
命名路由带路由参数params,地址栏是/home/10
<router-link :to="{name: 'homename', params: { id: 10 }}">home</router-link>
//编程式的常见传参写法
直接路由带查询参数query,地址栏变成 /home?id=10
router.push({path: 'home', query: {id: 10 }})
命名路由带查询参数query,地址栏变成/home?id=10
router.push({name: 'homename', query: {id: 10 }})
直接路由带路由参数params,params 不生效,如果提供了 path,params 会被忽略
router.push({path:'homename', params:{ id: 10 }})
命名路由带路由参数params,地址栏是/home/10
router.push({name:'homename', params:{ id: 10 }})
路由守卫
- 全局前置守卫 beforeEach:当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
- 全局后置守卫 afterEach:可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身。它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用
- 全局解析守卫 beforeResolve:和router.beforeEach类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之 后,解析守卫就被调用
- 路由专享守卫 beforeEnter:路由独享的守卫 只对配置的路由起作用
import { createRouter, createWebHashHistory ,createWebHistory} from 'vue-router'
//路由配置
const routes =[
{
path:'/',//初始化
redirect:'/login'
},
{
path:'/login',
name:'login',
component:()=>import('../components/login.vue')
//单页面多路由区域,在路由中定义多个组件加载
// components:{
// default:()=>import('../components/login.vue'),
// other1:()=>import('../components/other1.vue'),
// other2:()=>import('../components/other2.vue'),
// }
},
{
path:'/layout', //布局页
name:'layout',
component:()=>import('../components/layout.vue'),
redirect:'/home',
children:[ //子级路由
{
path:'/home',
name:'home',
component:()=>import('../components/home.vue')
},
{
path:'/news',
name:'news',
component:()=>import('../components/news.vue')
},
{
path:'/manage',
name:'manage',
component:()=>import('../components/manage/index.vue'),
children:[
{
// path:'/manage/census', //多级
path:'census',
name:'census',
component:()=>import('../components/manage/census.vue')
},
{
path:'/manage/order',
name:'order',
component:()=>import('../components/manage/order.vue'),
//路由专享守卫 beforeEnter
beforeEnter:(to,from,next)=>{
if(sessionStorage.getItem('sign')) {
next()
}else {
alert('请完成打卡');
next(false); //中断
}
}
},
]
},
]
},
]
//创建路由
//路由模式:
//hash: createWebHashHistory() http://127.0.0.1:8088/#/login
//history:createWebHistory() http://127.0.0.1:8088/login
const router = createRouter({
history: createWebHashHistory(),
routes
})
//路由守护
//1 、全局前置守卫 beforeEach
//next() next('/login')指定地址 next(false) 中断
router.beforeEach((to,from,next)=>{
//console.log(to)
//进入的地址不是login,就跳转到login
// if(to.name !== 'login'){
// next('/login')
// }else{
// next();
// }
//需求:如果有登录用户跳转到首页,没有回到登录
// if(!sessionStorage.getItem('username')){ //未登录
// //只要进的不是login,默认进入login
// if(to.path !== '/login'){
// next({name:'login'})
// }else {
// next()
// }
// }else {
// next()
// }
//打卡功能
// if(!sessionStorage.getItem('sign')){ //未打卡
// if(to.path !== '/home'){
// alert('请打卡!')
// next()
// }else {
// next()
// }
// }else {
// next()
// }
next()
})
//2 、全局后置守卫 afterEach
router.afterEach((to,from)=>{
if(to.path==='/login'){
document.title='欢迎一起学习!'
}else {
document.title='后台管理系统!'
}
})
export default router
导航解析流程
- 导航被触发
- 在失活的组件里调用离开守卫beforeRouteLeave(to,from,next)
- 调用全局前置守卫 beforeEach(to,from,next)
- 在复用的组件里调用beoreRouteUpdate(to,from,next)
- 在路由配置里调用路由独享的守卫beforeEnter()
- 解析异步路由组件
- 在被激活的组件里调用beforeRouteEnter(to,from,next)
- 调用全局解析组件beforeResolve
- 导航被确认
- 调用全局后置守卫afterEach()
- 触发DOM更新
- 用创建好的实例调用beforeRouteEnter守卫中传递给next的回调函数
七、Vuex4
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状 态,并以相应的规则保证状态以一种可预测的方式发生变化。
安装:cnpm i vuex@4 -S
什么情况下我应该使用 Vuex?
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。 如果不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。
如果应用够简单,最好不要使用 Vuex。一个简单的 store模式就足够您所需了。但是,如果需要构建一个中大型单页应用,很可能需要考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
核心概念
State单一状态树:用一个对象就包含了全部的应用层级状态
Getter:Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Mutation:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,类似于事件,每 个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。
不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,需要以相应的 type 调用
store.commit('increment')
Action: 类似于 mutation,不同在于
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
Action 通过 store.dispatch 方法触发:
store.dispatch('increment')
import { createStore } from 'vuex'
export default createStore({
state: {
num:100
},
getters:{
getNum(state){
return state.num
}
},
mutations: {
INCREASE(state,v){
state.num+=v;
}
},
actions: {
INCREASE({commit},v){
commit('INCREASE',v)
},
DECREASE(){
}
},
modules: {
}
})
八、Pinia
Pinia 是2019年由vue.js官方成员重新设计的新一代状态管理器,更替Vuex4成为Vuex5; 是最新一代的 轻量级状态管理插件。
安装:yarn add pinia -S
Pinia 的优点
- 简便,存储和组件变得很类似,你可以轻松写出优雅的存储。
- 类型安全,通过类型推断,可以提供自动完成的功能。
- vue devtools 支持,可以方便进行调试。
- Pinia 支持扩展,可以非常方便地通过本地存储,事物等进行扩展。
- 模块化设计,通过构建多个存储模块,可以让程序自动拆分它们。
- 非常轻巧,只有大约 1kb 的大小。
- 服务器端渲染支持。
使用
使用插件
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import {createPinia} from 'pinia'
const pinia = createPinia();//实例化pinia
createApp(App).use(router).use(pinia).mount('#app'
list.js
import { defineStore } from 'pinia'
import axios from 'axios'
//可以通过defineStore 来简单创建一个存储管理
//第一个参数:相当于为容器起一个名字。注意:这里的名字必须唯一,不能重复。
//第二个参数:可以简单理解为一个配置对象,对容器仓库的配置说明
export const listStore = defineStore('list', {
state: () => ({
lists:[]
}),
//在pinia中没有mutations,只有actions,不管是同步还是异步的代码,都可以在actions中完成
actions: {
//发送请求,拿到数据,赋值给lists
// async getData(){
// let d = await axios.get('http://xxxx/home/page/1/10');
// console.log(d)
// this.lists = d.data.data;
// }
getData(){
return new Promise((reslove,reject)=>{
axios.get('http://xxxx/home/page/1/10').then(res=>{
reslove(res)
}).catch(error=>{
reject(error)
})
})
}
}
})
index.vue
<template>
<div>
<p>{{store.title}}</p>
<p>数据:{{store.count}}</p>
<button @click="handleClick">修改状态数据</button>
</div>
</template>
<script setup>
import {ref,reactive} from 'vue'
import {mainStore} from '@/store/index' //导入状态仓库
import { storeToRefs } from "pinia";
const store = mainStore(); //实例化仓库
// 解构并使数据具有响应式
const {count} = storeToRefs(store);
function handleClick() {
count.value++;
}
</script>
state 状态值修改的多种方式
方式一:通过count.value+1;
方式二:仓库实例的$patch方法
store.$patch({
count: count.value+1,
});
$patch也可以传入一个函数,函数参数为state状态
store.$patch((state) => {
state.title = "hello 艾梯哎教育!";
state.count++;
});
方式三:通过action()
store.addCount()
数据持久化
安装:npm i pinia-plugin-persist -S
使用插件
import {createPinia} from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'
const pinia = createPinia(); //实例化pinia
pinia.use(piniaPluginPersist); //使用
createApp(App).use(router).use(pinia).mount('#app')
开启持久化
import { defineStore } from 'pinia'
export const userStore = defineStore('user', {
state: () => ({
title: 'itlove',
count: 1,
}),
getters: {
changeCount() {
return this.count + 10
}
},
actions: {
addCount(v) {
this.count += v;
}
},
persist: {
enabled: true, //开启
strategies: [ //个性化配置
{
key: 'user', //默认当前 Store的id
storage: sessionStorage, //存储的方式
paths: ['count'] //可以选择持久化的的字段
}
]
}
})