阅读 1122

Vue.js 2.0 手把手入门笔记

1 介绍

是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。

2 特点:

  • 核心只关注视图层(view)
  • 灵活、轻量、灵活的特点
  • 适用于移动端项目
  • 渐进式框架

3 什么是库,什么是框架?

  • 库是将代码集合成一个产品,库是我们调用库中的方法实现自己的功能
  • 框架则是为解决一类问题而开发的产品,框架是我们在指定的位置编写代码,框架帮我们调用。

框架是库的升级版

4 渐进式

  • 声明式渲染(无需关心如何实现)
  • 组件系统
  • 客户端路由(vue-router)
  • 大规模状态管理(vuex)
  • 构建工具(vue-cli)

5 Vue的两个核心点

  1. 响应的数据变化
  2. 当数据发生改变->视图的自动更新
  3. 组合的视图组件
  4. ui页面映射为组件树
  5. 划分组件可维护、可复用、可测试

6 MVC(backbone,react)

  • model 数据
  • view  视图
  • controller 控制器

7 MVVM(angular,vue) 双向

  • model 数据
  • view  视图
  • viewModel视图模型

8 Object.defineProperty(es5)没有替代方案

  • 不支持ie8<=

2 vue基础指令

2.1 安装vue

  • cdn方式
  • npm 方式

2.2 简单的尝试

这里使用cdn方便测试

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>

<body>
    <div id="content">
        <!-- moustache 小胡子语法 表达式 可以放赋值 取值 三元-->
        {{ msg }}
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<!-- 使用官网的vue地址 -->
<script>
    // 引用vue后会给一个vue构造函数
    var vm = new Vue({ // vm === viewModel
        el: '#content', // 告诉vue管理哪一部分,querySelector "document.querySelector("#content")"
        data: { // data中的数据会被vm所代理
            msg: 'Hello Vue!' // 可以通过vm.msg获取对应的呢日用
        }
    })// Object.defineProperty
    vm.msg = "wjw" // 修改视图
</script>
</html>
复制代码

image.png

2.3 模板语法

综上所属得出了一套模板语法

2.3.1 文本

<span>Message:{{msg}}</span>
复制代码

2.3.2 表达式

{{number + 1}}
{{ok?'YES':'NO'}}
{{message.split('').reverse().join('')}}
复制代码

但是vue的表单元素 input checkbox textarea radio select 非文本处理

vue的指令 directive 只是dom上的行间属性,vue给这类属性赋予了一些意义,来实现特殊功能所有指令都以v-开头value属性默认情况下回vue忽略掉 selected checked 都没有意义

2.3.3表单输入

v-model 会将msg赋予输入框,输入框的值改变会影响数据

<input v-model="msg">
<input type="checkbox" v-model="msg1" value="爬山">
复制代码

2.3.4 原始HTML

<p>Using mustache:<span v-html='rawHtml'></spn></p>
复制代码

2.3.5 指令

<p v-if='seen'>现在看到我了</p>
复制代码

2.3.6 特性

<div v-bind:id='dynamicld'></div>
复制代码

2.4 Object.defineProperty原理

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="content"></div>
    <input type="text" id="input">
</body>
  
<script>
    let obj = {}
    let temp = {};
    document.getElementById("content").innerHTML = obj.name
    // 'name' 代表属性
    Object.defineProperty(obj,'name',{
        configurable:false, //是否可删除
        // writable:true,// 是否可赋值(如果使用set方法,则不能使用)
        enumerable:true, // 是否可枚举,也是就for..in..
        // value:1,// 值(如果使用get方法,则不能使用)
        get(){ // 取obj的name会触发get方法
            return temp['name']
        },
        set(val){// 给obj赋值会触发get方法
            // console.log(val);
            temp['name'] = val // 改变temp的结果
            input.value = val // 将值赋值给输入框
        }
    });
    input.value = obj.name // 页面一加载,会将调用get方法
    input.addEventListener('input',function(){ // 等待输入框的变化
        obj.name = this.value // 当值变化时会调用set方法
        document.getElementById("content").innerHTML = obj.name
    })
</script>

</html>
复制代码

image.png

最后可以实现双向绑定的雏形

3 数据响应的变化

vue会循环data中的数据(数据劫持) 依次的增加getter和setter

 let vm = new Vue({
   el:'#content',
   data:{
     a:{}
   }
 })
复制代码

但是这时候我想添加一个school方法,发现没有产生getter和setter

1.1 方法一 $set

使用变量时 先要初始化,否则新加的属性不会导致页面刷新

 vm.$set(vm.a,"school",'1')// 此方法可以给对象添加响应式的变化
复制代码

1.2 方法二 替换原对象

vm.a = {"school":"heihei",age:8};
复制代码

1.3 数组问题

去改变数组中的某一项监控不到的,也不能改变数组的长度方法

let vm = new Vue({
  el:'#content',
  data:{
    a:[1,2,3,4,5,6]
  }
})
复制代码

错误方法

vm.a[0] =100
vm.a.length -=2 
复制代码

变异方法:pop push shift unshit sort reserve splice

 vm.a = vm.a.map(item=>item*3) 
复制代码

4 数组的循环v-for

vue 提供了一个v-for 解决循环问题 更高效 会复用原有结构

4.1 代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="content">
        <!--要循环谁就在谁身上增加v-for属性,类似于for...in..-->
        <!--默认是value of 数组/ (value,index) of 数组-->
        <li v-for="(todo,index) in todos">
 <!-- 会改变原始数组的方法,为变异方法 例如push(),pop()等;  非变异方法,不会改变原始数组,但是会返回一个新数组 -->
            {{ todo.text }} {{index+1}}
        </li>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<!-- 使用官网的vue地址 -->
<script>
    let vm = new Vue({
        el:'#content',
        data:{
            todos: [
                { text: '学习 JavaScript' },
                { text: '学习 Vue' },
                { text: '整个牛项目' }
            ]
        }
    })
</script>
</html>
复制代码

v-for循环数组 当用for来更新已被渲染的元素时,vue的“就地复用”机制 是不会改变数据项的顺序的。要想重新排序,需为每项添加key属性(也就是每项唯一的id)

想要改变

会改变原始数组的方法,为变异方法 例如push(),pop()等;  非变异方法,不会改变原始数组,但是会返回一个新数组

4.2 为什么v-for一定要有key

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        <div>
            <input type="text" v-model="name">
            <button @click="add">添加</button>
        </div>
        <ul>
            <li v-for="(item, i) in list">
                <input type="checkbox"> {{item.name}}
            </li>
        </ul>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<!-- 使用官网的vue地址 -->
