VUE基础篇

前奏:

关于vue中,vscode实用的插件: vetur和vue vscode snippets

Vue的设计思想

Vue的设计思想有以下:

  • 数据驱动应用

  • MVVM模式的践行者

MVVM框架的三要素:响应式、模板引擎及其渲染

响应式:vue如何监听数据变化?

模版:vue的模版如何编写和解析?

渲染:vue如何将模板转换为html?

安装

引入vue

vue官方安装地址

使用

官方地址

每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的。

创建第一个vue程序:显示hello world

	// html中
	<!-- 根节点(宿主容器) -->
  <div id="app">
  	<!-- Mustache语法不能作用在属性上,应该使用v-bind指令 -->
    <h2 v-bind:title='title'>  // 可以直接简写为:title='xxx'
      <!-- 插值文本: Mustache语法(双大括号)来进行数据绑定  -->
      {{title}} // 使用的是data中返回的数据对象中的title属性
    </h2>
  </div>
	
	
	<script src="vueJs所在路径"></script>
	<script>
    // 创建vue实例
   const vm = new Vue({
        el: '#app', // 提供一个在页面上已存在的DOM元素作为Vue实例的挂载目标。可以是CSS选择器,也可以是HTMLElement实例。(挂载到上面html中id为app的容器中)
        data() { // Vue实例的数据对象
          return {
            title: 'hello, vue!'
          }
        },
    });
    
    
    // 使用实例对象vm
    setTimeout(() => {
      vm.title = '看,我变了'
    }, 1000);
	</script>

Mustache表达式

  • 语法:

    {{ 表达式 }}   // 表达式:变量或能够输出唯一结果的。 注意:if else之类的不是
    
  • 作用: 把动态数据直接输出渲染到html中

  • 用法:

    	// html中
    	<!-- 根节点(宿主容器) -->
      <div id="app">
        <h2>
          <!-- 插值文本: Mustache语法(双大括号)来进行数据绑定  -->
          {{title}} // 使用的是data中返回的数据对象中的title属性
        </h2>
      </div>
      
      
      	<script src="vueJs所在路径"></script>
        <script>
          // 创建vue实例
         new Vue({
              el: '#app', // 提供一个在页面上已存在的DOM元素作为Vue实例的挂载目标。可以是CSS选择器,也可以是HTMLElement实例。(挂载到上面html中id为app的容器中)
              data() { // Vue实例的数据对象
                return {
                  title: 'hello, vue!'
                }
              },
          });
        </script>
    

指令

概念: 写在标签上的一种,以 “v-” 开头的自定义属性。一些指令能够接收一个“参数”,在指令名称之后以冒号表示。

作用: 帮助我们操作html,动态的获取或者渲染数据。

官方指令地址

v- bind

v-bind 指令可以用于响应式地更新HTMLattribute。

HTML特性不能用Mustache 语法,应该使用v-bind指令。

	// html中
  <div id="app">
  	<!-- Mustache语法不能作用在特性、属性值上,应该使用v-bind指令 -->
    <h2 v-bind:title='title'>  // 可以直接简写为:title='xxx'
      {{title}}
    </h2>
  </div>
	
	
	<script src="vueJs所在路径"></script>
	<script>
   new Vue({
        el: '#app', 
        data() {
          return {
            title: 'hello, vue!'
          }
        },
    });
	</script>

class与style绑定

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是属性,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind用于 class 和 style 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

使用:

<style> .active { background-color: red; } </style>


<ul>
  <!-- class绑定 --> 
  <li v-for="item in goodList" 
    :class="{active: (selected === item)}" 
    @click="selected = item">{{item}}</li> 
    <!-- style绑定 --> 
    <!-- <li v-for="item in goodList" 
					:style="{backgroundColor: (selected === item)?'#ddd':'transparent'}" 								@click="selectedCourse = item">{{item}}</li> --> 
</ul>



<script> 
	new Vue({ 
		data: { 
		// 保存选中项 
		selected: '', 
		goodList: ['花生','瓜子','啤酒']
	},
}) </script>

v-for

作用: 用于循环渲染,我们可以用 v-for 指令循环一个数组或者对象。

语法:

<标签 v-for="(值,索引) in 要循环的源数据数组"></标签>

<标签 v-for="(值,键名, 索引) in 要循环的源数据对象"></标签>

用法:

	// html中
  <div id="app">
    <ul>
    	<li v-for="item in goodList">{{item}}</li> 
    </ul>
  </div>
  
  
  	<script src="vueJs所在路径"></script>
    <script>
     new Vue({
          el: '#app', 
          data() {
            return {
              goodList: ['花生','瓜子','啤酒']
            }
          },
      });
    </script>

注意:在js中for...in循环,只能获得对象的键名(数组中就是索引),不能直接获取键值。但是在vue中使用v-for... inv-for... of去循环数组,第一个参数都是获取的数组元素。但是官方有一句话:你也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法。

v-model

v-model 本质上是语法糖。它将转换为输入事件以更新数据,并对一些极端场景进行一些特殊处理。也就是说可以让data中的数据,和表单的数据双向绑定。但是需要注意的是只能用于表单元素。

		<!-- 表单元素输入绑定 --> 
		<input v-model="good" type="text"/>
		<span>{{good}}</span>

		<script src="vueJs所在路径"></script>
    <script>
     new Vue({
          el: '#app', 
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
              good: ''
            }
          },
      });
    </script>

v-on

官方事件处理

我们可以使用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

语法:

<标签 v-on:事件类型="表达式或函数名或函数名()"></标签>

例如:

<标签 v-on:click="count++"></标签>
<标签 v-on:click="increase"></标签>
<标签 v-on:click="increase(实参)"></标签>

//  v-on可以简写为@
<标签 @事件类型="表达式或函数名或函数名()"></标签>

