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

目录

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>

效果
在这里插入图片描述

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生命周期在真实场景下的业务应用:

  • 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,默认安装插件
    在这里插入图片描述
  • 运行项目: npm run serve

目录结构
在这里插入图片描述
常见项目的目录结构,如下所示:

.
|-- 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是一个模板文件,webpack进行打包时会原封不动打包到dist文件夹中
src这里是我们要开发的目录,基本上要做的事情都在这个目录里。里面包含了几个目录及文件:assets: 放置一些资源文件,比如图片、字体等资源,webpack打包会把静态资源当作一个模块,打包到JS文件中。components: 目录里面放了一个组件文件,可以不用。App.vue: 项目入口文件,我们也可以直接将组件写这里,而不使用 components 目录。main.js: 项目的核心文件(入口文件)。
package.json模块基本信息项目开发所需要模块,版本,项目名称
package-lock.json是在 npm install时候生成一份文件,用以记录当前状态下实际安装的各个npm package的具体来源和版本号
babel.config.js是一个工具链,主要用于在当前和较旧的浏览器或环境中将ECMAScript 2015+代码转换为JavaScript的向后兼容版本
gitignoregit上传需要忽略的文件格式
vue.config.js保存vue配置的文件,可以用于设置代理,打包配置等
README.md项目说明

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

设置Eslint的语法检查
在这里插入图片描述
在这里插入图片描述

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/vue@2.6.14/dist/vue.js"></script>

//生产版本
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></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>
      <!-- 事件绑定v-on -->
      <div><button v-on:click="handle">点击</button></div>
    </div>
    <!-- //引入vue -->
    <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
    <p v-once>{{msg}}</p> 数据执行一次性插值,数据改变时,插值处内容不更新,不响应
    v-once的应用场景:以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

  2. v-pre
    <p v-pre>{{msg}}</p>,内容原封不动的展示,跳过其所在节点的编译过程。

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

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

data:{
	msg:'test message',
	title:`<h1 style='color:red'>Title</h1>`
}
<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: '<h1>HTML</h1>'
      }
    });

在这里插入图片描述

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+1</button>
   <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')
               }
	     	},
	     	
	     },
	 
	 },
})

在这里插入图片描述

效果
在这里插入图片描述

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: '<h1 style="color: red">Title</h1>',
    url: 'http://www.baidu.com'
}
export default {
  name: 'App',
  data() {
    return data
  }
}
</script>

4.3.1 绑定class属性

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

  • 字符串写法适用于:类名不确定,要动态获取
  • 数组写法适用于:要绑定多个样式,个数不确定,名字也不确定
  • 对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
<template>
<!-- 数组方法 -->
<div :class="[one,two]">box4</div>
<!-- 等价于(字符串用法) -->
<div :class="active">box5</div>

<!-- 对象方法(常用) -->
<!-- isOne和isTwo为布尔值,真则加该类(one/two),假则不加-->
<div :class="{one:isOne, two:isTwo}">box6</div>

<!-- 若键值对相同,则简写一个即可 -->
<div :class="{demo:demo}">box6</div> 
<!-- 等价于 -->
<div :class="{demo}">box6</div> 

<!-- 若样式太多,可采用方法 -->
<div :class="getStyleArr()">box7</div>
<div :class="getStyleObject()">box8</div>
</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']">box1</div>
<!-- 等价于 -->
<div :style="[fontSize, bgColor]">box2</div>
<div :style="['font-size:'+size+'px','background:'+color]">box3</div>

<!-- 对象方法-->
<div :style="{fontSize: '15px', 'background-color':'yellow'}">box9</div>
<div :style="{'font-size': '15px', 'background-color':'yellow'}">box9</div>
</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>

<!-- 动态参数的缩写 (2.6.0+) -->
<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 滚动事件的默认行为 (即滚动行为) 将会立即触发
  • 修饰符可串联
    <a @click.stop.prevent="doThat"></a>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 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 按键松开触发
<!-- enter回车 -->
<input @keyup.enter='submit'>
<!-- delete删除键 -->
<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>

在这里插入图片描述在这里插入图片描述
点击按钮,事件冒泡
若要停止事件冒泡,则<button @click.stop="three()">按钮</button>
可串行使用

<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>

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

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

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

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')

在这里插入图片描述

选中样式应用

<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')
<style scoped>
ul li {
  list-style: none;
}
.active {
  background-color: antiquewhite;
  color: coral;
}
</style>