<script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {
        name: '',
        newId: 3,
        list: [
          { id: 1, name: '蔬菜' },
          { id: 2, name: '奶酪' },
          { id: 3, name: '肉' }
        ]
      },
      methods: {
        add() {
         //注意这里是unshift
          this.list.unshift({ id: ++this.newId, name: this.name })
          this.name = ''
        }
      }
    });
  </script>
  </div>

</html>
复制代码

image.png

当你输入汤时

image.png

就会变成这个样子  =>

image.png

但是当你换成了key

可以简单的这样理解:加了key(一定要具有唯一性) id的checkbox跟内容进行了一个关联。是我们想达到的效果

vue和react的虚拟DOM的Diff算法大致相同,其核心是基于两个简单的假设
首先讲一下diff算法的处理方法,对操作前后的dom树同一层的节点进行对比,一层一层对比,如下图:

image.png

当某一层有很多相同的节点时,也就是列表节点时,Diff算法的更新过程默认情况下也是遵循以上原则。
比如一下这个情况:
image.png

我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的:
image.png

即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。

image.png

vue中列表循环需加:key="唯一标识" 唯一标识可以是item里面id index等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件 key的作用主要是为了高效的更新虚拟DOM

5 事件

5.1 定义&缩写

事件定义以及缩写

<div id="app">
	<button @click="msg"></button>
	<button @mousedown="add"></button>
  <!--如果不传递参数,则不要写括号会自动传入事件源,如果写括号了,要手动传入$event属性-->
</div>

let vm = new Vue({
		el:"#app",
    methods:{
    	msg(){
        console.log(Math.random());
      }
    }
})
复制代码

methods和data中的数据会全部放在vm上,而且名字不能冲突,冲突会报错,methods中的this指向的都是实例

5.2 mousedown

当鼠标指针移动到元素上方,并按下鼠标按键(左、右键均可)时,会发生 mousedown 事件。
与 click 事件不同,mousedown 事件仅需要按键被按下,而不需要松开即可发生。

5.3 mouseup

当在元素上松开鼠标按键(左、右键均可)时,会发生 mouseup 事件。
与 click 事件不同,mouseup 事件仅需要松开按钮。当鼠标指针位于元素上方时,放松鼠标按钮就会触发该事件。

5.4 click

当鼠标指针停留在元素上方,然后按下并松开鼠标左键时,就会发生一次 click 事件。
注意:触发click事件的条件是按下并松开鼠标左键!,按下并松开鼠标右键并不会触发click事件。
三个事件的触发顺序

5.5 总结

若在同一个元素上按下并松开鼠标左键,会依次触发mousedown、mouseup、click,前一个事件执行完毕才会执行下一个事件
若在同一个元素上按下并松开鼠标右键,会依次触发mousedown、mouseup,前一个事件执行完毕才会执行下一个事件,不会触发click事件

6 事件修饰符的使用

1 事件处理

如果需要在内联语句处理器中访问原生DOM事件。可以使用特殊变量$event,把它传入到methods中的方法中。
     在Vue中,事件修饰符处理了许多DOM事件的细节,让我们不再需要花大量的时间去处理这些烦恼的事情,而能有更多的精力专注于程序的逻辑处理。在Vue中事件修饰符主要有:

  • .stop:等同于JavaScript中的event.stopPropagation(),防止事件冒泡
  • .prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播)
  • .capture:与事件冒泡的方向相反,事件捕获由外到内
  • .self:只会触发自己范围内的事件,不包含子元素
  • .once:只会触发一次

1.1 stop 防止事件冒泡

冒泡事件:嵌套两三层父子关系,然后所有都有点击事件,点击子节点,就会触发从内至外  子节点-》父节点的点击事件

<!-- HTML --> 

<div id="app"> 
 &emsp;<div class="outeer" @click="outer"> 
   &emsp;<div class="middle" @click="middle"> 
     &emsp;<button @click="inner">点击我(^_^)</button>
     </div>
   </div> 
 &emsp;<p>{{ message }}</p> 
</div>

 let app = new Vue({
	 el: '#app', 
   data () { 
   &emsp;return { message: '测试冒泡事件' } 
   }, 
 &emsp;methods: { 
   &emsp;inner: function () {
       this.message = 'inner: 这是最里面的Button' 
   &emsp;}, 
   &emsp;middle: function () { 
     &emsp;this.message = 'middle: 这是中间的Div' 
   &emsp;}, 
   &emsp;outer: function () { 
     &emsp;this.message = 'outer: 这是外面的Div' 
   &emsp;} 
 &emsp;} 
})
复制代码

防止冒泡事件的写法是:在点击上加上.stop相当于在每个方法中调用了等同于event.stopPropagation(),点击子节点不会捕获到父节点的事件

<!-- HTML --> 

<div id="app"> 

 &emsp;<div class="outeer" @click.stop="outer"> 

   &emsp;<div class="middle" @click.stop="middle"> 

     &emsp;<button @click.stop="inner">点击我(^_^)</button>

     </div>

   </div> 

</div>
复制代码

1.2 prevent取消默认事件

.prevent等同于JavaScript的event.preventDefault(),用于取消默认事件。比如我们页面的<a href="#">标签,当用户点击时,通常在浏览器的网址列出#

1.3 .capture 捕获事件

捕获事件:嵌套两三层父子关系,然后所有都有点击事件,点击子节点,就会触发从外至内  父节点-》子节点的点击事件

<!-- HTML --> 
<div id="app"> 
 &emsp;<div class="outeer" @click.capture="outer"> 
   &emsp;<div class="middle" @click.capture="middle"> 
     &emsp;<button @click.capture="inner">点击我(^_^)</button>
     </div>
   </div> 
</div>
复制代码


 

1.4 .self

修饰符.self只会触发自己范围内的事件,不会包含子元素。

<!-- HTML --> 
<div id="app"> 
 &emsp;<div class="outeer" @click.self="outer"> 
   &emsp;<div class="middle" @click.self="middle"> 
     &emsp;<button @click.stop="inner">点击我(^_^)</button>
     </div>
   </div> 
</div>
复制代码

1.5 .once 只执行一次点击

如果我们在@click事件上添加.once修饰符,只要点击按钮只会执行一次。

2 键盘修饰符

在JavaScript事件中除了前面所说的事件,还有键盘事件,也经常需要监测常见的键值。在Vue中允许v-on在监听键盘事件时添加关键修饰符。记住所有的keyCode比较困难,所以Vue为最常用的键盘事件提供了别名:

  • .enter:回车键
  • .tab:制表键
  • .delete:含deletebackspace
  • .esc:返回键
  • .space: 空格键
  • .up:向上键
  • .down:向下键
  • .left:向左键
  • .right:向右键

