Vue框架的学习与使用

VUE简介

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

中文官方文档:

https://vuejs.bootcss.com/guide/

前端为主的MV时代

  • MVC(同步通信为主):Model 、View、Controller
  • MVP(异步通信为主):Model、View、Presenter
  • MVVM(异步通信为主):Model、View、ViewModel

双向绑定

介绍

​ Vue.js是一个MVVM框架,即数据双向绑定,当数据发生变化时,视图也就发生变化,当视图发生变化时,数据也会跟着同步发生变化,这也是Vue.js的精髓之处。

注意:我们说的数据双向绑定,一定是对于UI控件来说的,非UI控件不会涉及到数据双向绑定,单向数据绑定是使用 状态管理工具的前提。如果我们使用vuex,那么数据流也是单项的,这时就会和双向绑定有冲突

为什么要实现数据的双向绑定

​ 在Vue.js中,如果使用vuex,实际上数据还是单向的,之所以说是数据双向绑定,这是用的UI控件来说,对于我们处理表单,Vue.js的双向数据绑定用起来就特别舒服,即两者并不互斥,在全局性数据流使用单项,方便跟踪,局部性数据流使用双向,简单易操作

表单使用双向绑定

​ 你可以用v-model指令在表单**、、** 元素上创建双向数据绑定,它会根据控件类型自动选取正确的方法来更新元素,尽管有些神奇,但v-model本质上不过是语糖法。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理

注意:v-model会忽略所有表单元素的value、checked、selected特性的初始值而总是将Vue实例的数据作为数据来源,你应该通过JavaScript在组件的data选项中声明初始值!

代码体验:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p>普通文本</p>
    <div id="input">
        <input type="text" name="name" v-model="message">
        数据:{{message}}
    </div>
    <p>文本域</p>
    <div id="textarea">
        <textarea name="" id="" cols="30" rows="10" v-model="TextArea_data"></textarea>
        文本域中的数据: {{TextArea_data}}
    </div>
    <p>单选框</p>
    <div id="radio">
        <input type="radio" name="sex" value="" v-model="sexData"><input type="radio" name="sex" value="" v-model="sexData">女
        性别:{{sexData}}
    </div>
    <p>复选框</p>
    <div id="select">
        <select name="" v-model="selectData">
            <option value="" disabled>--请选择---</option>
            <option value="select1">选择一</option>
            <option value="select2">选择二</option>
            <option value="select3">选择三</option>
        </select>
        你选择了:{{selectData}}
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    var vm1 = new Vue({
        el: "#input",
        data: {
            message: "data"
        }
    });
    var vm2 = new Vue({
        el: "#textarea",
        data: {
            TextArea_data: "ffffdffff"
        }
    });
    var vm3 = new Vue({
        el: "#radio",
        data: {
            sexData: ""
        }
    });
    var vm4 = new Vue({
        el: "#select",
        data: {
            selectData: ""
        }
    });
</script>
</html>

注意:如果v-model表达式的初始值未能匹配任何选项,select元素将被渲染为 “未选择中” 状态,在IOS中,这会使用户无法选择第一个选项,因为这样的情况下,IOS不会触发change事件,因此,更推荐像上面这样提供一个值为空的禁用选项。

787原则

学习vue我们必须知道它的7个属性,8个 方法,以及7个指令。787原则

Vue的7种属性

  • el
    绑定id,用来指示vue编译器从什么地方开始解析 vue的语法,可以说是一个占位符
  • data
    用来组织从view中抽象出来的属性,可以说将视图的数据抽象出来存放在data中
  • methods
    放置页面中的业务逻辑,js方法一般都放置在methods中
  • computed
    定义计算属性的方法
  • template
    用来设置模板,会替换页面元素,包括占位符
  • watch
    watch:function(new,old){ … }监听data中数据的变化,两个参数,一个返回新值,一个返回旧值
  • render
    创建Virtual Dom