使用:

		<!-- 表单元素输入绑定 --> 
		<input v-model="good" type="text" v-on:keydown.enter="addGood"/>
		<button @click="addGood">添加商品</button>

		<script src="vueJs所在路径"></script>
    <script>
     new Vue({
          el: '#app', 
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
              good: ''
            }
          },
          methods: { 
          	addGood() { 
          		this.goodList.push(this.good); 
          	} 
          },
      });
    </script>

v-if

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。

作用: 控制元素的显示渲染逻辑。

概念: 逻辑和JS是一样的,从上往下,找到第一个满足条件的渲染,其他的不渲染。

<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else-if="Math.random() > 0.3"> // v-else-if 必须紧跟在带v-if或者v-else-if的元素之后。
  Now you can see me
</div>
<div v-else> // v-else元素必须紧跟在带v-if或者v-else-if的元素的后面,否则它将不会被识别。
  Now you don't
</div>

因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个<template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

v-show

另一个用于根据条件展示元素的选项是 v-show 指令。用法大致一样:

<h1 v-show="ok">Hello!</h1>

注意,v-show 不支持 <template> 元素。

v-if vs v-show

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。(v-show 只是简单地切换元素的display,所以 v-show 的元素始终会被渲染并保留在 DOM 中)。

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

v-if vs v-for

v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级。所以不推荐在同一元素上同时使用 v-ifv-for

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。

当你只想为部分项渲染节点时,这种优先级的机制会十分有用。

// 只渲染未完成的 todo。
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>

如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素上 (如果没有父级,那么可以在外层加一个 template标签。同样地,template也不会渲染出来) 。

<ul v-if="todos.length">
  <li v-for="todo in todos">
    {{ todo }}
  </li>
</ul>

计算属性和监听器

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护,此时就可以考虑计算属性和监听器。

计算属性computed

概念:computed的写法和methods一样,也是一个函数,但是computed中的函数,必须有一个返回值。而且这个函数的名字,就可以拿到返回值。

		<p> 
      <!-- 计算属性 -->
      商品总数:{{total}}
  	</p>

		<script src="vueJs所在路径"></script>
    <script>
     new Vue({
          el: '#app', 
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
            }
          },
          computed: {
              total() {// 和上面使用计算属性total,名字保存一致
              		// 一般把比较复杂的逻辑计算写在这里 最终返回结果
                  return this.goodList.length + '种'
              }
          }
      });
    </script>

计算属性computed,有依赖缓存,只要所依赖的数据发生变化,才会重新计算,否则,直接使用缓存,页面不会重新渲染,性能之高,令人发指。

监听器(侦听器)

		<p> 
     <!-- 监听器 --> 
     课程总数:{{totalCount}}
  	</p>

		<script src="vueJs所在路径"></script>
    <script>
     new Vue({
          el: '#app', 
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
              totalCount: 0
            }
          },
          // 这种不能生效,因为初始化时不会触发 
          // watch: { 
          	// goodList(newValue, oldValue) { 
          		// this.totalCount = newValue.length + '种' 
          	// } 
          // },
         watch: { 
           goodList: { // 监控goodList,值发生变化时,才执行
              immediate: true, // 立即执行一次
              // deep: true,  // 深层次监听,例如数组对象
              handler(newValue, oldValue) { 
                this.totalCount = newValue.length + '种' 
              } 
           } 
        },
      });
    </script>

计算属性 vs 监听器

监听器更通用,理论上计算属性能实现的监听器也能实现。

处理数据的场景不同,监听器适合一个数据影响多个数据,计算属性适合一个数据受多个数

据影响

计算属性有缓存性,计算所得的值如果没有变化不会重复执行。

监听器适合执行异步操作或较大开销操作的情况。

官方地址

组件

官方地址

定义:组件是可复用的 Vue 实例,准确讲它们是VueComponent的实例,继承自Vue。

优点:组件化可以增加代码的复用性、可维护性和可测试性。

使用场景:

  • 通用组件:实现最基本的功能,具有通用性、复用性,例如按钮组件、输入框组件、布局组件等。

  • 业务组件:它们完成具体业务,具有一定的复用性,例如登录组件、轮播图组件。

  • 页面组件:组织应用各部分独立内容,需要时在不同页面组件间切换,例如列表页、详情页组件

如何使用组件:

  • 定义:Vue.component(),components选项,sfc
  • 分类:有状态组件,functional,abstract
  • 通信:props,$emit()/$on(),provide/inject,$children/$parent/$root/$attrs/$listeners
  • 内容分发:<slot><template>v-slot
  • 使用及优化:is,keep-alive,异步组件

组件的本质:

vue中的组件经历如下过程: 组件配置 => VueComponent实例 => render() => Virtual DOM=> DOM

所以组件的本质是产生虚拟DOM。

需要注意的是,组件名、prop和自定义事件时,尽量使用‘羊肉串’的格式。特别是自定义事件,v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的)。所以例如 v-on:myEvent 将会变成 v-on:myevent,所以如果你使用 myEvent 事件句柄就不可能被监听到。因此推荐使用羊肉串格式:@my-event事件名。

组件组册

之前的代码是这样的:

		<div id="app">
        <ul>
          <li v-for="item in goodList" 
            :class="{active: (selected === item)}" 
            @click="selected = item">{{item}}</li> 
        </ul>
    </div>

    <script src="vue.js"></script>
    <script>
      new Vue({
          el: '#app', 
          data() {
            return {
              selected: '', 
              goodList: ['花生','瓜子','啤酒'],
            }
          },
      });

     </script>

