前言

基础篇链接:https://www.cnblogs.com/zixq/p/15782921.html

组件化开发篇链接:https://www.cnblogs.com/zixq/p/15823605.html

高级篇

组件的自定义事件

绑定自定义事件

这玩意儿也是为了子父组件间的通信。

这里实现方式有两种:

  • 第一种:用 v-on + VueComponent.$emit实现【PS:此种方式有点类似子传父 】;
  • 第二种:用 ref属性 + mounted()来实现【 此种方式:复杂一点,但是更灵活 】。

两种实现方式都可以,而二者的区别和computed与watch的区别很像。

  • PS:第二种方式可以实现异步操作,如:等到ajax发送请求得到数据之后,再进行事件绑定。

接下来看实例,从而了解更明确点,用如下实例做演示:

  • PS:不用前面玩的子传父,但是:自行可以先回顾一下子传父的传统方式。

image

v-on + VueComponent.$emit 实现

父给子一个函数,子执行,参数即为父要得到的数据。

image

image

image

ref属性 + mounted() 实现

在前面实现的基础上,子组件代码不变,在父组件中加入如下的代码:

image

image

mounted()中是可以进行异步操作的,所以才可以让这种自定义事件更灵活。

另外:既然是事件,那么也可以使用事件修饰符:prevent、stop、once

  • v-on 中,这三者和以前一样的玩法,都是加在事件名后面即可,如:@zixeiqing.once = "xxxxx"
  • 在ref属性中,是用在this.$refs.person.$on('zixieqing',this.demo )中的$on这里的,once就是使用$once,替换掉原来的$on

解绑自定义事件

这玩意用的就是 VueComponent.$off( ['要解绑的事件名'] ) 这个内置函数来实现解绑的。

自定义事件的核心话:给谁绑定事件,那么事件就在谁身上;给谁解绑自定义事件,那么就去谁身上解绑。

  • 当然:数组[ ]中,如果是解绑单个事件,那么[ ]这个括号不要也行;
  • 如果是解绑多个自定义事件,那么使用 , 逗号隔开即可;
  • 另外:$off()不传递参数时,默认是把组件的所有自定义事件都解绑了( PS:有一个解绑一个);

image

另外:前面玩Vue生命周期的beforeDestroy时有一个小点只是简单提了一下,生命周期图如下:

image

上图中的内容,有最后一点没有验证:

image

说在beforeDestroy中,会销毁子组件和自定义事件、说此时销毁自定义事件:

image

image

而所谓的销毁子组件也就好理解了,就是把父组件销毁之后,那么:子组件也活不成了。

  • PS:要验证的话,可以使用销毁vm,然后看旗下的子组件还能活不?答案肯定是活不成的。

自定义事件中的两大坑

子组件是如下的样子:

image

ref属性中,this指向问题

在ref属性实现的方式中,关于this的指向问题。

  1. 第一种就是将回调函数放到父组件的methods中。

image

image

此种方式,会发现this指向的是父组件。

  1. 第二种:将回调函数直接放到 this.$refs.people.$on( 'event',xxxx ) ] 中的xxxx中。

image

image

这种情况:会发现,this不再是父组件实例对象,而是子组件的实例对象,但是:可以让它变为子组件实例对象。

  • PS:把回调的普通函数写成兰姆达表达式就可以了。

image

image

组件用原生DOM事件(native修饰符)

组件使用原生DOM事件的坑【 PS:了解native修饰符 】。

image

image

image

image

自定义事件总结

  1. 自定义事件是一种组件间通信的方式,适用于:子组件 ——> 父组件通信。

  2. 使用场景:想让子组件给父组件传递数据时,就在父组件中给子组件绑定自定义事件(PS:事件回调在父组件methods / 其他地方 中),而要解绑自定义事件就找子组件本身。

  3. 绑定自定义事件:

  • 1)、在父组件中:<Person @zixieqing="demo"/><Person v-on:zixieqing="demo"/>

  • 2)、在父组件中:

<Person ref = "demo"/>
	.........
methods: {
	test(){......}
}
	.........
mounted(){
	this.$refs.demo.$on('eventName',this.test)
}
  • 3)、若想让自定义事件只能触发一次,可以使用once修饰符【PS:使用v-on的方式实现的那种 】 或 $once【PS:使用ref属性实现方式的那种 】。
  1. 触发自定义事件: this.$emit('eventName',sendData) 【PS:给谁绑定自定义事件,就找谁去触发 】。
  2. 解绑自定义事件:this.$off(['eventName',.......]) 【PS:给谁绑定自定义事件,就找谁解绑;另:注意解绑事件是单个、多个、全解的写法 】。
  3. 组件上也可以绑定元素DOM事件,但是:需要使用native修饰符。
  4. 注意项:通过this.$refs.xxxx.$on('eventName',回调)绑定自定义事件时,回调要么配置在父组件的methods中,要么用兰姆达表达式【PS:或箭头函数 】,否则:this执行会出现问题。

全局事件总线

这玩意儿吧,不算知识点,是开发中使用的技巧而已,里面包含的知识在前面全都玩过了,只是:把知识做了巧妙的应用,把自定义事件变化一下,然后加上Vue中的一个内置关系VueComponent.prototype._ _proto _ _ === Vue.prototype从而实现出来的一个开发技巧

此技巧:可以实现任意组件间的通信。

疏通全局事件总线逻辑

image

但是:现在把思路换一下来实现它

image

通过上面的分析图了解之后,就可以分析出,单独选取的那个组件需要具有如下的特性:

  1. 此组件能够被所有的组件看到。
  2. 此组件可以调用$on()$emit()$off()
能让所有组件都看得到

那么为了实现上一节结论的第一步:能够让所有的组件都看得到,可以怎么做?

  1. 使用window对象,可以做到,但是:不推荐用。

image

image

image

此种方式不推荐用呢,是因为:本来就是框架,谁还去window上放点东西呀,不是找事吗。

  1. 就是利用Vue中的内置关系VueComponent.prototype._ _proto _ _ === Vue.prototype

即:公共组件选为Vue实例对象vm。这个关系怎么来的,这里不再说明了,在基础篇VueComponent()中已经说明过了,利用此内置关系就是利用:VueComponent可以获取Vue原型上的属性和方法,同时选取了Vue实例对象之后,$on()$emit()$off()都可以调用了,这些本来就是Vue的内置函数,Vue实例对象还没有这些函数吗

全局事件总线实例

实现方式:

  • vm + beforeCreate()【ps:初始化嘛,让关系在一开始就建立】;
  • mounted() + $on() + $emit() + beforeDestroy()【ps:做收尾工作,解绑自定义事件 】+ $off()

实例演示:

image

注:上图中的$bus是自己起的名字,开发中一般起的都是这个名字,bus公交车嘛,谁都可以上,而且还可以载很多人,放到组件中就是:谁都可以访问嘛,加了一个$是因为迎合Vue的设计,内置函数嘛,假装不是程序员自己设计的(PS:实际上,bus还有总线的意思)。