Vue的8种方法

  • 生命周期的八个钩子函数,可以在vue的各个阶段添加代码。
  • beforeCreate() 创建实例
  • created() 创建完成
  • beforeMount() 创建模板
  • mounted() 创建完成
  • beforeUpdate() 更新状态
  • updated() 更新完成
  • beforeDestory() 销毁 vue 实例
  • destoryed() 销毁完成

Vue的7种指令

  • v-text 在元素中插入值
  • v-html 在元素中插入html标签和文本
  • v-if 根据表达式的真假值来动态插入和移除元素
  • v-show 根据表达式的真假值通过控制css的display来隐藏和显示元素
  • v-for 根据变量的值来循环渲染元素
  • v-on 监听元素事件,执行相应操作
  • v-bind 绑定元素属性,执行相应操作
  • v-model 绑定input值和变量,实现数据视图的双向绑定
<div id="app">
    {{msg}}
      <div>这是模板的第一种使用方法1</div>
</div>
<template id="bot">这是模板的第三种使用方法,不常用3</template>
<script>
<div id="bot">模板的第四种创建方法4</div>
</script>
<script>
    var vm1 = new Vue({
        data: {
            msg: "data属性"
        },
        methods: {
            Personal:function () {
                console.log("methods方法存放方法")
            }
        },
        template: '<div>模板的第二种使用方法2</div>',
        //template:"#bot",
        render: (createElement) => {
            return createElement("h1",{"class":"类名""style":"color: red"},"这一个render属性创建的虚拟dom")
        },
    })
</script>

methods和computed其中都可以写算法,有什么区别呢?

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="javascript/vue.min.js"></script>
</head> <body>
<div id="app">
    <p>{{message}}</p> //直接在模板中绑定表达式
    <p>{{message.split('').reverse().join('')}}</p> //运用计算属性
    <p>{{reverseMessage}}</p> //运用methods方式
    <p>{{methodMessage()}}</p>
</div>
<script>
    var vm=new Vue({
         el:"#app",
         data:{
             message:"hello"
         },
        computed:{
            reverseMessage:function(){
                 return this.message.split('').reverse().join('');
            }
        },
         methods:{
             methodMessage:function () {
                 return this.message.split('').reverse().join('');
             }
         }
    })
</script>
</body>
</html>

我在此将三种方式进行了比较。返回的结果是一样的,但在写法上computed计算属性的方式在用属性时不用加(),而methods方式在使用时要像方法一样去用,必须加().

两种方式在缓存上也大有不同,利用computed计算属性是将 reverseMessage与message绑定,只有当message发生变化时才会触发reverseMessage,而methods方式是每次进入页面都要执行该方法,但是在利用实时信息时,比如显示当前进入页面的时间,必须用methods方式

条件与循环

条件:

​ v-if v-else

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p>条件判断的结果是:</p>
    <div id="if_label">
        <span v-if="type===A">文本一</span>
        <span v-else-if="type===B">文本二</span>
        <span v-else="type===C">文本三</span>
    </div>
    <p>循环得到信息:</p>
    <div id="for_message">
        <li v-for="(item,index) in items">
            {{item.message}}-----{{index}}
        </li>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    var vm = new Vue({
        el: "#if_label",
        data: {
            type: "C"
        }
    })
    var vm = new Vue({
        el: "#for_message",
        data: {
            items: [
                { message: "信息一" },
                { message: "信息二" },
                { message: "信息三" },
                { message: "信息四" }
            ]
        }
    })
</script>
</html>

元素绑定

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="el_bing">
        <p>元素绑定</p>
        <span v-bind:title="message">鼠标悬停几秒查看此处动态绑定的提示信息</span>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    var vm = new Vue({
        el: "#el_bing",
        data: { 
            message: "页面加载于" + new Date().toLocaleString()
        }
    })
</script>

</html>

绑定事件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head> 
<body>
    <div id="app1">
        <button v-on:click="click_bing1">请点击按钮</button>
    </div>
    <div id="app2">
        <p>{{message}}</p>
        <button v-on:click="click_bing2">反转消息</button>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    var vm = new Vue({
        el: "#app1",
        data: {
            message: "已点击"
        },
        methods: {
            click_bing1: function () {
                alert(this.message);
            }
        }
    });
    var vm1 = new Vue({
        el: "#app2",
        data: {
            message: "已点击"
        },
        methods: {
            click_bing2: function () {
                this.message = this.message.split('').reverse().join('')
            }
        }
    })