3 鼠标修饰符

鼠标修饰符用来限制处理程序监听特定的滑鼠按键。常见的有:

  • .left:鼠标左键
  • .middle:鼠标中间滚轮
  • .right:鼠标右键

4 修饰键

可以用如下修饰符开启鼠标或键盘事件监听,使在按键按下时发生响应:

  • .ctrl
  • .alt
  • .shift
  • .meta

5 自定义按键修饰符别名

在Vue中可以通过config.keyCodes自定义按键修饰符别名。例如,由于预先定义了keycode 116(即F5)的别名为f5,因此在文字输入框中按下F5,会触发prompt方法,出现alert

<!-- HTML -->

<div id="app">

    <input type="text" v-on:keydown.f5="prompt()">

</div>



Vue.config.keyCodes.f5 = 116;



let app = new Vue({

    el: '#app',

    methods: {

        prompt: function() {

            alert('我是 F5!');

        }

    }

});
复制代码

6 总结

在Vue中,使用v-on来给元素绑定事件,而为了更好的处理逻辑方面的事物,Vue提供了一个methods。在methods中定义一些方法,这些方法可以帮助我们处理一些逻辑方面的事情。而在这篇文章中,我们主要介绍了一些事件的修饰符,比如常见的阻止事件冒泡,键盘修饰符等。除此之外,还提供了config.keyCodes提供自定义按键修饰符别名。

7 缩写

7.1 指令缩写

<a v-bind:href='url'></a>
<a :href='url'></a>
<a v-on:click='doSomething'></a>
<a @click='doSomething'></a>
复制代码

7.2 函数缩写

image.png

缩写后

image.png

8 组件化管理

1.组件化开发

我们可以很直观的将一个复杂的页面分割成若干个独立组件,每个组件包含组件的逻辑和样式,再将这些独立组件完成一个复杂的页面。这样既减少了逻辑复杂度,又实现了代码的重用。页面是组件的容器,组件自动组合形成完整的界面,当不需要某个组件时,或者想要替换某个组件时,可以随时进行替换和删除,而不影响整个应用的运行。

2、组件化开发的好处

  • 提高开发效率
  • 方便重复使用
  • 便于协同开发
  • 更容易被管理和维护

在vue中例如div、span都可以看做一个组件

3、全局组件

  • 全局组件:可以声明一次在任何地方使用
  • 局部组件:必须告诉这个组件属于谁

一般写插件的时候全局组件使用的多一些

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>

<body>
    <div id="app">
        <my-handsom></my-handsom>
        <my-handsom></my-handsom>
        <my-handsom></my-handsom>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    Vue.component("my-handsom",{ //一个对象可以看成一个组件
        data: function () {
            return {
                count: 0
            }
        },
        template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
    })
    var vm = new Vue({
        el: '#app'
    })
</script>
</html>
复制代码

image.png

  • 组件名不要带大写,多组件使用 -
  • 只要组件和定义相同是可以的(首字母可以大写)
  • html采用短横线隔开命名法js中转驼峰也是可以的

深入了解组件

props

组件的参数传递

slot

插槽在组件抽象设计中的应用

自定义事件

父子组件的通信方式

9 全局api- Vue.extend

使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数

<div id="mount-point"></div>
复制代码
// 创建构造器
var demo = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})

// 创建 Profile 实例,并挂载到一个元素上。
new demo().$mount('#mount-point')
复制代码

10 全局api-nextTick

官方说明

参数

  • {Function} [callback]
  • {Object} [context]

用法

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

// 修改数据
vm.msg = 'Hello'

// DOM 还没有更新
Vue.nextTick(function () {
  // DOM 更新了
})

// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick()
  .then(function () {
    // DOM 更新了
})
复制代码

2.1.0 起新增:如果没有提供回调且在支持 Promise 的环境中,则返回一个 Promise。请注意 Vue 不自带 Promise 的 polyfill,所以如果你的目标浏览器不原生支持 Promise (IE:你们都看我干嘛),你得自己提供 polyfill。

示例

先来一个示例了解下关于Vue中的DOM更新以及nextTick的作用。
模板

<div class="app">
  <div ref="msgDiv">{{msg}}</div>
  <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
  <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
  <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
  <button @click="changeMsg">
    Change the Message
  </button>
</div>
复制代码

Vue实例
**

new Vue({
  el: '.app',
  data: {
    msg: 'Hello Vue.',
    msg1: '',
    msg2: '',
    msg3: ''
  },
  methods: {
    changeMsg() {
      this.msg = "Hello world."
      this.msg1 = this.$refs.msgDiv.innerHTML
      this.$nextTick(() => {
        this.msg2 = this.$refs.msgDiv.innerHTML
      })
      this.msg3 = this.$refs.msgDiv.innerHTML
    }
  }
})
复制代码

点击前

image.png

点击后

image.png

从图中可以得知:msg1和msg3显示的内容还是变换之前的,而msg2显示的内容是变换之后的。其根本原因是因为Vue中DOM更新是异步的(详细解释在后面)。

应用场景

下面了解下nextTick的主要应用的场景及原因。

  • 在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中

created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。

  • 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。

具体原因在Vue的官方文档中详细解释:

Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.thenMessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0)代替。 例如,当你设置vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

11 全局api-set

官网说明

Vue.set( target, propertyName/index, value )

  • 参数
    • {Object | Array} target
    • {string | number} propertyName/index
    • {any} value
  • 返回值:设置的值。
  • 用法
    向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = 'hi')

注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

示例

<div id="div">  
	<p >{{items}}</p>
</div>
 
<script>
 
var vm = new Vue({
el:"#div",
  data: {
    items: ['a', 'b', 'c']
  }
});
 
Vue.set(vm.items,2,"ling")
 
</script>
复制代码

1 设置数组元素

Vue.set(vm.items,2,"ling") : 表示 把vm.items  这个数组的下标为2 的元素,改为"ling"
把数组  ["a","b","c"] 修改 后是 ["a","b","ling"] 

image.png

2 向响应式对象添加属性

<div id="div">  
	<p>{{person}}</p>
</div>
 
<script>
var vm = new Vue({
el:"#div",
data: {
   person:{
			name:"ling",
			job:"engineer"
   }
},
created:function(){
		alert(this.person.age)
  }
});
 
Vue.set(vm.person,"age","26")
</script>
复制代码