在这里插入图片描述

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,提高了渲染效率。
在这里插入图片描述

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>

在这里插入图片描述
在这里插入图片描述
列表排序

<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>

在这里插入图片描述

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:'男'}) 却有效果,原因解释如下:

数据代理过程
在这里插入图片描述
在这里插入图片描述
模拟数据监测

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数据属性同样修改
在这里插入图片描述
但是上诉代码的有个缺点,没有考虑到data里有对象,对象里还有属性的情况,上述的例子没法为对象里的属性匹配gettersetter,如下图
在这里插入图片描述

而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
            }
          }
        },
    })

在这里插入图片描述
原理:

  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 !== '抽烟';
 });

通过数组索引修改数组,无法响应
在这里插入图片描述
通过变更数组的方法来监测数组数据,具有响应式
在这里插入图片描述
所以,前面的例子使用this.persons.splice(0,1, {id:'001',name:'马老师',age:50,sex:'男'}) 有响应效果

除了变更方法,还可以使用前面提到的**Vue.set()vm.$set()来修改数组
在这里插入图片描述
例子

<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 !== '抽烟'
                })
            }

        }
    })

在这里插入图片描述

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">Jack</label>
  <input type="checkbox" id="john" value="John" v-model="checkedNames" />
  <label for="john">John</label>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
  <label for="mike">Mike</label>
  <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 one</option>
      <option>A</option>
      <option>B</option>
      <option>C</option>
  </select>
  <span>Selected: {{ selected }}</span>
</div>
Vue.createApp({
  data() {
    return {
      selected : ''
    }
  }
}).mount("#app")

在这里插入图片描述

选择框多选时

<select v-model="selected" multiple>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</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>

在这里插入图片描述

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

 <body>
    <div id="root">
      <h2>显示格式化后的时间</h2>
      <!-- 计算属性实现 -->
      <h3>现在是:{{fmtTime}}</h3>
      <!-- methods实现 -->
      <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>

在这里插入图片描述

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 模块与组件、模块化与组件化

传统方法编写应用问题
在这里插入图片描述
组件方法编写应用
在这里插入图片描述
模块
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: `
		<div class="demo">
			<h2>学校名称:{{schoolName}}</h2>
			<h2>学校地址:{{address}}</h2>
			<button @click="showName">点我提示学校名</button>	
		</div>`,
    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. 使用组件
    第一种写法:直接使用<school></school>调用组件
    第二种写法:<school/> (在使用Vue脚手架的情况下可用)
    **备注:**不使用脚手架时,<school/> 会导致后续组件不能渲染
    一个简写方式:
    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: `
				<div class="demo">
					<h2>学校名称:{{schoolName}}</h2>
					<h2>学校地址:{{address}}</h2>
					<button @click="showName">点我提示学校名</button>	
				</div>
			`,
    data() {
      return {
        schoolName: '尚硅谷',
        address: '北京昌平'
      }
    },
    methods: {
      showName() {
        alert(this.schoolName)
      }
    },
  })

  //第一步:创建student组件
  const student = Vue.extend({
    template: `
				<div>
					<h2>学生姓名:{{studentName}}</h2>
					<h2>学生年龄:{{age}}</h2>
				</div>
			`,
    data() {
      return {
        studentName: '张三',
        age: 18
      }
    }
  })

  //第一步:创建hello组件
  const hello = Vue.extend({
    template: `
				<div>	
					<h2>你好啊!{{name}}</h2>
				</div>
			`,
    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 组件的嵌套

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

<script type="text/javascript">
  //定义school的student子组件
  const student = Vue.extend({
    name: 'student',
    template: `
				<div>
					<h2>学生姓名:{{name}}</h2>	
					<h2>学生年龄:{{age}}</h2>	
				</div>
			`,
    data() {
      return {
        name: '尚硅谷',
        age: 18
      }
    }
  })

  //定义school子组件
  const school = Vue.extend({
    name: 'school',
    template: `
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<student></student>
				</div>
			`,
    data() {
      return {
        name: '尚硅谷',
        address: '北京'
      }
    },
    //注册组件(局部)
    components: {
      student
    }
  })

  //定义hello子组件
  const hello = Vue.extend({
    template: `<h1>{{msg}}</h1>`,
    data() {
      return {
        msg: '欢迎来到尚硅谷学习!'
      }
    }
  })

  //定义app父组件
  const app = Vue.extend({
    template: `
				<div>	
					<hello></hello>
					<school></school>
				</div>
			`,
    components: {
      school,
      hello
    }
  })

  //创建vm
  new Vue({
    template: '<app></app>',
    el: '#root',
    //注册组件(局部)
    components: {
      app
    }
  })
</script>

在这里插入图片描述

5.2.3 VueComponent构造函数

关于VueComponent

  • school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue内部Vue.extend()函数生成的
  • 我们只需要写<school/>或<school></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: `
		<div>
			<h2>学校名称:{{name}}</h2>	
			<h2>学校地址:{{address}}</h2>	
			<button @click="showName">点我提示学校名</button>
		</div>
			`,
    data() {
      return {
        name: '尚硅谷',
        address: '北京'
      }
    },
    methods: {
      showName() {
        console.log('showName', this)
      }
    },
  })
 //定义hello组件下 test子组件
  const test = Vue.extend({
    template: `<span>atguigu</span>`
  })
  //定义hello组件
  const hello = Vue.extend({
    template: `
		<div>
			<h2>{{msg}}</h2>
			<test></test>	
		</div>
	`,
    data() {
      return {
        msg: '你好啊!'
      }
    },
    components: {
      test
    }
  })
  //创建vm
  const vm = new Vue({
    el: '#root',
    components: {
      school,
      hello
    }
  })