</script>
</html>

Vue组件

Vue组件即为自定义标签,

组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:

Component Tree

在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:

步骤:

  • 定义组件 Vue.component(‘组件名’, { template: ‘模板’ } )

  • 构建模板 <组件名></组件名>

  • 接收参数 props: [‘todo’]

    注:自定义的标签中不能有大写

// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
  template: '<li>这是个待办项</li>'
})

var app = new Vue(...)

现在你可以用它构建另一个组件模板:

<ol>
  <!-- 创建一个 todo-item 组件的实例 -->
  <todo-item></todo-item>
</ol>

但是这样会为每个待办项渲染同样的文本,这看起来并不炫酷。我们应该能从父作用域将数据传到子组件才对。让我们来修改一下组件的定义,使之能够接受一个 prop

Vue.component('todo-item', {
  // todo-item 组件现在接受一个
  // "prop",类似于一个自定义 attribute。
  // 这个 prop 名为 todo。
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})

现在,我们可以使用 v-bind 指令将待办项传到循环输出的每个组件中:

<div id="app-7">
  <ol>
    <!--
      现在我们为每个 todo-item 提供 todo 对象
      todo 对象是变量,即其内容可以是动态的。
      我们也需要为每个组件提供一个“key”,稍后再
      作详细解释。
    -->
    <todo-item
      v-for="item in groceryList"
      v-bind:todo="item"
      v-bind:key="item.id"
    ></todo-item>
  </ol>
</div>
Vue.component('todo-item', {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})

var app7 = new Vue({
  el: '#app-7',
  data: {
    groceryList: [
      { id: 0, text: '蔬菜' },
      { id: 1, text: '奶酪' },
      { id: 2, text: '随便其它什么人吃的东西' }
    ]
  }
})
  1. 蔬菜
  2. 奶酪
  3. 随便其它什么人吃的东西

例2

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <wangping v-for="hobby in hobbies" v-bind:wang="hobby"></wangping>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    //定义组件
    Vue.component("wangping", {
        props: ['wang'],  //props用于接收参数
        template: '<li>{{wang}}</li>'
    })
    var vm = new Vue({
        el: '#app',
        data: {
            hobbies: ["Spring", "SpringMVC", "SpringBoot", "Mybatis", "Maven", "Vue", "Redis", "Mysql", "Oracle"]
        }
    })
</script>

</html>

尽管这只是一个刻意设计的例子,但是我们已经设法将应用分割成了两个更小的单元。子单元通过 prop 接口与父单元进行了良好的解耦。我们现在可以进一步改进 <todo-item> 组件,提供更为复杂的模板和逻辑,而不会影响到父单元。

在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。在后续教程中我们将详述组件,不过这里有一个 (假想的) 例子,以展示使用了组件的应用模板是什么样的:

<div id="app">
  <app-nav></app-nav>
  <app-view>
    <app-sidebar></app-sidebar>
    <app-content></app-content>
  </app-view>
</div>

与自定义元素的关系

你可能已经注意到 Vue 组件非常类似于自定义元素——它是 Web 组件规范的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot APIis attribute。但是,还是有几个关键差别:

  1. Web Components 规范已经完成并通过,但未被所有浏览器原生实现。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下,Vue 组件不需要任何 polyfill,并且在所有支持的浏览器 (IE9 及更高版本) 之下表现一致。必要时,Vue 组件也可以包装于原生自定义元素之内。
  2. Vue 组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流、自定义事件通信以及构建工具集成。

虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。

Vue的生命周期

如果只是单纯地知道vue里面的简单语法,你是可以实现对应的一些项目和对应的想法,但是如果一旦发生问题,我们就需要借助生命周期去寻找问题,甚至说有一些需求的话,你也通过生命周期的情况来定我们该把这个东西写在哪里。所以理解vue的生命周期还是很有必要的。