注意:person 是data 里面的子对象,所以可以使用 Vue.set( ) 方法。data 这个根对象就不能使用 set 方法

image.png

image.png

说明:控制台可以在person 里找到age 这个属性,说明添加成功 (响应式)

**

对比非响应式方法

vm.food="chocolate"
alert(vm.food)

image.png

控制台和网页上的 {{person}} 都没有显示food 这个属性,说明food 这个属性没有被添加 (非响应式)


image.png
**

12 全局api-delete

Vue.delete( target, propertyName/index )

  • 参数
    • {Object | Array} target
    • {string | number} propertyName/index

仅在 2.2.0+ 版本中支持 Array + index 用法。

  • 用法
    删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到属性被删除的限制,但是你应该很少会使用它。

在 2.2.0+ 中同样支持在数组上工作。

  • 目标对象不能是一个 Vue 实例或 Vue 实例的根数据对象。
data:{
   namelist : {
     id : 1, 
       name : '叶落森'
   }       
}
复制代码
// 删除name
delete this.namelist.name;//js方法
Vue.delete(this.namelist,'name');//vue方法
复制代码

13 全局api-fifer过滤器

8.1 介绍

允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示

8.2 优势

1、在Vue中使用过滤器(Filters)来渲染数据是一种很有趣的方式。
2、首先我们要知道,Vue中的过滤器不能替代Vue中的methodscomputed或者watch
3、过滤器不改变真正的data,而只是改变渲染的结果,并返回过滤后的版本。
4、在很多不同的情况下,过滤器都是有用的,比如尽可能保持API响应的干净,并在前端处理数据的格式。
5、在你希望避免重复和连接的情况下,它们也可以有效地封装成可重用代码块背后的所有逻辑。

8.3 过滤器例子

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        <div>{{ message | capitalize }}</div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm= new Vue({
      el: '#app',
      data: {
        message: 'world'
      },
      filters: { // 可以有好多的自定义过滤器
        capitalize(value) { // 这里的this指向的window
            if (!value) return ''
            value = value.toString()
            return value.charAt(0).toUpperCase() + value.slice(1)
        }
      }
    });
  </script>
</html>
复制代码

8.4 过滤器串连

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        {{ message | filterA | filterB }}
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm= new Vue({
      el: '#app',
      data: {
        message: 'world'
      },
      filters: { // 可以有好多的自定义过滤器
        filterA(value){
            return value.split('').reverse().join('');
        },
        filterB(value){
            return value.charAt(0).toUpperCase() + value.slice(1)
        }
      }
    });
  </script>
</html>
复制代码

8.5 过滤器传参

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        {{ message | filterA('hello',hi) }}
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm= new Vue({
      el: '#app',
      data: {
        hi:'!',
        message: 'world'
      },
      filters: { // 可以有好多的自定义过滤器
        filterA(value1,value2,value3){
            return `${value2} ${value1} ${value3}`;
        }
      }
    });
  </script>
</html>
复制代码

这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'hello' 作为第二个参数,表达式 hi 的值作为第三个参数。

14 插槽-slot

老版本vue

模板中只能有一个根元素

HTML内容模板(template)元素是一种用于保存客户端内容机制,该内容在加载页面时不会呈现,但随后可以在运行时使用JavaScript实例化。

<div id="app">
   <modal></modal> 
</div>

<template id="modal">
   <div>
     <h1>是否删除</h1>
  </div>
</template>
复制代码
let modal = {
 template:"#modal"
}

const app = new Vue({
 el:'#app',
 components:{
   modal
 },
 data:{
 }
})
复制代码

我们通常是想把h1的值动态放入,所以就要用到插槽

单个插槽 | 默认插槽 | 匿名插槽

首先是单个插槽,单个插槽是vue的官方叫法,但是其实也可以叫它默认插槽,或者与具名插槽相对,我们可以叫它匿名插槽。因为它不用设置name属性。 单个插槽可以放置在组件的任意位置,但是就像它的名字一样,一个组件中只能有一个该类插槽。相对应的,具名插槽就可以有很多个,只要名字(name属性)不同就可以了。

<div id="app">
  <modal>
    <h1>插入成功</h1>
  </modal>
</div>

<template id="modal">
  <div>
    <slot></slot>
  </div>
</template>
复制代码

image.png

当我们看到插入成功的时候,匿名插入就实现了

具名插槽

匿名插槽没有name属性,所以是匿名插槽,那么,插槽加了name属性,就变成了具名插槽。具名插槽可以在一个组件中出现N次,出现在不同的位置。下面的例子,就是一个有两个具名插槽单个插槽的组件,这三个插槽被父组件用同一套css样式显示了出来,不同的是内容上略有区别。

简单的来说,就是,我们可能遇到一个问题 我们想插入不同的插槽内的内容不一样

在 2.6.0+ 中已弃用

<div id="app">
    <modal>
        <h1>插入成功</h1>
        <h2 slot="title">标题</h2>
        <h2 slot="content">内容</h2>
    </modal>
</div>

<template id="modal">
  <div>
    <slot name="default"></slot>
    <slot name="title"></slot>
    <slot name="content"></slot>
  </div>
</template>
复制代码

我们可以发现没有name的情况下,默认就是default

作用域插槽 | 带数据的插槽

最后,就是我们的作用域插槽。这个稍微难理解一点。官方叫它作用域插槽,实际上,对比前面两种插槽,我们可以叫它带数据的插槽。什么意思呢,就是前面两种,都是在组件的template里面写

在 2.6.0+ 中已弃用


```html Vue作用域插槽
```

这种写法,习惯了element-ui的朋友一定就很熟悉了。

总结: 
1 . 使用slot可以在自定义组件内插入原生HTML元素,需要搭配使用name和slot属性,否则多个slot可能会返回重复的HTML元素。
2 . 使用slot-scope可以将slot内部的作用域指向该子组件,否则默认作用域指向调用slot的父组件。

新版本的 v-slot

vue@2.6.x 开始,Vue 为具名和范围插槽引入了一个全新的语法,即我们今天要讲的主角:v-slot 指令。目的就是想统一 slotscope-slot 语法,使代码更加规范和清晰。既然有新的语法上位,很明显,slotscope-slot 也将会在 vue@3.0.x 中彻底的跟我们说拜拜了。而从 vue@2.6.0 开始,官方推荐我们使用 v-slot 来替代后两者。


#### 具名插槽 > 实例化一个vue
// 组件
Vue.component('lv-hello', {
  template: `
    <div>
      <slot name="header"></slot>
      <h1>我的天呀</h1>
    </div>`
})

new Vue({
  el: '#app1',
  data: {

  }
});
复制代码

