目录
todolist案例 利用localStorage实现本地存储数据
localStorage和sessionStorage的区别:
- 在存储时间上:localStorage除非手动删除否则会一直存放到浏览器本地;sessionStorage在网页关闭时自动删除;
- 在存储大小上:两者存储空间大小都为5M
- 在生效空间上:localStorage限制在同一页面下才能获取;sessionStorage则没有这个要求;
localStorage基本使用语法
- 存放数据:对于不是基本数据类型的值,要通过JSON.stringify()转为JSON
localStorage.setItem(属性名,属性值); - 获取数据:
localStorage.getItem(属性名);
实现逻辑:
mounted生命周期中初始化本地存储和获取todolist数据,将本地数据赋值给todolist;在获取本地数据前判断是否有该属性,若没有,就添加默认本地数据
之后每当修改todolist都重新给本地数据赋值
todolist案例 利用自定义事件实现子组件到父组件的数据传递
自定义事件准则:给谁绑定就找谁触发
绑定自定义事件的方法:
- 通过v-on指定绑定
<my-header @addTodo=“handleAddTodo”>
addTodo是自定义事件名称,handleAddTodo是事件回调函数;
在my-header组件中触发该事件,并为回调传递参数,参数在app组件中的回调函数可以获取;
this.$emit(‘addTodo’,x,y,z); - 通过ref打标识,当模板载入页面后(mounted)通过$on(name,callback)绑定自定义事件;
- 以上两个方法的区别:
- 通过ref打标识的方法可以更加灵活的绑定自定义事件,例如延迟几秒后自定义事件才生效;
- 自定义事件只触发一次:通过$once(name,callback)绑定自定义事件
- 解绑自定义事件
- this.$off(event);//解绑指定事件
- this.$off([event1,event2]);//解绑多个自定义事件
- this.$off();//解绑所有的自定义事件
todolist案例 全局事件总线实现两两组件间数据传递
让每个组件实例对象和Vue实例对象都拥有一个公共对象,该对象具有$on()和$emit();vue中有vueComponent实例和vue实例满足条件;
- 在Vue的原型链上添加该对象:因为vueComponent的原型对象是Vue
- 通过添加一个vueComponent实例对象,这个实例对象通过Vue.extend({})返回一个组件构造函数,再通过new 获取实例
- 添加vm对象;在beforeCreate生命周期回调中为Vue添加原型属性,当中的this就是Vue实例对象
todolist案例 消息订阅与发布实现组件间数据传递
第三方库:pubsub.js
应用方法:
- pubsub.subscribe(name,callback);消息订阅
- pubsub.unsubscribe(name);取消订阅
- pubsub.publish(name,attribute);发布消息
需要注意的是:回调函数不仅接收publish传递的参数还有消息名,可以使用下划线占位
谁订阅谁发布:每回消息发布时,订阅了该消息的组件都要执行回调;
todolist案例中编辑功能
当点击编辑按钮时为当前item对象添加isEdit属性来保存编辑状态,事件回调传参获取当前item对象,添加属性通过this.$set(obj,key,value)才会被vue认可,来让vue进行数据代理和数据监听;注意添加时判断对象有没有该属性(hasOwnProperty方法);利用该属性控制输入框隐藏显示,当输入框失去焦点时isEdit值为false隐藏输入框,修改对象值;
因为本次案例使用本地存储所以需要对todolist数据进行深度侦听(deep:true);
github搜索案例
想要在请求未回来时显示加载,添加一个loading状态,初始值为false,在请求时修改该值;
技巧:多个状态需要在请求时修改,整合为一个对象,通过es6语法扩展运算符快速修改;
例如需要传四个值修改,但有个值不常修改的,想只传三个值;
this.info={…this.info,…att}
插槽
有三种插槽:
- 默认插槽:适合传入一个元素作为组件内容
使用:在组件中通过<slot></slot>插入元素 - 具名插槽:可传入多个元素,以name属性区分
传入:特殊写法<template v-slot:ownName></template>常用写法slot="ownName"做名称
使用:<slot name=“ownName”></slot> - 作用域插槽:数据在组件内,使用者决定数据的显示方式;可以理解为使用同一个数据以不同样式展示
传入:必须要用template包裹,<template scope=“slotDataObj”><ul>…</ul></template>scope接收的是一个对象,包含有多个key-value;
使用:<slot :data=“data”></slot>
问题:
- 当未传入插槽时,slot是空着的
- 具名插槽只传入一个,另一个是不是空着的
- 针对未传入插槽时,可以在slot内写入内容默认显示
- 同name的slot是可以追加的,但是建议使用一个父元素直接包裹,不用一个一个添加name属性
- 结构在哪,样式就写在哪里
Vuex插件 状态管理
由vue团队开发的状态管理插件;vue2对应使用vuex3,vue3使用vuex4;
创建store是通过Vuex.Store();需要传入配置对象;再将创建的store赋值给Vue实例对象
配置vuex运行环境
//store.js
import Vue from 'vue'
import Vuex from 'vuex'
//应用vuex插件
Vue.use(vuex)
//配置项对象
//用于存储数据
const state = {}
//用于响应组件中的动作
const actions = {}
//用于操作state数据
const mutations = {}
//用来对state加工,与computed计算属性类似,返回加工后的值
const getters = {}
export default new Vuex.Store({
state,
actions,
mutations,
getters
})
//main.js
import store from './store/store'
new Vue({
el:'#app',
render:h=>h('App'),
components:{App},
store
})
使用vuex操作数据
//App.vue
export default {
data(){
return {num:1}
},
methods:{
//对总数加一
increment(){
//引起increment动作
this.$store.dispatch('increment',this.num);
}
}
}
//store.js
const actions={
//函数第一个参数接收到的context上下文对象,即store对象;之后的参数是dispatch传递的参数
increment(context,num){
context.commit('increment',num);
//这里可以直接修改state的数据,但是开发工具是不记录这次的修改
//context.state.count += num;
//而且在这里可以做一些判断,网络请求等操作,再进行数据的操作
}
}
const mutations={
//函数第一个参数接收到的是state
increment(state,num){
state.count += num;
}
}
mapState状态映射 和 mapGetters
在模板中书写太多的store.state.xxx,想通过computed计算属性简写,而在计算属性又有许多重复的代码。借助vuex中的mapState状态映射自动生成计算属性;
import {mapState,mapGetters} from 'vuex'
computed:{
//dizhi是计算属性名称,address是指定获取的state属性名
//这里的sum不能触发对象的简写方式,简写方式是当值为变量且名字与属性名相同
//...mapState({sum:'sum',dizhi:'address'})
//当生成的计算属性名和获取属性名相同时,可以传入数组
...mapState(['sum','address']),
//从getters获取数据,生成计算属性
...mapGetters(['bigSum'])
}
mapActions 和 mapMutations
//App.vue
//需要注意的是,在调用时要传递参数,要不然默认传递的是事件对象
<button @click="increment(num)">+</button>
import {mapActions} from 'vuex'
export default {
methods:{
...mapActions({increment:'increment'})
//数组写法
//...mapAction(['increment'])
}
}
//mapMutations同mapActions相同
vuex 模块化管理
当vuex存放许多数据后,数据是可以分类的,连同操作数据的actions,mutations,getters;
//分为多个配置对象,每个对象都有自己的actions等操作数据动作
const personOption = {
//在mapState用到配置对象名称,需要给配置对象添加namespaced:true
namesapced:true,
state:{},
actions:{},
...
}
const countOption = {
state:{},
...
}
export default new Vuex.Store({
modules:{
personOption,
countOption
}
})
//获取对应的state
//$store.personOption.state.xxx
//mapState,mapGetters,mapMutations,mapActions自动生成代码
//...mapState('countOption',['count'])
//...mapGetters('countOption',['bigCount'])
//传递的第一个参数指定配置对象
//自己写methods方法和computed计算属性,在属性名前带着配置名称/
/**
methods:{
increment(){
this.$store.dispatch('countOption/increment',this.num)
}
},
computed:{
bigCount(){
return this.$store.getters['countOption/bigCount'];
},
count(){
return this.$store.state.countOption.count;
}
}
*/
模块化引起的注意事项
- 原先在里面直接使用this.state.xxx获取数据也是可行的,每个配置项里面的this是store对象;
但经过模块化区分后就不建议这样使用了,这时的this是总的store对象,包含了personOption
actions={
increment(context,num){
//调用countOption的commit
this.commit('countOption/INCREMENT',num);
//直接使用context上下文对象,就是当前配置对象
context.commit('INCREMENT',num);
//而且在前期也可以使用,后期就算修改为模块化也不需要修改
}
}
- 不通过mapState等方法自动生成代码,配置对象不添加namespaced:true,可以不添加**personOption/**但是会引起被其他配置对象同名属性覆盖问题报错问题,也就是说添加了namespaced:true后,每个配置对象中的属性名可以相同
vue-router 路由
配置
vue2搭配vue-router@3使用,vue3搭配vue-router@4使用;
使用vue-router 实现单页应用
1、创建路由器
//引入vue-router
import VueRouter from 'vue-router'
//引入路由组件
import Home from '../pages/Home'
import About from '../pages/About'
//创建路由器
export default new VueRouter({
//编写路由规则 是一个数组,以key-value的形式存放多个路由
routes:[
{path:'/home',component:Home},
{path:'/about',component:About}
]
})
2、使用路由链接
<router-link to="/home" active-class="active">Home</rouoter-link>
<router-link to="/about" active-class="active">About</rouoter-link>
//active-class="active"指定路由链接选中样式
3、定义路由组件放置位置
<router-view></router-view>
注意点
- 当路由不断切换时,路由组件是被不断挂载和加载的
- 路由组件比一般组件多了两个属性:$route $router
$route:与其他路由组件的$route都不相等,是当前路由组件独有的,是当前路由组件的路由规则
$router:是整个应用的路由器,所有路由组件的$router都相等
多级路由
//router.js
export default new VueRouter({
route:[
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
path:'messages',
component:Messages
}
]
}
]
})
路由传参
query传参
- 字符串写法
//Messages <router-link :to="`/home/messages/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>
- 对象写法
<router-link :to="{ path:'/home/messages/detail', query:{ id:m.id, title:m.titl } }">{{m.title}}</router-link>
- 获取传递的参数
$route.query.xxx
params传参
必须写成对象写法,因为路径是通过命名路由匹配路径,命名路由只能对象写法
//router.js
export default new VueRouter({
routes:[
{
path:'/home',
component:Home,
children:[
{
path:'messages',
component:Messages,
children:[
{
//命名路由
name:'detail',
//定义接收参数
path:'detail/:id/:title',
component:Detail
}
]
}
]
}
]
})
//Messages.vue
<router-link :to="{
name:'detail',
params:{
id:m.id,
title:m.title
}
}">{{m.title}}</router-link>
获取参数:
$route.params.xxxx
路由props配置
不在路由组件中通过$route.query.xxx或$route.params.xxx获取参数,直接在组件中props获取参数;
通过路由props配置,将参数传递给组件props:
//router.js
export default new VueRouter({
routes:[
{
...
{
//命名路由
name:'detail',
path:'detail/:id/:title',
component:Detail,
//第一种写法:props值为对象,将key-value作为props传给组件
props:{id:666,title:'test'}
//第二种写法:props值为boolean,将params参数通过props传递给组件,还是要老老实实定义接收的
props:true
//第三种写法:props值为函数,返回值作为props传递给组件
props($route){
return {id:$route.query.id,title:$route.query.title}
}
}
...
}
]
})
需要注意的是,路径里还是要定义接收params参数的。
router-link的replace属性
- 将路由模式转为replace替换模式,替换成当前路径,上一次路径不保留历史记录,无法退回。
- 路由模式分为两种:push追加历史记录、replace替换当前记录
- 应用:
<router-link replace to="/home">Home</router-link>
编程式路由导航
- 定义:不借助router-link跳转路由,让路由调转更加灵活。
- 常用api
- $router.forward()前进
- $router.back()后退
- $router.go(n)指定前进或回退到第几步,n>0前进,n<0后退
- $router.push({path:‘’,query:{…}}),追加模式跳转到指定路由
- $router.replace({…}),替换模式跳转到指定路由
缓存路由组件
作用:让不展示的路由组件不被销毁(卸载)
实现:
//缓存一个路由
<keep-alive include="News">
<router-link></router-link>
</keep-alive>
//缓存多个路由
<keep-alive :include="['News','Messages']">
<router-link></router-link>
</keep-alive>
需要注意的:
- include写的是组件名,组件名,组件名。要是不写那router-link展示的路由组件都会被缓存。
- 路由组件要配置name:'News’即组件名,就是在开发工具显示的那个,要不然include不起效
缓存路由引起的两个生命周期钩子
因为路由被缓存后当路由组件不显示时也不会被卸载,所以beforeDestroy()不会被调用,也就没办法通过这个生命周期删除计时器。可以使用以下的生命周期。
- deactivated():在路由组件不被显示(失活)时,调用该函数
- activated():当路由组件被显示(激活)时,调用该函数
缓存路由需要注意的事项
activated和deactivated是配合keep-alive一起使用的
activated和deactivated没有keep-alive的时候是不会被触发的
路由守卫
全局前置路由守卫
作用:在初始化时、路由切换前调用
//router.js
const router = new VueRouter({
routes:[
...
{
path:'/home',
component:Home,
meta:{
isAuth:true //添加路由元,属性名自定义
}
}
]
})
router.beforeEach((to,from,next)=>{
//to 下一个路径的信息对象,包含path、meta、name...
//from 当前路径的信息对象,包含path、meta、name...
//next() 让路径可以跳转
//首先可以通过meta自定义属性中的isAuth判断下一个路由是否需要路由守卫
if(to.path.meta.isAuth){
if(localStorage.getItem('xxx') === xxxx){
next()
}else{
alert('sorry,you can\'t go on')
}
}else{
next()
}
})
全局后置路由守卫
作用:在初始化时、路由切换后调用;虽然没法拦截,但是可以在路由切换后更改标签或其他操作
const router = new VueRouter({
routes:[
{
path:'/home',
meta:{title:'首页'},
component:Home
},
{
path:'/about',
meta:{title:'相关'},
component:About
}
...
]
})
//没有next
router.afterEach((to,from)=>{
document.title = to.meta.title || '测试'
})
独享路由守卫
作用:单独为一个路由设置守卫,特定路由被切换时调用
export default new VueRouter({
routes:[
...
children:[
{
path:'messages',
component:Messages,
beforeEnter(to,from,next){
if(localStorage.getItem('xxx')==='xxxx'){
next()
}else{
alert('you can\'t go on')
}
}
}
]
]
})
组件内路由守卫
作用:在路由组件中配置路由守卫
//Messages.vue
//通过路由规则,(注意是通过路由规则)进入该路由组件时被调用
beforeRouteEnter(to,from,next){
if(localStorage.getItem('xxx')==='xxx'){
next()
}else{
alert('you can\'t go on')
}
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave(to,from,next){
....
}
路由模式
- hash模式:#后边的值就是hash值,hash值不包含在HTTP请求中,不会带给服务器
- history模式
- 修改模式:
//router.js export default new VueRouter({ mode:'hash', route:[...] })
- 区别
- hash带有#不美观
- hash兼容性比history好
- history在项目部署时要后端人员支持,解决页面刷新时将路径当作资源请求造成的404问题
后端解决方法有:使用node.js中间件connect-history-api-fallback
Vue3
改变
- 改用proxy实现响应式
- 内存更小,更新渲染和初次渲染更快
vue引入方式
比vue2中App更加“轻盈”
import {createApp} from 'vue'
import App from './App'
createApp(App).mount('#app')
template模板可以不在外边使用div包裹全部元素
新增setup配置项
- 数据、方法等写在setup中,通过返回值让模板获取
- setup是所有Composition API(组合API)“ 表演的舞台 ”。
- 可以在配置中写vue2的data、methods,但是vue2配置项中可以获取vue3的配置,vue3获取不到vue2;
- vue3与vue2的配置重名时,vue3优先
- setup返回值有对象,渲染函数:
- 返回对象时在模板中可以直接使用
- 返回一个渲染函数时,可以更改渲染的内容,不根据模板渲染
//setup返回对象,利用作用域减少this使用 import {h} from 'vue' export default { name:'App', setup(){ let name='zhangsan' let age=44 function cslName(){ console.log(name) } return { name, age, cslName } } } //setup返回一个渲染函数,这个渲染函数在vue中获取 ... setup(){ ... //页面仅显示h渲染的内容 return ()=>h('h1','测试') } ...
- 返回对象时在模板中可以直接使用
ref函数
- 作用:定义一个响应式的数据
... setup(){ //基本数据类型还是通过defineProperty()实现响应式 let name=ref('zhangsan') //引用数据类型通过proxy代理实现响应式 let obj=ref({name:'zhangsan',age:22}) } ...
- 操作数据通过xxx.value,模板中直接{{xxx}}