image

image

image

全局事件总线总结

  1. 全局事件总线又名GlobalEventBus。

  2. 它是一种组件间的通信方式,可以适用于任何组件间通信。

  3. 全局事件总线的玩法:

  • 1)、安装全局事件总线。
new Vue({
    .......
    beforeCreate(){
        Vue.prototype.$bus = this
    },
    ......
})
  • 2)、使用事件总线。
    • 发送数据:this.$bus.$emit('EventName',sendData)

    • 接收数据:A组件想接收数据,则:在A组件中给$bus绑定自定义事件,把事件的回调放到A组件自身中【PS:靠回调来得到数据 】

// 使用methods也行;		不使用,把回调放到$on()中也可以		推荐使用methods,因为不必考虑$on()中的this问题
methods: {
    sendData(){ ...... }
},
........
mounted(){
    this.$bus.$on('eventName',receiveData)
},
.......
beforeDestroy(){
    this.$bus.$off([ 'eventName' , ..... ])
}

消息订阅与发布

什么是消息订阅与发布?

  • 这个东西每天都见到,就是:关注,关注了人,那别人发了一个通知 / 文章,自己就可以收到,这就是订阅与发布嘛。
  • 这里使用pubsub-js这个库来演示(使用其他库也行,这些玩意儿的思路都一样 ,这是第三方库啊,不是vue自己的),其中:
    • pub:就是publish,推送、发布的意思。
    • sub:就是subscribe,订阅的意思。
    • 也就是:一方发布、一方订阅嘛。

玩一下pubsub-js

基础代码:

image

image

  1. 给项目安装pubsub-js库。指令:npm install pubsub-js

image

  1. 消息发布方
  • 2.1、引入pubsub-js库。
  • 2.2、使用 publish( 'msgName' , sendData ) 这个API进行数据发送。

image

  1. 消息接收方
  • 3.1、引入pubsub-js库。
  • 3.2、使用 subscribe( 'msgName' , callback ) 这个API利用回调进行数据接收。
  • 3.3、关闭订阅。

image

效果如下:

image

消息订阅与发布总结

  • 它是一种组件间通信的方式,适用于:任意组件间通信。

  • 使用步骤:

    • 1)、安装pubsub-js 指令:npm install pubsub-js

    • 2)、消息接收方、发送方都要引入pubsub 代码;:import pubsub from "pubsub-js"

      • 数据发送方:pubsub.publish('msgName',sendData)

      • 数据接收方:

// methods可写可不写		推荐写,不用考虑this的指向问题,和自定义事件一样的
methods: {
	demo(){.....}
}
........
mounted(){
	// 使用this.msgName把每条订阅都绑在组件实例对象vc上,方便取消订阅时获取到这个订阅id
	this.msgName = pubsub.subscribe('msgName', callback)	// 如果不写methods,那么回调就写在这里,注意:使用箭头函数
}
.......
beforeDestroy(){
	pubsub.unsubscribe( this.msgName )
}

认识$nextTick()

  • 用法:this.$nextTick(回调函数)
  • 作用:在下一次DOM更新结束后执行其指定的回调。

PS:让代码的解析产生时间差,因为:有些东西等一部分视图已经出来了再把弄外的东西加上,如:input输入框,聚焦的问题,就需要等input到了页面,然后再把焦点放上去。

  • 什么时候用:在改变数据后,要基于更新后的新DOM进行某些操作,要在nextTick所指定的回调函数中执行(setTimeout定时器也可以做,只是官网推荐用这个)。

Vue封装的过度与动画

Vue中实现动画的套路

样式还是要自己写,只是:调用样式的逻辑Vue进行了封装,只需要按照标准把东西配好,Vue就会自动去帮忙执行动画的样式。

<template>
    <div>
        <button @click="isShow = ! isShow">显示/隐藏</button>
        <!-- 
            使用一个Vue的特定标签【PS:只限于标签中只有一个元素要有动画时( 即:h2中的东西 ) 】
            【PS:和template标签一样,在页面中不会解析,是Vue中特定的名字而已 】
            另外:还有一个标签transition-group是玩多个元素都有过度效果的( 如:多个h2元素也要过度 )
            【PS:但是需要给每个元素匹配一个key="xxx"属性,此种方式:可以实现多个元素相反过度,一个元素怎样
            另一个元素就不怎样之类的 】
         -->
        <transition appear>
            <!-- 
                谁要实现动画,那就把这个标签套在谁身上,其中:appear是设置 页面初始状态的,
                    即:页面一开始就有动静效果【 设置等价于 :appear = "true"  】
                另外:此标签还有一个name属性  
                但是:这里如果使用了name="xxx",
                那么:下面Vue套路处的v-enter-active中的v就要换成xxx,不写name默认就是v
                下面都是自己写动画:可以用第三方库,如:npm中的animate.css进去就知道怎么玩了
             -->
            <h2 v-show="isShow">玩Vue中编写动画的套路</h2>
        </transition>
    </div>
</template>

<script>
    export default {
        name: 'Cartoon',
        data() {
            return {
                isShow: true
            }
        },
    }
</script>

<style scoped>
    /* 1、动画样式还是要自己写 */
    h2 {
        background: purple;
        color: white;
    }
    @keyframes zixieqing {
        /* 从哪里来? */
        from {
            transform: translateX( -100px )
        }
        /* 到哪里去? */
        to {
            transform:translateX( 0px )
        }
    }


    /* 
      2、使用Vue的套路,让其执行动画的逻辑 
        下面的两个名字是固定的,不能改
            【ps: 若transition中加了name="xxx",那么下面的v必须为xxx,否则:不生效】
            实现动画用下面两个类名即可,要实现过度效果,需要再借助另外的类名:v-enter、v-enter-to 和 v-leave、v-leave-to
    */
    .v-enter-active {
        animation: zixieqing 0.5s linear;
    }
    .v-leave-active {
        animation: zixieqing 1s reverse;
    }
</style>

Vue中的过渡效果

image

Vue封装的过度 与 动画总结

作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加"样式类名"。

官网中的图示:

image

写法:

  1. 自己准备好样式
  • 元素进入的样式(下面的类名不可以改,把对应样式写在里面,然后搭配 transition / transition-group 标签即可):
    • 1)、v-enter:进入的起点。
    • 2)、v-enter-active:进入过程中。
    • 3)、v-enter-to:进入的终点。
  • 元素离开的样式:
    • 1)、v-leave:元素离开的起点。
    • 2)、v-leave-active:元素离开过程中。
    • 3)、v-leave-to:元素离开的终点。
  1. 使用 transition 标签包裹要过度的元素,并配置 name 属性:
<transition name="demo">
	<h2 v-show="isShow">
        这是单个元素过度使用的transition标签
    </h2>
</transition>
  • 备注:若有多个元素需要进行过度(如上面例子中还要多个h2元素),则:需要使用 <transition-group>来包裹过度元素,且每个元素都要使用key="xxx"属性来指定唯一标识。