老版本

<div id="app1">
  <!-- 老版本使用具名插槽 -->
  <lv-hello>
    <p slot="header">我是头部</p>
  </lv-hello>
</div>
复制代码

新版本的变化

  <!-- 新版本使用具名插槽 -->
  <lv-hello>
    <!-- 注意:这块的 v-slot 指令只能写在 template 标签上面,而不能放置到 p 标签上 -->
    <template v-slot:header>
      <p>我是头部</p>
    </template>
  </lv-hello>
</div>
复制代码

具名插槽的缩写

v-slot: 替换成 #

<div id="app">
  <lv-hello>
    <template #header>
      <p>我是头部</p>
    </template>
    <!-- 注意: #号后面必须有参数,否则会报错。即便是默认插槽,也需要写成 #default -->
    <template #default>
      <p>我是默认插槽</p>
    </template>
  </lv-hello>
</div>
复制代码

作用域插槽

所谓作用域插槽,就是让插槽的内容能够访问子组件中才有的数据。

Vue.component('lv-hello', {
  data: function () {
    return {
      firstName: '张',
      lastName: '三'
    }
  },

  template: `
    <div>
      <slot name="header" :firstName="firstName" :lastName="lastName"></slot>
      <h1>我的天呀</h1>
    </div>
  `
})
复制代码
<div id="app">
  <!-- 老版本使用具名插槽 -->
  <lv-hello>
  	<p slot="header" slot-scope="hh">我是头部 {{ hh.firstName }} {{ hh.lastName }}</p>
	</lv-hello>
<!-- 新版本使用具名插槽 -->
    <lv-hello>
      <!-- 注意:这块的 v-slot 指令只能写在 template 标签上面,而不能放置到 p 标签上 -->
      <template v-slot:header="hh">
         <p>我是头部 {{ hh.firstName }} {{ hh.lastName }}</p>
      </template>
  	</lv-hello>
</div>
复制代码

15 动态绑定样式-v-bind

13.1 对象语法

:class 绑定的样式和class绑定的不冲突

13.1.1 直接绑定一个data

<div v-bind:class="{ active: isActive }"></div>
复制代码

active 这个 class 存在与否将取决于数据属性 isActive 的 布尔值

<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
复制代码

13.1.2 data中使用一个对象绑定

data: {
  classObject: {
    active: true,
    'text-danger': false
  }
}
复制代码

13.1.3 计算属性中绑定

data: {
  isActive: true,
  error: null
},
computed: {
  classObject: function () {
    return {
      active: this.isActive && !this.error,
      'text-danger': this.error && this.error.type === 'fatal'
    }
  }
}
复制代码

13.2 数组语法

<div v-bind:class="[activeClass, errorClass]"></div>
复制代码

13.2.1 直接动态绑定一个class

data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
复制代码

13.2.2 三元表达式

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
复制代码

不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:

<div v-bind:class="[{ active: isActive }, errorClass]"></div>
复制代码

16 数据-计算属性(computed)

1 什么是计算属性

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
       <div id="example">
           {{ message.split('').reverse().join('') }}
       </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
      el: '#app',
      data: {
        message: 'Hello'
      }
    });
  </script>
  </div>

</html>
复制代码

image.png

这里的表达式包含3个操作,并不是很清晰,所以遇到复杂逻辑时应该使用Vue特带的计算属性computed来进行处理。

2 计算属性的用法

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
       <div id="example">
           {{getMessage}}
       </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
      el: '#app',
      data: {
        message: 'Hello'
      },
      computed: { // 放在computed中最后也会放在vm上,不能和methods与data重名
        getMessage() {
            return this.message.split('').reverse().join('')
        }
      }
    });
  </script>
  </div>

</html>
复制代码

3 计算属性使用技巧

计算属性可以依赖其他计算属性
计算属性不仅可以依赖当前Vue 实例的数据,还可以依赖其他实例的数据

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app1"></div>
    <div id="app2">
         {{getMessage}}
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm= new Vue({
      el: '#app1',
      data: {
        message: 'World'
      }
    });
    var vm2 = new Vue({
      el: '#app2',
      data: {
        message: 'Hello'
      },
      computed: { 
        getMessage() {
            return `${this.message} ${vm.message}`
        }
      }
    });
  </script>
  </div>
</html>
复制代码

4 getter和setter

每一个计算属性都包含一个getter 和一个setter ,我们上面的两个示例都是计算属性的默认用法, 只是利用了getter 来读取。
在你需要时,也可以提供一个setter 函数, 当手动修改计算属性的值就像修改一个普通数据那样时,就会触发setter 函数,执行一些自定义的操作

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="getMessage"> <--模拟修改--!>
        {{getMessage}}
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm= new Vue({
      el: '#app',
      data: {
        hi:'Hello',
        message: 'World'
      },
      computed:{
        getMessage:{ //get,set方法
           // getter
           get(){
             return this.hi + ' ' + this.message
           },
           // setter
           set(newValue){
              console.log('====================================');
              console.log(newValue);
              console.log('====================================');
              var names = newValue.split(' ');
              this.hi = names[0];
              this.message = names[names.length - 1];
           }
        }
      }
    });
  </script>
</html>
复制代码


绝大多数情况下,我们只会用默认的getter 方法来读取一个计算属性,在业务中很少用到setter,所以在声明一个计算属性时,可以直接使用默认的写法,不必将getter 和setter 都声明。

5 质疑什么不直接用methods

我们可以将同一函数定义为一个方法而不是一个计算属性,两种方式的最终结果确实是完全相同的。只是一个使用getMessage()取值,一个使用getMessage取值。
然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。
这就意味着只要 hi还没有发生改变,多次访问 getMessage计算属性会立即返回之前的计算结果,而不必再次执行函数。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        <div>{{getMessage}}</div>
        <div> {{getMessage1()}}</div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm= new Vue({
      el: '#app',
      data: {
        hi:'Hello',
        message: 'World'
      },
      computed:{
        getMessage(){ //get,set方法
            return this.hi + ' ' + this.message 
            //而使用计算属性,只要title没变,页面渲染是不会重新进这里来计算的,而是使用了缓存。
        }
      },
      methods:{
        getMessage1(){
            return this.hi + ' ' + this.message
            //进这个方法,再次计算。不是刷新,而是只要页面渲染,就会进方法里重新计算。
        }
      }
    });
  </script>
</html>
复制代码

17 数据-观察(watch)

一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。

为什么一定要有watch,不用可以吗?我们已经有了computed,能不能不去使用?

1 watch的出现

