Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画

目录

  • 1.Vue概述
    • 1.1 认识Vue
    • 1.2 Vue的两核心
    • 1.3 Vue的初体验
    • 1.4 Vue的生命周期
  • 2. Vue-CLI (Command Line Interface)
  • 3. Vue基本使用
    • 3.1 传统开发模式对比
    • 3.2 Vue.js引入
    • 3.3 Vue.js 案例分析
      • 3.3.1 实例参数el、data、methods的写法
  • 4. Vue模板语法
    • 4.1 插值语法 {{xxx}}
    • 4.2 指令语法
      • 4.2.1 v-text、v-html、v-pre
      • 4.2.2 v-cloak 指令
      • 4.2.3 自定义指令
    • 4.3 属性绑定 v-bind
        • 4.3.1 绑定class属性
        • 4.3.2 绑定style属性
    • 4.4 计算属性
        • 4.4.1 methods VS computed
        • 4.4.2 监听属性watch及computed VS watch
        • 4.4.3 computed与watch、methods的区别
    • 4.3 事件监听 v-on
      • 4.3.1 v-on事件修饰符号
      • 4.3.2 v-on按键修饰符号
      • 4.3.3 获取事件对象
    • 4.4 条件渲染 v-if、v-show
    • 4.5 列表渲染 v-for
      • 4.5.1 基本用法
      • 4.5.2 虚拟DOM、Diff算法、Key选用
      • 4.5.3 列表过滤与排序
      • 4.5.4 Vue数据监视
    • 4.6 数据绑定 v-model
    • 4.7 过滤器filter(Vue3已经移除)
    • 4.8 模板语法应用——简易购物车
  • 5. 组件基础
    • 5.1 模块与组件、模块化与组件化
    • 5.2 非单文件组件
      • 5.2.1 组件基本使用
      • 5.2.2 组件的嵌套
      • 5.2.3 VueComponent构造函数
      • 5.2.4 一个重要的内置关系
    • 5.3 单文件组件
      • 5.3.1 组件基本实例
      • 5.3.2 组件间的通信
        • 5.3.2.1 父组件传递子组件 props
        • 5.3.2.2 子组件给传递父组件数据 $emit
      • 5.3.3 父子组件之间的访问方法
        • 5.3.3.1 子组件调用父组件的方法 $parent
        • 5.3.3.2 父组件调用子组件的方法 $refs
      • 5.3.4 全局事件总线(任意组件间的通信)
      • 5.3.5 消息订阅与发布(任意组件通信)
    • 5.4 组件插槽 slot
      • 5.4.1 默认插槽
      • 5.4.2 后备内容
      • 5.4.3 具名插槽
      • 5.4.4 作用域插槽
    • 5.5 `mixin` 混入
    • 5.6 plugin 插件
    • 5.7 scoped 样式
    • 5.8 nextTick
  • 6. TodoList案例
    • 6.1 目标功能界面
    • 6.2 界面模块拆分
    • 6.3 浏览器本地存储
  • 7. 过渡和动画

1.Vue概述

1.1 认识Vue

Vue是一套用于构建用户界面的渐进式框架。
渐进式就跟这个图片一样,开发可以根据需求,逐渐递增所要的方式,但每个方式有不是依靠行特别强
在这里插入图片描述

Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
库是一个模块,而Vue是一套架构,会基于自身特点向用户提供一套相当完整的解决方案,而且控制权在框架本身;对项目的侵入性较大,使用者要按照框架所规定的某种特定规范进行开发,项目如果需要更换框架,则需要重新架构整个项目。

1.2 Vue的两核心

响应式的数据绑定:当数据发生改变,视图可以自动更新,可以不用关心dom操作,而专心数据操作
可组合的视图组件:把视图按照功能切分成若干基本单元,组件可以一级一级组合整个应用形成倒置组件树,可维护,可重用,可测试

1.3 Vue的初体验

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./js/vue.global.js"></script>
    <style>
        .box {
            width: 200px;
            height: 200px;
            background-color: darksalmon;
            display: none;
        }
        div.show {
            display: block;
        }
    </style>
</head>
<body>
    <div id="app">
        {{message}}
        <h2 @click="isShow()">{{title}}</h2>

        <div class="box" :class="{show}" style="color: #333; background-color: antiquewhite" :style="{width:'200px', height:h}">
            <ul>
                <li v-for="item in articles.slice(0,5)" :title="item.content">
                    <p>{{item.title}}</p>
                    <span>{{item.content}}</span>
                </li>
            </ul>
        </div>
    </div>

</body>
<script>
	//Vue2
	var vm = new Vue({
        el: '#app',
        data: {
          num: 0,
        },
        methods: {
          ……
        },
      });
	//Vue3
    const app = Vue.createApp({
        data() {
            return {
                h: '500px',
                message: 'this is a test',
                title: 'Vue demo',
                show: true,
                articles: [
                    {title:'Google',content:'Vue 的核心库只关注视图层'},
                    {title:'souhu',content:'Vue 的核心库只关注视图层'},
                    {title:'Google',content:'Vue 的核心库只关注视图层'},
                    {title:'Google',content:'Vue 的核心库只关注视图层'},
                    {title:'Google',content:'Vue 的核心库只关注视图层'},
                    {title:'Google',content:'Vue 第三方库或既有项目整合'},
                    {title:'xifang',content:'第三方库或既有项目整合'},
                    {title:'baidu',content:'第三方库或既有项目整合'},
                    {title:'baidu',content:'第三方库或既有项目整合'},
                    {title:'baidu',content:'第三方库或既有项目整合'}
                ]
            }
        },
        methods: {
            isShow() {
                this.show = !this.show;
            }
        }
    }).mount("#app")

</script>
</html>

效果
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第1张图片

1.4 Vue的生命周期

生命周期:事物从诞生到消亡的整个过程
vue生命周期:Vue 实例从创建到销毁的过程,vue生命周期钩子:在达到某一阶段时去触发的函数
生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的,生命周期函数中的this指向是vm 或 组件实例对象。
它可以总共分为8个阶段:
创建前/后, 挂载前/后,更新前/后,销毁前/销毁后
create阶段:vue实例被创建
mount阶段: vue实例被挂载到真实DOM节点
update阶段:当vue实例里面的data数据变化时,触发组件重新渲染
destroy阶段:vue实例被销毁

  1. beforeCreate(创建前)
    表示实例完全被创建出来之前,vue 实例的挂载元素$el和数据对象 data 都为 undefined,还未初始化。

  2. created(创建后)
    数据对象 data 已存在,可以调用 methods 中的方法,操作 data 中的数据,但 DOM 未生成,$el未存在 。

  3. beforeMount(挂载前)
    vue 实例的 $eldata 都已初始化,挂载之前为虚拟的 DOM节点,模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中。data.message未替换。

  4. mounted(挂载后)
    vue 实例挂载完成,data.message 成功渲染。内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了。实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,DOM 渲染在 mounted 中就已经完成了。

  5. beforeUpdate(更新前)
    当 data 变化时,会触发beforeUpdate方法 。data 数据尚未和最新的数据保持同步。

  6. updated(更新后)
    当 data 变化时,会触发 updated 方法。页面和 data 数据已经保持同步了。

  7. beforeDestory(销毁前)
    组件销毁之前调用 ,在这一步,实例仍然完全可用。

  8. destoryed(销毁后)
    组件销毁之后调用,对 data 的改变不会再触发周期函数,vue 实例已解除事件监听和 dom绑定,但 dom 结构依然存在。

常用的生命周期方法:
mounted():初始化操作,发送ajax请求, 启动定时器、绑定自定义事件、订阅消息等异步任务
beforeDestroy(): 做收尾工作, 清除定时器、解绑自定义事件、取消订阅消息等

关于销毁Vue实例:
销毁后借助Vue开发者工具看不到任何信息
销毁后自定义事件会失效,但原生DOM事件依然有效
一般不会在beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了。

Vue生命周期流程图
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第2张图片
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第3张图片
vue生命周期在真实场景下的业务应用:

  • created:vue实例被创建
  • mounted: 挂载元素,获取dom节点
  • nextTick: 针对单一事件更新数据后立即操作dom
  • updated: 任何数据的更新,做统一的业务逻辑处理
  • watch: 监听具体数据变化,并做相应的处理

2. Vue-CLI (Command Line Interface)

Vue-CLI 是Vue官方提供的脚手架工具
Vue脚手架指的是vue-cli,它是一个专门为单页面应用快速搭建繁杂的脚手架,它可以轻松的创建新的应用程序而且可用于自动生成vue和webpack的项目模板。
利用vue-cli脚手架来构建Vue项目需要先安装Node.js和NPM环境。

  • 开发环境:WebStorm
  • 命令安装:npm install -g @vue/cli (安装最新版本vue-cli)
  • 检查版本:vue --version
  • 创建项目:vue create 项目名称
  • 选择Vue3,默认安装插件
    Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第4张图片
  • 运行项目: npm run serve

目录结构
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第5张图片
常见项目的目录结构,如下所示:

.
|-- build                            // 项目构建(webpack)相关代码
|   |-- build.js                     // 生产环境构建代码
|   |-- check-version.js             // 检查node、npm等版本
|   |-- dev-client.js                // 热重载相关
|   |-- dev-server.js                // 构建本地服务器
|   |-- utils.js                     // 构建工具相关
|   |-- webpack.base.conf.js         // webpack基础配置
|   |-- webpack.dev.conf.js          // webpack开发环境配置
|   |-- webpack.prod.conf.js         // webpack生产环境配置
|-- config                           // 项目开发环境配置
|   |-- dev.env.js                   // 开发环境变量
|   |-- index.js                     // 项目一些配置变量
|   |-- prod.env.js                  // 生产环境变量
|   |-- test.env.js                  // 测试环境变量
|-- node_modules		   //所需要依赖资源
|-- src                              // 源码目录
|   |-- assets                   	   //存放资产文件,如静态资源
|   |-- components                   // vue公共组件
|   |-- router                    	//存放路由js文件,用于页面的跳转
|   |-- App.vue                        // 页面入口文件
|   |-- main.js                        // 程序入口文件,加载各种公共组件
|-- static                           // 静态文件,比如一些图片,json数据等
|   |-- data                           // 群聊分析得到的数据用于数据可视化
|-- .babel.config.js                 // bale的配值文件
|-- .editorconfig                    // 定义代码格式
|-- .gitignore                       // git上传需要忽略的文件格式
|-- README.md                        // 项目说明
|-- favicon.ico 
|-- index.html                       // 入口页面
|-- package.json                     // 项目基本信息
|-- package-lock.json				 //包版本控制文件
|-- vue.config.js					 // vue可选的配值文件
.
文件名 说明
dist 存放使用npm run build打包的项目文件
node_modules 存放项目的依赖包
public 存放静态文件。里面包含了几个文件: index.html是一个模板文件
src 这里是我们要开发的目录,基本上要做的事情都在这个目录里。里面包含了几个目录及文件:assets: 放置一些资源文件,比如图片、字体等资源。components: 目录里面放了一个组件文件,可以不用。App.vue: 项目入口文件,我们也可以直接将组件写这里,而不使用 components 目录。main.js: 项目的核心文件(入口文件)。
package.json 模块基本信息项目开发所需要模块,版本,项目名称
package-lock.json 是在 npm install时候生成一份文件,用以记录当前状态下实际安装的各个npm package的具体来源和版本号
babel.config.js 是一个工具链,主要用于在当前和较旧的浏览器或环境中将ECMAScript 2015+代码转换为JavaScript的向后兼容版本
gitignore git上传需要忽略的文件格式
vue.config.js 保存vue配置的文件,可以用于设置代理,打包配置等
README.md 项目说明

输入npm run dev命令来启动项目
运行成功后在浏览器输入:http://localhost:8080

设置Eslint的语法检查
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第6张图片
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第7张图片

3. Vue基本使用

3.1 传统开发模式对比

//原生JS
 <div id="msg">div>
  <script type="text/javascript">
    var msg = 'Hello World'
    var div = document.querySelector('#msg');
    div.innerHTML = msg
  script>
<div id="msg">div>
  <script type="text/javascript" src="js/jquery.js">script>
  <script type="text/javascript">
    var msg = 'Hello World';
    $('#msg').html(msg);
  script>