另外:可以使用第三方库;如:npm中的animate.css

image

image

image

Vue中解决跨域问题

学了Nginx,那么熟悉得不得了,为什么有Nginx,一是跨域、二是解决负载均衡的问题。

image

使用vue cli 的devServer.proxy配置

这个东西在vue cli官网中有(ps:是修改默认配置vue.config.js中的一种配置)。

  1. 配置默认配置

image

  1. 使用axios发起请求
  • 给项目安装axios插件。指令:npm install axios

  • 在代码中引入axios。代码:import axios from "axios"

  • 使用axios。代码:

axios.get / post('ip:port/sourceName'),then(    // port是请求地的port,如:8080 ——> 8081,此时port就是8080
    // 成功时
    response =>{
        // response是一个对象,数据在这个对象的data中
        console.log( response.data )
    },

    // 失败时
    error => {
        // error也是一个对象,失败信息在message中
        console.log( error.message )
    }
)
  1. 经过上述操作就可解决跨域问题,但是有缺陷:

1)、使用vue cli解决跨域时,只能请求一台服务器

image

2)、如果发起请求的地方中已经有了要请求的sourceName资源名,那么:优先获取发起请求地中的资源

image

那么就会导致:vue不会去vue cli中开启的代理服务器获取资源,而是直接在本地获取

使用进化版 devServer.proxy

  1. 配置默认设置vue.config.js

image

module.exports = {
    devServer = {
    	proxy: {
    		'/api': {	// 匹配所有以 '/api' 开头的请求路径,此名字可自定义
    			target: 'http://localhost:8081',  // 资源所在地的请求路径【PS:不加sourceName资源名 】
    			ws: true,	// 是否支持websocket 【PS:vue中默认值就是true,react中是false 】
    			changeOrigin: true,		// 代理服务器是否要伪装请求路径【PS:vue中默认值就是true,react中是false 】
    			pathRewrite: { '^/api': '' }	// 去掉请求路径中的 api 前缀名  如:ip:port/api/student
			},

            // 配置多套代理
            '/api2': {
    			target: 'http://localhost:8082',
    			ws: true,
    			changeOrigin: true,
    			pathRewrite: { '^/api2': '' }
			}
		}
	}
}

其中:

  • target:就是资源所在的服务器地址 http://ip:port
  • ws:就是是否支持websocket。
  • changeOrigin:就是代理服务器在请求资源地服务器时,代理服务器是否伪装路径

PS:假设资源地服务器地址是8081,而请求地是8080,则:此配置就是代理服务器在帮忙请求8081时,把自身伪装成8081,因为:有些服务器是有限定的,不是同路径不能访问资源。

  • 如果要配置多套代理,那么:复制粘贴,改成对应的服务器即可。
  1. 在代码中使用axios发起请求。
axios.get / post('http://ip:port/preName/sourceName'),then(	// 其中:preName就是vue.config.js中配置的'/api'这个名字
    response => {
        console.log( response.data )
    },

    error => {
        console.log( error.message )
    }
)

其中:

  • 加上preName 路径前缀名就是为了控制是否走代理服务器(为了解决前面本地中有资源时,默认获取本地资源的问题)。
  • 不加preName 就是默认走本地,不走代理服务器。

vue cli的两种跨域配置总结

第一种方式:

  • vue.config.js中添加如下配置:
devServer: {
    proxy: 'http://ip:port'  // ip和port为资源所在地的port
}
  • 优点:配置简单,请求资源时直接发给发起请求的地方。

  • 缺点:不能配置多个代理、不能灵活控制是否走代理。

  • 工作方式:优先走本地【PS:发起请求的地方 】,没有要找的资源才去配置的服务器中找。

第二种方式:

  • vue.config.js中添加如下配置:
module.exports = {
    devServer = {
    	proxy: {
    		'/api': {	// 匹配所有以 '/api' 开头的请求路径,此名字可自定义
    			target: 'http://localhost:8081',  // 资源所所在地的请求路径【PS:不加sourceName资源名 】
    			ws: true,	// 是否支持websocket 【PS:vue中默认值就是true,react中是false 】
    			changeOrigin: true,		// 代理服务器是否要伪装请求路径【PS:vue中默认值就是true,react中是false 】
    			pathRewrite: { '^/api': '' }	// 去掉请求路径中的 api 前缀名  如:ip:port/api/student
			},

            // 配置多套代理
            '/api2': {
    			target: 'http://localhost:8082',
    			ws: true,
    			changeOrigin: true,
    			pathRewrite: { '^/api2': '' }
			}
		}
	}
}

  • 使用axios发起请求即可
axios.get / post('http://ip:port/preName/sourceName'),then(	// 其中:preName就是vue.config.js中配置的'/api这个名字
    response => {
        console.log( response.data )
    },

    error => {
        console.log( error.message )
    }
)

注意:这两种配置不能同时存在,因为它们都是占用了proxy这个对象【PS:二者都是配置在这里面的,写法不一样 】

插槽

基础代码:

image

image

image

默认插槽

此种插槽适合只占用一个位置的时候。

需求、让食品分类中显示具体的一张食品图片、让电影分类中显示某一部具体的电影,使用默认插槽改造。

<template>
  <div class="container">
    <Category title = "食品">
      <!-- 2、将内容套在组件标签里面从而携带到slot处 
              此种方式:是vue在解析完App这个组件的模板时,
                        将整个组件中的内容给解析完了,然后放到了Category组件里面使用slot占位处
                        所以:slot所放位置 和 这里面代码解析之后放过去的位置有关
              另外:由于是先解析完APP组件中的东西之后 再放到 所用组件里面slot处的位置
                    因此:这里可以使用css+js,这样就会让 模板 + 样式解析完了一起放过去
                          不用css+js就是先把模板解析完了放过去,然后找组件里面定义的css+js
      -->
      <img src="./assets/food.png" alt="照片开小差去了">
    </Category>

    <Category title = "游戏">
      <ul>
          <li v-for=" (game,index) in games" :key="index">{{game}}</li>
      </ul>
    </Category>

    <Category title = "电影">
      <video controls src="./assets/枕刀歌(7) -  山雨欲来.mp4"></video>
    </Category>
  </div>
</template>

<script>
import Category from "./components/Category.vue"
  export default {
    name: 'App',
    components: {Category},
    data() {
      return {
        foods: ['紫菜','奥尔良烤翅','各类慕斯','黑森林','布朗尼','提拉米苏','牛排','熟寿司'],
        games: ['王者荣耀','和平精英','英雄联盟','文明与征服','拳皇','QQ飞车','魔兽争霸'],
        filems: ['无间道','赤道','禁闭岛','唐人街探案1','肖申克的救赎','盗梦空间','无双']
      }
    },
  }
</script>

<style>
  .container {
    display: flex;
    justify-content: space-around;
  }

  img,video {
    width: 100%;
  }
</style>

image

image

