Spiga

前端提升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打包更加快速
    • 快速的冷启动
    • 即时的模块热更新
    • 真正的按需编译
  1. 创建vite3项目:npm init vite,输入项目名称

  2. 安装依赖

    cd vite-project  进入项目
    依赖安装
    npm install 或 cnmp i
    
  3. 运行vite项目:npm run dev

  4. 访问:http://localhost:5173

包管理工具Yarn的使用

  1. yarn create vite:输入项目名称
  2. cd vite-d2
  3. 安装依赖:yarn
  4. 项目启动: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

导航解析流程

  1. 导航被触发
  2. 在失活的组件里调用离开守卫beforeRouteLeave(to,from,next)
  3. 调用全局前置守卫 beforeEach(to,from,next)
  4. 在复用的组件里调用beoreRouteUpdate(to,from,next)
  5. 在路由配置里调用路由独享的守卫beforeEnter()
  6. 解析异步路由组件
  7. 在被激活的组件里调用beforeRouteEnter(to,from,next)
  8. 调用全局解析组件beforeResolve
  9. 导航被确认
  10. 调用全局后置守卫afterEach()
  11. 触发DOM更新
  12. 用创建好的实例调用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'] //可以选择持久化的的字段
            }
        ]
    }
})