3.2 Vue.js引入

//script 引入
<script src="js/vue.js">script>

//CDN 引入
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>

//生产版本
<script src="https://cdn.jsdelivr.net/npm/[email protected]">script>

3.3 Vue.js 案例分析

Vue的基本使用步骤:

  1. 提供标签用于填充数据
  2. 引入Vue.js库文件
  3. 创建Vue实例
  4. 并配置对象
  5. 把vue提供的数据填充到标签里面
DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>title>
  head>

  <body>
    <div id="app">
      
      <div>{{num}}div>
      
      <div><button v-on:click="handle">点击button>div>
    div>
    
    <script type="text/javascript" src="js/vue.js">script>
    <script type="text/javascript">
      //创建vue实例  vm (ViewModel) 
      var vm = new Vue({
        el: '#app',
        data: {
          num: 0,
        },
        methods: {
          // ES6 的对象字面量方法简写允许我们省略对象方法之后的冒号及function关键字
          // handle:function(){
          // this.num++;
          // }
          handle() {
            this.num++;
          },
        },
      });
    script>
  body>
html>

3.3.1 实例参数el、data、methods的写法

el:指定当前Vue实例为哪个标签服务(值可以是CSS选择器或者DOM元素),el有2种写法:
(1) new Vue时候配置el属性。

const vm = new Vue({
	el:'#root', //第一种写法
	data:{
		msg:' '
	}
})
  1. 先创建Vue实例,随后再通过vm.$mount(’#root’) 指定el的值。
    除了数据 property,Vue 实例还暴露了一些有用的实例 property 与方法。它们都有前缀 $,以便与用户定义的 property 区分开来
const vm = new Vue({
	data:{
		msg:' '
	}
})
vm.$mount('#root') //第二种写法 */

data
用于存储数据(值是一个对象或函数),数据供el所指定的标签使用,data有2种写法:
(1) 对象式

data:{
	msg:' '
}

(2) 函数式

data(){
	return{
		msg:' '
	}
}

如何选择:目前data的哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。

methods: 该属性用于在Vue对象中定义方法。

  • 事件的回调需要配置在methods对象中,最终会在vm上;
  • methods中配置的函数,不要用箭头函数!否则this就不是vm
  • methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象;

4. Vue模板语法

Vue模板语法包括两大类:

4.1 插值语法 {{xxx}}

功能:用于解析标签体内容
双大括号表达式{{xxx}} xxx 是js 表达式,可以直接读取到 data 中的所有区域,也可以调用对象的方法和计算属性

{{ number + 1 }}
{{ ok ? ‘YES’ : ‘NO’ }}
{{message.split(‘’).reverse().join(‘’)}}

4.2 指令语法

功能:用于解析标签(包括:标签熟悉、标签体内容、绑定事件…)
指令(以v-xxx开头的自定义标签属性)【很多】

4.2.1 v-text、v-html、v-pre

(在{{}}和v-指令进行数据绑定时,支持js单个表达式)

  1. v-once

    {{msg}}

    数据执行一次性插值,数据改变时,插值处内容不更新,不响应
    v-once的应用场景:以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

  2. v-pre

    {{msg}}

    ,内容原封不动的展示,跳过其所在节点的编译过程。

  3. v-text

    ,向其所在的节点中渲染文本内容。
    v-text与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。

  4. v-html

    ,可以输出html代码
    v-html与插值语法的区别:
    (1). v-html会替换掉节点中所有的内容,{{xxx}}则不会。
    (2). v-html可以识别html结构。
    严重注意:v-html有安全性问题!!!!
    (1). 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
    (2). 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!

data:{
	msg:'test message',
	title:`

Title

`
}
<div id="app">
    <div>{{msg}}div>
    <div v-text='msg'>div>
    <div v-html='msg1'>div>
    <div v-pre>{{msg}}div>
  div>
var vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello Vue',
        msg1: '

HTML

'
} });

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第8张图片

4.2.2 v-cloak 指令

v-cloak应用场景:解决插值表达式存在的问题——“闪烁”
渲染普通文本有2种方式:{{}}v-text

<div id="app">{{msg}}div>
<div id="app" v-text="msg">div>
new Vue({
    el: '#app',
     data: {
         msg: '欢迎Vue!'
     }
 })

“闪动”的意思:
在使用{{}}展示或更新页面数据时:当网速比较慢时,会出现一个不好的过度现象,会让用户先看到我们的表达式(上面页面中的{{msg}}),然后才看到data中的值(欢迎Vue!)------->即所谓的闪烁问题!

如何解决该问题:
使用v-cloak指令,不让未经解析的html模板显示在页面
使用v-cloak指令,然后为其设置css样式display:none;即上述代码可修改为:
ref : 为某个元素注册一个唯一标识, vue对象通过$refs属性访问这个元素对

v-cloak指令的用法:

  1. 提供样式
[v-cloak]{
  display: none;
}
  1. 在插值表达式所在的标签中添加v-cloak指令
<div id="app" v-cloak>{{msg}}div>

原理: 先通过样式隐藏内容,然后在内存中进行值的替换,替换好之后再显示最终的结果。
v-cloak 指令本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性

说明: 但有时添加完毕后变量仍会显示(即闪烁问题没解决),这是怎么回事呢?原来是 v-cloak 的display属性被优先级别高的样式覆盖所导致,所以最好再添加一个 !important ,将其优先级设置为最高,防止被其他优先级高的dispaly:none样式所覆盖。

[v-cloak]{
	display: none !important;
}

4.2.3 自定义指令

自定义指令:内置指令不满足需求,需要自己定义指令使用

需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。

一、定义语法

  1. 局部指令
//写法一:
new Vue({															
	directives:{指令名:回调函数}	
}) 	
//写法二:		
new Vue({
	directives{指令名:配置对象}
})

实例1

<div id="root">
   <h2>当前的n值是: <span v-text = 'n'>span>h2>
   <h2>放大10倍后的n值是:<span v-big='n'>span>h2>
   <button @click="n++">n+1button>
   <input type="text" v-fbind:value = 'n'>
div>
const vm = new Vue({
    el: '#root',
    data: {
         n: 12
    },
	directives: {
	    //写法一:回调函数写法
	    //el,指令所绑定的元素(真实DOM). binding,一个对象,包含以下属性:name:指令名,不包括 v- 前缀。value:指令的绑定值 arg:传给指令的参数,
	     big(el, binding) {
	       console.log(el, binding,this) //注意,此处的this是window
	       el.innerText = binding.value * 10;
	     },
	     fbind(el, binding){
            onsole.log(el, binding)
            el.value = binding.value
            el.focus()         //不能获取焦点
          },
          
	   //写法二:配置对象写法  在不使用inserted钩子函数时可以使用方法一简写
	     big: {
	       //指令与元素成功绑定时(一上来)
	       bind(el, binding) {
	         el.innerText = binding.value * 10;
	       },
	       //指令所在元素被插入页面时
	       inserted(el, binding) {},
	       //指令所在的模板被重新解析时
	       update(el, binding) {
	         el.innerText = binding.value * 10;
	         },
	      },
	      fbind: {
	      	   bind(el, binding) {
                   el.value = binding.value
                   console.log('bind')
               },
               inserted(el, binding){
                   el.focus()         //有效
                   console.log('inserted')
               },
               update(el, binding) {
                   el.value = binding.value
                   console.log('update')
               }
	     	},
	     	
	     },
	 
	 },
})

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第9张图片

效果
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第10张图片

big函数何时会被调用?
1.指令与元素成功绑定时
2.指令所在的模板被重新解析时

  1. 全局指令
//方法一
Vue.directive(指令名,配置对象) 
//方法二
Vue.directive(指令名,回调函数)
Vue.directive('big',function(el, binding){
    console.log(el, binding,this) //注意,此处的this是window
    el.innerText = binding.value * 10;
}) 
Vue.directive('fbind',{
	//指令与元素成功绑定时(一上来)
     bind(el, binding) {
          el.value = binding.value
          console.log('bind')
      },
      //指令所在元素被插入页面时
      inserted(el, binding){
          el.focus()
          console.log('inserted')
      },
      //指令所在的模板被重新解析时
      update(el, binding) {
          el.value = binding.value
          console.log('update')
      }
  })
  1. 钩子函数
    a. 自定义指令也像组件那样存在钩子函数。
  • bind: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
  • inserted: 被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)
  • update: 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
  • componentupdated: 指令所在组件的 VNode 及其子VNode 全部更新后调用
  • unbind: 只调用一次,指令与元素解绑时调用

b. 所有的钩子函数的参数都有以下

  • el: 指令所绑定的元素,可以用来直接操作 DOM
  • binding:一个对象,包含以下 property:
    • name: 指令名,不包括 v- 前缀
    • value: 指令的绑定值,例如: v-my-directive="1 + 1”中,绑定值为 2。
    • oldvalue: 指令绑定的前一个值,仅在 update 和componentupdated 钩子中可用。无论值是否改变都可用。
    • expression: 字符串形式的指令表达式。例如 v-0my-directive=“1 + 1”中,表达式为“1 + 1”。
    • arg: 传给指令的参数,可选。例如 v-my-directive:foo 中,参数为“foo”
    • modifiers:一个包含修饰符的对象。例如: v-my-directive.foo.bar 中,修饰符对象为{ foo:true, bar: true }
  • vnode: Vue 编译生成的虚拟节点。
  • oldVnode: 上一个虚拟节点,仅在 update 和componentUpdated 子中可用