</script>

在这里插入图片描述

5.2.4 一个重要的内置关系

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

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

构造函数.prototype === 实例对象.__ proto __
在这里插入图片描述

  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: `
		<div>
			<h2>学校名称:{{name}}</h2>	
			<h2>学校地址:{{address}}</h2>	
			<button @click="showX">点我输出x</button>
		</div>
	`,
    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>
	<!-- 这里用于定义Vue组件的模板内容 --> 
</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.Vue</span>
  </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: `<App></App>`
	el:"#app",
	components:{App}	
})

index.html

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

在这里插入图片描述
关于不同版本的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: `<App></App>`
	//template: `<h1>APP信息</h1>`
	render: createElement => {
		return createElement('h1','APP信息')
	}
	//等价于
	render: q=>q('h1','APP信息')
	//下面这行代码会解释,完成这个功能:将App组件放入容器中
	render: h=>h(App)
})

5.3.2 组件间的通信

在这里插入图片描述
例子组件层次
在这里插入图片描述

父组件 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>

效果
在这里插入图片描述

5.3.2.1 父组件传递子组件 props

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

  • 传递数据
    <Demo name="xxx":age="18"/>这里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>

效果
在这里插入图片描述

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

<template>
  <div class="myconn">
    <p>conn content</p>
    <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就会随之变化

效果
在这里插入图片描述

5.3.2.2 子组件给传递父组件数据 $emit
  1. 一种组件间通信的方式,适用于: 子组件 ===>父组件
  2. 使用场景: 子组件B想给父组件A传数据,那么就要在父组件中给子组件绑定自定义事件(事件的回调在A中)
  3. 绑定自定义事件
    • 第一种方式,在父组件中的子组件标签上<SubComponents @事件名="方法"/><SubComponents v-on:事件名="方法”/>
 <!-- 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>

    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据-->
    <School :getSchoolName="getSchoolName" />

    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on)-->
    <!-- <Student @studentEvent="getStudentName" @studentEvent2='test' /> -->

    <!-- 单次触发 -->
    <!-- <Student @studentEvent.once="getStudentName" /> -->

    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用refs)-->
    <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">把学生名给App</button>
        <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">把学校名给App</button>
    </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>

效果
在这里插入图片描述

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>

效果
在这里插入图片描述

若是孙子组件想要访问爷爷组件的方法,也用$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. 打标识:<h1 ref='xxx'></h1><School ref='xxx'></School>
    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>

效果
在这里插入图片描述

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>

在这里插入图片描述

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>

效果
在这里插入图片描述

5.4 组件插槽 slot

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

5.4.1 默认插槽

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

  1. 使用方法
//父组件中
<Category>
	<div>html结构1</div>
</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>

效果
在这里插入图片描述

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>

效果
在这里插入图片描述

5.4.3 具名插槽

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

  • v-slot: 简写为 #
  • v-slot:header 可以被简写为 #header
  • 一个不带 name 的 <slot> 出口会带有隐含的名字“default”。
  • 注意:v-slot 只能添加在 <template>
  1. 使用方法