做一个实验

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="a">
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>

    var vm = new Vue({
        el:'#app',
        data:{
            a:"1"
        },
        computed: {
            a(){
               setTimeout(() => {
                   this.a=1;
               }, 500); 
            }
        }
    })
</script>
</html>
复制代码

不难发现在_异步的情况下就不好使用了_

2 代码实现

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="a">
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
        el:'#app',
        data:{
            a:""
        },
        watch: { // 只有值变化的时候才会触发 支持异步了,其他情况我们更善于使用
            a(newVal,oldVal){ // watch的属性名字要和观察的人的名字一致
                console.log(newVal);
                console.log(oldVal);
            }
        },
    })
</script>
</html>
复制代码

3 computed与watch的区别

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        {{ fullName }}
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
</html>
复制代码
    var vm = new Vue({
    el: '#app',
        data: {
            firstName: 'Foo',
            lastName: 'Bar',
            fullName: 'Foo Bar'
        },
        watch: {
            firstName: function (val) {
                this.fullName = val + ' ' + this.lastName
            },
            lastName: function (val) {
                this.fullName = this.firstName + ' ' + val
            }
        }
    })
复制代码

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

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

是不是感觉优雅很多

4 侦听器

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
       <input type="text" v-model="something">
       {{somethingShow}}
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
    el: '#app',
        data: {
            something: '',
            somethingShow:''
        },
        watch: {
            something(val){
                this.somethingShow = "loading"
                this.getSomething()
            }
        },
        methods:{
            getSomething(){
                setTimeout(() => {
                    this.somethingShow = "hello"
                }, 1000);// 我们使用延迟模拟一个网络请求
            }
        }
    })
</script>
</html>
复制代码

5 vm.$watch

vm.$watch( expOrFn, callback, [options] )

观察 Vue 实例变化的一个表达式或计算属性函数。回调函数得到的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
       <input type="text" v-model="something">
       {{somethingShow}}
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
    el: '#app',
        data: {
            something: '',
            somethingShow:''
        }
    })
    vm.$watch('something',(newVal,oldVal)=>{// watch的属性名要和观察的人名字一致
        vm.somethingShow = "loading"
        console.log('====================================');
        console.log(newVal);
        console.log('====================================');
        vm.somethingShow = newVal
    })
</script>
</html>
复制代码

18 数据-属性(props)

组件接受的选项之一 props 是 Vue 中非常重要的一个选项。父子组件的关系可以总结为: props down, events up 父组件通过 props 向下传递数据给子组件;子组件通过 events 给父组件发送消息。

父子级组件

比如我们需要创建两个组件 parent 和 child。需要保证每个组件可以在相对隔离的环境中书写,这样也能提高组件的可维护性。
这里我们先定义父子两个组件和一个 Vue 对象

var childNode = {
  template: `
        <div>childNode</div>
        `
};
var parentNode = {
  template: `
        <div>
          <child></child>
          <child></child>
        </div>
        `,
  components: {
    child: childNode
  }
};
new Vue({
  el: "#example",
  components: {
    parent: parentNode
  }
});
复制代码
<div id="example">
  <parent></parent>
</div>
复制代码

这里的 childNode 定义的 template 是一个 div,并且内容是"childNode"字符串。 而在 parentNode 的 template 中定义了 div 的 class 名叫 parent 并且包含了两个 child 组件。

静态 props

组件实例的作用域是孤立的。这意味着不能(也不应该)在子组件的模板中直接引用父组件的数据。要让子组件使用父组件的数据,需要通过子组件的 props 选项。 父组件向子组件传递数据分为两种方式:动态和静态,这里先介绍静态方式。 子组件要显示的用 props 声明它期望获得的数据 修改上例中的代码,给 childNode 添加一个 props 选项和需要的forChildMsg数据; 然后在父组件中的占位符添加特性的方式来传递数据。

var childNode = {
  template: `
        <div>
          {{forChildMsg}}
        </div>
        `,
  props: ["for-child-msg"] // 直接把参数作为数组放进去
};
var parentNode = {
  template: `
        <div>
          <p>parentNode</p>
          <child for-child-msg="aaa"></child>
          <child for-child-msg="bbb"></child>
        </div>
        `,
  components: {
    child: childNode
  }
};
复制代码

命名规范
**

对于 props 声明的属性,在父组件的 template 模板中,属性名需要使用中划线写法; 子组件 props 属性声明时,使用小驼峰或者中划线写法都可以;而子组件的模板使用从父组件传来的变量时,需要使用对应的小驼峰写法。别担心,Vue 能够正确识别出小驼峰和下划线命名法混用的变量,如这里的forChildMsgfor-child-msg是同一值。

动态props

原则上很简单,for-child-msg作为一个变量

var parentNode = {
  template: `
        <div>
          <p>parentNode</p>
          <child :for-child-msg="childMsg1"></child>
          <child :for-child-msg="childMsg2"></child>
        </div>
        `,
  components: {
    child: childNode
  },
  data: function() {
    return {
      childMsg1: "child-1",
      childMsg2: "child-2"
    };
  }
};
复制代码

在父组件的 data 的 return 数据中的 childMsg1 和 childMsg2 会被传入子组件中

props 验证

验证传入的 props 参数的数据规格,如果不符合数据规格,Vue 会发出警告。

能判断的所有种类(也就是 type 值)有: String, Number, Boolean, Function, Object, Array, Symbol

Vue.component("example", {
  props: {
    // 基础类型检测, null意味着任何类型都行
    propA: Number,
    // 多种类型
    propB: [String, Number],
    // 必传且是String
    propC: {
      type: String,
      required: true
    },
    // 数字有默认值
    propD: {
      type: Number,
      default: 101
    },
    // 数组、默认值是一个工厂函数返回对象
    propE: {
      type: Object,
      default: function() {
        console.log("propE default invoked.");
        return { message: "I am from propE." };
      }
    },
    // 自定义验证函数
    propF: {
      isValid: function(value) {
        return value > 100;
      }
    }
  }
});

let childNode = {
  template: "<div>{{forChildMsg}}</div>",
  props: {
    "for-child-msg": Number
  }
};

let parentNode = {
  template: `
          <div class="parent">
            <child :for-child-msg="msg"></child>
          </div>`,
  components: {
    child: childNode
  },
  data() {
    return {
      // 当这里是字符串 "123456"时会报错
      msg: 123456
    };
  }
};
复制代码

还可以在 props 定义的数据中加入自定义验证函数,当函数返回 false 时,输出警告。 比如我们把上述例子中的 childNode 的for-child-msg修改成一个对象,并包含一个名叫validator的函数,该命名是规定叫validator的,自定义函数名不会生效