具名插槽

指的就是有具体名字的插槽而已,也就比默认插槽多了两步罢了。

在使用slot进行占位时利用name属性起一个名字,然后在传递结构时使用slot="xxx"属性指定把内容插入到哪个插槽就OK了。

需求、在电影分类的底部显示"热门"和"悬疑*。

image

image

image

作用域插槽

这个玩意儿的玩法和具名插槽差不多,只是多了一点步骤、多了一个要求、以及解决的问题反一下即可。

基础代码:

image

image

image

需求:多个组件一起使用,数据都是一样的,但是有如下要求:

  • 数据显示时要求不是全都是无序列表,还可以用有序列表、h标签......
  • 要求data数据是在子组件中【PS:就是Category中 】,而结构需要写在父组件中【PS:就是App中 】,也就是父组件要拿到子组件中的data。
  • 利用前面已经玩过的子父组件通信方式可以做到,但是麻烦。因此:改造代码。

改造代码:

  1. 第一步:提data。

image

查看效果:

image

可能会想:明明可以将data放到父组件中 / 将结构ul放到子组件中,从而实现效果,为什么非要像上面那样折腾,没事找事干?开发中有时别人就不会把数据交给你,他只是给了你一条路,让你能够拿到就行。

  1. 第二步:使用作用域插槽进行改造。

image

image

image

image

既然作用域插槽会玩了,那就实现需求吧。

image

image

image

另外:父组件接收数据时的scope还有一种写法,就是使用slot-scope

image

插槽总结

作用:让父组件可以向子组件指定位置插入HTML结构,也是一种组件间通信的方式,适用于:父组件 ===》 子组件

分类:默认插槽、具名插槽、作用域插槽。

使用方式:

  1. 默认插槽
// 父组件
<Category>
    <div>
        HTML结构
    </div>
</Category>

// 子组件
<template>
    <div>
        <!-- 定义插槽 -->
        <slot>插槽默认内容</slot>
    </div>
</template>
  1. 具名插槽
// 父组件
<template>
    <!-- 指定使用哪个插槽 -->
    <Category slot = "footer">也可以加入另外的HTML结构</Category>
</template>



// 子组件
<template>
    <div>
        <!-- 定义插槽 并 起个名字-->
        <slot name = "footer">插槽默认内容</slot>
    </div>
</template>
  1. 作用域插槽
// 子组件
<template>
  <div class="category">
      <h3>{{title}}分类</h3>
      <!-- 作用域插槽
        1、子组件( 传递数据 ) 
            提供一个路口,把父组件想要的数据传给它【PS:有点类似于props的思路,只是反过来了 】
            :filems中的filems就是提供的路,给父组件传想要的东西【PS:对象、数据都行 】
      -->
      <slot :filems = "filems">这是默认值</slot>
  </div>
</template>

<script>
    export default {
        name: 'Category',
        props: ['title'],
        data() { // 数据在子组件自身中
            return {
                filems: ['无间道','赤道','禁闭岛','唐人街探案1','肖申克的救赎','盗梦空间','无双']
            }
        },
    }
</script>


// 父组件
<template>
  <div class="container">
    <Category title = "电影">
      <!-- 
        2、父组件( 接收数据 )
          前面说的 多了一个要求   就是这里"必须用template标签套起来"
          怎么接收?使用scope="xxxx"属性   xxx就是接收到的数据,这个名字随便取
              这个名字不用和子组件中用的 :filems 这个filems这个名字保持一致,因:它接收的就是这里面传过来的东西
              但是:这个数据有点特殊,需要处理一下
       -->
       <template scope="receiveData">
         <!-- {{receiveData}} -->
         <!-- 拿到了数据,那"页面的结构就可以随插槽的使用者随便玩"了 -->
         <ul>
           <li v-for="(filem,index) in receiveData.filems" :key="index">{{filem}}</li>
         </ul>
       </template>
    </Category>

    <Category title = "电影">
      <!-- ES6中的"结构赋值"简化一下 -->
       <template scope="{filems}">
         <ol>
           <li v-for="(filem,index) in filems" :key="index">{{filem}}</li>
         </ol>
       </template>
    </Category>

    <Category title = "电影">
      <!-- ES6中的"结构赋值"简化一下 -->
       <template scope="{filems}">
          <h4 v-for="(filem,index) in filems" :key="index">{{filem}}</h4>
       </template>
    </Category>

    <!-- scope还有一种写法 使用slot-scope-->
    <Category title = "电影">
      <!-- ES6中的"结构赋值"简化一下 -->
       <template slot-scope="{filems}">
          <h4 v-for="(filem,index) in filems" :key="index">{{filem}}</h4>
       </template>
    </Category>
  </div>
</template>

<script>
import Category from "./components/Category.vue"
  export default {
    name: 'App',
    components: {Category},
  }
</script>

状态机 Vuex

概念:在Vue实例中集中式状态(数据,状态和数据是等价的)管理的一个插件。说得通俗一点就是:数据共享,对多个组件间的同一个数据进行读/写,不就是集中式管理了吗(对立的观点就是"分布式",分布式是后端的人玩的)。

github地址:https://github.com/vuejs/vuex

什么时候用vuex?

  1. 多个组件依赖同一状态(数据)【PS:两个帅哥,都想要同一个靓妹】。
  2. 不同组件的行为 需要 变更同一状态【PS:洗脚城的两个妹子 都想要把 客户钱包中的money变到自己荷包中】。
  3. 上面的内容用一句话概括:多组件需要分享数据时就使用Vuex

Vuex 原理

首先这个东西在官网中有,但是:不全。

image

所以:把官网的原图改一下。

image

上述内容只是对原理图有一个大概认识而已,接下来会通过代码逐步演示就懂了。

搭建Vuex环境

  1. 在项目中安装vuex。指令:npm install vuex

  2. 编写store。

image

  1. 让store在任意组件中都可以拿到。

image

image

这样store就自然出现在其他组件身上了【PS:利用了vc和vm的内置关系 ,验证自行验证,在其他组件中输出this就可以看到了】。

小结:vuex环境搭建

  1. 创建文件 src/store/index.js
// 引入vuex
import vuex from "vuex"

// 使用vuex —— 需要vue 所以引入vue
import Vue from "vue"
Vue.use(vuex)


// 创建store中的三个东西actions、mutations、state
const actions = {}

const mutations = {}

const state = {}

// 创建store ———— 和创建vue差不多的套路
export default new vuex.Store({
    // 传入配置项 ———— store是actions,mutations,state三者的管理者,所以配置项就是它们
    actions,    // 完整写法 actions:actions ,是对象嘛,所以可以简写
    mutations,state
})
  1. main.js 中配置store。
import App from "./App.vue"
import Vue from "vue"

// 引入store
import store from "./store"     
// 由于起的名字是index,所以只写./store即可,这样默认是找index,没有这个index才报错