//父组件中
<Category>
	<!-- 另外一种写法 -->
    <template slot="center">
    	<div>html结构1</div>
    </template>
    <!-- 简写 v-slot:footer  ==> #footer -->
	<template v-slot:footer>
    	<div>html结构2</div>
    </template>
</Category>

//子组件中
<template>
	<div>
		<!-- 定义插槽 -->
		<slot name='center'>插槽默认内容……</slot>
        <slot name='footer'>插槽默认内容……</slot>
	</div>
</template>
  1. 案例

子组件 MyBar.vue

<template>
  <div class="mybar">
    <h6>{{title}}</h6>
    <!--第一个slot设置了后备内容-->
    <slot name="one"><button>提交</button></slot><br>
    <slot name="two">first</slot><br>
    <slot>second</slot>
  </div>
</template>

父组件MySiderBar.vue

<template>
  <my-bar></my-bar>
  <my-bar>
     <!-- 指定替换哪个插槽 -->
     <!-- v-slot: 简写为 # -->
    <template v-slot:one>
      <a href="#" >替换文本</a>
    </template>
    <!-- v-slot:default 缺省名字 -->
    <template v-slot:default>
      <a href="#" >更新文本</a>
    </template>
  </my-bar>
  <my-bar> </my-bar>
</template>

效果
在这里插入图片描述

5.4.4 作用域插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的。当一个组件被用来渲染一个项目数组时,这是一个常见的情况,我们希望能够自定义每个项目的渲染方式。

我们在子组件里的 <slot> 元素绑定为属性绑定在 <slot > 元素上的 attribute 被称为插槽 prop

  1. 使用方法
//子组件
<slot name="up" :data="data"></slot>
<script>
 export default {
    data: function(){
      return {
        data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
      }
    },
}
</script>

父组件往子组件插模板的情况,那到底插一套什么样的样式呢,这由父组件的html+css共同决定,但是这套样式里面的内容呢?

作用域插槽绑定了一套数据,父组件可以拿来用。于是,情况就变成了这样:
样式父组件说了算,但内容可以显示子组件插槽绑定的。

我们可以使用scope属性
scope 用于父组件往子组件插槽放的 html 结构接收子组件的数据
理解: 数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定(games 数据在 Category 组件中,但使用数据所遍历出来的结构由 App 组件决定)

<Category>
	<template scope="scopeData">
		根据需求设定不一样的样式
	</template>
</Category>
  1. 案例

子组件 Categoru.vue

<template>
	<div class="category">
		<h3>{{title}}分类</h3>
		<slot :games="games" msg="hello">我是默认的一些内容</slot>
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title'],
		data() {
			return {
				games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
			}
		},
	}
</script>
<style scoped>
	.category{
		background-color: skyblue;
		width: 200px;
		height: 300px;
	}
	h3{
		text-align: center;
		background-color: orange;
	}
	video{
		width: 100%;
	}
	img{
		width: 100%;
	}
</style>

父组件 App.vue

<template>
	<div class="container">
		<!--第一次使用:无序列表显示数据-->
		<Category title="游戏">
			<!--旧写法 scope的名称可以随意设定,不一定要是games,这里atguigu是一个对象-->
			<template scope="atguigu">
				<ul>
					<li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li>
				</ul>
			</template>
		</Category>
		<!--第二次使用:有序列表显示数据-->
		<Category title="游戏">
			<!--可以解构赋值,直接获取games-->
			<template scope="{games}">
				<ol>
					<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
				</ol>
			</template>
		</Category>
		
		<!--第三次使用:h4标签直接显示数据-->
		<Category title="游戏">
			<!--新写法:slot-scope-->
			<template slot-scope="{games}">
				<h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
			</template>
		</Category>

	</div>
</template>

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

<style scoped>
	.container,.foot{
		display: flex;
		justify-content: space-around;
	}
	h4{
		text-align: center;
	}
</style>

在这里插入图片描述

  1. 在使用插槽时,父组件若想使用子组件在插槽里的属性值,可以像下面这样:
    在这里插入图片描述

要想使 user和sex可用于父级提供的 slot 内容,我们可以添加一个 <slot> 元素并将其绑定为属性
绑定在 <slot > 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

  1. 案例
    子组件MyBar.vue
<template>
  <div class="mybar">
    <slot name="one"><button>提交</button></slot><br>
    <slot name="two" :sex="sex">性别:男</slot><br>
    <slot :user="user">名字:{{ user.name }}</slot>
  </div>
</template>
<script>
export default {
name: "MyBar",
  data() {
    return {
      title: 'title',
      user: {name:'zhangsan'},
      sex: '男'
    }
  }
}
</script>