vue的生命周期就是每个vue实例被创建之前一直到结束经历的一系列的初始化过程。

下面是官网关于vue生命周期的图解,

img

可以看出在vue整个生命周期中会有很多钩子函数(图中标红的部分),虽然我们用到的就那么几个,但我们都来了解一下。首先先明白一个概念:Vue的实例。它是Vue框架的入口,可以理解为前端的ViewModel,它包含它包含了页面中的业务逻辑处理、数据模型等,它也有自己的一系列的生命周期的函数钩子,辅助我们进行对整个Vue实例生成、编译、挂着、销毁等过程进行js控制。

1、beforeCreated()

在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。(这里是在index.html里做的测试)

img

2、created()

实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

img

3、beforeMount()

在挂载开始之前被调用:相关的 render 函数首次被调用。

img

4、mounted()(钩子函数)

el 被新创建的 vm. e l 替 换 , 并 挂 载 到 实 例 上 去 之 后 调 用 该 钩 子 。 如 果 r o o t 实 例 挂 载 了 一 个 文 档 内 元 素 , 当 m o u n t e d 被 调 用 时 v m . el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm. elrootmountedvm.el 也在文档内。

img

5、beforeUpdate()

数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。

img

6、updated()

由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。

该钩子在服务器端渲染期间不被调用。

7、beforeDestroy()

实例销毁之前调用。在这一步,实例仍然完全可用。

8、destroyed()

Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

Axios异步通信

由于Vue.js是一个视图层框架,并且作者严格遵守SoC(关注度分离原则),所以Vue.js并不包括AJAX的通信功能,为了解决通信问题而推出Axios框架(可以去该官网学习),少用jQuery,因为它操作Dome太频繁了

引入JS文件:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="vue">
        <div>{{info.datail.name}}</div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    var vm = new Vue({
        el: '#vue',
        data() {  //区分 data方法和data:属性
            return {
                info: {  //摆格式
                    name: null
                }
            }
        },
        mounted() {  //钩子函数
            axios.get('data.json').then(response => (this.info = response.data))
            //axios.get('data.json').then(response => (console.log(response.data)))  //axios完成通信
        }
    });
</script>
</html>

Vue的计算属性

是一个将计算结果缓存起来的属性(将行为转化成了静态的属性),可以想象成为缓存

计算属性的特点就是为了将不经常计算的结果进行缓存,以节约我们的系统开销

有时候我们可能需要在{{}}里添加一些需要计算再展示出来数据
例如:在页面中展示学生的成绩总分和平均分:

<div id="app">
    <table border="1">
        <thead>
            <th>学科</th>
            <th>分数</th>
        </thead>
        <tbody>
            <tr>
                <td>数学</td>
                <td><input type="text" v-model="Math"></td>
            </tr>
            <tr>
                <td>英语</td>
                <td><input type="text" v-model="English"></td>
            </tr>
            <tr>
                <td>化学</td>
                <td><input type="text" v-model="chemistry"></td>
            </tr>
            <tr>
                <td>总分</td>
                <td>{{Math + English + chemistry}}</td>
            </tr>
            <tr>
                <td>平均分</td>
                <td>{{(Math + English + chemistry)/3}}</td>
            </tr>
        </tbody>
    </table>
</div>
new Vue({
    el:'#app',
    data:{
        Math:88,
        English:77,
        chemistry:99,
    }
})

以上是通过在{{}}里运算,得出总分和平均分
虽然也能解决问题,但是特别不清晰,特别是当运算比较复杂的时候
那怎么办呢?
了解一点的,应该会想到methods
没错,确实methods也可以!但事实上,vue给我们提供了一个更好的解决方案叫计算属性
计算属性是vue实例中的一个配置选项:computed
通常里面都是一个个计算相关的函数,函数里头可以写大量的逻辑,最后返回计算出来的值
即我们可以把这些计算的过程写到一个计算属性中去,然后让它动态的计算。