我们想把列表渲染,提出去成一个组件,那么我们怎么做呢。

 		<div id="app">
        <!-- 组件列表 -->  
      <!-- goodList是父组件的数据对象中的属性, goodlist是子组件中用于接收数据的字段,在props中去设置类型和默认值和接收 -->
 	  	<good-list :goodlist='goodList'></good-list>
    </div>

    <script src="vue.js"></script>
    <script>
     Vue.component('good-list',{
        data(){
          return {
            selected: '', // 这个属性是这个组件自己的,所以就从父组件中提到这个组件来维护
          }
        },
        props: {
          // 一定要注意,如果上面写:goodList='goodList',这儿用goodList接收不到,还是要使用goodlist
          // 因为 HTML 是大小写不敏感的,会被自动转换为全小写 
          'goodlist': { 
            type: Array,
            default: []
          }
        },
        template: `
          <ul>
            <!-- class绑定 --> 
            <li v-for="item in goodlist" 
              :class="{active: (selected === item)}" 
              @click="selected = item">{{item}}</li> 
          </ul>
        `
      });
     new Vue({
      el: '#app',
      data() {
        return {
          goodList: ['花生','瓜子','啤酒'],
        }
      },
    })
     </script>

自定义事件及其监听

新增时,原本的代码是:

			<input v-model="good" type="text" v-on:keydown.enter="addGood"/>
      <button @click="addGood">添加商品</button>

      <ul>
        <li v-for="item in goodList">{{item}}</li> 
      </ul>
      
    <script src="vue.js"></script>
    <script>
      new Vue({
           el: '#app',
           data() {
             return {
              goodList: ['花生','瓜子','啤酒'],
              good: ''
             }
           },
           methods: { 
          	addGood() {
          		this.goodList.push(this.good); 
          	} 
          },
       });

     </script>

把新增功能提出去成组件之后:

 		<div id="app">
       <good-add @good-add-handle='addGood'></good-add>
  
        <ul>
          <li v-for="item in goodList">{{item}}</li> 
        </ul>
    </div>

    <script src="vue.js"></script>
    <script>
      Vue.component('good-add',{
        data(){
          return {
            good: '', //  将good从父组件提取到自己去维护
          }
        },
        template: `
        <div>
          <input v-model="good" type="text" v-on:keydown.enter="add"/>
          <button @click="add">添加商品</button>
        </div>
        `,
        methods: {
          add(){
            // 发送自定义事件通知父组件新增商品(派发事件)
            // 注意事件名称定义时不要有大写字母出现
            this.$emit('good-add-handle', this.good);
          }
        }
      })

      new Vue({
          el: '#app',
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
            }
          },
          methods: {
            addGood(good) { // 接收子组件传递过来的参数 然后维护goodList
              this.goodList.push(good); 
            } 
        },
      });
     </script>

组件实现v-model

v-model默认转换是:value和@input,如果想要修改这个行为,可以通过定义model选项。

	Vue.component('good-add',{
        model: {
          prop: 'value',
          event: 'change'
        },
  })

代码如下:

<div id="app">
      <!-- 子组件v-model的是父组件中的good状态 -->
      <!-- 组件支持v-model需要实现内部input的:value和@input -->
       <good-add v-model='good' @good-add-handle='addGood'></good-add>
  
        <ul>
          <li v-for="item in goodList">{{item}}</li> 
        </ul>
    </div>

    <script src="vue.js"></script>
    <script>
      Vue.component('good-add',{
        model: {
          prop: 'value',
          event: 'change'
        },
        // 接收父组件传递value,不需要额外维护good了
        props: {
          value: String,
          default: ''
        },
        template: `
        <div>
          <input 
            @change="$emit('change', $event.target.value)"
             :value=value  
             type="text" 
             @keydown.enter='add'
          />
          <button @click="add">添加商品</button>
        </div>
        `,
        methods: {
          add(){
            // 派发事件不再需要传递数据
            this.$emit('good-add-handle');
          }
        }
      })

      new Vue({
          el: '#app',
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
              good: '' // good是父组件来维护
            }
          },
          methods: {
            addGood() {
              this.goodList.push(this.good); // 拿自己的good,因为子组件绑定的就是自己的这个属性
            } 
        },
      });
     </script>

slot

官方地址

通过插槽分发内容,也就是说子组件预留位置,让父组件在使用子组件的时候,可以把数据插入进去。

通过使用vue提供的 slot 可以给组件传递内容.

		<div id="app">
      <!-- 只是传递true测试时,需要加上v-bind代表表达式,否则会当成字符串 -->
      <!-- <message :show="true">新增成功!!</message> -->

      <!-- 
        方式1: 声明一个closeMessage方法去修改状态
        <message @close='closeMessage' :show="isShowMessgae">新增成功!!</message> 
      -->
      <!-- 方式2: 直接修改isShowMessgae状态 -->
      <message @close='isShowMessgae=$event' :show="isShowMessgae">新增成功!!</message> 

      <good-add v-model='good' @good-add-handle='addGood'></good-add>
      <good-list :goodlist='goodList'></good-list>
    </div>

    <script src="vue.js"></script>
    <script>
      // 弹窗
      Vue.component('message',{
        props: {
          show: {
            type: Boolean,
            default: false
          }
        },
        template: `
          <div class='message-box' v-if='show'>
            <!--通过slot获取传入内容-->
            <!--slot作为占位符(占坑位)-->
            <slot></slot>
            <span class='message-box-close' @click='$emit("close",false)'>X</span>
          </div>
        `
      })
      // 新增商品
      Vue.component('good-add',{
        model: {
          prop: 'value',
          event: 'change'
        },
        // 接收父组件传递value,不需要额外维护good了
        props: {
          value: String,
          default: ''
        },
        template: `
          <div>
            <input
              @change="$emit('change', $event.target.value)"
              :value=value
              type="text"
              @keydown.enter='add'
              />
            <button @click="add">添加商品</button>
          </div>
        `,
        methods: {
          add(){
            // 派发事件不再需要传递数据
            this.$emit('good-add-handle');
          }
        }
      })
      //商品列表
      Vue.component('good-list',{
        data(){
          return {
            selected: '', // 这个属性是这个组件自己的,所以就从父组件中提到这个组件来维护
          }
        },
        props: {
          // 一定要注意,如果上面写:goodList='goodList',这儿用goodList接收不到,还是要使用goodlist
          // 因为 HTML 是大小写不敏感的,会被自动转换为全小写 
          'goodlist': { 
            type: Array,
            default: []
          }
        },
        template: `
          <ul>
            <!-- class绑定 --> 
            <li v-for="item in goodlist" 
              :class="{active: (selected === item)}" 
              @click="selected = item">{{item}}</li> 
          </ul>
        `
      });
      
      new Vue({
          el: '#app',
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
              good: '',
              isShowMessgae: false, // 控制弹窗是否显示
            }
          },
          methods: {
            addGood() {
              this.goodList.push(this.good); 
              this.isShowMessgae = true; // 新增成功时,打开弹窗
            },
            closeMessage($evnet){
              this.isShowMessgae = $evnet; // 点击弹窗组件中的X时,父组件修改状态为false
            }
        },
      });
    </script>