父组件MySiderBar.vue

 <my-bar>
    <template v-slot:one>
      <a href="#" >替换文本</a>
    </template>
    <!-- 插槽prop:setSex-->
   <template v-slot:two="setSex">
      {{ setSex.sex}}
    </template>
    <!-- 插槽 prop:newuser-->
    <template v-slot:default="newuser">
      <a href="#" >{{ newuser.user.name }}</a>
    </template>
  </my-bar>

效果
在这里插入图片描述

5.5 mixin 混入

  1. 功能:可以把多个组件共用的配置提取成一个混入对象
  2. 使用方式:
  • 定义混入
//定义混合 
export const mixin = {
  methods: {
    showName() {
      alert(this.name);
    },
  },
};
  • 使用混入
    a. 全局混入 Vue.mixin(xxx)
    b. 局部混入 mixins:['xxx']

备注:

  1. 组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先。
  2. 在遇到生命周期钩子时,如mounted等,混入对象有mounted,组件里也设置了mounted,则两个mounted都被调用,同名生命周期钩子将合并为一个数组。另外,混入对象的钩子将在组件自身钩子之前调用。

src/mixin.js

export const mixin = {
  methods: {
    showName() {
      alert(this.name);
    },
  },
  mounted() {
  	console.log('mixin混入对象的mounted')
  }
};

export const hunhe2= {
  data() {
	return {
		x:100,
		y:200
	}
}
};

全局混入
src/main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
import {mixin} from './mixin'
//全局混入
//Vue.mixin(mixin)
//Vue.mixin(hunhe)

//创建vm
new Vue({
	el:'#app',
	render: h => h(App)
})

局部混入
src/compoents/School.vue子组件

<template>
  <div class="demo">
    <h2 @click="showName">学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
  </div>
</template>

<script>
import { mixin,hunhe } from "../mixin";
export default {
  name: 'School',
  data() {
    return {
      name: "尚硅谷",
      address: "北京",
      x: 666
    };
  },
  mixins: [mixin, hunhe],    //局部混入
};
</script>
<style></style>

src/compoents/Student.vue

<template>
  <div class="demo">
    <h2 @click="showName">学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
  </div>
</template>

<script>
import { mixin,hunhe } from "../mixin";
export default {
  name: 'Student',
  data() {
    return {
      name: "张三",
      sex: "男",
    };
  },
  mixins: [mixin, hunhe],    //局部混入
};
</script>
<style></style>

src/App.vue

<template>
  <div >
    <School/>
    <hr>
    <Student/>
  </div>
</template>

<script>
import School from "./components/School"
import Studentfrom "./components/Student"
export default {
  name: 'App',
  components: {School, Student}
};
</script>
<style></style>

在这里插入图片描述

5.6 plugin 插件

  1. 功能: 用于增强 Vue
  2. 本质: 包含 install 方法的一个对象,install 的第一个参数是 Vue ,第二个以后的参数是插件使用者传递的数据
  3. 定义插件(见下 src/plugin.js)
  4. 使用插件: Vue.use()
//定义插件
对象.install = function (Vue, options) {
    // 1. 添加全局过滤器
    Vue.filter(....)
    // 2. 添加全局指令
    Vue.directive(....)
    // 3. 配置全局混入(合)
    Vue.mixin(....)
    // 4. 添加实例方法
    Vue.prototype.myMethod = function () {...}
    Vue.prototype.myProperty = xxxx
}

实例
src/plugin.js

export default {
  install(Vue, x, y, z) {
  	console.log(x,y,z)
    //全局过滤器
    Vue.filter("mySlice", function (value) {
      return value.slice(0, 4);
    })
    //给Vue原型上添加一个方法(vm和vc就都能用了)
    Vue.prototype.hello = () => {
      alert("你好啊");
    }
    //定义全局指令
	Vue.directive('fbind',{
		//指令与元素成功绑定时(一上来)
	     bind(el, binding) {el.value = binding.value},
	     //指令所在元素被插入页面时
	     inserted(el, binding){el.focus() },
	     //指令所在的模板被重新解析时
	     update(el, binding) {el.value = binding.value }
  	})
	
	//定义混入
	Vue.mixin({
		data() {return {x:100,y:200}}
	})
  },
};

src/main.js