<div v-demo="{ color: "white',text: "hello!' }">div>
<script>
	Vue.directive('demo', function (el, binding) {
	console.log(binding.value.color)    // white
	console.log(binding.value.text)    // "hello!"
script>
  1. 应用场景
    使用自定义组件组件可以满足我们日常一些场景,这里给出几个自定义组件的案例:
  • 防抖
  • 图片懒加载
  • 一键 Copy的功能
    输入框防抖防抖这种情况设置一个v-debounce自定义指令来实现
    关于自定义组件还有很多应用场景,如: 拖拽指令、页面水印、权限校验等等应用场景

二、配值对象中常用的3个回调

  1. bind:指令与元素成功绑定时调用
  2. inserted:指令所在元素被插入页面时调用
  3. update:指令所在模板结构被重新解析时调用

三、备注:
指令定义时不加v-,但使用时要加v-;
指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。写法如下:

<span v-big-number='n'>span>
directives:{'big-number'(el, binding){……}}

4.3 属性绑定 v-bind

插值{{}}只能用在模板内容中,用于动态内容绑定
如果希望元素的属性也可以动态绑定,需要通过v-bind指令


<a v-bind:href="url"> ... a>


<a :href="url"> ... a>


<a :[key]="url"> ... a>
  • 绑定有意义元素中的属性
<template>
<h2 title="this is a test">{{msg}}h2>

<h2 v-bind:title="msg">{{msg}}h2>

<h2 :title="info">{{info}}h2>

<img :src="imgsrc" width="100" height="100" alt="">
<a :href="url">百度a>
template>
 
<script>
const data = {
    msg: 'this is a test',
    info: 'new info',
    imgsrc: 'https://v3.vuejs.org/logo.png',
    title: '

Title

'
, url: 'http://www.baidu.com' } export default { name: 'App', data() { return data } }
script>

4.3.1 绑定class属性

class 样式有四种用法(字符串,数组,对象,方法)

  • 字符串写法适用于:类名不确定,要动态获取
  • 数组写法适用于:要绑定多个样式,个数不确定,名字也不确定
  • 对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
<template>

<div :class="[one,two]">box4div>

<div :class="active">box5div>



<div :class="{one:isOne, two:isTwo}">box6div>


<div :class="{demo:demo}">box6div> 

<div :class="{demo}">box6div> 


<div :class="getStyleArr()">box7div>
<div :class="getStyleObject()">box8div>
template>

<script>
const data = {
    one: 'one',
    two: 'two',
    active: ['one','two'],
    isOne: true,
    isTwo: false,
    demo: true
}
export default {
  name: 'App',
  data() {
    return data
  },
  methods() {
    getStyleArr() {
      return [this.one,this.two];
    },
    getStyleObject() {
      return {
        one: this.isOne,
        two: this.isTwo
      }
}
script>
<style scoped>
.one {
  background-color: rebeccapurple;
  font-weight: bold;
}
.two {
  background-color: #42b983;
}
.demo {
  background-color: #333;
}
style>

4.3.2 绑定style属性

有种方法,一种数组语法、一种是对象语法

<template>

<div :style="['font-size:100px','background:red']">box1div>

<div :style="[fontSize, bgColor]">box2div>
<div :style="['font-size:'+size+'px','background:'+color]">box3div>


<div :style="{fontSize: '15px', 'background-color':'yellow'}">box9div>
<div :style="{'font-size': '15px', 'background-color':'yellow'}">box9div>
template>

<script>
const data = {
    fontSize: 'font-size:50px',
    bgColor: 'background-color:green',
    size: 90,
    color: 'yellow'
}
export default {
  name: 'App',
  data() {
    return data
  }
script>

4.4 计算属性

**使用:**在computed属性对象中定义计算属性的方法,在页面中使用{{方法名}}来显示计算的结果。
原理:底层借助了Objcet.defineproperty()方法提供的gettersetter

get有什么作用?
当读取computed中的方法时,get就会被调用,且返回值就作为该方法的值
get函数什么时候执行?
(1) 初次读取时会执行一次。
(2) 当依赖的数据发生改变时会被再次调用。

优势: computed计算属性有缓存的功能,计算属性在处理一些复杂逻辑时是很有用的。

<template>
<div>
    <h3>{{name}} - {{slogen}}h3>
    <h3>{{name +' - ' + slogen}}h3>
    <h3>{{getTitle()}}h3>
    <h3>{{title}}h3>
div>
template>

<script>
const data = {
    name: '张三',
    slogen: '新的世界'
}
export default {
  name: 'App',
  data() {
    return data
  },
  computed: {
    title: {
      get() {
        console.log('get computed')
        return this.name+ ' - '+this.slogen;
      }
    }
  },
  methods: {
    getTitle() {
      console.log('get methods')
      return this.name+ ' - '+this.slogen;
    }
  }
}
script>

在这里插入图片描述

4.4.1 methods VS computed

如果有多个数据

    <h3>{{getTitle()}}h3>
    <h3>{{getTitle()}}h3>
    <h3>{{getTitle()}}h3>
    <h3>{{title}}h3>
    <h3>{{title}}h3>
    <h3>{{title}}h3>

运行查看控制台
在这里插入图片描述

methods里的getTitle()是使用多少次方法就调用多少次。
而computed有缓存的作用,只计算一次,computed里的title依赖于name和sologen,只要name和slogen没有变化,这个两个属性值就一直保存在缓存中,若更改,则相应title绑定也会更新。

计算属性默认只有getter,需要时也提供一个setter
计算属性完整写法

// ...
computed: {
  fullName: {
    // getter 可以省略,
    //当fullName所依赖的firstNameh和lastName发生改变时会被再次调用
    get() {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set(newValue) {
      const names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

现在在运行 vm.fullName = 'John Doe' 时, setter 会被调用, vm.firstNamevm.lastName 也会被对应更新。

计算属性常用简写(只考虑读取,不考虑修改的情况下)

computed: {
  fullName()  {
     return this.firstName + '-' + this.lastName;
  }
 }

例子

总价:<small></small>{{totalPrice}}
.....
const data = {
    books: [
      {id:1, name:'JS大红本第一版',price:120},
      {id:1, name:'JS大红本第二版',price:130},
      {id:1, name:'JS大红本第三版',price:150},
      {id:1, name:'JS大红本第四版',price:190}
    ]
}
export default {
  name: 'App',
  data() {
    return data
  },
	//计算属性
  computed: {
    totalPrice: {
      get() {
        //汇合
        return this.books.reduce((s,n)=> s+=n.price, 0)
      }
    }
  }
}

在这里插入图片描述

4.4.2 监听属性watch及computed VS watch

Vue.js提供了一个方法 $watch, 它用于观察 Vue 实例上的数据变动。当一些数据需要根据其它数据变化时。

监听属性watch: 监听具体数据变化,当数据属性变化时, 回调函数handler自动调用, 在函数内部进行计算

写法:
(1)在vue实例vm中传入watch配置来监视指定的属性
(2)通过vm对象的 $watch()

应用场景: 数据变化时执行异步或开销较大(比较耗时)的操作
注意: 监听的属性必须在vm中存在,才能进行监听

思考下面例子:

<div id="demo">{{ fullName }}div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
	//完整写法
	//xxx为vm实例中存在且被监听的属性
     /*   
     xxx: {
          immediate: true, //初始化时让handler调用一下
          //handler(固定函数) 什么时候调用?当函数中的数据发生改变时
          handler(newValue, oldValue) {....},
        },
     */
    //简写 (当只需要handler属性时)
    firstName(val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName(val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

//还有另外一种写法,但是需要保证vm实例已创建
// vm.$watch 正常写法
vm.$watch('fistName', {
	immediate: true,
	deep: true,
	handler(newValue ,oldVelue) {
		this.fullName = val + ' ' + this.lastName
	}	
})
//vm.$watch 简写
vm.$watch('fistName', function(newValue ,oldVelue){
	this.fullName = val + ' ' + this.lastName
	}	
})

上面代码是命令式的和重复的。跟计算属性对比:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
  	fullName() {
      return this.firstName + ' ' + this.lastName
    }
  }
})

计算属性的方法更好点。

深度监听:
(1) Vue中的watch默认不监测对象内部值的改变,只检测第一层。
(2) 配置deep:true可以监测对象内部值改变,可以检测多层。
备注:
(1). Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2). 使用watch时根据数据的具体结构,决定是否采用深度监视。

var vm = new Vue({
	el: '#demo',
	data: {
		numbers:{
			a:1,
			b:2
		} 
	},
	watch: {
		//监视多级结构中某个属性的变化
		'numbers.a':{
			handler() {……}
		},
		//监视多级结构中所有属性的变化
		numbers: {
			deep: true,
			handler() {……}
		}
	}
})

4.4.3 computed与watch、methods的区别

computed:计算属性,依赖其他属性,当其他属性改变的时候,下一次获取computed值时也会改变,computed的值会有缓存
watch:监听属性,监听具体数据变化,当数据属性变化时, 回调函数handler自动调用, 在函数内部进行计算
methods : 该属性用于在Vue对象中定义方法。

computedwatch区别:

  • computed能完成的功能,watch都可以完成。watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
  • 当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
  • 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化

计算属性computed在大多数情况下更合适,但当需要在数据变化时执行异步或开销较大的操作时,使用watch更适用。

computedmethods区别:
计算属性是基于它们的依赖进行缓存,如果多次使用时,计算属性只会调用一次,性能上计算属性明显比methods好,如果依赖改变则重新缓存,而方法不缓存

4.3 事件监听 v-on

在前端开发中,需要经常和用户交互
绑定事件监听器指令:v-on
缩写: @ (语法糖)
参数: $event (获取事件对象)

注意:

  • 事件的回调需要配置在methods对象中,最终会在vm上;
  • methods中配置的函数,不要用箭头函数!否则this就不是vm
  • methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象;
  • 事件函数的调用方式:
    @click="demo”@click="demo($event)"效果一致,但后者可以传参

<a v-on:click="doSomething"> ... a>


<a @click="doSomething"> ... a>


<a @[event]="doSomething"> ... a>
<template>
  <div>
    msg = {{msg}}<br>
    <input type="text" v-model="msg"><br><br>
    num = {{num}}<br>
    //<button @click="num--">-button>
    <button @click="sub">-button>
    <input type="text" size="3" v-model="num">
    //<button @click="num++">+button>
    <button @click="add">+button>
  div>
template>

<script>
const data = {
    msg: 'this is a test',
    num: 0,
    max: 10,
    min: 0
}
export default {
  name: 'App',
  data() {
    return data
  },
  methods: {
    add() {
      if (this.num >= this.max) {
        this.num = this.max;
      } else {
        this.num++;
      }

    },
    sub() {
      if (this.num <= this.min) {
        this.num = this.min;
      }else {
        this.num--;
      }
    }
  }
}
script>

<style>
.....
style>

效果
在这里插入图片描述

4.3.1 v-on事件修饰符号

  • .stop阻止事件冒泡
  • .self 当事件在该元素本身触发时才触发事件
  • .capture 添加事件侦听器是,使用事件捕获模式
  • .prevent 阻止默认事件
  • .once 事件只触发一次
  • .enter 只有在 keyEnter 时调用
  • .passive 滚动事件的默认行为 (即滚动行为) 将会立即触发
  • 修饰符可串联

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

4.3.2 v-on按键修饰符号

  • .enter 只有在 keyEnter 时调用
  • .delte (捕获“删除”和“退格”键)
  • .esc 退出
  • .space 空格
  • .tab 换行 (特殊,必须配合keydown去使用)
  • .up.down.left.right

系统修饰键(用法特殊):ctrl、alt、shift、meta
(1). 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。比如ctrl+s 然后释放s
(2). 配合keydown使用:正常触发事件。
也可以使用keyCode去指定具体的按键(不推荐),但注意要转为kebab-case(短横线命名)
Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

//keyup 按键松开触发

<input @keyup.enter='submit'>

<input @keyup.delete='handle'>
<div id="app">
   <form action="">
     <div>
       用户名: <input type="text" @keyup.delete='clearContent' v-model='uname'>
     div>
     <div>
       密码:<input type="text" @keyup.enter='handleSubmit' v-model='pwd'>
     div>
     <div>
       <input type="button" @click='handleSubmit' value="提交">
     div>
   form>
 div>
 //……
  <script type="text/javascript">
    var vm = new Vue({
      el: '#app',
      data: {
        uname: '',
        pwd: '',
        age: 0
      },
      methods: {
        clearContent(){
          // 按delete键的时候,清空用户名
          this.uname = '';
        },
        handleSubmit(){
          console.log(this.uname,this.pwd)
        }
      }
    });
  script>
//……

除了系统的修饰符,还有自定义按键修饰符
规则:自定义按键修饰符名字是自定义的,但是对应的值必须是按键对应event.keyCode值

  <div id="app">
    <input type="text" @keyup.f1='handle' v-model='msg'>
  div>
  <script type='text/javascript'>
  //a的ACSII值为65
    Vue.config.keyCodes.f1 = 65
    const app = new Vue({
      el: "#app",
      data: {
        msg: ''
      },
      methods: {
        handle: function (event) {
          console.log(event.keyCode);
        }
      }
    })
  script>

4.3.3 获取事件对象

<button @click="sub('sub',$event)">-button>
<input type="text" size="3" v-model="num">
<button @click="add">+button>
 add(e) {
      console.log(e);          //没传参,获取得到事件对象
      if (this.num >= this.max) {
        this.num = this.max;
      } else {
        this.num++;
 }

},
sub(p) {
	 console.log(p,e);          //传参了,一个为’sub',一个为事件对象
	 if (this.num <= this.min) {
	   this.num = this.min;
	 }else {
	   this.num--;
	 }
 }

在这里插入图片描述

<div @click="one()" class="box1">
    <div @click="two()" class="box2">
      <button @click="three()">按钮button>
    div>
div>

<script>
.....
export default {
  name: 'App',
  data() {
    return data
  },
  methods: {
    one() {
      console.log('one');
    },
    two() {
      console.log('two');
    },
    three() {
      console.log('three');
    }

  }
}
script>
<style scoped>
.box1 {
  width: 150px;
  height: 150px;
  background-color: #42b983;
}
.box2 {
  width: 100px;
  height: 100px;
  background-color: rebeccapurple;
}
style>

在这里插入图片描述在这里插入图片描述
点击按钮,事件冒泡
若要停止事件冒泡,则
可串行使用

<div @click.self.stop="two()" class="box2">
   <button @click="three()">按钮button>
div>

4.4 条件渲染 v-if、v-show

v-ifv-show
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染并保留在 DOM 中,并且只是简单地基于 CSS 进行切换(display)

v-if和v-show的区别
v-if 指令是直接销毁和重建DOM达到让元素显示和隐藏的效果
v-show指令是通过修改元素的display属性让其显示或者隐藏

v-if有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

条件分支 v-if v-else
多条件分支 v-if v-else-if v-else
注意:v-if 可以和v-else-if v-else一起使用,但要求结构不能被打断

<h1 v-if="awesome">Vue is awesome!h1>
<h1 v-else>Oh no h1>


<button @click="isShow = !isShow">显示/隐藏button>
<h1 v-if="isShow">Vue is awesome!h1>
<h1 v-show="isShow">Oh no h1>

显示状态
在这里插入图片描述

隐藏状态
在这里插入图片描述

4.5 列表渲染 v-for

4.5.1 基本用法

遍历指令:v-for
遍历数组 v-for=”(item, [index]) in 数组”
遍历对象 v-for=”(value, [key], [index]) in 对象”
vue中列表循环需加:key="唯一标识" 唯一标识可以是item里面id index等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件 key的作用主要是为了高效的更新虚拟DOM,使用diff算法的处理方法,对操作前后的dom树同一层的节点进行对比,一层一层对比

<div id="app">
	<ul>
      <p>遍历数组p>
      <li v-for="(item,index) in list" :key="item">{{ index+1}} - {{ item }}li>
    ul>
    <ul>
      <p>遍历对象p>
      <li v-for="(item,key,index) in obj" :key="item">{{index+1}} - {{ key}} - {{ item }}li>
    ul>
    <ul>
      <p>遍历数组对象p>
      <li v-for="(item,index) in books" :key="item.id">{{ index+1 }} - {{ item.name }} - {{item.price}}li>
    ul>
    <ul>
      <p>遍历字符串p>
      <li v-for="(char,index) in str" :key="item.id">{{ char }} - {{ index }}li>
    ul>
    
div>
Vue.createApp({
  data() {
    return {
      str: 'hello',
      list: ['Java','Python','C/C++','PHP','Vue'],
      obj: {
	    name: '百度',
	    url: 'http://www.baidu.com',
	    slogen: 'get it'
	  },
	 books: [
	    {id:1, name:'Foo',price:20},
	    {id:2, name:'Bar',price:25},
	    {id:3, name:'Zun',price:50},
	    {id:4, name:'Dom',price:40},
	  ]
    }
  }
}).mount('#app')

在这里插入图片描述Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第11张图片

选中样式应用

<div id="app">
    <ul>
      <p>遍历数组对象p>
     <li :class="{active:item.active}" @mouseenter="over(index)" v-for="(item,index) in books" :key="item.id">{{ index+1 }} - {{ item.name }} - {{item.price}}li> 
    ul>
    
div>
Vue.createApp({
  data() {
    return {
      books: [
	   {id:1, name:'Foo',price:20, active: false},
	    {id:2, name:'Bar',price:25, active: false},
	    {id:3, name:'Zun',price:50, active: false},
	    {id:4, name:'Dom',price:40, active: false},
	  ]
    }
  },
  methods: {
    over(index) {
      for (let i in this.books) {
        if (index == i)
          this.books[index].active = true;
        else
          this.books[i].active = false;
      }
   }
}).mount('#app')

在这里插入图片描述

4.5.2 虚拟DOM、Diff算法、Key选用

原始JS实现功能:数据 => 真实DOM
Vue实现功能:数据 => 虚拟DOM(内存中的数据)=> 真实DOM
虚拟DOM 可以理解为虚拟节点,是一个用来描述真实DOM结构的js对象。

  • 优点:减少了DOM操作,减少了回流与重绘,保证性能的下限,比正常的DOM性能更好
  • 缺点:首次渲染DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。

Diff算法是用于比较新旧虚拟节点之间差异的一种算法,每个虚拟节点都有一个唯一标识key,通过对比新旧节点的key来判断节点是否改变,将两个节点不同的地方存储在patch对象中,最后利用patch记录的消息局部更新DOM。
Key主要用在虚拟Dom算法中,每个虚拟节点都有一个唯一标识Key,通过对比新旧节点的key来判断节点是否改变,可以大大提高渲染效率。

面试题: React vue 中的 key 有什么作用? ( key 的内部原理)
虚拟DOM 中 key 的作用: key是 虚拟DOM 中对象的标识,当数据发生变化时, Vue 会根据新数据生成新的虚拟DOM,随后 Vue 进行新 虚拟DOM 与旧虚拟DOM 的差异比较,比较规则如下

  1. 对比规则:
    (1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
    ①.若新节点中内容没变(和旧节点一样), 直接复用之前的旧节点生成真实DOM
    ②.若新节点中内容变了(和旧节点不一样), 则生成新的真实DOM,随后替换掉页面中之前旧虚拟DOM生成的真实DOM
    (2). 旧虚拟DOM中未找到与新虚拟DOM相同的key,则创建新的真实DOM,随后渲染到到页面。

  2. index作为key可能会引发的问题:
    (1).若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ,这时界面效果没问题, 但渲染效率低。
    (2).如果结构中还包含输入类的DOM,会产生错误DOM更新,使界面有问题

  3. 开发中如何选择key:
    (1).最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
    (2).如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

由下图可知,第一次有三项数据渲染为真实DOM,第二次渲染中的数据比第一次多了一项,此时Vue使用Diff算法按照规则在虚拟DOM中依次对比每一项数据,发现前三项数据key和内容都相同,则复用第一次的真实DOM,最后一项数据没有则需渲染新数据为真实DOM,提高了渲染效率。
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第12张图片

4.5.3 列表过滤与排序

列表过滤:可以使用watch也可以使用计算属性,使用计算属性更加简单方便一点

<div id="root">
	<h2>人员列表h2>
	<input type="text" placeholder="请输入名字" v-model="keyWord">
	<ul>
		<li v-for="(p,index) of filPerons" :key="index">
			{{p.name}}-{{p.age}}-{{p.sex}}
		li>
	ul>
div>
<script type="text/javascript">
	Vue.config.productionTip = false
	
	//用watch实现
	/* 
	new Vue({
		el:'#root',
		data:{
			keyWord:'',
			persons:[
				{id:'001',name:'马冬梅',age:19,sex:'女'},
				{id:'002',name:'周冬雨',age:20,sex:'女'},
				{id:'003',name:'周杰伦',age:21,sex:'男'},
				{id:'004',name:'温兆伦',age:22,sex:'男'}
			],
			filPerons:[]
		},
		watch:{
			keyWord:{
			    //初始化自动调用handler,输入框为空,handler的val为空字符串
				immediate:true,
				//'abcd'.indexOf('') =0 一个字符串indexOf空字符串结果为0,filPerons就获取到了persons所有值
				handler(val){
					this.filPerons = this.persons.filter((p)=>{
						return p.name.indexOf(val) !== -1
					})
				}
			}
		}
	}) */
	//#endregion
	
	//用computed实现
	new Vue({
		el:'#root',
		data:{
			keyWord:'',
			persons:[
				{id:'001',name:'马冬梅',age:19,sex:'女'},
				{id:'002',name:'周冬雨',age:20,sex:'女'},
				{id:'003',name:'周杰伦',age:21,sex:'男'},
				{id:'004',name:'温兆伦',age:22,sex:'男'}
			]
		},
		computed:{
			filPerons(){
				return this.persons.filter((p)=>{
					return p.name.indexOf(this.keyWord) !== -1
				})
			}
		}
	}) 
</script>

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第13张图片
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第14张图片
列表排序

<div id="root">
	<h2>人员列表h2>
	<input type="text" placeholder="请输入名字" v-model="keyWord">
	<button @click="sortType = 2">年龄升序button>
	<button @click="sortType = 1">年龄降序button>
	<button @click="sortType = 0">还原顺序
	<ul>
		<li v-for="(p,index) of filPerons" :key="p.id">
			{{p.name}}-{{p.age}}-{{p.sex}}
		li>
	ul>
	
<script type="text/javascript">
	Vue.config.productionTip = false
	//用computed实现
	new Vue({
		el:'#root',
		data:{
			keyWord:'',
            sortType: 0, //0代表原本, 1代表升序, 2代表降序
           	persons: [
                   {id:'001',name:'马冬梅',age:19,sex:'女'},
                   {id:'002',name:'周冬雨',age:20,sex:'女'},
                   {id:'003',name:'周杰伦',age:21,sex:'男'},
                   {id:'004',name:'温兆伦',age:22,sex:'男'}
                ],
		},
		computed: {
            filPerons() {
                const arr = this.persons.filter((p)=>{
                        return p.name.indexOf(this.keyWord) !== -1
                    })
                // 判断下是否需要排序
                if (this.sortType) {
                    arr.sort((p1, p2) => {
                        return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
                    })
                }
                return arr
            }
        }
	}) 
</script>

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第15张图片

4.5.4 Vue数据监视

更新时的一个问题
this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} 更改data数据,Vue不监听,模板不改变

<div id="root">
    <h2>人员列表h2>
    <button @click="updateMei">更新马冬梅的信息button>
    <ul>
        <li v-for="(p,index) of persons" :key="index">
            {{p.name}}-{{p.age}}-{{p.sex}}
        li>
    ul>
div>
<script type="text/javascript">
	Vue.config.productionTip = false
	//用computed实现
	new Vue({
		el:'#root',
		data:{
			keyWord:'',
            persons: [
                    {id:'001',name:'马冬梅',age:19,sex:'女'},
                    {id:'002',name:'周冬雨',age:20,sex:'女'},
                    {id:'003',name:'周杰伦',age:21,sex:'男'},
                    {id:'004',name:'温兆伦',age:22,sex:'男'}
                ],
		},
		methods: {
            updateMei() {
                // this.persons[0].name = '马老师'  //奏效
                // this.persons[0].age = 50     //奏效
                // this.persons[0].sex = '男'    //奏效
                this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'}  // 不奏效
                this.persons.splice(0,1, {id:'001',name:'马老师',age:50,sex:'男'})  //奏效
               
            }
        }
	}) 
</script>

this.persons.splice(0,1, {id:'001',name:'马老师',age:50,sex:'男'}) 却有效果,原因解释如下:

数据代理过程
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第16张图片
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第17张图片
模拟数据监测

let data= {
	name: 'JJ boy',
	address: 'BeiJing',
	childs: {
        ch1: {
           name: 'Bebond',
           age: 20
        },
        ch2:  {
           name: 'Mary',
           age: 25
        }
    }
}

// 创建一个监视的实例对象,用于监视data中的属性变化
const obs = new Observer(data)
console.log(obs)
//准备一个vm实例对象
let vm = {}
vm._data = data = obs 
function Observer(obj) {
   //汇总对象中所有属性形成一个数组
   const keys = Object.keys(obj)
   //遍历
   keys.forEach(k => {
       Object.defineProperty(this, k, {
           get(){
               return obj[k]
           },
           set(val) {
               console.log('$(k)被改了,去解析模板,生成虚拟DOM……')
               obj[k] = val
           }
       })
       
   });
}

控制台,data的数据修改了,vm._data数据属性同样修改
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第18张图片
但是上诉代码的有个缺点,没有考虑到data里有对象,对象里还有属性的情况,上述的例子没法为对象里的属性匹配gettersetter,如下图
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第19张图片

而Vue中会为所有对象属性匹配gettersetter,即vue会监视data中所有层次的数据

//这样声明,可查看控制台
const vm = new Vue({
	el: '#root',
    data: { 
    	name: 'JJ boy',
        address: 'BeiJing',
        hobby: ['h1','h2','h3'],
        childs: {
           ch1: {
                name: 'Bebond',
                age: 20
            },
            ch2:  {
                name: 'Mary',
                age: 25
            }
          }
        },
    })

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第20张图片
原理:

  1. Vue监测数据:vue会监视data中所有层次的数据。
  2. 如何监测对象中的数据?
    通过setter实现监视,且要在new Vue时就传入要监测的数据。
    (1).对象中后追加的属性,Vue默认不做响应式处理(页面没有更改);
    (2).如需给后添加的属性做响应式,请使用如下API:
    Vue.set(target, propertyName/index, value)
    Vm.$set(target, propertyName/index, value)
    target :追加属性的目标
    key : 追加的属性
    value:追加属性的值
    特别注意:Vue.set()vm.$set() 不能给Vue实例对象或是Vue实例的根数据对象(data)添加属性
//监测对象数据
const vm = new Vue({
	el: '#root',
    data: { 
    	name: 'JJ boy',
        address: 'BeiJing',
        hobby: ['h1','h2','h3'],
        childs: {
           ch1: {
                name: 'Bebond',
                age: 20
            },
            ch2:  {
                name: 'Mary',
                age: 25
            }
        }}methods: {
        	// 这种方式追加的属性是响应式的
			addSex() {
				Vue.set(this.childs.ch1, 'sex', 'man')
				//或是
				this.$set(this.childs.ch1, 'sex', 'man')
				
			}
		}
      
    })
  1. 如何监测数组中的数据?
    Vue监测数组数据是通过包装数组更新元素的常用方法实现的,其本质就是做了两件事:
    a.调用原生对应的方法对数组进行更新
    b.重写解析模板,进而更新页面
    在Vue中修改数组中的某个元素一定要用如下方法:
    push()、pop()、unshift()、shift()、splice()、sort()、reverse()
    这几个方法被Vue重写了,使用这些方法会变更原数组
    或者是**Vue.set()vm.$set()变更数组

变更方法:

  • push / pop: 末尾添加、删除,改变原数组, 返回添加之后新数组的长度或删除的这个值
  • unshift / shift: 头部添加、删除,改变原数组,返回添加之后新数组的长度或删除的这个值
  • sort/ reverse: 排序、反转,改变原数组
  • splice(start开始的位置, number删除/更改的个数, 替换的值): 一般用于删除或更改数组中的元素,返回删除或更改元素组成的数组,改变原数组

还有一些非变更方法,不会改变原数组,总是返回一个新数组,如:filter()、concat()、slice()

非变更方法:

  • filter(item => true(满足条件为true))返回的是满足条件的一个新数
  • concat: 连接数组,不影响原数组, 浅拷贝
  • slice(start开始的索引, end结束的索引): 返回截断后的新数组,不改变原数组

当使用非变更方法时,用一个含有相同元素的数组去替换原来的数组是非常高效的操作,例如:

//h !== '抽烟'的元素组成一个新数组
this.student.hobby = this.student.hobby.filter((h) => {
    return h !== '抽烟';
 });

通过数组索引修改数组,无法响应
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第21张图片
通过变更数组的方法来监测数组数据,具有响应式
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第22张图片
所以,前面的例子使用this.persons.splice(0,1, {id:'001',name:'马老师',age:50,sex:'男'}) 有响应效果

除了变更方法,还可以使用前面提到的**Vue.set()vm.$set()来修改数组
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第23张图片
例子

<div id="root">
        <h1>学生信息h1>
        <button @click="student.age++">年龄+1岁button><br />
        <button @click="addSex">添加性别属性,默认值为:男button><br />
        <button @click="student.sex='' ">修改性别为女button> <br />
        <button @click="addFriend">在列表首位添加一个朋友button> <br />
        <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三button><br />
        <button @click="addHobby">添加一个爱好button> <br />
        <button @click="updateFirstHobby">修改第一个爱好为:开车button> <br />
        <button @click="removeSmoke">过滤掉爱好中的抽烟button> <br />
        <h3>姓名:{{student.name}}h3>
        <h3>年龄:{{student.age}}h3>
        <h3 v-if="student.sex">性别:{{student.sex}}h3>
        <h3>爱好:h3>
        <ul>
            <li v-for="(p,index) in student.hobby" :key="index">
                {{p}}
            li>
        ul>
        <h3>朋友们:h3>
      <ul>
        <li v-for="(f,index) in student.friends " :key="index">
          {{f.name}}--{{f.age}}
        li>
      ul>
        
    div>
const vm = new Vue({
        el: '#root',
        data: {
            student: {
                name: 'JJ boy',
                age: 18,
                hobby: ['抽烟', '喝酒', '烫头'],
                friends: [
                { name: 'jerry', age: 35 },
                { name: 'tony', age: 36 },
                ],
            }
        },
        methods: {
            addSex() {
                this.$set(this.student, 'sex', '男')
            },
            addFriend() {
                this.student.friends.unshift({name:'Mary', age: 10})
            },
            updateFirstFriendName() {
                this.student.friends[0].name = '张三'
            },
            addHobby() {
                this.student.hobby.push('打游戏')
            },
            updateFirstHobby(){
                this.$set(this.student.hobby, 0, '开车')
            },
            removeSmoke(){
                this.student.hobby = this.student.hobby.filter(h=>{
                    return h !== '抽烟'
                })
            }

        }
    })

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第24张图片

4.6 数据绑定 v-model

v-model指令
本质: 它负责监听用户的输入事件,从而更新数据,并对一些极端场景进行一些特殊处理。同时,v-model会忽略所有表单元素的value、checked、selected特性的初始值,它总是将vue实例中的数据作为数据来源。 然后当输入事件发生时,实时更新vue实例中的数据。
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value 属性 和 input 事件;
  • checkbox 和 radio 使用 checked 属性 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

特点: 数据不仅能从 data 流向视图,还能从视图流向 data
实现原理:

<input v-bind:value="message" v-on:input="message = $event.target.value" /> 

v-model 组件上的 v-model默认会利用名为 value 的 prop 和名为 input 的事件。 v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
  1. v-bind绑定一个value属性
  2. v-on指令给当前元素绑定input事件
  
v-model的修饰符号:

  • .lazy 懒加载修饰符
  • .number 修饰符让其转换为 number 类型
  • .trim修饰符可以自动过滤掉输入框的首尾空格

使用v-model时要切记:
v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

自定义组件:
自定义组件使用v-model,有以下操作:
1. 接收一个value prop
2. 触发input事件,并传入新值


 <input v-model="parentData">
 

 <input v-bind:value="parentData" v-on:input="parentData = $event.target.value"> 
 

<input :value="parentData" @input= "parentData = $event.target.value">

@input是对输入事件的一个监听,:value="parentData"是将监听事件中的数据放入到input。

在自定义组件中


 <my-component v-model="inputValue">my-component>
 

<my-component v-bind:value="inputValue" v-on:input="inputValue = argument[0]"> my-component>
 

这个时候,inputValue接受的值就是input事件的回调函数的第一个参数,所以在自定义组件中,要实现数据绑定,还需要$emit去触发input的事件。
this.$emit('input', value)

v-model不仅可以给input赋值还可以获取input中的数据,而且数据的获取是实时的,因为语法糖中是用@input对输入框进行监听的。

  1. 输入框
  • 双向绑定
<input type="text" v-model="msg"><br>{{msg}}

在这里插入图片描述

单向绑定

<input type="text" :value="msg"><br>
    {{msg}}


<input type="text" :value="msg" @input="msg=$event.target.value"><br>
    {{msg}}

在这里插入图片描述

  1. 单选框
<label for="one">
   <input type="radio" id="one" value="" v-model="sex">label>
 <label for="two">
   <input type="radio" id="two" value="" v-model="sex">label>
 <br> sex: {{sex}}
Vue.createApp({
  data() {
    return {
      sex: '男'
    }
  }
}).mount("#app")

在这里插入图片描述

  1. 复选框
    单个复选框
<input type="checkbox" v-model="checked" id="checkbox">
<label for="checkbox">{{checked}}label>
Vue.createApp({
  data() {
    return {
      checked: false
    }
  }
}).mount("#app")

在这里插入图片描述在这里插入图片描述

多个复选框

<div id="app">
  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
  <label for="jack">Jacklabel>
  <input type="checkbox" id="john" value="John" v-model="checkedNames" />
  <label for="john">Johnlabel>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
  <label for="mike">Mikelabel>
  <br />
  <span>Checked names: {{ checkedNames }}span>
div>
Vue.createApp({
  data() {
    return {
      checkedNames: []
    }
  }
}).mount("#app")

在这里插入图片描述

  1. 选择框
<div id="app">
  <select v-model="selected">
      <option disabled value="">Please select oneoption>
      <option>Aoption>
      <option>Boption>
      <option>Coption>
  select>
  <span>Selected: {{ selected }}span>
div>
Vue.createApp({
  data() {
    return {
      selected : ''
    }
  }
}).mount("#app")

在这里插入图片描述

选择框多选时

<select v-model="selected" multiple>
  <option>Aoption>
  <option>Boption>
  <option>Coption>
select>
<br />
<span>Selected: {{ selected }}span>

在这里插入图片描述
v-for渲染的动态选项

<div id="app">
  <select v-model="selected">
     <option v-for="op in options" :value="op.value" :key="op.value">{{op.name}}option>
  select>
  <span>Selected: {{ selected }}span>
div>
Vue.createApp({
  data() {
    return {
      selected: 'zs',
	  options: [
	    {name:'张三',value:'zs'},
	    {name:'王五',value:'ww'},
	    {name:'李四',value:'ls'},
	    {name:'梅芳',value:'mf'},
	  ]	
    }
  }
}).mount("#app")

在这里插入图片描述

4.7 过滤器filter(Vue3已经移除)

**定义:**对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:

//全局过滤器
Vue.filter(filterName, function(value[,arg1,arg2,...]){
  // 进行一定的数据处理
  return newValue
})
//局部过滤器
new Vue{
	filters:{
		filterName(value){
			return newValue
		}
	}
}

//使用方法
<div>{{myData | filterName}}div>
<div>{{myData | filterName(arg)}}div>

备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据

日期格式化例子

<body>
    <div id="demo">
        <h2>显示格式化的日期时间h2>
        <p>{{date}}p> 
        <p>完整版:{{date | dateString}}p>
        <p>年月日:{{date | dateString('YYYY-MM-DD')}}p>
        <p>时分秒:{{date | dateString('HH:mm:ss')}}p>
    div>
    <script src="../js/vue.js">script>
    //引入时间格式化插件
    <script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.29.1/moment.js">script>
    <script>
        Vue.filter('dateString', function(value, format='YYYY-MM-DD HH:mm:ss'){
            return moment(value).format(format);
        });

        new Vue({
            el: '#demo',
            data: {
                date: new Date()
            }
        })
    script>
body>

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第25张图片

处理时间的库 moment 体积较大 dayjs 轻量级

 <body>
    <div id="root">
      <h2>显示格式化后的时间h2>
      
      <h3>现在是:{{fmtTime}}h3>
      
      <h3>现在是:{{getFmtTime()}}h3>
      
      <h3>现在是:{{time | timeFormater}}h3>
      
      <h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}h3>
      <h3 :x="msg | mySlice">尚硅谷h3>
    div>

    <div id="root2">
      <h2>{{msg | mySlice}}h2>
    div>
  body>
	<script type="text/javascript" src="../js/vue.js">script>
    <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.0/dayjs.min.js">script>
	<script type="text/javascript">
	    Vue.config.productionTip = false;
	    //全局过滤器
	    Vue.filter('mySlice', function (value) {
	      return value.slice(0, 4);
	    });
	
	    new Vue({
	      el: '#root',
	      data: {
	        time: 1621561377603, //时间戳
	        msg: '你好',
	      },
	      computed: {
	        fmtTime() {
	          return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss');
	        },
	      },
	      methods: {
	        getFmtTime() {
	          return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss');
	        },
	      },
	      //局部过滤器
	      filters: {
	        timeFormater(value, str = 'YYYY年MM月DD日 HH:mm:ss') {
	          // console.log('@',value)
	          return dayjs(value).format(str);
	        },
	      },
	    });
	
	    new Vue({
	      el: '#root2',
	      data: {
	        msg: 'hello!',
	      },
	    });
	  script>
body>

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第26张图片

4.8 模板语法应用——简易购物车

<template>
  <div>
    <div v-if="cartlist.length <= 0">您没有选择的商品,购物车为空,<a href="#">去购物a>div>
    <table v-else>
      <caption><h1>购物车h1>caption>
      <tr>
        <th>th>
        <th>编号th>
        <th>商品名称th>
        <th>商品价格th>
        <th>购买数量th>
        <th>操作th>
      tr>
      <tr v-for="(item,index) in cartlist" :key="item.id">
        <td><input type="checkbox" v-model="item.checked">td>
        <td>{{item.id}}td>
        <td>{{item.name}}td>
        <td><small>small>{{item.price.toFixed(2)}}td>
        <td>
          <button @click="item.count--" :disabled="item.count <= 1 ">-button>
          {{ item.count}}
          <button @click="item.count++">+button>
        td>
        <td><a href="#" @click.prevent="del(index)">删除a>td>
      tr>
      <tr>
        <td colspan="3" align="right">总价td>
        <td colspan="3">{{ totalPrice }}td>
      tr>
    table>
  div>

template>

<script>
export default {
  name: 'App',
  data() {
    return {
      cartlist: [
        {id:1, checked:true, name:'《活着》', price:80, count:1},
        {id:2, checked:true, name:'《岛上世界》', price:40, count:1},
        {id:3, checked:true, name:'《权力属于有自制力的人》', price:50, count:1},
        {id:4, checked:true, name:'《朝花夕拾》', price:120, count:1},
        {id:5, checked:true, name:'《完美世界》', price:99, count:1},
        {id:6, checked:true, name:'《无间道》', price:39, count:1},
      ]
    }
  },
  computed: {
    totalPrice: {
      get() {
        let sum = 0;
        for (let book of this.cartlist) {
          if (book.checked)
            sum += book.count * book.price;
        }
        return '¥'+sum.toFixed(2);
      }
    }
  },
  methods: {
    del(index) {
          this.cartlist.splice(index,1)
      }
    }
}
script>

<style scoped>
table {
  width: 600px;
  border: 1px solid #333;
  border-collapse: collapse;
}
th {
  background-color: #d2d2d2;
}
td, th {
  bord`在这里插入代码片`er: 1px solid #333333;
  padding: 10px;
}
style>

在这里插入图片描述

5. 组件基础

5.1 模块与组件、模块化与组件化

传统方法编写应用问题
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第27张图片
组件方法编写应用
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第28张图片
模块
a. 理解: 向外提供特定功能的 is 程序,一般就是一个is 文件
b. 为什么: js 文件很多很复杂
c. 作用: 复用、简化js 的编写,提高js 运行效率

组件
a. 定义: 用来实现局部功能的代码和资源的集合(html/css/js/image…)
b. 为什么: 一个界面的功能很复杂
c. 作用: 复用编码,简化项目编码,提高运行效率

模块化
当应用中的 is 都以模块来编写的,那这个应用就是一个模块化的应用

组件化
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用

5.2 非单文件组件

非单文件组件: 一个文件中包含有 n 个组件
单文件组件: 一个文件中只包含有 1 个组件

5.2.1 组件基本使用

Vue中使用组件的三大步骤:

  • 定义组件
  • 注册组件
  • 使用组件
  1. 定义组件
    使用Vue.extend(options)创建,其中 optionsnew Vue(options)时传入的 options 几乎一样,但也有点区别
    a. el 不要写,因为最终所有的组件都要经过一个 vm 的管理,由 vm 中的 el才决定服务哪个容器
    b. data 必须写成函数,避免组件被复用时,数据存在引用关系,(若data为对象不是函数,那么创建出来的实例保持的都是对同一个对象的引用(同一数据的引用地址复制了多份,但它们还是指向相同的数据),其中一个组件更改了data数据,则另外的组件data数据也会更改)
    c. 组件模板内容只包含一个根元素div ,单文件组件template下有且只能有一个根元素div(遍历起始点)
    d. 组件模板内容可以是模板字符串(ES6 新的声明字符串的方式)
//data为对象形式
let data = {
  a: 1,
  b: 2,
};
const x1 = data;
const x2 = data;
x1.a = 99;
console.log(x1.a); //99
console.log(x2.a); //99

//data为函数形式
function data1() {
  return {
    a: 1,
    b: 2,
  };
}
const x3 = data1();
const x4 = data1();
x3.a = 99;
console.log(x3.a); //99
console.log(x4.a); //1

组件命名方式:
一个单词组成
第一种写法(首字母小写):school
第二种写法(首字母大写):School

多个单词组成
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool(在使用Vue脚手架的情况下可用)

组件名尽可能回避HTML中已有的元素名称,例如h2、H2,也可以使用name配置项指定组件在开发者工具中呈现的名字

  const school = Vue.extend({
    template: `
		

学校名称:{{schoolName}}

学校地址:{{address}}

`
, data() { return { schoolName: '尚硅谷', address: '北京昌平' } }
  1. 注册组件
    a. 局部注册:new Vue()的时候options 传入components 选项
    b. 全局注册:Vue.component('组件名','组件')使用组件
//全局注册
Vue.component('school', school)

//局部注册  
const app = new Vue({
   el:"#app",
   components:{//局部组件创建
     //'school': school
     school
   }
 })
  1. 使用组件
    第一种写法:直接使用调用组件
    第二种写法: (在使用Vue脚手架的情况下可用)
    **备注:**不使用脚手架时, 会导致后续组件不能渲染
    一个简写方式:
    const school = Vue.extend(options)可简写为 const school = options,因为父组件 components 引入的时候会自动创建

组件案例

<body>
  <div id="root">
    <hello>hello>
    <hr>
    <h1>{{msg}}h1>
    <hr>
    
    <school>school>
    <hr>
    
    <student>student>
  div>

  <div id="root2">
    <hello>hello>
  div>
body>

<script type="text/javascript">
  //第一步:创建school组件
  const school = Vue.extend({
    template: `
				

学校名称:{{schoolName}}

学校地址:{{address}}

`
, data() { return { schoolName: '尚硅谷', address: '北京昌平' } }, methods: { showName() { alert(this.schoolName) } }, }) //第一步:创建student组件 const student = Vue.extend({ template: `

学生姓名:{{studentName}}

学生年龄:{{age}}

`
, data() { return { studentName: '张三', age: 18 } } }) //第一步:创建hello组件 const hello = Vue.extend({ template: `

你好啊!{{name}}

`
, data() { return { name: 'Tom' } } }) //第二步:全局注册组件 Vue.component('hello', hello) //创建vm new Vue({ el: '#root', data: { msg: '你好啊!' }, //第二步:注册组件(局部注册) components: { school, student } }) new Vue({ el: '#root2', })
script>

5.2.2 组件的嵌套

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第29张图片

<body>
  <div id="root">
  div>
body>

<script type="text/javascript">
  //定义school的student子组件
  const student = Vue.extend({
    name: 'student',
    template: `
				

学生姓名:{{name}}

学生年龄:{{age}}

`
, data() { return { name: '尚硅谷', age: 18 } } }) //定义school子组件 const school = Vue.extend({ name: 'school', template: `

学校名称:{{name}}

学校地址:{{address}}

`
, data() { return { name: '尚硅谷', address: '北京' } }, //注册组件(局部) components: { student } }) //定义hello子组件 const hello = Vue.extend({ template: `

{{msg}}

`
, data() { return { msg: '欢迎来到尚硅谷学习!' } } }) //定义app父组件 const app = Vue.extend({ template: `
`
, components: { school, hello } }) //创建vm new Vue({ template: '', el: '#root', //注册组件(局部) components: { app } })
script>

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第30张图片

5.2.3 VueComponent构造函数

关于VueComponent

  • school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue内部Vue.extend()函数生成的
  • 我们只需要写或<school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行new VueComponent(options)
  • 每次调用Vue.extend,返回的都是一个全新的VueComponent(因为Vue.extend在vue内部是函数,data使用函数式是一个道理,保证每个模板的数据是相互独立的)
  • 关于this指向:
    ① 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是VueComponent(组件)实例对象
    ② new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是Vue实例对象

VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)
Vue的实例对象,以后简称为vm

<body>
  <div id="root">
    <school>school>
    <hello>hello>
  div>
body>
<script type="text/javascript">
  //定义school组件
  const school = Vue.extend({
    name: 'school',
    template: `
		

学校名称:{{name}}

学校地址:{{address}}

`
, data() { return { name: '尚硅谷', address: '北京' } }, methods: { showName() { console.log('showName', this) } }, }) //定义hello组件下 test子组件 const test = Vue.extend({ template: `atguigu` }) //定义hello组件 const hello = Vue.extend({ template: `

{{msg}}

`
, data() { return { msg: '你好啊!' } }, components: { test } }) //创建vm const vm = new Vue({ el: '#root', components: { school, hello } })
script>

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第31张图片

5.2.4 一个重要的内置关系

这样组件实例对象vc就可以访问到Vue原型上的属性和方法(本来VueComponent原型对象的__ proto __应该指向Object的原型对象,vue强行更改的)
组件实例对象就是小型的实例对象vm,但它没有el配置对象

每一个构造函数都有原型对象prototype,把所有不变的的方法都直接定义在原型对象上,然后从构造函数中new出来的实例对象就可以共享这些方法。
实例对象都会有__proto__属性,指向构造函数的原型对象prototype,之所以实例对象可以使用构造函数原型对象的属性和方法,就是因为对象有__proto__属性的存在。

构造函数.prototype === 实例对象.__ proto __
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第32张图片

  1. 一个重要的内置关系: VueComponent.prototype.__proto__ === Vue.protot ype
  2. 为什么要有这个关系: 让组件实例对象 vc 可以访问到 Vue原型 上的属性、方法
<body>
  <div id="root">
    <school>school>
  div>
body>

<script type="text/javascript">
  Vue.prototype.x = 99

  //定义school组件
  const school = Vue.extend({
    name: 'school',
    template: `
		

学校名称:{{name}}

学校地址:{{address}}

`
, data() { return { name: '尚硅谷', address: '北京' } }, methods: { showX() { console.log(this.x)//99 } }, }) //创建一个vm const vm = new Vue({ el: '#root', data: { msg: '你好' }, components: { school } }) //定义一个构造函数 /* function Demo(){ this.a = 1 this.b = 2 } //创建一个Demo的实例对象 const d = new Demo() console.log(Demo.prototype) //显式原型属性 console.log(d.__proto__) //隐式原型属性 console.log(Demo.prototype === d.__proto__) //程序员通过显式原型属性操作原型对象,追加一个x属性,值为99 Demo.prototype.x = 99 console.log('@',d) */
script>

5.3 单文件组件

传统组件的问题与解决方案
问题:
1.全局定义的组件必须保证组件的名称不重复
2.字符串模板缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的
3.不支持 CSS 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏
4.没有构建步骤限制,只能使用 HTML 和 ES5 JavaScript, 而不能使用预处理器(如:Babel)

解决方案:
针对传统组件的问题,Vue 提供了一个解决方案 —— 使用 Vue 单文件组件。
单文件组件:一个文件只包含1个组件
单文件组件的组成结构

  • template 组件模板区域
  • script 组件交互区域(业务逻辑)
  • style 组件样式区域
// 模板
<template>
	 
template> 

<script> 
	// 这里用于定义Vue组件的业务逻辑 
	export default { 
	    // 私有数据
		data: () { return {} },  
		// 处理函数
		methods: {}  
		// ... 其它业务逻辑 
	} 
script> 

// 加scoped 组件私有
<style scoped> 
	/* 这里用于定义组件的样式 */ 
style>

5.3.1 组件基本实例

App.vue

<template>
  <div>
    <span style="color: blueviolet">App.Vuespan>
  div>
  <HelloWorld>HelloWorld>
template>

<script>
import HW from './components/newHelloWorld'
export default {
  name: 'App',
  data() {
    return {
    }
  },
  components: {
   //若键值名称相同,则写一个即可
    HelloWorld: HW      
  }
}
script>

<style scoped>
style>

newHelloWorld.vue

<template>
  <div>
    <h1>{{msg}}h1>
  div>
template>

<script>
export default {
name: "newHelloWorld",
  data() {
    return {
      msg: 'Hello World!!!!'
    }
  }
}
script>

<style scoped lang='scss'>
/*scoped 可设置样式只在当前组件使用,不往下传递给子组件*/
style>

main.js

import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

//或者
new Vue({
	template: ``
	el:"#app",
	components:{App}	
})

index.html

<div id="app">div>
<script src="../../vue.js">script>
<script src="./main.js">script>

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第33张图片
关于不同版本的Vue
vue.js与vue.runtime.xxx.js(main.js中引入的运行版)的区别:
vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。render 函数和 template 一样都是创建 html 模板的

new Vue({
	el:"#app",
	//template: ``
	//template: `

APP信息

`
render: createElement => { return createElement('h1','APP信息') } //等价于 render: q=>q('h1','APP信息') //下面这行代码会解释,完成这个功能:将App组件放入容器中 render: h=>h(App) })

5.3.2 组件间的通信

Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第34张图片
例子组件层次
在这里插入图片描述

父组件 App.vue

<template>
  <section class="conn">
    <header class="header">
      <my-header>my-header>
    header>
    <div class="main">
      <div class="content">
        <my-main>my-main>
      div>
      <div class="siderbar">
        <my-sider-bar>my-sider-bar>
      div>
    div>
    <footer class="footer">footer>
  section>
template>

<script>
import MyHeader from "@/components/MyHeader";
import MySiderBar from "@/components/MySiderBar";
import MyMain from "@/components/MyMain";
export default {
  name: 'App',
  //子组件声明
  components: {
    MyHeader,
    MyMain,
    MySiderBar
  }
}
script>

<style scoped lang="scss">
$w:600px;
$color1:#ccc;
$color2:#888;

html,body {
  margin: 0;
  padding: 0;
}
.conn {
  width: $w;
  background-color: $color1;
  height: 500px;
  margin: 0 auto;
}
.header {
  width: $w;
  height: 80px;
  background-color: $color2;
}
.main {
  width: 100%;
  height: 300px;
  background-color: yellow;
}
.footer {
  width: 100%;
  height: 100px;
  background-color: green;
}
.content {
  width: 70%;
  height: 300px;
  float: left;
  background-color: rebeccapurple;
}
.siderbar {
  width: 30%;
  height: 300px;
  float: left;
  background-color: aqua;
}
style>

MyHeader.vue

<template>
  <div>
    <h1>{{msg}}h1>
  div>
  <my-conn>my-conn>
  <my-bar>my-bar>
template>

<script>
import MyConn from "@/components/childComp/MyConn";
import MyBar from "@/components/childComp/MyBar";
export default {
name: "MyHeader",
  data() {
    return {
      msg: 'Hello World!!!!'
    }
  },
  //子组件
  components: {
    MyBar,
    MyConn
  }
}
script>

<style scoped>style>

MyMain.vue

<template>
  <my-conn>my-conn>
template>

<script>
import MyConn from "@/components/childComp/MyConn";
export default {
  name: "MyMain",
  components: {
    MyConn
  }
}
script>

<style scoped>style>

MySiderBar.vue

<template>
  <my-bar>my-bar>
  <my-bar>my-bar>
  <my-bar>my-bar>
template>

<script>
import MyBar from "@/components/childComp/MyBar";
export default {
  name: "MySiderBar",
  components: {
    MyBar
  }
}
script>

<style scoped>style>

MyConn.vue

<template>
  <div class="myconn">
    {{mess}}
  div>
template>

<script>
export default {
  name: "MyConn",
  data() {
    return {
      mess: 'this is main test'
    }
  }
}
script>

<style scoped>
.myconn {
  width: 90%;
  height: 150px;
  background-color: brown;
  margin: 10px;
}
style>

MyBar.vue

<template>
  <div class="mybar">
    bar
  div>
template>

<script>
export default {
name: "MyBar"
}
script>

<style scoped>
.mybar {
  width: 50px;
  height: 50px;
  margin: 10px;
  background-color: cornflowerblue;
}
style>

效果
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第35张图片

5.3.2.1 父组件传递子组件 props

props 让组件接收外部传来的数据
props传递数据原则:单向数据流,只能父传子

  • 传递数据
    这里age前加 : ,通过v-bind使得里面的18是数字
  • 接收数据
 <!-- 子组件 -->
//第一种方式(只接收)最常用
props:['name','age']
//第二种方式(限制类型)

props:{name:String, age:Number}
//第三种方式(限制类型、限制必要性、指定默认值)
props:{
	name:{
	type:String, //类型
	required:true, //必要性
	default:'张三' //默认值
	}
}

//以对象形式列出所有 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:
props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

Prop 是你可以在组件上注册的一些自定义attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何prop

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
传递的属性值时,属性名父组件和子组件最好一样,传递MyTitle,子组件就使用MyTitle
v-bind是不支持使用驼峰标识的,例如cUser要改成c-User

备注: props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

传递单值例子:
App.vue传递给MyMain.vue一个msg值和title值
v-bind 来动态传递 prop

<my-main msg="hello" :title="msg">my-main>
......
data() {
    return {
      msg:'this is app data msg'
    }
}

MyMain.vue从props接收,会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样。

<template>
  <my-conn>my-conn>
  {{msg}} {{title}}
template>

<script>
import MyConn from "@/components/childComp/MyConn";
export default {
  name: "MyMain",
  //从上一层组件传过来的一个值,接收一个值
  props: ['msg','title'],
  components: {
    MyConn
  }
}
script>
<style scoped>style>

效果
在这里插入图片描述

传递数组例子
App.vue

<my-main msg="hello" :title="msg" :article="article">my-main>
......
data() {
    return {
      msg:'this is app data msg',
      article: ['one','two','three']
    }
}

MyMain.vue

<template>
  <my-conn>my-conn>
  {{msg}} {{title}}
  <br>
  <span v-for="(item,index) in article" :key="index">{{item}}<br>span>
template>

<script>
import MyConn from "@/components/childComp/MyConn";
export default {
  name: "MyMain",

  //就要采用对象的方法,写法与数组方式不同
  props: {
    msg: {
      type: String,
      default:'#####'   //设置缺省值,若无传值,等同于在data处声明一个msg:'####'一样
    },
    title: {
      type: String,
      required: true    //表明该属性值必传,否则报错
    },
    article: {
      type: Array,
      default () {                    //Vue2的写法
        return ['aaa','bbb','ccc']
      },
      // default: ['aaa','bbb','ccc']   Vue3支持的写法
    }
  },
  components: {
    MyConn
  }
}
script>
<style scoped>style>

效果
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第36张图片

props可以多层传递,MyMain可以传递给MyConn,写法一样
MyConn.vue

<template>
  <div class="myconn">
    <p>conn contentp>
    <span v-for="(item,index) in article" :key="index">{{item}}<br>span>
  div>
template>

<script>
export default {
  name: "MyConn",
  props: {
    article: {
      type: Array
    }
  }
}
script>

<style scoped>
.myconn {
  width: 90%;
  height: 150px;
  background-color: brown;
  margin: 10px;
}
style>

MyMain.vue

<template>
  <my-conn :article="article">my-conn>
  {{msg}} {{title}}
  <br>
  <span v-for="(item,index) in article" :key="index">{{item}}<br>span>
template>

而App.vue里声明的article属性,一旦有变化,MyMain和MyConn就会随之变化

效果
Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第37张图片

5.3.2.2 子组件给传递父组件数据 $emit

  1. 一种组件间通信的方式,适用于: 子组件 ===>父组件
  2. 使用场景: 子组件B想给父组件A传数据,那么就要在父组件中给子组件绑定自定义事件(事件的回调在A中)
  3. 绑定自定义事件
    • 第一种方式,在父组件中的子组件标签上 <!-- App.vue父组件 --> // 在父组件中给子组件 xxx为自定义事件 getStudentName为回调函数(在父组件中) <Student @xxx="getStudentName" /> //若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法 <Student @xxx.once="getStudentName" /> ... methods: { //回调函数 getStudentName(name) { this.studentName = name; }, },
      • 第二种方式,在父组件中 this.$refs.demo.$on('事件名',方法)
      //通过ref给Student组件打标识
      <Student ref="student"  />
      ...
        methods: {
        //回调函数
          getStudentName(name) {
            this.studentName = name;
          },
        },
       mounted() {
          //通过$refs获取Student组件
          //在获取到的Student组件上绑定自定义事件xxx getStudentName为回调函数
          this.$refs.student.$on("xxx", this.getStudentName); //$on当...时
          //若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
          this.$refs.student.$once("xxx", this.getStudentName);
        },
      
      • 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
      1. 触发自定义事件 this.$emit(xxx,data)
      <!-- Student.vue子组件 -->
      <button @click="sendStudentName">把学生名给App</button>
      ...
        methods: {
          sendStudentName() {
            //触发Student组件实例身上的xxx自定义事事件
            this.$emit("xxx", this.name);
          }
        },
      
      
      1. 解绑自定义事件 this.$off(‘xxx’)
      this.$off('studentEvent')            //只使用解绑一个自定义事件
      this.$off(['studentEvent','studentEvent2'])     //解绑多个自定义事件
      this.$off()                             //所有自定义的事件都解绑
      
      1. 组件上也可以绑定原生 DOM 事件,需要使用 native 修饰符 @click.native="show"上面绑定自定义事件,即使绑定的是原生事件也会被认为是自定义的,需要加 native ,加了后就将此事件给组件的根元素
      2. 注意: 通过 this.$refs.xxx.$on('事件名,回调函数) 绑定自定义事件时,回调函数要么配置在 methods 中,要么用箭头函数,否则 this 指向会出问题

      例子

      src/App.vue

      <template>
        <div id="app">
          <h1>{{msg}}, APP父组件——学校是: {{schoolName}}h1>
          <h1>{{msg}}, APP父组件——学生姓名是: {{studentName}}h1>
      
          
          <School :getSchoolName="getSchoolName" />
      
          
          
      
          
          
      
          
          <Student ref="student" @click.native="show" />
          
        div>
      template>
      
      <script>
      import Student from './components/Student.vue'
      import School from './components/School.vue'
      
      export default {
        name: 'App',
        components: {
          School, Student
        },
        data() {
          return {
            msg: '你好啊',
            studentName: '',
            schoolName: ''
          }
        },
        methods: {
          getSchoolName(name) {
            this.schoolName = name
          },
          getStudentName(name,...params) {
            console.log('App接收学生数据-----',name, params);
            this.studentName = name
          },
          test(value) {
            console.log('studentEvent2被触发了-----',value);
          },
          show() {
            alert('show')
          }
        },
        mounted() {
      
          // console.log('多次');
          //方法二:使用refs绑定自定义事件
          // this.$refs.student.$on('studentEvent', this.getStudentName)
      
          //$once单次触发
          // this.$ref.student.$once('studentEvent', this.getStudentName)
          
          //可以把函数写在这里,但是必须写成箭头函数的形式,因为箭头函数没有自己this,那么箭头函数会往外找this(找到mounted,则找到vm实例对象)
          //如果写成普通函数形式,this指向student的vc实例对象,则实例对象中没有studentName
          this.$refs.student.$on('studentEvent', (name,...params)=>{
            console.log('App接收学生数据-----',name, params);
            this.studentName = name
          })
      
          
        }
      }
      script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        /* text-align: center; */
        color: #2c3e50;
        margin-top: 60px;
      }
      style>
      
      

      子组件src/components/Student.vue

      <template>
          <div class="student">
              <h2>学生姓名:{{ name }}h2>
              <h2>性别:{{ sex }}h2>
              <h2>当前求和为: {{ number }}h2>
              <button @click="add">点我number++button>
              <button @click="sendStudentName">把学生名给Appbutton>
              <button @click="unbind">解绑事件button>
              <button @click="death">销毁当前Student组件的实例(vc)button>
          div>
      template>
      <script >
      export default ({
          name: "Student",
          data() {
              return {
                  name: "张三",
                  sex: '男',
                  number: 0
              };
          },
          methods: {
              add() {
                  console.log('add调用了');
                  this.number++
              },
              sendStudentName() {
                  //触发Student组件实例身上的xxx自定义事事件
                  this.$emit('studentEvent', this.name)
                  // this.$emit('studentEvent2', this.name)
              },
              unbind() {
                  this.$off('studentEvent')            //只使用解绑一个自定义事件
                  // this.$off(['studentEvent','studentEvent2'])     //解绑多个自定义事件
                  // this.$off()                             //所有自定义的事件都解绑
              },
              death() {
                  this.$destroy(); //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
              }
          } 
      })
      script>
      <style scoped>
      .student {
        background-color: pink;
        padding: 5px;
        margin-top: 30px;
      }
      style>
      

      子组件src/components/School.vue

      <template>
          <div class="school">
              <h2>学校名:{{ name }}h2>
              <button @click="sendSchoolName">把学校名给Appbutton>
          div>
      template>
      <script >
      export default ({
          name: "School",
          data() {
              return {
                  name: "XXXX大学",
              };
          },
          props: ['getSchoolName'],
          methods: {
              sendSchoolName() {
                  this.getSchoolName(this.name)
              }
          } 
      })
      script>
      <style scoped>
      .school {
        background-color: rgb(112, 255, 188);
        padding: 5px;
        margin-top: 30px;
      }
      style>
      

      效果
      Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第38张图片

      5.3.3 父子组件之间的访问方法

      5.3.3.1 子组件调用父组件的方法 $parent

      子组件MyConn.vue
      子组件调用父组件可以采用$parent

      <template>
        <div class="myconn">
          <button @click="changenum">++button>
          <br>
        div>
      template>
      
      <script>
      export default {
        name: "MyConn",
        methods: {
          changenum() {
            this.$parent.add();
          }
        }
      }
      script>
      
      <style scoped>
      ......
      style>
      

      父组件MyMain.vue
      父组件声明了一个add()

      <template>
        <div style="width: 200px;height: 50px;background-color: yellow">父组件的count:{{count}}div>
      template>
      
      <script>
      import MyConn from "@/components/childComp/MyConn";
      export default {
        name: "MyMain",
        components: {
          MyConn
        },
        data() {
          return {
            count:0
          }
        },
        methods: {
          add() {
            this.count ++;
          }
        }
      }
      script>
      
      <style scoped>style>
      

      效果
      Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第39张图片

      若是孙子组件想要访问爷爷组件的方法,也用$parent但是要打两个$parent,也可直接直接使用$root
      孙子组件MyConn.vue

      methods: {
          changenum() {
            this.$parent.add();
            console.log(this.$parent.count)              //访问MyMain.vue的count
            console.log(this.$parent.$parent.msg)        //访问App.vue的msg
            this.$parent.$parent.appmet()                //访问App.vue的appmet方法
            //等价于
            this.$root.appmet()
          }
        }
      

      在这里插入图片描述

      5.3.3.2 父组件调用子组件的方法 $refs

      $children$refs
      ref被用来给元素或子组件注册引用信息(id的替代者

      • 应用在html标签上获取的是真实DOM元素或应用在组件标签上是获取组件实例对象(vc)
      • 使用方法
        a. 打标识:


        b.获取: this.$refs.xxx

      父组件MyMain.vue

      <template>
        <button @click="subson" ref="btn">让子组件-1 button>
        <my-conn ref="child">my-conn>
      template>
      
      <script>
      import MyConn from "@/components/childComp/MyConn";
      export default {
        name: "MyMain",
        components: {
          MyConn
        },
        data() {
          return {
            count:0
          }
        },
        methods: {
          subson() {
            console.log('父组件的subson()');
            this.$refs.child.sub()         //MyConn组件的实例对象vc
            console.log(this.$refs.btn)    //真实DOM元素
          }
        }
      }
      script>
      
      <style scoped>style>
      

      子组件MyConn.vue

      <template>
        <div class="myconn">
          子组件的num:{{num}}
        div>
      template>
      
      <script>
      export default {
        name: "MyConn",
        data() {
          return {
            num: 0
          }
        },
        methods: {
          sub() {
            this.num--;
          }
        }
      }
      script>
      
      <style scoped>
      ....
      style>
      

      效果
      Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第40张图片

      5.3.4 全局事件总线(任意组件间的通信)

      一种可以在任意组件间通信的方式,本质上就是一个对象,它必须满足以下条件

      1. 所有的组件对象都必须能看见他
      2. 这个对象必须能够使用 $on $emit $off 方法去绑定、触发和解绑事件

      使用步骤

      1.定义全局事件总线

      //main.js
      new Vue({
        el: '#app',
        render: h => h(App),
        //安装全局事件总线 beforeCreate创建实例前
        beforeCreate() {
          //组件实例对象vc可以访问到Vue原型上的属性和方法,往Vue原型上添加$bus属性 $bus为傀儡
          //那么子组件可以使用$bus,而$bus值为vm(this) 因为vm可以调用$on $emit这些方法
          Vue.prototype.$bus = this;
        },
      });
      
      1. 使用事件总线
        **接收数据:**A组件想接收组件B的数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      A组件

      methods(){
        demo(data){......}
      }
      ......
      //mounted():初始化操作,绑定自定义事件
      //xxx为自定义事件 this.demo为回调函数
      mounted() {
        this.$bus.$on('xxx',this.demo)
        //还可以使用箭头函数来,这一部分可以看前面5.3.2.2 子组件给传递父组件数据 $emit部分的第7点
        this.$bus.$on('xxx',(data)=>{……})
      }
      //使用完之后 beforeDestroy解绑自定义事件
      beforeDestroy() {
          this.$bus.$off("xxx");
       },
      

      发送数据:
      ​B组件

      this.$bus.$emit('xxx',数据)
      
      1. 解绑事件
        最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
      beforeDestroy() {
          //解绑当前组件用到的事件
          this.$bus.$off("hello");
      },
      

      案例:兄弟组件传值(Student => School)
      main.js

      //引入Vue
      import Vue from 'vue'
      //引入App
      import App from './App.vue'
      //关闭Vue的生产提示
      Vue.config.productionTip = false
      
      //创建vm
      new Vue({
      	el:'#app',
      	render: h => h(App),
      	beforeCreate() {
      		Vue.prototype.$bus = this //安装全局事件总线
      	},
      })
      
      

      School.vue

      <template>
        <div class="school">
          <h2>学校名称:{{ name }}</h2>
          <h2>学校地址:{{ address }}</h2>
        </div>
      </template>
      
      <script>
      export default {
        name: "School",
        data() {
          return {
            name: "尚硅谷",
            address: "北京",
          };
        },
        methods: {
          demo(data) {
            console.log("我是School组件,收到了数据", data);
          },
        },
        mounted() {
          //在School组件给傀儡绑定自定义事件hello,借助傀儡身上的$on方法获取数据
          this.$bus.$on("hello", this.demo);
        },
        beforeDestroy() {
          //解绑当前组件用到的事件
          this.$bus.$off("hello");
        },
      };
      </script>
      <style scoped>
      .school {
        background-color: skyblue;
        padding: 5px;
      }
      </style>
      

      Student.vue

      template>
      	<div class="student">
      		<h2>学生姓名:{{name}}</h2>
      		<h2>学生性别:{{sex}}</h2>
      		<button @click="sendStudentName">把学生名给School组件</button>
      	</div>
      </template>
      
      <script>
      	export default {
      		name:'Student',
      		data() {
      			return {
      				name:'张三',
      				sex:'男',
      			}
      		},
      		methods: {
      			sendStudentName(){
      				this.$bus.$emit('hello',this.name)
      			}
      		},
      	}
      </script>
      <style lang="less" scoped>
      	.student{
      		background-color: pink;
      		padding: 5px;
      		margin-top: 30px;
      	}
      </style>
      

      Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第41张图片

      5.3.5 消息订阅与发布(任意组件通信)

      消息订阅与发布 (pubsub) 消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信使用步骤

      1. 安装pubsub:npm i pubsub-js
      2. 引入: import pubsub from 'pubsub-js
      3. 接收数据: A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
       methods: {
          demo(msgName, data) {
          ...
          },
        },
        mounted() {
          //订阅消息 每一次订阅都会产生一个id
          //msgName代表数据名xxx data代表接收的数据
          this.pubId = pubsub.subscribe("xxx", this.demo);
        },
        beforeDestroy() {
          //通过id取消订阅
          pubsub.unsubscribe(this.pubId);
        },
      
      
      1. 发送数据:
        methods: {
          sendStudentName() {
            //发布消息
            pubsub.publish("hello", this.name);
          },
        },
      
      
      1. 最好在 beforeDestroy 钩子中,使用 pubsub.unsubscribe(pid) 取消订阅

      案例:兄弟组件传值(Student => School)
      School.Vue

      <template>
        <div class="school">
          <h2>学校名称:{{ name }}</h2>
          <h2>学校地址:{{ address }}</h2>
        </div>
      </template>
      
      <script>
      import pubsub from "pubsub-js";
      export default {
        name: "School",
        data() {
          return {
            name: "尚硅谷",
            address: "北京",
          };
        },
        methods: {
          demo(msgName, data) {
            console.log("我是School组件,收到了数据", msgName, data);
          },
        },
        mounted() {
          //订阅消息 每一次订阅都会产生一个id
          //msgName代表数据名hello data代表接收的数据
          this.pubId = pubsub.subscribe("hello", this.demo);
        },
        beforeDestroy() {
          //通过id取消订阅消息
          pubsub.unsubscribe(this.pubId);
        },
      };
      </script>
      
      <style scoped>
      .school {
        background-color: skyblue;
        padding: 5px;
      }
      </style>
      
      

      Student.Vue

      <template>
        <div class="student">
          <h2>学生姓名:{{ name }}</h2>
          <h2>学生性别:{{ sex }}</h2>
          <button @click="sendStudentName">把学生名给School组件</button>
        </div>
      </template>
      
      <script>
      import pubsub from "pubsub-js";
      export default {
        name: "Student",
        data() {
          return {
            name: "张三",
            sex: "男",
          };
        },
        methods: {
          sendStudentName() {
            //发布消息
            pubsub.publish("hello", this.name);
          },
        },
      };
      </script>
      
      <style lang="less" scoped>
      .student {
        background-color: pink;
        padding: 5px;
        margin-top: 30px;
      }
      </style>
      
      

      效果
      Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第42张图片

      5.4 组件插槽 slot

      Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 元素作为承载分发内容的出口。
      插槽: 让父组件可以向子组件指定位置插入 html 结构,也是一种组件间通信的方式,适用于父组件===>子组件
      特性:插槽可以实现组件的扩展性 , 抽取共性, 保留不同
      分类: 默认插槽、具名插槽、作用域插槽

      5.4.1 默认插槽

      插槽slot是把父组件把数据渲染完了,再插到子组件里

      1. 使用方法
      //父组件中
      <Category>
      	<div>html结构1div>
      Category>
      
      //子组件中
      <template>
      	<div>
      		
      		<slot>插槽默认内容……slot>
      	div>
      template>
      
      1. 案例
        子组件 MyBar.vue
      <template>
        <div class="mybar">
          <h6>{{title}}h6>
          
          <slot>slot>      
        div>
      template>
      
      <script>
      export default {
        name: "MyBar",
        data() {
          return {
            title: 'title'
          }
        }
      }
      script>
      <style scoped>
      .mybar {
        width: 80px;
        height: 80px;
        margin-bottom:10px;
        background-color: cornflowerblue;
      }
      style>
      

      父组件MySiderBar.vue

      <template>
        <my-bar>
          <button>提交button>
        my-bar>
        <my-bar>
          <a href="#">提交a>
        my-bar>
        <my-bar>
          <p>提交文本p>
        my-bar>
      template>
      
      <script>
      import MyBar from "@/components/childComp/MyBar";
      export default {
        name: "MySiderBar",
        components: {MyBar}
      }
      script>
      <style scoped>style>
      

      效果
      Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第43张图片

      5.4.2 后备内容

      插槽slot还可以包含任何模板代码,包括 HTML,即设置默认值,若父组件中的不提供任何插槽内容时:

      <my-bar>my-bar>
      <my-bar>my-bar>
      <my-bar>my-bar>
      

      而子组件为一个插槽设置具体的后备 (也就是默认的) 内容,它会在没有提供内容的时候被渲染

      <div class="mybar">
          <h6>{{title}}h6>
          <slot><button>提交button>slot>
      div>
      

      效果
      Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画_第44张图片

      5.4.3 具名插槽

      多个插槽,若没有设置插槽名称,会把所有的slot都替换掉,我们需要把slot插槽设置名称(具名插槽),按名字指定替换哪个slot

      • v-slot: 简写为 #
      • v-slot:header 可以被简写为 #header
      • 一个不带 name 的 出口会带有隐含的名字“default”。
      • 注意:v-slot 只能添加在