<div class="app">
    <table border="1">
        <thead>
            <th>学科</th>
            <th>成绩</th>
        </thead>
        <tbody>
            <tr>
                <td>数学</td>
                <td><input type="text" v-model.number="Math"></td>
            </tr>
            <tr>
                <td>英语</td>
                <td><input type="text" v-model.number="English"></td>
            </tr>
            <tr>
                <td>化学</td>
                <td><input type="text" v-model.number="chemistry"></td>
            </tr>
            <tr>
                <td>总分</td>
                <td>{{sum}}</td>
            </tr>
            <tr>
                <td>平均分</td>
                <td>{{average}}</td>
            </tr>

        </tbody>
    </table>
</div>
var vm = new Vue({
    el:'.app',
    data:{
        Math:88,
        English: 77,
        chemistry:99,
    },
    computed:{
        sum:function(){
            return this.Math+ this.English+this.chemistry;
        },
        average:function(){
            return Math.round(this.sum/3);
        }
    }
});

计算属性一般就是用来通过其他的数据算出一个新数据,而且它有一个好处就是,它把新的数据缓存下来了,当其他的依赖数据没有发生改变,它调用的是缓存的数据,这就极大的提高了我们程序的性能。而如果写在methods里,数据根本没有缓存的概念,所以每次都会重新计算。这也是为什么这里我们没用methods的原因
以上就是Vue的计算属性的基本用法!

Vue缩写

v- 前缀作为一种视觉提示,用来识别模板中 Vue 特定的 attribute。当你在使用 Vue.js 为现有标签添加动态行为 (dynamic behavior) 时,v- 前缀很有帮助,然而,对于一些频繁用到的指令来说,就会感到使用繁琐。同时,在构建由 Vue 管理所有模板的单页面应用程序 (SPA - single page application) 时,v- 前缀也变得没那么重要了。因此,Vue 为 v-bindv-on 这两个最常用的指令,提供了特定简写:

v-bind 缩写

<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>

v-on 缩写

<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>

它们看起来可能与普通的 HTML 略有不同,但 :@ 对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。缩写语法是完全可选的,但随着你更深入地了解它们的作用,你会庆幸拥有它们。

插槽slot

背景

​ 在vue中,引入的子组件标签中间是不允许写内容的。为了解决这个问题,官方引入了插槽(slot)的概念。

插槽,其实就相当于占位符。它在组件中给你的HTML模板占了一个位置,让你来传入一些东西。插槽又分为匿名插槽、具名插槽以及作用域插槽。

你可能不太明白,为什么我要给子组件中传入HTML,而不直接写在子组件中呢?答案是这样的。你可以想象一个场景,你有五个页面,这五个页面中只有一个区域的内容不一样,你会怎么去写这五个页面呢?复制粘贴是一种办法,但在vue中,插槽(slot)是更好的做法。

初体验

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <todo>
            <todo-title slot="todo-title" :title="title"></todo-title>
            <todo-items slot="todo-items" v-for="item in todoItems" :item="item"></todo-items>
        </todo>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<script>
    //定义组件在前,new Vue在后,否则报错
    //插槽
    Vue.component("todo", {
        template: '<div>\
                        <slot name="todo-title"></slot>\
                        <ul>\
                            <slot name="todo-items"></slot>\
                        </ul>\
                    </div>'
    });
    //填插槽
    Vue.component("todo-title", {
        props: ['title'],
        template: '<div>{{title}}</div>'
    });
    Vue.component("todo-items", {
        props: ['item'],
        template: '<li>{{item}}</li>'
    });
    var vm = new Vue({
        el: "#app",
        data: {
            title: '书籍列表',
            todoItems: ['java', 'javascript', 'vue', 'Spring', 'SpringMVC', 'SpringBoot']
        }
    });

</script>

</html>

匿名插槽

匿名插槽,我们又可以叫它单个插槽或者默认插槽。与具名插槽相对,它不需要设置name属性。(它隐藏的name属性为default。)

具名插槽

上面已经说过,插槽有一个name属性。与匿名插槽相对,加了name属性的匿名插槽就是具名插槽。