.sync 修饰符

.sync官方地址

双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。因此官方推荐使用update:xxxx的模式触发事件取而代之。

因此,上面的代码修改两处:

<message @update:show='isShowMessgae=$event' :show="isShowMessgae">新增成功!!</message> 


// js 弹框组件message -> template中(弹框的X)
<span class='message-box-close' @click='$emit("update:show",false)'>X</span>

官方为了方便起见,为这种模式提供一个缩写,即 .sync 修饰符。

在上面的基础上,修改html中message组件:

<message :show.sync="isShowMessgae">新增成功!!</message> 

注意: js 弹框组件message -> template中,依然且必须是@click='$emit("update:show",false)

<span class='message-box-close' @click='$emit("update:show",false)'>X</span>

具名slot

如果存在多个独立内容要分发,可以使用具名插槽v-slot:name(可以简写为#name)

// html中 修改组件message
<message :show.sync="isShowMessgae">
        <template v-slot:title>
          <strong>
            恭喜你,
          </strong>
        </template>
        <!-- 当不写v-slot时,其实默认v-slot值为default <template v-slot:default>新增成功!!</template> -->
        <template>新增成功!!</template>
      </message> 
      
      
 // js中 message组件 ->  template
 	template: `
          <div class='message-box' v-if='show'>
            <!--通过slot获取传入内容-->
            
            <!--具名插槽-->
            <slot name="title"></slot>
            <!--slot作为占位符(占坑位)-->
            <slot></slot>
            <span class='message-box-close' @click='$emit("update:show",false)'>X</span>
          </div>
        `

插槽总结

匿名插槽:

// 父组件 【使用插槽的时候 父组件必须是双标签】
<子组件标签>我是要插入插槽的内容</子组件标签>

// 子组件
<template>
    <div>
       <h1>我是子组件</h1>
       <div class="content">
           <slot><slot />  // 插槽: 预留位置 将来父组件把内容插入此位置。
       </div>
    </div>
</template>

具名插槽:

// 父组件
<Son>
  <p slot="header">我是头部内容 aaa</p>
  <p slot="main">我是主体内容 bbb</p>
  <p slot="footer">我是尾部内容 ccc</p>
</Son>

// 子组件
  <!-- 具名插槽 -->
  <div class="header">
    <slot name="header" />
  </div>
  <div class="main">
    <slot name="main" />
  </div>
  <div class="footer">
    <slot name="footer" />
  </div>

作用域插槽:

// 父组件
<p slot="box" slot-scope="scope">
    我是外部数据:
    <br />
    {{ scope.msg }}
    {{ scope.news }}
</p>

// 子组件
<div class="box">
   <slot name="box" :msg="sonMsg" :news="news" />
</div>

export default {
  data() {
    return {
      sonMsg: "我是son数据",
      news: "我是新闻",
    };
  },
};

v-slot:

// 父组件
 <!-- 指令v-slot -->
<template #header>
    <p>header 哈哈哈</p>
</template>

<template #footer="scope">
    <p>footer 嘻嘻嘻 : {{ scope.news }}</p>
</template>

// 子组件
<!-- v-slot -->
<div class="header">
    <slot name="header" />
</div>

<div class="footer">
    <slot name="footer" :news="news" />
</div>

组件通信总结

概念: 组件通信就是组件之间相互传递数据

常见的方式:

  • 父传子

  • 子传父

  • bus

  • vuex状态机

父传子

// 父组件
<子组件标签 属性名1="属性值" :属性名2="动态数据"></子组件标签>

// 子组件
export default {
    props: ['属性名1', '属性名2']  // 数组格式
    
    // 封装组件  使用对象写法 更严谨
    props: {
        属性名1: {
        		type: String, // 限制类型  Number Array Object Boolean
            default: 'a', // 默认值
            required: true, // 必填
        },
        属性名2: {
            
        }
    }
}

子传父

// 父组件
<子组件标签 @自定义事件="函数名"></子组件标签>
export default {
    methods: {
        函数名(data) {
            // data就是子组件传递过来的数据
        }
    }
}

// 子组件
export default {
    methods: {
        toFa() {
            // 把数据传递给父组件
            this.$emit('自定义事件', 数据)
        }
    }
}

bus

// 1. 在main.js入口文件中, 创建一个Vue的空的实例对象,挂载在Vue的原型上
Vue.prototype.$bus = new Vue()

// 2. 在 A 组件把数据传递出去
export default { // new Vue({})
   methods: {
       sendMsg() {
           this.$bus.$emit('自定义事件', 数据)
       }
   } 
}
// 3. 在 B 组件 接收数据
export default {
    created() {
        this.$bus.$on('自定义事件', (data) => {
            // data就是接收到的数据
        })
    }
}

必会API

数据相关API

Vue.set

向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。

使用方法: Vue.set(target, propertyName/index, value)

官方地址

在之前例子上改造,增加一个批量修改价格功能:

		<div id="app">
       <good-add @good-add-handle='addGood'></good-add>
  
       <!-- 批量修改价格功能 -->
       <div>
         <!-- 加上.number修饰符,得到的值就是number类型,否则默认得到字符串类型 -->
          <input v-model.number="price"> 
          <button @click="batchUpdatePrice">批量更新价格</button>
       </div>

        <ul>
          <li v-for="item in goodList">{{item.name}} - ¥{{item.price}}</li> 
        </ul>
    </div>

    <script src="vue.js"></script>
    <script>
      Vue.component('good-add',{
        data(){
          return {
            good: '', //  将good从父组件提取到自己去维护
          }
        },
        template: `
        <div>
          <input v-model="good" type="text" v-on:keydown.enter="add"/>
          <button @click="add">添加商品</button>
        </div>
        `,
        methods: {
          add(){
            // 发送自定义事件通知父组件新增商品(派发事件)
            // 注意事件名称定义时不要有大写字母出现
            this.$emit('good-add-handle', this.good);
          }
        }
      })

      new Vue({
          el: '#app',
          data() {
            return {
              goodList: [{name: '花生'},{name: '瓜子'},{name: '啤酒'}],
              price: 0
            }
          },
          methods: {
            addGood(good) { // 接收子组件传递过来的参数 然后维护goodList
               // this指向的是实例对象
              // 需要注意 数组需要变异方法,才能被检测更新
          		this.goodList.push({name: good}); 

              // Vue 不能检测以下数组的变动: 1. 当你利用索引直接设置一个数组项时  2.当你修改数组的长度时
              // this.goodList[this.goodList.length] = this.good; // 不会更新视图
              // console.log(this.goodList); // 数据是变化了的
            },
            // 添加批量更新价格方法
            batchUpdatePrice(){
              this.goodList.forEach(item => {
                // item.price = this.price; // 不能更新视图,但是数据是修改了的
                // 方式1
                // Vue.set(item, 'price', this.price); // 全局方法 效果都是一样的
                // 方式2
                this.$set(item, 'price', this.price); // 推荐使用
              });
            }
        },
      });
     </script>

Vue.delete

删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。

使用方法: Vue.delete(target, propertyName/index)

Vue.nextTick

官方地址

事件相关API

vm.$on

监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的

额外参数。

vm.$on('事件名', function (payload) { console.log(payload) })
// 我们常在template中,以v-on(简写为@)指令去监听事件

vm.$emit

触发当前实例上的事件。附加参数都会传给监听器回调。

vm.$emit('事件名', 参数)

事件总线bus

通过在Vue原型上添加一个Vue实例作为事件总线,实现组件间相互通信,而且不受组件间关系的影响(无论层级有多深,不再局限于父子组件之间通信)。

Vue.prototype.$bus = new Vue();
// 我们可以在任意组件中使用this.$bus访问到该Vue实例

在具名slot上修改:

  1. style上把弹框的样式,分为成功和失败

  2. 新增一个message组件,添加class为error(之前那个添加为success),把里面文本内容修改成错误提示

  3. 添加事件总线,在第一行添加Vue.prototype.$bus = new Vue();

  4. 在弹窗组件message中,监听关闭事件

             mounted () { 
              this.$bus.$on('close-message', () => { 
                this.$emit('update:show', false) 
              }); 
            },
    
  5. 新增商品good-add组件中,新增一个按钮,监听点击事件。

    				removeAllMessage(){
                this.$bus.$emit('close-message'); // 名字和上面,$on中事件句柄名close-message一致
              }
    
 		 <style>
      .active {
      background-color: red;
    }
    .message-box {
      padding: 10px 20px;
      
    }
    .success{
      background: #4fc08d;
      border: 1px solid #42b983;
    }
    .error{
      background: red;
      border: 1px solid red;
    }
    .message-box-close {
      float: right;
    }
    </style>
    
    
 		
 		<div id="app">
      <!-- 错误时的消息提示 -->
      <message :show.sync="isShowMessgaeError" class="error">
        <template #title>
          <strong>
            输入有误!
          </strong>
        </template>
        <template>内容不能为空</template>
      </message> 
      <!-- 成功的消息提示 -->
      <message :show.sync="isShowMessgae" class="success">
        <!-- v-slot:可以简写为# -->
        <template #title>
          <strong>
            恭喜你,
          </strong>
        </template>
        <template>新增成功!!</template>
      </message> 

      <good-add v-model='good' @good-add-handle='addGood'></good-add>
      <good-list :goodlist='goodList'></good-list>
    </div>

    <script src="vue.js"></script>
    <script>
      // 放到第一行
      Vue.prototype.$bus = new Vue();
      // 弹窗
      Vue.component('message',{
        props: {
          show: {
            type: Boolean,
            default: false
          }
        },
        template: `
          <div class='message-box' v-if='show'>
            <!--通过slot获取传入内容-->
            
            <!--具名插槽-->
            <slot name="title"></slot>
            <!--slot作为占位符(占坑位)-->
            <slot></slot>
            <span class='message-box-close' @click='$emit("update:show",false)'>X</span>
          </div>
        `,
         // 监听关闭事件
         mounted () { 
          this.$bus.$on('close-message', () => { 
            this.$emit('update:show', false) 
          }); 
        },
      })
      // 新增商品
      Vue.component('good-add',{
        model: {
          prop: 'value',
          event: 'change'
        },
        props: {
          value: String,
          default: ''
        },
        template: `
          <div>
            <input
              @change="$emit('change', $event.target.value)"
              :value=value
              type="text"
              @keydown.enter='add'
              />
            <button @click="add">添加商品</button>
            <button @click="removeAllMessage">清空提示框</button>
          </div>
        `,
        methods: {
          add(){
            this.$emit('good-add-handle');
          },
          removeAllMessage(){
            this.$bus.$emit('close-message');
          }
        },
       
      })
      //商品列表
      Vue.component('good-list',{
        data(){
          return {
            selected: '',
          }
        },
        props: {
          'goodlist': { 
            type: Array,
            default: []
          }
        },
        template: `
          <ul>
            <!-- class绑定 --> 
            <li v-for="item in goodlist" 
              :class="{active: (selected === item)}" 
              @click="selected = item">{{item}}</li> 
          </ul>
        `
      });
      
      new Vue({
          el: '#app',
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
              good: '',
              isShowMessgae: false,
              isShowMessgaeError: false,
            }
          },
          methods: {
            addGood() {
              if(this.good){ // 如果输入有内容
                this.goodList.push(this.good); 
                this.isShowMessgae = true;
              } else {
                this.isShowMessgaeError = true;
              }
              
            },
            closeMessage($evnet){
              this.isShowMessgae = $evnet;
            }
        },
      });
    </script>

