大学生自学一个月Vue笔记,微瑕(第二部分)

159 阅读9分钟

学习笔记第二部分

作用域插槽

使用场景:当插槽中使用的变量不在当前组件内,而在被插入的组件内时,可以通过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多个组件共享的状态进行管理(读/写),也是一种组件间通信的方式,而且适用于任意组件之间

什么时候用?

  1. 多个组件依赖同一个状态
  2. 多个组件的行为会改变同一个状态

说白了,就是数据需要共享

用什么版本

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

为了规范有两种方法

  1. 「src」​下新建「store」​再新建index.js​进行配置(⭐官方推荐)
  2. 「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:路由

  1. 下载 vue-router​ vue2使用vue-router3 vue3使用vue-router4

    npm install vue-router@3
    
  2. 引入vue-router并使用vue-router插件

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
  3. 创建路由器并进行配置,在『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      
            },
        ]
    
    })
    
  4. 引入路由器并进行配置路由器

    main.js
    
    import router from './router/index.js'
    new Vue({
        render :h => h(app),
        router//配置路由器
    })
    
  5. 在组件中使用路由

    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属性对象,在这个对象里有路由传的参数使用route ,在这个对象中有 query属性对象,在这个对象里有路由传的参数 使用`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开发

  1. 拆组件
  2. 分析数据、确定数据格式
  3. 确定API

先拆组件,先写结构和样式,再进行数据的交互

数据在哪个组件,操作数据的方法就在哪个组件,如果是一个组件在使用的数据,就放在这个组件就行,如果一个数据是一些组件在用,就把数据放在他们共同的父亲,将数据放到父组件中,这种行为叫做状态提升​​

v-model可以改变props传递过来的对象内的属性值,但是不推荐这样用,违反了props的单向数据原则

$nextTick:在下一次DOM节点更新后再执行,用于解决关于获取焦点的问题