let childNode = {
  template: "<div>{{forChildMsg}}</div>",
  props: {
    "for-child-msg": {
      validator: function(value) {
        return value > 100;
      }
    }
  }
};
复制代码

在这里我们给for-child-msg变量设置了validator函数,并且要求传入的值必须大于 100,否则报出警告。

单向数据流

props 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件五一修改父组件的状态。
所以不应该在子组件中修改 props 中的值,Vue 会报出警告。

let childNode = {
  template: `<div class="child">
            <div>
              <span>子组件数据</span>
              <input v-model="forChildMsg"/>
            </div>
            <p>{{forChildMsg}}</p>
          </div>`,
  props: {
    "for-child-msg": String
  }
};

let parentNode = {
  template: `
          <div class="parent">
            <div>
              <span>父组件数据</span>
              <input v-model="msg"/>
            </div>
            <p>{{msg}}</p>
            <child :for-child-msg="msg"></child>
          </div>`,
  components: {
    child: childNode
  },
  data() {
    return {
      msg: "default string."
    };
  }
};
复制代码

传递的过程将短横分割命名,转成驼峰命名法即可

这里我们给父组件和子组件都有一个输入框,并且显示出父组件数据和子组件的数据。当我们在父组件的输入框输入新数据时,同步的子组件数据也被修改了;这就是 props 的向子组件传递数据。而当我们修改子组件的输入框时,浏览器的控制台则报出错误警告

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "forChildMsg"

修改 props 数据

通常有两种原因:

  1. prop 作为初始值传入后,子组件想把它当做局部数据来用

  2. prop 作为初始值传入后,由子组件处理成其他数据输出

  3. 定义一个局部变量,并用 prop 的值初始化它

但是由于定义的 ownChildMsg 只能接受 forChildMsg 的初始值,当父组件要传递的值变化发生时,ownChildMsg 无法收到更新。

let childNode = {
  template: `
          <div class="child">
            <div>
              <span>子组件数据</span>
              <input v-model="forChildMsg"/>
            </div>
            <p>{{forChildMsg}}</p>
            <p>ownChildMsg : {{ownChildMsg}}</p>
          </div>`,
  props: {
    "for-child-msg": String
  },
  data() {
    return { ownChildMsg: this.forChildMsg };
  }
};
复制代码

这里我们加了一个

用于查看 ownChildMsg 数据是否变化,结果发现只有默认值传递给了 ownChildMsg,父组件改变只会变化到 forChildMsg,不会修改 ownChildMsg。


  1. 定义一个计算属性,处理 prop 的值并返回

由于是计算属性,所以只能显示值,不能设置值。我们这里设置的是一旦从父组件修改了 forChildMsg 数据,我们就把 forChildMsg 加上一个字符串"---ownChildMsg",然后显示在屏幕上。这时是可以每当父组件修改了新数据,都会更新 ownChildMsg 数据的。

let childNode = {
  template: `
          <div class="child">
            <div>
              <span>子组件数据</span>
              <input v-model="forChildMsg"/>
            </div>
            <p>{{forChildMsg}}</p>
            <p>ownChildMsg : {{ownChildMsg}}</p>
          </div>`,
  props: {
    "for-child-msg": String
  },
  computed: {
    ownChildMsg() {
      return this.forChildMsg + "---ownChildMsg";
    }
  }
};
复制代码
  1. 更加妥帖的方式是使用变量存储 prop 的初始值,并用 watch 来观察 prop 值得变化。发生变化时,更新变量的值。
let childNode = {
  template: `
          <div class="child">
            <div>
              <span>子组件数据</span>
              <input v-model="forChildMsg"/>
            </div>
            <p>{{forChildMsg}}</p>
            <p>ownChildMsg : {{ownChildMsg}}</p>
          </div>`,
  props: {
    "for-child-msg": String
  },
  data() {
    return {
      ownChildMsg: this.forChildMsg
    };
  },
  watch: {
    forChildMsg() {
      this.ownChildMsg = this.forChildMsg;
    }
  }
};
复制代码

19 生命周期

1 vue生命周期简介

image.png

image.png

2 生命周期探究

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">{{message}}</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var app = new Vue({
        el: '#app',
        data: {
            message: "hello is world"
        },
        beforeCreate() {
            console.group('beforeCreate 创建前状态===============》');
            console.log("%c%s", "color:red", "el     : " + this.$el); //undefined
            console.log("%c%s", "color:red", "data   : " + this.$data); //undefined 
            console.log("%c%s", "color:red", "message: " + this.message)
        },
        created() {
            console.group('created 创建完毕状态===============》');
            console.log("%c%s", "color:red", "el     : " + this.$el); //undefined
            console.log("%c%s", "color:red", "data   : " + this.$data); //已被初始化 
            console.log("%c%s", "color:red", "message: " + this.message); //已被初始化
        },
        beforeMount() {
            console.group('beforeMount 挂载前状态===============》');
            console.log("%c%s", "color:red", "el     : " + (this.$el)); //已被初始化
            console.log(this.$el);
            console.log("%c%s", "color:red", "data   : " + this.$data); //已被初始化  
            console.log("%c%s", "color:red", "message: " + this.message); //已被初始化  
        },
        mounted() {
            console.group('mounted 挂载结束状态===============》');
            console.log("%c%s", "color:red", "el     : " + this.$el); //已被初始化
            console.log(this.$el);
            console.log("%c%s", "color:red", "data   : " + this.$data); //已被初始化
            console.log("%c%s", "color:red", "message: " + this.message); //已被初始化 
        },
        beforeUpdate() {
            console.group('beforeUpdate 更新前状态===============》');
            console.log("%c%s", "color:red", "el     : " + this.$el);
            console.log(this.$el);
            console.log("%c%s", "color:red", "data   : " + this.$data);
            console.log("%c%s", "color:red", "message: " + this.message);
        },
        updated() {
            console.group('updated 更新完成状态===============》');
            console.log("%c%s", "color:red", "el     : " + this.$el);
            console.log(this.$el);
            console.log("%c%s", "color:red", "data   : " + this.$data);
            console.log("%c%s", "color:red", "message: " + this.message);
        },
        beforeDestroy() {
            console.group('beforeDestroy 销毁前状态===============》');
            console.log("%c%s", "color:red", "el     : " + this.$el);
            console.log(this.$el);
            console.log("%c%s", "color:red", "data   : " + this.$data);
            console.log("%c%s", "color:red", "message: " + this.message);
        },
        destroyed() {
            console.group('destroyed 销毁完成状态===============》');
            console.log("%c%s", "color:red", "el     : " + this.$el);
            console.log(this.$el);
            console.log("%c%s", "color:red", "data   : " + this.$data);
            console.log("%c%s", "color:red", "message: " + this.message)
        }
    })