const vm = new Vue({
    render: h=>h(App),
    components: {App},
    // 让store能够被任意组件看到 ———— 加入到vm配置项中【PS:和全局事件总线很像 】
    store,
    template: `<App></App>`,
}).$mount('#app')

简单玩一下Vuex的流程

对照原理图来看:

image

  1. 先把要操作的数据 / 共享数据放到state中。

image

image

  1. 在组件中使用dispatch这个API把key-value传给actions。

image

actions就是第一层处理【PS:服务员嘛 】。

image

actions接收key-value【PS:要是有逻辑操作,也放在这里面,ajax也是 】。

  • 这里就是调用commit这个API把key-value传给mutation,这个mutation才是真正做事的人【PS:后厨嘛 】。

image

image

  1. mutations接收key-value。

image

image

image

image

  1. 查看开发者工具【PS:简单了解,自行玩一下 】。

另外:vuejs devtools开发工具版本不一样,则:页面布局也不一样,但是:功能是一样的。

image

当然:前面说过,直接在组件中调commit这个API从而去和mutations打交道,这种是可以的,适用于:不需要逻辑操作的过程,示例就自行玩了。

以上便是简单了解vuex,前面的例子看起来没什么用,但是vuex这个东西其实好用得很。

认识 getters 配置项

这玩意儿就和data与computed的关系一样

image

image

image

四个map方法
mapState 和 mapGetters

image

1、改造源代码 —— 使用计算属性实现。

image

image

但是:上面这种是我们自己去编写计算属性,从而做到的,而Vuex中已经提供了一个东西,来帮我们自动生成计算属性中的哪些东西,只需一句代码就搞定。

2、使用mapState改造获取state中的数据,从而生成计算属性。

  • 1)、引入mapState。代码:import {mapState} from "vuex"

  • 2)、使用mapState【PS:对象写法 】。

image

image

2.1、数组写法【PS:推荐用的一种 】。

...mapState({sum:'sum'}) 这里面的 sum:'sum' 这两个是一样的,那么:一名多用

image

image

3、使用mapGetters把getters中的东西生成为计算属性

  • 1、引入mapGetters。代码:import {mapState,mapGetters} from "vuex"

  • 2、使用mapGetters【PS:和mapState一模一样 ,对象写法 】。

image

image

3.1、数组写法

image

mapActions 和 mapMutations

会了mapState,那么其他的map方法也会了,差不多的,只是原理是调了不同的API罢了,当然:也会有注意点。

mapState 和 mapGetters是生成在computed中的,而mapActions和mapMutations是生成在methods中的。

  1. mapActions —— 调的API就是dispatch。

image

1、使用mapActions改造:

  • 1、引入mapActions 代码: import {mapActions} from 'vuex'

  • 2、使用mapActions【PS:对象写法 】 —— 准备调入坑中。

image

image

原因:

image

image

1.1、数组写法 —— 一样的,函数名和actions中的名字一样【PS:一名多用 】

image

image

2、mapMutations —— 这个和mapActions一模一样,只是调用的API是commit

image

所以:此种方式不演示了,会了前面的三种中任意一种,也就会了这个。

简单玩一下组件共享数据

  1. 在state中再加一点共享数据。

image

  1. 新增Person组件。

image

image

image

  1. 共享数据:在Count组件中获取person,在person中获取Count【PS:此步不演示,会了前者后者就会了 】

操作如下:

image

image

Vuex模块化开发

modules + namespac

这玩意儿就是把actions、mutations、state、getters按照功能点进行对象封装【PS:但是注意这里面需要使用namespace:true开启命名空间,否则:在下一步中会报错,不允许配置。

然后在 new vuex.store 中使用modules配置项将前面封装的对象配置进来。

  1. 对于使用map那四个的方式时,操作如下:

image

image

  1. 对于自己写计算属性和methods时,操作如下:

image

image

image

vuex模块化开发总结
  • 目的:让代码更好维护,让多种数据分类,更加明确。

  • 修改store.js

const countAbout = {
    namespace: true,	// 开启命名空间
    state: {
        x:1
    },
    actions: {......},
    mutations: {......},
    getters: {
        bigSum( state ){
            return state.sum * 10
        }
    }
}


const personAbout = {
    namespace: true,
    state: {.....},
    actions: {......}
    getters: {.......}
}


const store = new vuex.store({
    modules: {
        countAbout,personAbout   // 触发对象简写形式
    }
})
  • 开启命名空间后,组件中读取state数据。
// 方式一【PS:自己写计算属性时 】
this.$store.state.personAbout.list

// 方式二【PS:直接让mapState生成计算属性时 】
...mapState('countAbout',['sum','school','subject'])
  • 开启命名空间后,组件中读取getters数据。
// 方式一
this.$store.getters['personAbout/firstPersonName']

// 方式二
...mapGetters('countAbout',['bigSum'])
  • 开启命名空间后,组件中调用dispatch。
// 方式一
this.$store.dispatch('personAbout/addPersonWang',person)

// 方式二
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
  • 开启命名空间后,组件中调用commit。
// 方式一
this.$store.commit('personAbout/ADD_PERSON',person)

// 方式二
...mapActions('countAbout',{incrementOdd:'JIA',incrementWait:'JIAN'})

路由器 router

认识路由和路由器

路由 route:就是一组key-value的映射关系。

  • key:就是网站中的哪个路径。
  • value:就是function 或 component组件。
    • function:是因为后端路由(后端调用函数,对该路径的请求做响应处理)。
    • component:组件就不用多说了。

路由器 router:就是专门用来管理路由的【PS:理解的话,就参照生活中的那个路由器,它背后有很多插孔,然后可以链接到电视机,那个插孔就是key ,而链接的电视机就是value】。

在vue中,router路由器是一个插件库,所以需要使用npm install vue-router来进行安装,这个东西就是专门用来做单页面网站应用的。

所谓的单页面网站应用就是 SPA,即:只在一个页面中进行操作,路径地址发生改变即可,然后就把相应的东西展示到当前页面,不会发生新建标签页打开的情况。

  • SPA 整个应用只有一个完整的页面、点击页面中的导航链接不会刷新页面,只会做页面的局部刷新、数据需要通过ajax请求获取。

简单使用路由器

  1. 准备工作:引入bootstrap.css

image

  1. 开始玩路由器 router
  • 1)、给项目安装路由 指令:npm install vue-router

  • 2)、在 main.js 中引入并使用路由器【PS:路由器是一个插件 】。

image

  • 3)、编写组件

image

  • 4)、配置路由器【PS:这也叫配置路由规则,就是key-value的形式,在前端中,key是路径,value是组件 】。

image

  • 5)、把配置的路由规则引入到 main.js

image

  • 6)、在静态页面中使用路由【PS:需要记住两个标签 <router-link ..... to = "路径名"></router-link><router-view></router-view> ]。

1、<router-link ..... to = "路径名"></router-link> 是指:跳转。其中:路径名 就是 路由规则中配置的 path,参照a标签来理解,本质就是转成了a标签。

