学习笔记第二部分
作用域插槽
使用场景:当插槽中使用的变量不在当前组件内,而在被插入的组件内时,可以通过slot标签进行变量传递
使用作用域插槽,在进行传递DOM的时候,必须使用<template>标签
如下代码:父组件在插槽中用到的变量存在于子组件内,通过对子组件的slot标签添加自定义属性进行存放变量,在父组件使用template标签并添加 scope 或者slot-scope属性(两种属性 效果一致),scope后面的名字自定义,下面第3行的data将会以对象形式存放slot传过来的所有值,在子组件第11行我们传递了两个变量,在父组件的data就是一个包含games和foods的一个对象
作用域插槽也可以指定名字
//@父组件
<Sl>
<template scope="data">
<h2 v-for="(game,index) in data.games" :key="index">{{game}}</h2>
</template>
</Sl>
//@子组件
<div>
<h1>这是插槽</h1>
<slot :games='games' :foot='foods'>哈哈</slot>
</div>
</template>
<script>
export default {
name:"Sl",
data() {
return {
games:['APEX','OwerWatch','CS:GO','盗贼之海'],
foods:['牛肉面','披萨','纳豆','香蕉']
}
},
}
</script>
Vuex
Vuex是专门在vue中实现集中式状态(数据)管理的一个vue插件,对vue多个组件共享的状态进行管理(读/写),也是一种组件间通信的方式,而且适用于任意组件之间
什么时候用?
- 多个组件依赖同一个状态
- 多个组件的行为会改变同一个状态
说白了,就是数据需要共享
用什么版本
Vue2 使用 Vuex3,Vue3 使用 Vuex4
Vuex原理图
根据上图分析,Vuex中有三个配置项 actions、mutations、state 他们都是对象
actions:用于响应组件的操作
mutations:用于执行组件的操作,对状态进行改变
state:用于存储数据
大概流程:组件通过dispatch函数告诉actions函数要执行的操作或者要执行的操作和数据,dispatch的参数,第一个是动作,第二个可选参数是数据,然后actions根据动作名去找对象内的键值对,每一个键名都对应着一个函数,这个函数接收的参数就是dispatch传过来的数据,第一个参数是miniStore,提供了一些store上面的方法,第二个参数是传递过来的值,在actions对象中对应键的函数中手动调用commit方法,commit方法传递两个参数('操作名',数据)并将操作传递到mutations对象中,mutations对象中会根据操作名去找对应的函数,mutations对象中的函数接受两个参数(state,commit传过来的数据),在这个函数中会自动调用mutate方法去改变state中的值,然后Vuex重新渲染界面
上面的流程看起来,actions的存在可有可无,那是因为组件通过dispatch提交的可以只有动作,当只有动作的时候,actions可以通过动作名去向服务器要数据,当组件知道自己要执行什么动作并改变什么数据的时候,组件可以不执行dispatch,直接去执行commit操作
然而,Vuex需要一个 store(仓库) 进行管理,而dispatch和commit等方法都是由store提供的
#重要概念-Vue#:创建store实例前,需要Vue.use(Vuex), import 在 script 的执行优先级最高
store如何建立并配置Vuex
为了规范有两种方法
- 在
「src」下新建「store」再新建index.js进行配置(⭐官方推荐) - 在
「src」下新建「vuex」再新建store.js进行配置
// scr/store/index.js 该文件用于创建最为核心的store
// 1.引入Vuex 和 Vue
import Vue from 'vue';
import Vuex from 'vuex';
// 使用Vuex
Vue.use(Vuex);//在创建 store 实例前必须先使用Vuex
//2.创建 actions、mutations、state对象
// actions 用于响应组件中动作
const actions = {}
// mutations 用于改变stata的值
const mutations = {}
// state 用于存储数据
const state = {}
// 3.创建并暴露 store
export default new Vuex.Store({
// 写入配置项 actions mutations state
actions,
mutations,
state,
});
这是第三步的另一种写法
// 3.创建 store 实例
// const store = new Vuex.Store({
// // 写入配置项 actions mutations state
// actions,
// mutations,
// state,
// });
// // 暴露store
// export default store;
在main.js中
import Vue from 'vue'
import App from './App.vue'
//引入store
import store from './store/index.js'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store,//挂载 store 到vm上
beforeCreate(){
Vue.prototype.$bus = this;
}
}).$mount('#app')
流程中各部分的参数
模拟实现点击一个按钮,使得numberValue加一
组件->actions:使用this.$store.dispatch('jia',1)向actions对象中传入
下图 context 存在的意义是提供了 store 中的一些方法,不光提供了commit方法,还提供了 dispatch 方法,也就是说,在actions中的函数可以调用actions中的其他函数
actions对象内有一个同名的函数,函数接受两个参数,(上下文,value(可选))
const actions = {
jia(context,value){
//使用commit方法提交到mutations
//提交的操作名一般采用同名大写进行区分
context.commit('JIA',value)
}
}
actions -> mutations :上述代码的commit已经将操作和数据传入 mutations中,在mutations中去寻找 JIA 并执行同名函数
const mutations = {
//mutations 内的函数接收两个参数,第一个是State,提供了一个用来操纵state的对象,就当做是state用就行,第二是value
JIA(State,value){
State.numberValue += value;
}
}
store中其他的配置项
getters
getters像是计算属性,由于store是挂载到全局的,所以getters中的函数也是挂载到全局的,主要作用就是提供用于操纵state中数据的方法
const getters = {
//函数接受一个参数,就是state,通过返回值确定最终结果,和计算属性差不多,只不过是全局的
ageFilter(state){
return
}
}
mapState和mapGetter
mapState
解决了为了取用state中的值而代码大量重复的操作,因为每次取用state中的值都需要使用
this.$store.state.value,如果通过computed去写的话,也是需要写大量的计算属性,本质就是return一个值,借助 mapState 可以快速的创建这些函数
使用方法:import { mapState } from 'vuex'
我们有三个方法,分别获取state中的 id、school、name三个属性
有两种方法使用mapState,一种是对象式写法,一种是数组式写法
通过计算属性写
computed:{
toId(){
return this.$store.state.id;
},
toSchool(){
return this.$store.state.school;
},
toName(){
return this.$store.state.name;
},
}
//通过mapState写,注意:也是写在了计算属性里面
import { mapState } from 'vuex'
computed:{
//mapState返回的是一个对象,包含了返回state中数据的函数,通过扩展运算符将对象中的函数展开到 computed 中
//对象式写法
...mapState({toID:'id',toSchool:'school',toName:'name'});
//数组式写法
...mapState(['id', 'school', 'name']);
数组式写法只能写和state中同名的属性,当体量上来了,使用对象式,因为数组式有潜在的变量冲突危险
}
mapGetters
和mapState用法一样,可以使用对象式,也可以使用数组式,记得引入再用
mapActions和mapMutations
mapActions和mapMutations和mapState那些方法一样,都有两种写法:1.对象式 2.数组式
mapMutations
无论使用对象式和数组式,都只是定义了所执行的函数,并为传参,需要自己手动传参,比如下面的第2、3行,如果不写参数,默认会传入event对象
<template>
<button @click="increase(1)">点击我加1</button> //对象式
<button @click="JIA(1)">点击我加1</button> //数组式
</template>
methods: {
// 对象式 mapMutations
...mapMutations({increase:'JIA'})
// 数组式 mapMutations
...mapMutations(['JIA'])
},
mapActions
和mapMutations使用方式一样,不要忘记引入就行
vuex模块化
当组件太多的时候,如果不进行模块化,那么store中的actions、state、mutations都会变的无比庞大,难以维护,所以为每个组件单独划分一个模块是很有必要的
现在的state有如下,假设这是两个组件的数据,Data_v、Id、Age是Student组件中需要共享的数据,School、adress是SchoolInfo组件需要共享的数据,现在要把他们拆分为两个模块studentOptions、schholOptions
Data_v:0,
Id:'2568',
School:'枝江大学',
Age:18,
adress:'赛博枝江10086号路'
在『src』->『store』->「index.js」中,定义两个对象,分别拥有state、actions、mutations、getters属性
// 学生模块对象
const studentOptions = {
namespaced:true,//启用模块空间索引
state: {
Data_v: 0,
Id: '2568',
Age: 18,
},
actions: {},
mutations: {},
getters: {}
};
// 学校模块对象
const schoolOptions = {
namespaced:true,//启用模块空间索引
state: {
adress: '赛博枝江10086号路',
School: '枝江大学',
},
actions: {},
mutations: {},
getters: {}
}
//在配置项中使用modules进行模块对象配置
export default new Vuex.Store({
modules:{
studentOptions,
schoolOptions
}
})
在组件中使用
...mapState('studentOptions',['Data_v', 'Id', 'Age'])
以此类推,其他一样,不要忘了添加 namespaced:true
在组件中不使用map类的方法,手动cmooit怎么写
语法:this.$store.commit('模块名/mutations方法名',value) 怎么这么麻烦,艹了
//以 studentOption 模块中 mutations 中的 JIA函数为演示
methods:{
add(){
this.$store.commit('studentOption/JIA',1)
}
}
模块化语法总结
手动获取state语法:this.$store.state.模块名.具体的键名
手动获取getters语法:this.$store.getter['模块名/具体getters的方法名']
手动获取actions语法:this.$store.dispatch('模块名/具体actions的方法名',value)
手动获取mutations语法:this.$store.commit('模块名/具体mutations方法名',value)
使用mapState获取模块中的state: ...mapState('模块名',['keyname']) 也可以采用对象写法
使用mapGetters获取模块中的getters方法: ...mapGetters('模块名',['keyname']) 也可以采用对象写法
使用mapActions获取模块中的actions方法: ...mapActions('模块名',['方法名'])也可以采用对象写法
使用mapMutations获取模块中的actions方法: ...mapMutations('模块名',['方法名'])也可以采用对象写法
如何跨模块调用 getters、state、actions、mutations?#🎨Question-Vue:#
目前我了解到的只有 actions中调用 其他模块的state、getters、actions、mutations
在actions中函数的第一个参数context中,有rootState和rootGetters属性,通过这两个属性可以访问到其他模块中的state和getters
actions:{
test1(context,value){
console.log(context.rootState.module1.value);//获取别的模块中的state
console.log(context.rootGetters.module1.value);//获取别的模块中的getters
//在actions中调用其他模块的actions
context.dispatch('moudule1/getfunction',{},{root:true})
//参数一:是其他模块的 actions 路径,。
参数二:是传给 actions 的数据, 如果不需要传数据, 也必须预留,
参数三:是配置选项, 申明这个 acitons 不是当前模块的
//在actions中调用其他模块的mutations
context.commit('moudule1/muationfunction',{},{root:true})
参数一:其他模块的 mutations 路径
参数二:传给 mutations 的数据, 如果不需要传数据, 也必须预留,
参数三:第三个参数是配置选项, 申明这个 mutations 不是当前模块的
}
}
#🎨Question-Vue:#vuex模块化在引用时为什么无效呢?
在配置项中注册之后,如果想用vuex提供的map方 .+法去寻找对应的模块,需要在模块对象中加入 namespaced:true
Vue路由 Router
基本路由的使用
router:路由器 route:路由
-
下载
vue-router vue2使用vue-router3 vue3使用vue-router4npm install vue-router@3 -
引入vue-router并使用vue-router插件
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) -
创建路由器并进行配置,在『src』->『router』->「index.js」
import VueRouter from 'vue-router' import Home from '../components/Home.vue' import About from '../conponents/About.vue' export default new VueRouter({ //使用routes进行配置 routes:[ { path:'/home', component:Home }, { path:'/about', component:About }, ] }) -
引入路由器并进行配置路由器
main.js import router from './router/index.js' new Vue({ render :h => h(app), router//配置路由器 }) -
在组件中使用路由
router-link标签会默认解析为 a 标签
<router-link to="路由地址" active-class="激活时自动添加的类"> <router-link to="/home" active-class="avtive"> <router-view></router-view>//路由显示组件的地方
开发中:路由组件会单独放在『pages』文件夹,一般组件会放在『components』文件夹中,路由组件在切换时,默认是被销毁的
嵌套路由
在一级路由的配置项中写 children:[ { } ]进行配置,子级路由的path不需要写 /,但是在router-link标签的to属性要写完整的跳转路径
routes: [
{
path: '/home',
component: Home,
children: [//子级路由
{
path: 'message',
component:Messages,
},
{
path: 'news',
component:News,
}
]
}];
//在标签中写
<router-link to='/home/news'>News</router-link>
<router-link to='/home/message'>Message</router-link>
路由query传参
在路径中使用query传参,比如: /home/message/detail?id=001&title=这是传的参数
使用 ? 号作为开始,使用 & 进行连接
如果想在vue传动态参数,需要在使用v-bind绑定to属性,然后在引号内使用模板字符串就可以动态传参
解释:如果使用了v-bin,引号里面的内容会直接被解析为变量,再使用模板字符串就会被解析为字符串,就可以进行赋值
//to的字符串写法
<router-link :to='`/home/message/detail?id=${item.id}&title=${item.title}`'>{{ item.title }}</router-link>
to的对象写法传参
<router-link :to='{
path:'/home/message/detail',
query:{
id:item.id,
title:item.title
}
}'>
{{ item.title }}
</router-link>
如何接收参数?
在路由组件中都存在一个属性对象 route.query.values`进行取值
路由params参数
使用params传参,如果要向detail组件传id:666和title:'太秀了',两个参数在to写 to="/home/message/detail/666/太秀了"
在路由的『index.js』中对应的配置项中的path写入: path:'detail/:id/:title',如果要传参数,就要在path中去声明参数
取参数:使用$route.params.参数名进行取参数
注意:如果使用params传参,并且to使用了对象式的写法,那么只能使用name不能使用path
//路由配置
path:'detail/:id/:title'
//router-link 中的传参
<router-link to="`/home/message/detail/${item.id}/${item.title}`"> </router-link>
//在detail组件中取用
$route.params.id
$route.params.title
命名路由
当有了多级路由之后,path路径就变得特别繁琐,为了解决这个问题,在to的对象式写法中,不使用path写路由,使用name属性进行传name,在路由设置中也要设置相应的name,采用对象式写法,to一定要使用双引号进行包裹
路由的props
使用传统的 query 传参和 params 传参,当数据量上来之后,组件中的逻辑就很复杂了,为了解决这个问题,使用路由的props传参
路由的props配置在相应的路由配置中,写入props属性,有三种写法
1.对象式写法 缺点:只能传死的数据,使用场景很少,可以用于调试
props:{ }
2.布尔型写法 缺点:只能传递params参数
props:true
3.函数式写法 缺点:写的麻烦,props函数接受一个参数,是接收数据的组件的$route,通过route可以取用 query 和 params 的参数
可以使用解构进行只取$route中的 query 或者 params 参数
props($route){
return { }
}
在组件中接受参数:就用props接就行,没变化
路由的replace属性
控制路由跳转时操作浏览器操作历史记录的操作
默认使用push模式,也叫压栈,每一次的跳转都会被记录到栈里面,可以前进后退
对 router-link标签添加 replace="true"属性,简写为replace,使用replace模式,他会替换掉栈底部的记录
编程式路由导航
不使用 router-link 标签进行跳转,使用函数进行跳转,为了解决 router-link 会解析为 a 标签的问题,就可以使用函数式,借助了全局API $router,上面存在了两种方法 push 和 replace对应着两种跳转方式
//push方法
pushShow(m) {
this.$router.push({
name: "xiangqing",
params: {
id: m.id,
title: m.title,
},
});
},
//replace方法
replaceShow(m) {
this.$router.replace({
name: "xiangqing",
params: {
id: m.id,
title: m.title,
},
});
},
$router上一些其他的方法
$router.back():相当于浏览器的后退
$router.forward():相当于浏览器的前进
$router.go(num):根据参数num的正负,决定前进几步或后退几步
缓存路由组件
默认情况下,在切换路由是,被切换的路由组件会被销毁,有时的需求是不需要被销毁的,比如表单中数据,这时需要找到不想被销毁的组件相对应的 <router-view>标签,比如News这个组件展示在Home这个组件中,就要去Home组件中的<router-view>标签,然后使用<keep-alive>标签包裹起来,这样默认是全部都缓存,使用<keep-alive include="组件名">指定需要换成的路由组件
include中所写的组件名,就是组件中的name属性,组件名可以是个数组,用来缓存指定的多个路由组件
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
路由特有的生命周期函数
在设置了缓存路由之后,如果一个组件上有定时器之类的功能,在切出这个组件的时候我希望这个组件的定时器被清除,但是又给这个组件设置了缓存,就需要使用路由的两个特殊的生命周期函数
这两个生命周期函数只有在使用了 <keep-alive> 标签的时候才会被调用
activated(){ }:路由组件被激化时触发
deactivated(){ }:路由组件被切换时触发
路由守卫
路由守卫是为了管理权限的,让页面应用的跳转更为安全
如果要使用路由守卫,就不能直接暴露路由配置,new一个VueRouter实例并命名,在这个实例上去调用一些方法进行设置,以下说的router都指的是VueRouter实例
全局前置路由守卫
router.beforEach((to,from,next)=>{ }):这个方法接受一个函数参数,他会在每一次的路由切换之前执行函数内容,初始化也会被调用,函数有三个参数,to、from、next,to和from是对象,分别给出要去到哪个组件的对象和从哪个组件来的对象,next是一个方法,调用此方法才能切换路由
如果有些界面不需要进行权限校验,那么我们就需要一个标志位去判断是否需要校验,在路由的配置对象中,提供了一个meta的属性对象用来写入自定义的属性
全局后置路由守卫
router.afterEach((to,from)=>{ }),在路由切换后要干的事情,已经已经通过全局前置路由守卫校验过了,这里就干一些校验过后的事,比如修改网页的标题
独享路由守卫
当不对全局进行设置,只想对某一个路由组件设置路由守卫时,在相应组件的配置项中写入配置beforeEnter:(to,from,next) = { },写法和全局一样,独享的路由守卫只有前置没有后置!,但是可以和全局后置路由守卫一起使用
组件内路由守卫
就是他的名字,能写在组件配置里面的路由守卫
注意:只有通过路由方式切换时才会被触发
beforeRouteEnter (to, from, next) { }:通过路由进入到此组件触发,使用next方法控制是否跳转
beforeRouteLeave (to, from, next) { }:通过路由离开到此组件触发,使用next方法控制是否跳转
history模式和hash模式
默认使用的hash模式:浏览器地址#号后面(包括#)的字符都做为hash值,不会传递给服务器
TodoList开发
- 拆组件
- 分析数据、确定数据格式
- 确定API
先拆组件,先写结构和样式,再进行数据的交互
数据在哪个组件,操作数据的方法就在哪个组件,如果是一个组件在使用的数据,就放在这个组件就行,如果一个数据是一些组件在用,就把数据放在他们共同的父亲,将数据放到父组件中,这种行为叫做状态提升
v-model可以改变props传递过来的对象内的属性值,但是不推荐这样用,违反了props的单向数据原则
$nextTick:在下一次DOM节点更新后再执行,用于解决关于获取焦点的问题