vm.$once

监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。

vm.$once('事件名', function (payload) { console.log(payload) })

vm.$off

移除自定义事件监听器。

  • 如果没有提供参数,则移除所有的事件监听器;

  • 如果只提供了事件,则移除该事件所有的监听器;

  • 如果同时提供了事件与回调,则只移除这个回调的监听器。

vm.$off() // 移除所有的事件监听器 
vm.$off('事件名') // 移除该事件所有的监听器 
vm.$off('事件名', callback) // 只移除这个回调的监听器

元素引用

ref和vm.$refs

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例。

例如:

		<div id="app">
       <!-- 错误时的消息提示 -->
       <message ref='error-message' class="error">
          <template #title>
            <strong>
              输入有误!
            </strong>
          </template>
          <template>内容不能为空</template>
        </message> 
        <!-- 成功的消息提示 -->
        <message ref='success-message' class="success">
          <!-- v-slot:可以简写为# -->
          <template #title>
            <strong>
              恭喜你,
            </strong>
          </template>
          <template>新增成功!!</template>
        </message> 

       <good-add @good-add-handle='addGood'></good-add>
  
        <ul>
          <li v-for="item in goodList">{{item}}</li> 
        </ul>
    </div>

    <script src="vue.js"></script>
    <script>
       // 弹窗
       Vue.component('message',{
        data() {
          return {
            show: false
          }
        },
        template: `
          <div class='message-box' v-if='show'>
            <!--通过slot获取传入内容-->
            
            <!--具名插槽-->
            <slot name="title"></slot>
            <!--slot作为占位符(占坑位)-->
            <slot></slot>
            <span class='message-box-close' @click='changeShow'>X</span>
          </div>
        `,
        methods: {
          changeShow() {
            this.show = !this.show;
          }
        },
      })

      Vue.component('good-add',{
        data(){
          return {
            good: '',
          }
        },
        template: `
        <div>
          <input ref='addInput' v-model="good" type="text" v-on:keydown.enter="add"/>
          <button @click="add">添加商品</button>
        </div>
        `,
        mounted () {
          // mounted及之后才能访问到ref
          // 在普通的dom元素上使用,指向dom元素
          this.$refs.addInput.focus();
        },
        methods: {
          add(){
            // 发送自定义事件通知父组件新增商品(派发事件)
            // 注意事件名称定义时不要有大写字母出现
            this.$emit('good-add-handle', this.good);
          }
        }
      })

      new Vue({
          el: '#app',
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
            }
          },
          methods: {
            addGood(good) {
              if(good){
                this.goodList.push(good); 
                // 在子组件上使用,指向组件实例
                this.$refs['success-message'].changeShow();
              } else {
                this.$refs['error-message'].changeShow();
              }
            } 
        },
      });
     </script>