2、<router-view></router-view> 是指:视图显示。就是告知路由器 路由规则中配置的component应该显示在什么位置,和slot插槽一样,占位。

<template>
  <div>
    <div class="row">
      <div class="col-xs-offset-2 col-xs-8">
        <div class="page-header"> <h2>Vue Router Demo </h2></div>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-2 col-xs-offset-2">
        <div class="list-group">
          <router-link class="list-group-item" active-class="active" to="./about">About</router-link>
          <router-link class="list-group-item" active-class="active" to="./home">Home</router-link>
          <!--对照a标签 <a class="list-group-item active" href="./home.html">Home</a> -->
        </div>
      </div>
      <div class="col-xs-6">
        <div class="paner">
          <div class="paner-body">
            <router-view></router-view>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'App',
  }
</script>
  • 7)、运行效果如下:

image

另外:从此处开始,可以先摸索饿了么的 Element-ui 组件库 了,这是专门搭配Vue来做页面的网站,和Bootstrap一个性质。@紫邪情

小结:vue-router使用

  1. 安装vue-router ,命令:npm install vue-router

  2. main.js中引入,指令:import VueRouter from "vue-router"

  3. main.js中应用vue-router插件,指令:Vue.use(VueRouter)

  4. src/rooter/xxx.js中编写router路由规则:

// 引入路由器
import VueRouter from "vue-router"
// 引入需要进行跳转页面内容的组件
import About from "../components/About.vue"
import Home from "../components/Home.vue"

// 创建并暴露路由器
export default new VueRouter({
    routes: [   // 路由器管理的就是很多路由  所以:routes 是一个数组
        {   // 数组里面每个路由都是一个对象 它有key和value两个配置项【PS:还有其他的 】
            path: '/about',     // 就是key  也就是路径名,如:www.baidu.com/about这里的about
            component: About    // 就是value  也就是组件
        },{
            path: '/home',
            component: Home
        },
    ]
})
  1. 把配置的路由规则绑定到Vue实例上(main.js
// 把4中router文件夹中配置的路由规则引入进来
import router from "4中的router文件夹"

new Vue((
	render: h->(App),
	router,		// 完整写法	router:router	前为配置项名字		后为引入的路由规则
)).$mount('#app')

6、在需要跳转的组件中实现切换(active-class 可配置高亮样式)

<router-link class="list-group-item" active-class="active" to="./about">About</router-link>
<!--to	要跳转到哪个组件		上一步4中路由规则中配置的 path -->

<!--对照a标签 <a class="list-group-item active" href="./home.html">Home</a> -->

7、指定展示位置

<!-- 告知路由器 路由规则中配置的component应该显示在什么位置 -->
<router-view></router-view>

聊聊路由器的一些细节

  1. 路由组件以后都放到page文件夹下(非绝对),而一般组件都放到components中。

  2. 路由切换时,“隐藏”了的路由组件,默认是被销毁掉了,需要时再去重新挂载的【PS:示例自行通过beforeDestroy和mounted进行测试 】。

image

  1. 每个组件都有自己的$route属性,里面存储着自己的路由信息。

image

image

  1. 整个应用只有一个router,可以通过组件的$router属性获取到【PS:验证自行把不同组件的这个东西绑定到window对象上,然后等路由组件挂载完毕了,拿到它们进行比对,答案是:false 】。

image

多级路由(子菜单)

  1. src/page下再新建两个路由组件。

image

  1. 给home路由规则编写多级路由。

image

  1. 重新编写Hmoe.vue路由组件。
<template>
    <div>
        <h2>我是Home的内容</h2>
        <div>
            <ul class="nav nav-tabs">
                <li>
                    <!-- 多级路由,这里的to后面需要加上父级路径  先这么写,它可以简写,后续进行处理 -->
                    <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
                </li>
                <li>
                    <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
                </li>
            </ul>
            <ul>
                <router-view></router-view>
            </ul>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'Home'
    }
</script>
  1. 运行效果如下:

image

路由传参

query的字符串写法:v-bind + 模板字符串

也就是路径传参,适合传递少量参数。

1)、编写数据,改造Message.vue路由组件【PS:传递数据者 v-bind + 模板字符串】。

image

2)、编写Detail.vue路由组件【PS:数据接收者 $route.quesy.要取的参数名】。

image

3)、编写路由规则。

image

4)、效果如下:

image

适合传递大量参数。

image

运行效果和上一节一样的。

命名路由

这玩意儿就是为了处理path中的那个字符串很长的问题,相当于起个别名。@紫邪情

实例:

1)、修改路由规则

image

2)、使用命名路由精简path

image

3)、运行效果如下

image

小结:命名路由

作用:简化路由跳转时的path写法

使用:

  1. 给命令命名
   {
       path: '/home',
       component: Home,
       children: [
           {
               path: 'news',
               component: News
           },{
               path: 'message',
               component: Message,
               children: [
                   {
                       path: 'detail',
                       // 使用另一个配置项name  命名路由,从而让path更精简
                       name: 'detail',
                       component: Detail
                   }
               ]
           },
       ]
   },
  • 简化路由跳转写法:
<!-- 简化前写法 -->
<router-link 
   :to="{
       path: '/home/message/detail',
       query: {
           id: m.id,
           title: m.title
       }
   }">
   {{m.title}}
 </router-link>

<!-- 简化后写法 -->
<router-link 
   :to="{
       name: 'detail',
       query: {
           id: m.id,
           title: m.title
       }
   }">
   {{m.title}}
 </router-link>

路由另一种传参:params

注意:这种传参对象式写法必须基于name配置项才可以。

另外就是:这种传参也就是后端中的RESTful传参。

使用params传递参数【PS:数据的传递者 】,这一步和以前的query传递没什么两样,只是改了一个名字而已。

1)、字符串写法 / 对象写法。

image

2)、修改路由规则。

image

3)、获取参数 【PS:和query相比,就是数据存放的位置变了一下而已,可以在mounted中输出this.$route看一下结构 】。

image

4)、效果如下:

image

小结:路由params传参

1、配置路由

{
   path: '/home',
   component: Home,
   children: [
       {
           path: 'news',
           component: News
       },{
           path: 'message',
           component: Message,
           children: [
               {
                   // 使用params传参,则:需要把path的规则改了,就是占位,接对应参数
                   path: 'detail/:id/:title',
                   name: 'detail',     // 对象写法,必须保证有这个配置项
                   component: Detail
               }
           ]
       },
   ]
},

2、传递参数

 <!-- 使用params传递参数 -->

 <!-- 字符串写法 -->
 <router-link :to="`/home/message/detail/${m.id}/${m.title}`">
   {{m.title}}
 </router-link>

 <!-- 对象写法 
   这种写法必须保证里面是name,而不是params,否则:页面内容会丢失的
 -->
 <router-link 
   :to="{
       name: 'detail',
       params: {
           id: m.id,
           title: m.title
       }
   }">
   {{m.title}}
 </router-link>
  • 注意点:路由携带params参数时,若使用的to的对象写法,则:不能使用path配置项,必须用name配置项