</script>
</html>
复制代码

chrome浏览器里打开,F12console就能发现

image.png

3 beforecreated

el 和 data 并未初始化

4 created

完成了 data 数据的初始化,el没有

5 beforeMount

完成了 el 和 data 初始化

6 mounted

完成挂载

7 update

在console控制台中输入

app.message= 'hello!!';
复制代码

image.png

8 destroy

我们在console里执行下命令对 vue实例进行销毁。销毁完成后,我们再重新改变message的值,vue不再对此动作进行响应了。但是原先生成的dom元素还存在,可以这么理解,执行了destroy操作,后续就不再受vue控制了。

app.$destroy();
复制代码

image.png

9 生命周期总结

9.1 beforecreate

可以在这加个loading事件,加载的动画

9.2 created

在这结束loading,还做一些初始化,实现函数自执行

9.3 mounted

在这发起后端请求,拿回数据,配合路由钩子做一些事情

9.4 beforeDestroy

你确认删除XX吗? destroyed :当前组件已被删除,清空相关内容

20 指令-条件判断(v-if&v-show)

1 v-if&v-show

  • 条件渲染 (使用 v-if)
  • 条件展示 (使用 v-show)

if操作的是dom show 操作的样式 如果频繁切换dom使用v-show,当数据一开时就确定下来使用v-if更好一些,如果if通过内部指令不会执行了 只有dom从显示到隐藏 或者隐藏到显示 才能使用vue的动画

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
        <span v-if="flag">你看的见我</span>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
        el:'#app',
        data:{
            flag:true
        }
    })
  </script>
</html>
复制代码

2 区别总结

  • v-show:操作的是元素的display属性
  • v-if:操作的是元素的创建和插入
  • 相比较而言v-show的性能要高

21 内置组件-动画(transition)

1 组件的过渡

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

image.png

在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
  4. v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

1.1 初步代码实现

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<style>
    div>div{
        width:100px;height: 100px;background: red;
    }
    .v-enter{
        opacity: 1;
    }
    /* 激活的时候 */
    .v-enter-avtive{
        opacity: 0;
        transition: 1s linear;
    }
    /* 离开 */
    .v-leave-active{
        opacity: 0;
        background: black;
        transition: 1s linear;
    }
</style>
<body>
    <div id="app">
        <button @click="flag=!flag">切换</button>
        <!-- vue自定义的组件 -->
        <transition>
            <div v-show="flag"></div>
        </transition>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
        el:'#app',
        data:{
            flag:true
        }
    })
</script>
</html>
复制代码

1.2 多个transition

遇上了多个transition的时候,同一个class肯定是会冲突的,那么如何处理呢

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<style>
    div>div{
        width:100px;height: 100px;background: red;
    }
    .jw-enter-active {
        transition: all .3s ease;
    }
    .jw-leave-active {
        transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
    }
    .jw-enter, .jw-leave-to
    {
        transform: translateX(10px);
        opacity: 0;
    }
</style>
<body>
    <div id="app">
        <button @click="flag=!flag">切换</button>

        <!-- vue自定义的组件 -->
        <transition name="jw">
            <div v-show="flag"></div>
        </transition>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
        el:'#app',
        data:{
            flag:true
        }
    })
</script>
</html>
复制代码

简单的理解就是就 transition有一个name属性
在css中name-状态即可调用

22 自定义指令-directives

1 介绍

Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

举一个栗子:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<body>
    <div id="app">
       <div v-color='flag'>123</div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
        directives:{
            color(el,bindings){ //el值指代的是button按钮
                console.log(arguments);
                el.style.background = bindings.value;
            }
        },
        el: '#app',
        data: {
            flag: 'red'
        },
        methods:{
            getSomething(){
                return "hello"
            }
        }
    })
</script>
</html>
复制代码

出现如图情况

image.png

image.png

再来个栗子

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>vue</title>
</head>
<style>
    .a{
        position: absolute;width: 100px;height: 100px;background: red;
    }
</style>
<body>
    <div id="app">
       <div class="a" v-drag></div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script> 
<script>
    var vm = new Vue({
        directives:{
            drag(el){
                el.onmousedown = function (e) {
                    var disx = e.pageX - el.offsetLeft;
                    var disy = e.pageY - el.offsetTop;
                    document.onmousemove = function (e) {
                        el.style.left = e.pageX - disx +'px';
                        el.style.top = e.pageX - disy + 'px';
                    }

                    document.onmouseup = function (e) {
                        document.onmousemove = document.onmousemove = null;
                    }

                    e.preventDefault();
                }
            }
        },
        el: '#app',
        data: {
            flag: 'red'
        },
        methods:{
            getSomething(){
                return "hello"
            }
        }
    })
</script>
</html>
复制代码

image.png

可以拖动

2 钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

3 钩子函数参数

  • el:指令所绑定的元素,可以用来直接操作 DOM 。
  • binding:一个对象,包含以下属性:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1"中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

23 实例属性-$ref

官网针对-ref的解释

  • 预期string
    ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例:
<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>
<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>
复制代码
  • 当 v-for 用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。
    关于 ref 注册时间的重要说明:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!$refs 也不是响应式的,因此你不应该试图用它在模板中做数据绑定。

操作dom

如果我们用jQuery的话,一般性都可以操作dom

$("#id").text('xxx')   // 使用Jquery
document.getElementById("id")  // 使用原生Dom
复制代码

现在我们牛逼了,我们用vue。那vue中,如果我要获取Dom,该怎么做?
这就进入本文的主题ref, $refs,官网解释:

<div id="app">
   <div>{{msg}}</div>
</div>
复制代码

在JavaScript中我们习惯了使用document.getElementsByTagName


```javascript var vm = new Vue({ el: '#app', data:{ msg:'hello' }, mounted() { console.log(document.getElementsByTagName("div")[0].innerHTML); } }) ```

vue操作dom

那么我们在vue中呢

<div id="app">
   <div ref="msg">{{msg}}</div>
</div>
复制代码

    var vm = new Vue({
      el: '#app',
      data:{
        msg:'hello'
      },
      mounted() {
        // console.log(document.getElementsByTagName("div")[0].innerHTML);
        console.log('====================================');
        console.log(this.$refs.msg);
        console.log('====================================');
      }
    })
复制代码
关注下面的标签,发现更多相似文章
评论