注意:

  1. ref 是作为渲染结果被创建的,在初始渲染时不能访问它们

  2. $refs 不是响应式的,不要试图用它在模板中做数据绑定

  3. 当 v-for 用于元素或组件时添加ref,引用信息将是包含 DOM 节点或组件实例的数组。

进阶知识

过滤器filters

作用: 过滤处理数据的格式,可被用于一些常见的文本格式化。

使用场景:过滤器可以用在两个地方:双花括号插值和v-bind表达式,注意过滤器要被添加在表达式的尾部,由“管道”符号|表示。

语法:

		<!-- 在双花括号中 -->
		<div>{{ msg | 函数名 }}</div>
		
		<!-- 在 `v-bind` 中 --> 
		<div v-bind:id="msg | 函数名"></div>


 	// 过滤器
    filters: {
        函数名(msg) {
            return 过滤结果
        }
    }

例如:

<div id="app">
      <ul>
        <li v-for='item of goodList'>
        	<!-- 不使用过滤器时这么写,但是货币符号是固定的 -->
          <!-- {{item.name}} -  ${{item.price}} -->
          {{item.name}} -  {{item.price | symbol}}
        </li>
      </ul>
    </div>

    <script src="vue.js"></script>
    <script>
    
    new Vue({
      el: '#app',
      data() {
        return {
          goodList: [{name: '花生', price: 10},{name: '瓜子', price: 40},{name: '啤酒', price: 90}],
        }
      },
      filters: {
        symbol: function(value) {
          return '$' + value;
        }
      }
    });