3、接收参数

{{$route.params.id}}
{{$route.params.title}}

路由的props配置项

props这个东西在组件的父传子通信时见过,但是:不是一回事,那是组件间的,现在是路由的,写法几乎不一样。

这里有一句话:哪个路由组件要接收数据,props就配置在哪个路由规则。

回到问题:插值语法讲究的就是简单的模板语法,而下图这种就要不得,所以:要简化。

image

布尔值写法

为true时,则把path接收到的所有params参数以props的形式发给所需路由组件。

注意:是params传递的参数,所以:这就是缺点之一。

另外就是:以props形式传给所需组件,所以:在所需路由组件那边需要使用pros:['xxxx']来进行接收,从而在所需组件中使用数据时就可以进行简化了。

image

函数写法

这种写法:是最灵活的一种,props为函数时,该函数返回的对象中每一组key-value都会通过props传给所需数据的路由组件。

这种写法可以获取query传递的数据,也可以接收params传递的,注意点就是:在函数中调用时的名字变一下即可。

这种的好处就是:一是query和params都可以接收,二是:把数据接收的逻辑写到要接收数据的路由组件的路由规则去了,逻辑更清晰,不至于到处找逻辑代码。

image

另外的方式:不推荐使用,所以:简单了解即可。

1)、在数据使用者处自行抽离代码,弄成计算属性 【 PS:如示例中的Detail路由组件,取数据时在插值语法中麻烦,那就在下方配置计算属性从而抽离代码,但是:画蛇添足,因为:在计算属性中拿数据时会使用this.$route.queru / params.xxxx 代码量大的话,这不就还得多写N多this吗。

2)、对象写法 —— 不用了解,知道有这么一个东西即可,需要时自行百度即可【 PS:这个东西接收数据是死的,开发中基本上用都不用 】。

  • 此种方式:是将该对象中所有的key-value的组合最终通过props传给所需路由组件。

router-link中的 replace 属性

image

上面这种模式就是push模式,它的原理就是栈空间,压栈push嘛【 PS:router-link中的默认模式就是push模式

image

但是还有一种模式是:replace模式,这种模式是产生一个记录之后,就把上一次的记录给干掉了,所以效果就是不会有回退的记录,回退按钮都点不了,使用此种模式就是在router-link中加一个replace属性即可。

image

这样之后,再点击Home / About时,它的历史记录不会留下

image

小结:router-link的replace属性

  • 作用:控制路由跳转时操作浏览器历史记录的模式。
  • 浏览器的历史记录有两种写入方式,分别为pushreplace,其中:push是追加历史记录,replace是替换当前记录,路由跳转时默认为push
  • 开启replace模式的方式:<router-link replace ......>News</router-link>

编程式路由导航

这玩意儿就是不再借助router-link来实现路由跳转,前面玩了$route,而现在就是来玩的$router

先看一下$router这个东西,顺便知道掌握哪些API。

image

push 和 replace 两个API

push和replace的原理就是前面说的,一个保留历史记录,一个会清除上一次的历史记录。

image

image

back 和 forward 两个API

这两个就是浏览器中的前进和后退。

image

image

go() API

go()中可以用正数和负数,正数表示:前进所填数字步;负数就是后退所填数字的绝对值步。

image

image

缓存路由组件

前面不是说了:路由组件在被切换走之后,是会被销毁的,因此:这种情况就会导致有时某个路由组件中的数据不应该被销毁,而是保留起来,所以:就需要借助即将说明的知识点,就是使用了一个<keep-alive include = "componentName"></keep-alive>标签来实现。

字符串写法:缓存一个路由组件

image

image

image

方便验证,加上如下的代码:

image

image

数组写法:缓存多个路由组件

image

演示就不做了。

另外的生命钩子

在基础篇中就说过:除了哪里讲的8个生命钩子【PS:4对 】,其实还有三个生命钩子没有玩,也说了等到路由之后再整。

activated 和 deactivated

activated:就是激活 【PS:我想见你了,就调它 】。

deactivated:就是失活 【PS:我不想见你了,你离开吧,就调它,有点类似于beforeDestroy这个钩子,但是:处理的情况不一样 】。

适用场景:提前使用了keep-alive include = "xxx"保留该组件,切换后不让其销毁【PS:beforeDestroy不起作用了 】,那么:又想要最后关掉定时器之类的,就可以使用这两个钩子函数。

实例:

image

image

nextTick

这个东西不演示了,它是为了:让解析时产生时间差

因为:有些东西需要把解析完之后的样子插入到页面中了才可以继续做另外的事情,因此:就可以借助nextTick,从而:让一部分模板先被解析好,放入页面中,然后再解析后面的一些东西时执行一些我们想要的逻辑。如:input框。有这么一个场景:让input渲染到页面时,我鼠标的焦点就在input框中,这就可以使用此钩子函数,在挂载时调用此钩子,然后把光标聚焦的逻辑放到nextTick回调中。

nextTick钩子的玩法官网有,一看就懂。

image

路由守卫 - 重要

所谓的路由守卫,就是权限问题,即:拥有什么权限,才可以访问什么路由。

全局前置路由守卫

所谓的全局前置路由守卫,特点之一就体现在前置二字上,它的API是 beforeEach((to, from, next) => {}),也就是:在路由切换之前回调会被调用 / 初始化渲染时回调会被调用 。

实例:

image

image

不方便演示,所以直接说:

  • to:就是切换路由组件之后的组件位置【PS:即 去哪里,目标组件 】;
  • from:是 切换路由组件之前的组件位置 【PS:即 来自哪里 从哪个路由组件来 】;
  • next:是一个函数next(),就是放行的意思。

现在做一个操作,在浏览器中缓存一个key-value,然后访问News、Message路由时判断key-value是否对得上,对就展示相应的路由组件,否则:不展示。

image

image

玩点小动作

image

当然:前面的过程有一个小技巧可以简化

image

给路由规则添加一个meta配置项,就是路由元数据,利用这个配置项,我们可以给路由中放一些我们想放的东西进去【PS:哪个路由需要权限验证,就在哪个路由中加入meta配置项 】,那就改造吧。

image

image

image

全局后置路由守卫

这玩意儿就和全局前置路由守卫反着的嘛,但是:处理场景不一样。

全局后置路由守卫 调用的API是 afterEach((to, from) => {})。这个API是 初始化渲染时回调会被调用 / 路由组件切换之后会被调用。

注意:和全局前置路由守卫相比,少了next参数,后置了嘛,都已经在前面把权限判断完了,你还考虑放不放行干嘛。

这个全局后置路由守卫做的事情其实不是去判断权限问题,而是收尾,做一些过了权限之后的判断问题,比如:点击某个路由组件之后,只要可以查看这个组件内容,那么:就把网页的页签标题给换了。

实例:

image

操练一手:【PS: 先用纯的全局前置路由守卫来做 】。