注意 v-slot 只能添加在template标签上 (只有一种例外情况)。

例外情况(被废弃的slot=‘name’)
带slot属性的具名插槽自 2.6.0 起被废弃,vue3.x被完全废弃。只有vue3之前的cli可以使用

具名插槽的小知识点
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header。

作用域插槽
作用域插槽其实就是可以传递数据的插槽。子组件中的一些数据想在父组件中使用,必须通过规定的方法来传递。在官方文档中提出了一条规则,**父级模板里的所有内容都是在父级作用域中编译的。子模板里的所有内容都是在子作用域中编译的。**如果你在父组件直接使用子组件中的值,是会报错的。

匿名插槽的作用域插槽
为了让 子组件中的数据 在父级的插槽内容中可用,我们可以将 数据 作为 元素的一个特性绑定上去:

自定义事件

事件名

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。举个例子,如果触发一个 camelCase 名字的事件:

this.$emit('myEvent')

则监听这个名字的 kebab-case 版本是不会有任何效果的:

<!-- 没有效果 -->
<my-component v-on:my-event="doSomething"></my-component>

不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent将会变成 v-on:myevent——导致 myEvent 不可能被监听到。

因此,推荐你始终使用 kebab-case 的事件名

自定义组件的 v-model

2.2.0+ 新增

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

现在在这个组件上使用 v-model 的时候:

<base-checkbox v-model="lovingVue"></base-checkbox>

这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。

注意你仍然需要在组件的 props 选项里声明 checked 这个 prop。

将原生事件绑定到组件

你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on.native 修饰符:

<base-input v-on:focus.native="onFocus"></base-input>

在有的时候这是很有用的,不过在你尝试监听一个类似 <input> 的非常特定的元素时,这并不是个好主意。比如上述 <base-input> 组件可能做了如下重构,所以根元素实际上是一个 <label> 元素:

<label>
  {{ label }}
  <input
    v-bind="$attrs"
    v-bind:value="value"
    v-on:input="$emit('input', $event.target.value)"
  >
</label>

这时,父级的 .native 监听器将静默失败。它不会产生任何报错,但是 onFocus 处理函数不会如你预期地被调用。

为了解决这个问题,Vue 提供了一个 $listeners property,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:

{
  focus: function (event) { /* ... */ }
  input: function (value) { /* ... */ },
}

有了这个 $listeners property,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input> 的你希望它也可以配合 v-model 工作的组件来说,为这些监听器创建一个类似下述 inputListeners 的计算属性通常是非常有用的:

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` 将所有的对象合并为一个新对象
      return Object.assign({},
        // 我们从父级添加所有的监听器
        this.$listeners,
        // 然后我们添加自定义监听器,
        // 或覆写一些监听器的行为
        {
          // 这里确保组件配合 `v-model` 的工作
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})

现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native 监听器。

.sync 修饰符

2.3.0+ 新增

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。

这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之。举个例子,在一个包含 title prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:

this.$emit('update:title', newTitle)

然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

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

<text-document v-bind:title.sync="doc.title"></text-document>

注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的 property 名,类似 v-model

当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用:

<text-document v-bind.sync="doc"></text-document>

这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。

v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。

splice()方法

splice()方法是修改Array的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:

var arr = ['Nicrosoft " , ‘Apple ''vahoo ' , 'AoL ', 'Excite ' , 'oracle' ];
//[从索引2开始翻除3个元素,然后再添加两个元素:
arr.splice(23'6oogle', 'Facebook');//返回剧除的元素[ 'vahoo', 'AOL ', 'Excite']
arr; // [ "Nicrosoft ', "Apple " , 'Google ',, "Facebook ' , "oracle']
//只删除,不添加:
arr.splice(2,2); //[ "Google ' , 'Facebook " ]
arr; //[" wicrosoft ' ," apple " , 'oracle']
//只添加i,不删除:
arr.splice(20'6oogle ','Facebook ' );//返回[],因为没有删除任何元素
arr; // [ 'wicrosoft ', "Apple' , 'Google', 'Facebook ', 'oracle']
  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值