把上面的例子稍微改造一下,符号可以动态传递而不是写死的。

		<div id="app">
      <ul>
        <li v-for='item of goodList'>
          <!-- 和方法一样调用,传递参数 -->
          {{item.name}} - {{item.price | symbol('¥')}}
        </li>
      </ul>
    </div>

    <script src="vue.js"></script>
    <script>
    
    new Vue({
      el: '#app',
      data() {
        return {
          goodList: [{name: '花生', price: 10},{name: '瓜子', price: 40},{name: '啤酒', price: 90}],
        }
      },
      filters: {
        symbol: function(value, sym = '$') { // 第一个参数,理解为上面的item.price  第二个参数,就是('¥')中传递过来的符号,  为了容错处理 默认值给个$
          return sym + value;
        }
      }
    });
    </script>

自定义指令

除了核心功能默认内置的指令 ,Vue 允许注册自定义指令。有的情况下,仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

官方地址

例如官方输入框自动获取焦点例子:

		<div id="app">
      <input type="text" v-focus>
    </div>

    <script src="vue.js"></script>
    <script>
      // 注册一个全局自定义指令 `v-focus`
      Vue.directive('focus', {
        // 当被绑定的元素插入到 DOM 中时……
        // binding很重要,详细信息参看官网
        inserted: function (el, binding) {
          // 聚焦元素
          el.focus()
        }
      });

      new Vue({
        el: '#app',
      });
    </script>

然后我们再来自定义做一个,根据当前登陆用户级别,来做权限设置。

		<div id="app">
      <input type="text" v-focus>

      <!-- 特别需要注意: 指令里,""中是表达式,如果需要传递字符串,则需要加上字符串 -->
      <button v-permission="'superAdmin'">删除</button>

    </div>

    <script src="vue.js"></script>
    <script>
      // 假设当前登陆用户是会员
      const user = 'member';

      // 注册一个全局自定义指令 `v-focus`
      Vue.directive('focus', {
        // 当被绑定的元素插入到 DOM 中时……
        // binding很重要,详细信息参看官网
        inserted: function (el, binding) {
          // 聚焦元素
          el.focus()
        }
      });
      
      // 第一个参数: 指令名,注意使用时要加上v-
      // 第二个参数: 配置项
      Vue.directive('permission', {
        inserted: function (el, binding) {
          console.log(binding);
           // 若指定用户角色和当前用户角色不匹配则删除当前指令绑定的元素
          if (user !== binding.value) {
            el.parentElement.removeChild(el)
          }
        }
      });

      new Vue({
        el: '#app',
      });
    </script>

渲染函数

官方地址

Vue 推荐在绝大多数情况下使用模板来创建HTML。然而在一些场景中,真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。

基础:

render: function (createElement) { 
	// createElement函数返回结果是VNode(虚拟DOM) 
	return createElement( // 接收三个参数
    tagname, // 标签名称 
    data, // 传递数据 
    children // 子节点数组
	) 
}

基于官网的例子:

		<div id="app">
      <!-- 用render实现一个组件 : 实现标题 -->
      <!-- level是指需要生成h1-6哪一个标签 -->
      <my-head :level='1' :title='title'>{{title}}</my-head>
      <my-head :level='3' :title='title'>我是另一个我</my-head>

      <!-- <h2 :title='title'>
        {{title}}
      </h2> -->
    </div>

    <script src="vue.js"></script>
    <script>
      Vue.component('my-head',{
        props: ['level', 'title'],        
        // render函数接收一个 createElement参数,我们一般简写为h    h === createElement
        // 因为Vdom底层的算法是snabbdom算法,这个算法里面生成虚拟dom的方法名就叫h
        render(h){ 
          // 注意这儿一定要有return, return出createElement返回的Vnode。
         return h(
            'h'+this.level, // 参数1:标签名字
            {attr:  { title: this.title }},// 参数2
            this.$slots.default, // 参数3: 子节点数组(虚拟节点)   标签之间的内容,需要使用默认插槽来获取
          )
        }
      });

      new Vue({
          el: '#app', 
          data() { 
            return {
              title: 'hello, vue!'
            }
          },
      });
    </script>

然后我们再来进阶来试一试:

当用户使用组件时,

<my-head :level='1' :title='title' icon='Food'>{{title}}</my-head>

我们希望渲染成:

			<!-- 阿里矢量图使用方式 -->
			<h1 :title='title'>
          <svg class="icon"><use xlink:href="#icon-iconfinder_Food_C_"></use></svg>
        {{title}}
      </h1>

最终代码为:

		<div id="app">
      <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head>

      <!-- <h3 :title='title'>
          <svg class="icon"><use xlink:href="#icon-iconfinder_Food_C_"></use></svg>
        {{title}}
      </h3> -->
    </div>

    <script src="./iconfont.js"></script>
    <script src="vue.js"></script>
    <script>
      Vue.component('my-head',{
        props: ['level', 'title', 'icon'],        
        render(h){ 
          let children = [];           
          // 思路: 第一步,把用户传入的icon,生成<svg class="icon"><use xlink:href="#icon-icon名"></use></svg>添加到children数组中
          // 第二步: 把默认插槽内容this.$slots.default放到children数组中
          // 第三步:h函数参数3就替换为children数组
          // 第一步: 生成svg,添加图标   同样是调用h函数生成
          const svgVnode = h(
            'svg',
            { class: 'icon' }, // 添加固定类名为icon  详见官网createElement参数2
            [h('use',{attrs: {"xlink:href": `#icon-iconfinder_${this.icon}_C_`}})] // 参数3: 子节点数组(虚拟节点),svg还有个子级use,所以再调用h方法生成use,需要注意的是需要是数组,所以将返回的vnode放到一个数组中
          );
          children = [svgVnode, ...this.$slots.default];

         return h(
            'h'+this.level, // 参数1:标签名字
            {attrs:  { title: this.title }},// 参数2
            children, // 参数3: 子节点数组(虚拟节点)   标签之间的内容,需要使用默认插槽来获取
          )
        }
      });

      new Vue({
          el: '#app', 
          data() { 
            return {
              title: 'hello, vue!'
            }
          },
      });
    </script>