//引入Vue
import Vue from "vue";
//引入App
import App from "./App.vue";
//引入插件
import plugins from "./plugins";
//使用插件
Vue.use(plugins,1,2,3);
//创建vm
new Vue({
  el: "#app",
  render: (h) => h(App),
});

src/compoents/School.vue子组件

<template>
  <div class="demo">
  	<!-- 用了插件里的过滤器 -->
    <h2 @click="showName">学校名称:{{ name | mySlice }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <button @click="test">点我测试一个hello方法</button>
  </div>
</template>

<script>
export default {
  name: 'School',
  data() {
    return {
      name: "尚硅谷",
      address: "北京",
      x: 666
    };
  },
  methods: {
	test() {this.hello()}
  }
};
</script>
<style></style>

src/compoents/Student.vue

<template>
  <div class="demo">
    <h2 @click="showName">学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <input type="text" v-fbind:value="name">
  </div>
</template>

<script>
import { mixin,hunhe } from "../mixin";
export default {
  name: 'Student',
  data() {
    return {
      name: "张三",
      sex: "男",
    };
  }
};
</script>
<style></style>

在这里插入图片描述

5.7 scoped 样式

scoped样式:让样式在局部生效,防止冲突。

<style scoped></style>

5.8 nextTick

应用场景举例:鼠标点击编辑按钮时,span标签消失,同时input输入框显示,并且输入框已经自动获取焦点,失去焦点时就显示编辑按钮

分析:当需要浏览器重绘重排的时候,是需要时间的,当点击按钮时Input还没有渲染出来,此时如果的编辑按钮里的方法里直接写focus(),可以发现是不起作用的,input没有何来焦点?

实现:

  1. 点击button按钮展示输入框,失去焦点时展示button按钮
  • 在data中定义visibility,确定输入框的展示状态,默认为false
  • 定义changeVisibility方法,并给button绑定点击事件@click="changeVisibility",实现点击按钮展示输入框,给输入框绑定失去焦点事件@blur="handleBlur"
  • 定义inputbutton元素,绑定条件渲染指令v-show,当visibility=false时展示按钮,反之展示输入框
  • 如果是需要频繁切换显示,使用v-show来绑定条件
<template>
	<div>
        <label for="input">
        	<span v-show="!visibility">{{title}}</span>
            <input type="text" ref="inputRef" v-show="visibility" @blur="handleBlur($event)"> 
    	</label>
    	<button @click="changeVisibility" v-show="!visibility">编辑</button>
    </div>
</template>

<script>
	export default {
        data() {
            return {
                visibility: false,
                title: 'testTitle'
            }
        },
        methods: {
            changeVisibility() {
                this.visibility = !this.visibility // 切换 visibility,控制输入框的显示状态
                //定时器+ref引用
                setTimeout(() => {
					this.$refs.inputRef.focus()
				}, 0);
                //nexTick
                this.$nextTick(()=>{
		            this.$refs.inputRef.focus()
		         })
            }handleBlur(e) {
            	this.visibility = !this.visibility
            	if(!e.target.value.trim()) return alert('输入不能为空')
            	/*
            	传递数据操作……
            	*/
            }
            
        }
    }
</script>

自动获取对话框焦点

  • 定时器+ref引用
    this.$refs.inputRef.focus()获取输入框的焦点,inputRef是创建输入框时添加的ref引用)。但是方法定义在组件渲染之前,因此直接在方法中添加,效果无法实现。这是由于浏览器执行到this.$refs.inputRef.focus()时,input元素还没有被渲染到页面上,此是的DOM还不存在指定的input元素,可以设置定时器,推迟焦点的获取
setTimeout(() => {
	this.$refs.inputRef.focus()
}, 0);

设置一个0ms的定时器,当浏览器执行到定时器时,会将定时器内部的函数放入延迟队列中,当定时器的等待事件结束后,会将函数放入消息队列的末尾,消息队列的执行按照先进先出原则,当前面的任务执行完成后,浏览器会自动执行this.$refs.inputRef.focus()实现焦点的获取

  • 使用 $nextTick 方法, 将获取焦点推迟到下一个 DOM 更新周期
this.$nextTick(()=> {
	this.$refs.inputRef.focus()
})

组件的this.$nextTick(cb)方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。
通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。

  • 将获取焦点放到updated生命周期中执行(不推荐)
    使用这个方法时,需要加上if判断,否则每一次this.visibility发生变化时,都会执行一次更新,增加服务器的负担
  updated() {
      if (this.visibility){
          this.$refs.inputRef.focus()
      }
  }