先加点东西进去:

image

image

image

上面看起来成功了,但是有bug,可以试着把network中的网络调成slow 3G,可以稍微清楚地看到效果 ,想要改成功,就算把项目中 public/index.html的title改了,也是一样,会有加载过程,因此:想在全局前置路由守卫中达到想要的效果,改出花儿来也莫得办法,而且在全局前置路由守卫中写两遍一样的代码根本不标准。

image

image

想要实现前面的效果,那就需要全局后置路由守卫登场了,掉一下API,里面一行代码搞定。

image

效果就不演示了,已经达到效果了【PS:注意得把 public/index.html 中的title改成'大数据智慧云生平台',不然访问根目录时也有加载过程,这不是此知识点的锅,因为:原生的 public/index.html 的title是读取的 package.json 中第二行的name 】。

独享路由守卫

这玩意儿就是指:某一个路由独享的路由守卫,调用的API是:beforeEnter( ( to, from, next )=>{ } ),它是指:在进入配置这个API的路由之前回调会被调用,其中:to、from、next的意思和前面全局前置路由守卫一样。

但注意:这种没有什么后置之类的,它只有这一个前置,即:独享路由守卫。

另外:路由守卫之间,是可以随意搭配的。

实例:

image

image

组件内路由守卫 - 了解

这种路由守卫就是组件内的,它使用的API是 beforeRouteEnter( ( to, from, next )=>{ } )beforeRouteLeave( ( to, from, next ) =>{ } )

beforeRouteEnter(( to, from, next )=>{ })		指的是:通过路由规则,进入配置了这个API的路由组件之前回调会被调用

beforeRouteLeave(( to, from, next )=>{ })		指的是:通过路由规则,离开配置了这个API的路由组件之前回调会被调用

注意:是通过路由规则进入 / 离开这个API所在的路由组件,切记是通过路由规则做到的,因为想要路由组件工作,不用经过路由规则也可以让其工作【PS:就是以前组件的正常使用流程,引入、注册、使用组件标签 】。

image

路由器的两种工作模式:hash 和 history

  1. hash模式 路由器的默认模式

就是路径中有一个#,这#后面的内容不用随http传给服务器,服务器也不会收到【PS:前端玩一下而已 】。

image

  1. history模式

这个就好理解了嘛,就是没有了那个#,然后路径中ip:port之后的东西是会随着http传给服务器的。

hash和history两种模式的区别:

  1. hash模式 路径中有#,且#后的内容不会随http传给服务器;而history模式 路径中没有#ip:port之后的东西会随http传给服务器。
  2. hash模式的兼容性好,而history模式的兼容性略差。

在路由器中hash和history两种模式的切换:在路由规则中加个全新的配置项mode即可。

image

关于项目上线的问题

了解这个东西是因为前面说的hash和history的另一个区别,在上线时有个坑 / 注意点。

  1. 编写完了程序之后打包项目。

启动项目一直用的是npm run serve,在脚手架时就说过还有一个命令:npm run build,那时说过:后端要的前面资源是HTML+CSS+JS,所以此时项目打包就需要用到它了。

先把路由器的工作模式切换成history模式,然后再打包,这样方便演示bug。

image

image

  1. 使用node+express框架编写一台小服务器模拟一下上线。

自行新建一个文件夹,然后使用vscode打开。

1)、让文件夹变成合法包 指令:npm init

image

2)、安装express 指令:npm install express

注意:这一步很容易因为自己当初配置nodejs时操作不当,导致权限不够啊,就会报一堆warn和error。

image

3)、新建一个js文件,编写内容如下

image

源码如下:

// 1、引入express  注意:这里就不是ES6的模块化了,而是commanjs模块化
const express = require('express')

// 2、创建一个app服务实例对象
const app = express()

// 3、端口号监听
app.listen(8001,(err)=>{    // err是一个错误对象
    if( !err ) console.log("服务器启动成功");
})

// 4、配置一个后端路由
app.get('/person',(req,res)=>{      // req就是request res就是response
    res.send({
        name: '紫邪情',
        age: 18
    })
})

4)、启动服务器 指令:node server

image

5)、访问服务器中的端口测试一下

image

  1. 准备工作弄完了,现在把刚刚使用npm run build打包的dist中的文件复制到服务器中去。

在服务器中新建一个static / public文件夹【PS:后端的人,这两个文件夹就是SpringBoot中的那两个,这两个文件夹建哪一个都可以 】。

image

  1. 让复制进去的文件能够被服务器认识。

image

重新执行node server 开始演示路由器的两种工作模式的另一个坑【PS:别忘记有个权限认证啊,在缓存把对应东西放上,不然有些路由组件点不了 】

image

整bug,随便点一些路由组件之后,刷新页面【PS:只要保证路径不是 localhost:8001 即可,让它后面有点东西 】。

image

这就是history模式的坑(切换成hash就不会出现这样)。

出现上述的情况是因为:刚刚我们在页面中随便点路由组件都有页面是因为:那些都是静态页面,那些数据啊、历史记录啊都是原本就有的,即:不走网络请求,但是:刷新之后,是走网络请求的,也就会把路径中ip:port之后的东西随着http发给服务器了,它去服务器找资源就是找http://localhost:8001/home/news中的 /home/news,服务器中哪有这个资源,所以:404呗。

想要history模式也和hash一样,刷新不出错,就需要找后端人员进行处理,需要后端人员配合你这边拿过去的资源来做,后端处理这种问题的方式有很多,如:Nginx、跨域配置、通过注解.....【PS:但是嘛,有时别人甩你个锤子,最终代码出问题是自己背锅罢了】,所以:自己解决,需要借助一个插件 connect-history-api-fallback

image

image

image

image

image

Vue UI组件库 - Element-UI

使用:

  • 1)、给项目安装Element-UI 指令:npm i element-ui

  • 2)、在项目的main.js中引入Element-UI 和 其样式。

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
  • 3)、在main.js中使用element-ui 指令:Vue.use(ElementUI);

但是:直接通过引入Element-UI和其样式,然后通过Vue.use( xxxx )来使用Element-UI会造成项目中的Element-UI的js体积太大了,不好,所以:按需引入

按需引入

image

  1. 修改babel.config.js文件内容。

image

  1. 引入自己想要的组件和样式。

image

注意:后续遇到的坑,还是需要在main.js中把全部样式引入,命令:import 'element-ui/lib/theme-chalk/index.css';

image

image

当然:也可能报not found xxx,这种情况:直接npm install xxx即可,即:缺啥拉啥。

解决上面报的问题:

image

结语

Vue2到此结束,另外还有一些Vue UI组件库

移动端

PC端

接下来的技术点就是:Vue3

链接:https://www.cnblogs.com/zixq/p/15875530.html

对应MD文档

连接:https://github.com/zixq-stack/Java-Note/tree/master/前端/Vue系列

posted on 2022-02-10 13:49  紫邪情  阅读(3623)  评论(0编辑  收藏  举报