模板语法是如何实现的

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

之前的例子中原本代码如下:

 <!-- 宿主容器(根节点) -->
  <div id="app">
    <ul>
      <!-- class绑定 --> 
      <li v-for="item in goodList" 
        :class="{active: (selected === item)}" 
        @click="selected = item">{{item}}</li> 
        <!-- style绑定 --> 
        <!-- <li v-for="item in goodList" 
              :style="{backgroundColor: (selected === item)?'#ddd':'transparent'}" 								@click="selectedCourse = item">{{item}}</li> --> 
    </ul>
  </div>
  
  
  <script src="vueJs所在路径"></script>
    <script>
     const vm = new Vue({
          el: '#app', 
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
              selected: ''
            }
          },
      });
    </script>

然后我们去输出vue替我们生成的渲染函数 :

执行代码: console.log(vm.$options.render)

我们看到输出信息:

(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('ul',_l((goodList),function(item){return _c('li',{class:{active: (selected === item)},on:{"click":function($event){selected = item}}},[_v(_s(item))])}),0)])}
})

然后我们基于这一个点,改写为渲染函数版本。

 <!-- 宿主容器(根节点) -->
  <div id="app"></div>
  
  
  // 创建vue实例
  new Vue({
      el: '#app',
      data() {
        return {
          goodList: ['花生','瓜子','啤酒'],
          selected: ''
        }
      },
      methods: {},
      render() {
        with(this){
          return _c('div',{attrs:{"id":"app"}},[_c('ul',_l((goodList),function(item){return _c('li',{class:{active: (selected === item)},on:{"click":function($event){selected = item}}},[_v(_s(item))])}),0)])}
      }
    })

我们可以看到,结果是一样的。

结论:Vue通过它的编译器将模板编译成渲染函数,在数据发生变化的时候再次执行渲染函数,通过对比两次执行结果得出要做的dom操作,模板中的神奇魔法得以实现。

函数式组件

官方地址

没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数。在这样的场景下,我们可以将组件标记为 functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。

修改上一个例子为函数式组件:

		<div id="app">
      <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head>
    </div>

    <script src="./iconfont.js"></script>
    <script src="vue.js"></script>
    <script>
      Vue.component('my-head',{
        functional: true,  // 1. functional设置为true,标示是函数式组件
        props: ['level', 'title', 'icon'],   
        // 在函数式组件中,没有this
        // 所以render函数,提供第二个参数作为上下文             
        render(h, context){ 
          // 之前从this上拿取'level', 'title', 'icon',就要变化了
          // 2. 从context.props上去拿取
          const { level, title, icon } = context.props;
          let children = [];           
          const svgVnode = h(
            'svg',
            { class: 'icon' },
            [h('use',{attrs: {"xlink:href": `#icon-iconfinder_${icon}_C_`}})] 
          );
          // 3. 子元素获取: 增加context参数,并将this.$slots.default更新为context.children,然后将this.level更新为context.props.level。
          children = [svgVnode, ...context.children];

         return h(
            'h'+level, 
            {attrs:  { title: title }},
            children,
          )
        }
      });

      new Vue({
          el: '#app', 
          data() { 
            return {
              title: 'hello, vue!'
            }
          },
      });
    </script>

混入

官方地址

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

// 定义一个混入对象 
let myMixin = { 
	created: function () { 
		this.hello() 
	},
	methods: { 
		hello: function () { 
			console.log('hello from mixin!') 
		} 
	} 
}
// 定义一个使用混入对象的组件 
Vue.component('mycomponent', { mixins: [myMixin] })

插件

官方地址

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一

个可选的选项对象.

const MyPlugin = { 
	install (Vue, options) { 
		Vue.component('my-head', {...}) 
	} 
}
if (typeof window !== 'undefined' && window.Vue) { 
	window.Vue.use(MyPlugin) 
}

例如把上面的标题组件,封装成插件。

首先新建个js文件,存放插件代码:

const MyPlugin = { 
  // 插件需要install方法
	install (Vue, options) { 
		Vue.component('my-head',{
      functional: true,  // 1. functional设置为true,标示是函数式组件
      props: ['level', 'title', 'icon'],   
      // 在函数式组件中,没有this
      // 所以render函数,提供第二个参数作为上下文             
      render(h, context){ 
        // 之前从this上拿取'level', 'title', 'icon',就要变化了
        // 2. 从context.props上去拿取
        const { level, title, icon } = context.props;
        let children = [];           
        const svgVnode = h(
          'svg',
          { class: 'icon' },
          [h('use',{attrs: {"xlink:href": `#icon-iconfinder_${icon}_C_`}})] 
        );
        // 3. 子元素获取: 增加context参数,并将this.$slots.default更新为context.children,然后将this.level更新为context.props.level。
        children = [svgVnode, ...context.children];

       return h(
          'h'+level, 
          {attrs:  { title: title }},
          children,
        )
      }
    });
	} 
}
// 判断当前环境  并且判断是否已经存在Vue
if (typeof window !== 'undefined' && window.Vue) { 
	window.Vue.use(MyPlugin);
}

然后在页面上,直接使用插件即可。

 		<div id="app">
      <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head>
    </div>

    <script src="./iconfont.js"></script>
    <script src="vue.js"></script>
    <script src="./plugins/head.js"></script>
    <script>   
      new Vue({
          el: '#app', 
          data() { 
            return {
              title: 'hello, vue!'
            }
          },
      });
    </script>

代码github地址

原文地址:https://www.cnblogs.com/zz-zrr/p/14438068.html