当组件处于updated时,页面已经根据最新的数据渲染完成了,此时我们执行this.$refs.inputRef.focus()就可以正常获取输入框的焦点

6. TodoList案例

6.1 目标功能界面

在这里插入图片描述

6.2 界面模块拆分

在这里插入图片描述
组件化编码流程

  1. 拆分静态组件: 组件要按照功能点拆分,命名不要与 html 元素冲突
  2. 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用。
    • 一个组件在用: 放在组件自身即可
    • 一些组件在用: 放在他们共同的父组件上 (状态提升)
  3. 实现交互:从绑定事件开始

小技巧:
一堆数据用数组,每一个数据中的属性太多用对象
数据在哪里,操作数据的方法就在哪里

6.3 浏览器本地存储

存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
浏览器端通过 sessionStoragelocalStorage 属性来实现本地存储机制。
相关API:
xxxxxStorage.setItem(‘key’, ‘value’) 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
xxxxxStorage.getItem(‘person’) 该方法接受一个键名作为参数,返回键名对应的值。
xxxxxStorage.removeItem(‘key’) 该方法接受一个键名作为参数,并把该键名从存储中删除。
xxxxxStorage.clear() 该方法会清空存储中的所有数据。
备注:
SessionStorage存储的内容会随着浏览器窗口关闭而消失。
LocalStorage存储的内容,需要手动清除才会消失,关闭浏览器不会消失
xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null
JSON.parse(null)的结果依然是null

<h2>localstorage</h2>
<button onclick="saveDate()">点我保存数据</button><br/>
<button onclick="readDate()">点我读数据</button><br/>
<button onclick="deleteDate()">点我删除数据</button><br/>
<button onclick="deleteAlIDate()">点我清空数据</button><br/>
<script>
	let person = (name:"JoJo", age:20]
	function saveDate(){
		localstorage.setItem( 'msg',"localstorage')
		localstorage.setItem('person,JSON.stringify(person)
	}
	function readDate() {
		console.log(localstorage.getitem('msg')
		const person = localstorage.getItem('person')
		console.log(JSON.parse(person)
	}
	function deleteDate(){
		localstorage.removeitem('msg')
		localstorage.removeitem('person')
	}
	function deleteAllDate(){
		localstorage.clear()
	}
</script>
<h2>sessionStorage</h2>
<button onclick="saveDate()">点我保存数据</button><br/>
<button onclick="readDate()">点我读数据</button><br/>
<button onclick="deleteDate()">点我删除数据</button><br/>
<button onclick="deleteAlIDate()">点我清空数据</button><br/>
<script>
	let person = (name:"JoJo", age:20]
	function saveDate(){
		sessionStorage.setItem( 'msg',"sessionStorage')
		sessionStorage.setItem('person,JSON.stringify(person)
	}
	function readDate() {
		console.log(sessionStorage.getitem('msg')
		const person = sessionStorage.getItem('person')
		console.log(JSON.parse(person)
	}
	function deleteDate(){
		sessionStorage.removeitem('msg')
		sessionStorage.removeitem('person')
	}
	function deleteAllDate(){
		sessionStorage.clear()
	}
</script>

7. 过渡和动画

1.过渡与动画作用
在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名
2.图示

在这里插入图片描述
3. 写法:
前提:组件元素务必要有v-ifv-show指令才可以进行
3.1 准备好样式

  • 元素进入的样式
    • v-enter 进入的起点
    • v-enter-active 进入过程中
    • v-enter-to 进入的终点
  • 元素离开的样式
    • v-leave 离开的起点
    • v-leave-active 离开过程中
    • v-leave-to 离开的终点

3.2 使用 <transition>包裹要过度的元素,并配置 name 属性,此时需要将上面样式名的 v换为 name
3.3 要让页面一开始就显示动画,需要添加 appear

<transition name="hello" appear>
  <h1 v-show="isShow">你好啊!</h1>
</transition>

3.4 备注:若有多个元素需要过渡,则需要使用<transition-group>,且每个元素都要指定key

<!-- 多个元素使用过渡动画 -->
<transition-group name="hello" appear>
  <h1 v-show="isShow" key="1">你好啊!</h1>
  <h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>

案例
App.vue

<template>
  <div>
    <Test />
    <Test2 />
    <Test3 />
  </div>
</template>

<script>
import Test from "./components/Test";
import Test2 from "./components/Test2";
import Test3 from "./components/Test3";
export default {
  name: "App",
  components: { Test, Test2, Test3 },
};
</script>

<style>
</style>

  1. 使用动画代码实现

Test.vue

<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<transition name="hello" appear>
			<h1 v-show="isShow">你好啊!</h1>
		</transition>
	</div>
</template>

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

<style scoped>
	h1{
		background-color: orange;
	}

	.hello-enter-active{
		animation: atguigu 0.5s linear;
	}

	.hello-leave-active{
		animation: atguigu 0.5s linear reverse;
	}

	@keyframes atguigu {
		from{
			transform: translateX(-100%);
		}
		to{
			transform: translateX(0px);
		}
	}
</style>

  1. 使用过渡代码实现
<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!-- 多个元素使用过渡动画 -->
    <transition-group name="hello" appear>
      <h1 v-show="isShow" key="1">你好啊!</h1>
      <h1 v-show="isShow" key="2">尚硅谷!</h1>
    </transition-group>
  </div>
</template>

<script>
export default {
  name: "Test",
  data() {
    return {
      isShow: true,
    };
  },
};
</script>

<style scoped>
h1 {
  background-color: orange;
}
/* 进入的起点 离开的终点  */
.hello-enter,
.hello-leave-to {
  transform: translateX(-100%);
}
/* 进入的整个过程 离开的整个过程 */
.hello-enter-active,
.hello-leave-active {
  transition: 0.5s linear;
}
/* 进入的终点 离开的起点 */
.hello-enter-to,
.hello-leave {
  transform: translateX(0);
}
</style>

  1. 使用第三方动画库实现(https://animate.style/)
    Test3.vue

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition-group
      appear
      name="animate__animated animate__bounce"
      enter-active-class="animate__swing"
      leave-active-class="animate__backOutUp"
    >
      <h1 v-show="!isShow" key="1">你好啊!</h1>
      <h1 v-show="isShow" key="2">尚硅谷!</h1>
    </transition-group>
  </div>
</template>

<script>
import "animate.css";
export default {
  name: "Test",
  data() {
    return {
      isShow: true,
    };
  },
};
</script>

<style scoped>
h1 {
  background-color: orange;
}
</style>

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Vite是一个用于快速构建现代的Web项目的构建工具,它专注于开发阶段的快速热重载,并使用ES模块作为原生的开发模式。Vue3是Vue.js的最新版本,它在性能、开发体验和可维护性上都有所提升。 针对你提到的具体的库和框架: - Vue Router是Vue.js官方的路由管理器,用于实现页面之间的导航和路由控制。你可以通过npm安装vue-router,并在项目中进行配置和使用。 - Pinia是Vue.js的状态管理库,它提供了一种简单而强大的方式来管理应用程序的状态。你可以集成Pinia到你的Vue项目中,以便更好地组织和共享你的应用程序状态。 - Axios是一个基于Promise的HTTP客户端,用于通过网络发送异步请求。你可以使用Axios来处理与服务器的通信,并获取数据来更新你的Vue应用程序。 - Element Plus是一套基于Vue3的组件库,包含了丰富的UI组件,可以帮助你快速搭建漂亮的用户界面。你可以在项目中安装和使用Element Plus来实现各种交互效果和用户界面。 如果你想使用Vite、Vue3和以上提到的库和框架来创建一个项目,你可以按照以下步骤进行: 1. 安装Vite:通过npm全局安装Vite,然后使用Vite命令初始一个新的项目。 2. 配置Vite:根据你的项目需求,在Vite的配置文件中添加Vue Router、Pinia、Axios和Element Plus的相关配置。 3. 安装和配置Vue Router:通过npm安装Vue Router,并在项目中配置和使用Vue Router来管理应用程序的路由。 4. 集成Pinia:通过npm安装Pinia,并在项目中引入和配置Pinia,以便在应用程序中使用Pinia来管理状态。 5. 使用Axios:通过npm安装Axios,并在项目中引入和配置Axios,以便进行网络请求和数据获取。 6. 引入Element Plus:通过npm安装Element Plus,并在项目中按需引入和使用Element Plus的组件,以搭建漂亮的用户界面。 希望以上信息对你有帮助,祝你在使用Vite、Vue3和这些库和框架时取得成功!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [vite-vue-ts精简模版集成pinia+svg+router+@src](https://download.csdn.net/download/ldy889/85018930)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [vite+vue3+ts+vue-router+pinia+axios+element-plus](https://blog.csdn.net/zy_080400/article/details/127125359)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值