Vue初识

135 阅读27分钟

你在浏览各大网站、论坛、编程社区的时候是否发现vue.js异常的火爆。 那么vue是什么呢?在前端中又主要负责什么呢? 那么我们需要一步步进行了解

前端的主要工作是什么?

  • 主要负责MVC中的 v 这一层,主要工作就是和界面打交道,制作前端页面效果

什么是vue?

vue.js是目前最火的前端框架。这应该是你听过最多的答案。真正的答案是什么?

vue.js(读音 /vjuː/, 类似于 view)是一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue采用自底向上增量开发的设计。Vue 的核心库只关注视图层,并且非常容易学习,非常容易与其它库或已有项目整合。另一方面,Vue完全有能力驱动采用单文件组件Vue 生态系统支持的库开发的复杂单页应用

Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

如果你是有经验的前端开发者,想知道 Vue.js 与其它库/框架的区别,查看对比其它框架

这是官方的介绍,是不是觉得很抽象。看完之后是不是依旧不懂,什么是"渐进式框架"? 什么是"自底向上增量开发"? 什么是"视图层"? 什么是"单文件组件"? 什么是"复杂单页应用"? "通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件"这句是不是更懵? "对比其他框架"又是什么? 所以vue.js到底是什么?

  • 渐进式框架

    什么是渐进式框架: 主张最少

    主张:每个框架都有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式

    比如说,Angular,它两个版本都是强主张的,如果你用它,必须接受以下东西: 必须使用它的模块机制- 必须使用它的依赖注入- 必须使用它的特殊形式定义组件(这一点每个视图框架都有,难以避免) 所以Angular是带有比较强的排它性的,如果你的应用不是从头开始而是要不断考虑是否跟其他东西集成,这些主张会带来一些困扰

    Vue可能有些方面是不如React,不如Angular,但它是渐进的,没有强主张,你可以在原有大系统的上面,把一两个组件改用它实现,也可以整个用它全家桶开发.还可以用它的视图,搭配你自己设计的整个下层用。你可以在底层数据逻辑的地方用OO和设计模式的那套理念,也可以函数式,都可以,它只是个轻量视图而已,只做了自己该做的事,没有做不该做的事,仅此而已。

    个人理解:渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,使用时没有必要把全部组件都拿出来,需要什么用什么就是了,而且Vue也可以与其它已有项目或框架相结合

    关于更详细的"渐进式概念"和"自底向上增量开发的设计"参考链接

  • 视图层

    HTML中的DOM元素其实就是视图层。

    一个网页就是通过DON元素的组合和嵌套,形成了基本的视图结构,在通过CSS的修饰。让基本视图结构看起来更加美观。使用JavaScript来接收用户的交互请求,再通过时事件机制响应用户的交互操作,并且在事件处理函数中进行数据的修改,最终实现页面的刷新或加载。

    我们通常会把HTML中的DON与其他部分独立开,来划分出一个层次,这个层次就叫做视图层

  • 单文件组件

    单文件组件:是指组件化开发,把一个单页面应用中的各种模块拆分成一个一个的单独的组件来开发

    在传统的前端开发,我们是每人做一页面,最后套入各种后端模板引擎,这种开发模式不利于后期的维护的需求变更,修改起来非常麻烦,生怕改动其中一个div,其他div跟着雪崩,整个页面就全乱套了。或者由于JavaScript的事件冒泡机制,导致修改一些内层的DOM事件处理函数之后,出现各种莫名其妙的诡异BUG。

    而现在我们做单页面应用,我们把单个页面应用中的模块拆分成单个的组件进行开发。对于相同功能的组件。我们只需要写一次就可以了,哪里需要哪里引入。这样利于后期的维护。需求的变更

  • 复杂单页应用

    单页面应用:顾名思义一个页面就是一个应用。

    单页应用程序中一般交互处理非常多,而且页面中的内容需要根据用户的操作动态变化。

    所以它虽然叫做单页面应用,其实是十分复杂

  • 响应的数据绑定

    这里的响应式不是@media 媒体查询中的响应式布局。而是指vue.js会自动对页面中某些数据的变化做出响应。 把下面这段代码复制粘贴带扩展名为html的文件中。用浏览器打开,在输入框中输入内容,观察页面变化。

        <!DOCTYPE html>
        <html>
        <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title></title>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        </head>
        <body>
            <div id="app">
            <!-- v-model表示了用户输入的这个数据-->
            <!-- <input type="text" v-bind:value="txtVal" v-on:input="handleInput"> -->
            <input type="text" :value="txtVal" @input="handleInput">
            <p>{{ txtVal }}</p>
            </div>
            <script>
                let vm = new Vue({
                    el: "#app",
                        data: {
                        txtVal: ''
                    },
                    methods: {
                        handleInput(e){//方法都有一个事件对象 e
                        console.log(e.target.value);
                        this.txtVal = e.target.value;
                        }
                    },
                })
            </script>
        </body>
        </html>
    

    是不是很神奇的现象,输入框输入的内容,页面上会显示一模一样的。

    vue.js会自动响应数据的变化情况,并且根据用户在代码中预先写好的绑定关系,对所有绑定在一起的数据和视图内容都进行修改。因此你在别的地方可能也会看到有人粗略的称vue.js为声明式渲染的模版引擎。

以上是对vue官方介绍中一些关键词的解答。下面介绍下vue的特性

vue的特点和web开发中常见的高级功能

  • 解耦视图和数据
  • 双向数据绑定
  • 可复用的组件
  • 前端路由技术
  • 状态管理
  • 虚拟DOM

注意:学习Vue框架,必须严格遵循他的语法规则,Vue代码的书写思路和以前的JQuery完全不一样。所以,在项目中使用Vue之前,必须先学习Vue的基本语法规则

Vue的基础语法

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
    <div id="app">
        <p>{{str1}}</p>
        <p>{{ num1 > num2 ? num1 : num2}}</p>
        <p>{{num1+num2}}</p>
    </div>
</body>
<script>
    let vm = new Vue({
        el:"#app",
        data:{
            str1: "hello World",
            num1: 30,
            num2: 50,
        }
    })
</script>
</html>

看看上图代码中,js、html代码分别做了什么事情? 复制上面的代码放到HTML中观查

    1. 先看js代码,先是创建一个vue对象,vue对象中传入了一个对象,这个对象中存在两个属性:el、data
    1. el:该属性是决定这个vue对象挂载到哪一个元素上,上面代码挂载到了ID为app的div元素上
    1. data:该属性用来存储数据,现在里面定义了三个数据:str1、num1、num2

下面在看看HTML代码

首先body中div元素的中、所有的p标签中都是使用双花括号"{{变量}}"来接收变量。

在双花括号内使用一些加减运算,三目运算。

vue对象中的属性和方法

除了上面的两个属性,vue中还有哪些方法和属性呢?一起来看下

 let vm = new Vue({
        el: "#app",
        data: {
            num: 1
        },
        watch: {
           
        },
        computed: {
        
        },
        methods: {
            
        },
        components:{

        },
        filters: {

        },
        directives:{

        }
    })

computed:计算属性,该属性一定要有返回值

使用条件:1、有逻辑要处理

可能有人会有疑问,定义方法不是一样可以处理逻辑吗? 让我们来看看他们的区别

复制下面代码查看:控制台分别打印了多少次的 getTotaltotal

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<div id="ads">

    <p>总价为:{{total}}</p>
    <p>总价为:{{total}}</p>
    <p>总价为:{{total}}</p>

    <p>总价为:{{ getTotal() }}</p>
    <p>总价为:{{ getTotal() }}</p>
    <p>总价为:{{ getTotal() }}</p>

</div>
<body>
</body>
<script>
    let vm = new Vue({
        el: "#ads",
        data: {
            arr: [
                { name: 'Vuejs入门', price: 99, count: 3 },
                { name: 'Vuejs底层', price: 89, count: 1 },
                { name: 'Vuejs从入门到放弃', price: 19, count: 5 },
            ]
        },
        methods: {
            // 使用一次、方法就运行一次
            getTotal(){
                 // 此处arr前面要使用 this.
                console.log("getTotal")
                var a = this.arr.reduce(function (pre, current) {
                    // var total = 当前这次的 price*count + 上一次的total
                    var total = current.price * current.count + pre
                    return total
                }, 0)
                return a
            }
        },
        computed: {
            // 直接当做普通属性调用不加括号
            // 任何data中数据变化立即重新计算
            // 计算属性会缓存
            total() {
                console.log('total')
                let ret = this.arr.reduce(function (pre, current) {

                    let ret = pre + current.price * current.count;
                    return ret;
                }, 0);
                return ret;
            }
        },
    })
</script>

</html>

methods:方法,vue中的事件处理程序都定义在这里面

在vue中定义的方法,绝大部分都书写在此属性里面,语法与普通函数一样。两个方法之间使用 逗号 隔开这里不再举例说明

methods:{
    handleClick(){
        ...
    },
    add(){
        ...
    },
}

watch:侦听/监听属性。用来监听data中的数据或当前活跃组件中的数据。进行数据更新赋值操作

语法:

watch: {
    要观察的对象(newVal, oldVal){
        函数处理程序
    }
  },

复制下面代码查看:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>

<body>
    <div id="app" v-cloak>
        <p>{{num}}</p>
        <hr>
        <button @click="num++">按钮</button>
    </div>
</body>

</html>
<script src="https://unpkg.com/vue"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            num: 1
        },
        watch: {
            num(newVal, oldVal) {
                if(newVal){
                    console.log(newVal, oldVal)
                }
            }
        },
    })
</script>
  • watch在第一次绑定值时、不会执行监听函数,只有值发生改变是才会执行
  • 要观察的对象:就是你要监控的那个(可以是变量、路由、url传入的参数、对象)
  • newVal:观查对象的数据发生改变时的值
  • oldVal:watch第一次绑定的值

watch更多属性详细链接地址

filter/filters:全局/局部过滤器,,用来定义过滤器

首先看看局部过滤器直接上代码、复制查看

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
    <div id="app">
        <!-- 由“管道”符指示:| -->
        <!-- {{ 变量名 | 过滤器名 }} -->
        <p>{{ num | formatNum }}</p>
        <p>{{ num }}</p>
    </div>
</body>
<script>
        let vm = new Vue({
            el:"#app",
            data:{
                num: 10,
            },
            filters: {
                filters: {
                formatNum(val){ // 此形参接收 管道符 前面的变量数据->此处是:num
                    return val + 10;  //return后面的值就是将来展示在页面上的值(即过滤之后的值)
                },
                formatSum(val){ // 此形参接收 管道符 前面的变量数据->此处是:formatNum的返回值
                    return val+10
                }
            }
            }
        })
</script>
</html>

有发现什么没?在 filters 过滤器中,我们 return 出去的值是:20、30,但是 num 真正的值依旧是:10。

现在我们来看看全局的过滤器:全局过滤 filter 没有 s,语法:Vue.filter("过滤器名字", (val)=>{})

注意:当有局部和全局两个名称相同的过滤器时候,会以就近原则进行调用,即:局部过滤器优先于全局过滤器被调用!
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
    <div id="app">
        <p>时间一:{{ timestamp | formaDate }}</p>
    </div>

</body>
<script>
    // 全局过滤器的定义和使用
    // Vue.filter("过滤器名字",(val)=>{})
    
    Vue.filter('formaDate', (val) => {
        let now = new Date(val)
        var year = now.getFullYear();
        var month = now.getMonth() + 1;
        var date = now.getDate();
        var hour = now.getHours();
        var minute = now.getMinutes();
        var second = now.getSeconds();
        return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
    })

    let vm = new Vue({
        el: "#app",
        data: {
            timestamp: new Date().getTime()
        },
    })
    
</script>
</html>

过滤器同样可以用于过滤数组。

   let arr = [1,2,3,4];

    let ret1 = arr.filter(item =>{
        return item > 2
    })
    console.log(ret1) //[3,4]

注意:

  • 1.Vue中的过滤器不能替代Vue中的methods、computed或者watch。
  • 2.过滤器不改变真正的data数据,而只是改变渲染的结果,并返回过滤后的版本

directive/directives:全局/局部指令,用来自定义指令

vue中除了内置的指令外,也允许用户注册自定义指令: v-xxx 此处的xxx为自己定义

  • 全局指令:Vue.directive( '指令名称',{钩子函数}) 下面我们定义一个类似 v-show的指令,把这段代码复制到HTML中打开
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
    <div id="app">
        <h1 v-demo="flag">你好!</h1>
        <button @click='flag = !flag'>按钮</button>
    </div>
</body>
</html>
    <script>
        Vue.directive( 'demo', {
            // 此处的 'demo' 与 h1 标签中 v-demo 对应
            inserted(el, binding, vnode){
                console.log(el)
                console.log(binding)
                console.log(vnode)
                // binding.value 指令绑定的值
                if (binding.value) {
                    el.style.display = 'block'
                    console.log(binding.value)
                } else {
                    el.style.display = 'none'
                }
            },
            // 更新操作。根据获得的值,执行相应的更新操作
            update (el, binding, vnode) {
                if (binding.value) {
                    el.style.display = 'block'
                } else {
                    el.style.display = 'none'
                }
            }
        })

        let vm = new Vue({
            el:"#app",
            data:{
                flag:true
            }
        })
    </script>

发现了什么?点击按钮h1标签里面的文字会在隐藏/显示之间切换。我们显示生成一个Vue实例对象。并在data里面定义: flag:true。 然后把这个值绑定给了 v-demo 指令,在directive的钩子函数中,我们判断通过判断v-deom的值,给h1标签设置style.display样式。当我们点击button按钮时设置:flag的值取反,flag值更新了。钩子函数中的update方法开始执行,从而控制好h1标签显示隐藏。

  • 局部指令:定义在vue的实例对象里面:directives:{'指令名称', {钩子函数} }。除了名字多了个S和书写位置和不同,使用方法与全局指令一样
// 写在vue的实例对象中去
let vm = new Vue({
            el:"#app",
            data:{
                flag:true
            },
            directives:{
                // mydemo 指令名  不用加引号
                mydemo: {
                    inserted(el, binding, vnode) {
                        if (binding.value) {
                            el.style.display = 'block'
                        } else {
                            el.style.display = 'none'
                        }
                    },
                    update(el, binding, vnode) {
                        if (binding.value) {
                            el.style.display = 'block'
                        } else {
                            el.style.display = 'none'
                        }
                    }
                }
            }
        })

下面是钩子函数中的属性

1、bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个绑定时执行一次的初始化动作。

2、inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。

3、update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。

4、componentUpdated:被绑定元素所在模板完成一次更新周期时调用。

5、unbind:只调用一次,指令与元素解绑时调用。

钩子函数的参数:(el, binding, vnode, oldVnode)

  • el:指令所绑定的元素,可以用来直接操作 DOM 。
  • binding:一个对象,包含以下属性

    • name:指令名,不包含v-的前缀;

    • value:指令的绑定值;例如:v-my-directive="1+1",value的值是2;

    • oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子函数中可用,无论值是否改变都可用;

    • expression:绑定值的字符串形式;例如:v-my-directive="1+1",expression的值是'1+1';

    • arg:传给指令的参数;例如:v-my-directive:foo,arg的值为 'foo';

    • modifiers:一个包含修饰符的对象;例如:v-my-directive.a.b,modifiers的值为{'a':true,'b':true}

  • vnode:Vue编译的生成虚拟节点;

  • oldVnode:上一次的虚拟节点,仅在update和componentUpdated钩子函数中可用。

component/components:全局/局部组件,用来自定义组件

全局组件:通过使用:Vue component('组件名称', {}) 定义的组件,就是全局组件。

<body>
    <div id="app">
        <my-comp></my-comp> // html中只需写组件的名称
    </div>
</body>

<template id="ads"> // 组件的ID
    <div>
        <h2>Hello 组件 !</h2>
        <button>按钮</button>
    </div>
</template>

<script>
    /* 通过 Vue.component 方式注册的组件,称之为全局组件。任何地方都可以使用
    Vue.component( '组件名称', {
        template: `html`
    })
     */

    /* 简化写法 */
    Vue.component('my-comp', {
        template: '#ads' // 自定义组件的ID
    })
    let vm = new Vue({
        el: "#app",
        data: {

        }
    })
</script>

局部组件:定义在在vue实例对象中的组件。

<template id="ads">
    <div>
        <h2>Hello 组件 !</h2>
        <h3 >很明显了东风</h3>
        <button>按钮</button>
    </div>
</template>
<script>
    // 局部组件使用--组件需要传参数推荐使用这种写法
    let MyComp = {
        template: '#ads',
    }

    let vm = new Vue({

        el: "#app",
        data: {

        },

        components: {
            MyComp: MyComp,
           // 'my-comp':{ //如果组件不需要传参数。推荐这种写法
           //     template: '#ads'
            //}
        }

    })

</script>

以上是vue对象中的属性方法。


模板语法差值操作

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
    <div id="asd">
        <!-- v-html 解析html、进行渲染 -->
        <div v-html="txtHtml"></div>

        <!-- v-text 解析text文本、进行渲染 -->
        <div v-text="txt"></div>

        <!-- v-pre 阻止解析插值语法 -->
        <p v-pre>{{txt}}</p>
        
        <!-- v-cloak 在vue对象加载之前。隐藏插值对象 -->
        <p v-cloak>{{txt}}</p>
    </div>
    <script>
        setInterval( ()=>{
            let vm = new Vue({
                el: "#asd",
                data: {
                    txtHtml: '<p><strong>我是HTML</strong></p>',
                    txt: 'hello world!'
                }

            })
        },3000)
    </script>
</body>

我们继续介绍基础语法

1、v-bind:主要用于属性绑定,简写方式--> : (冒号)+属性名字

复制下面代码查看

    <!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
    <style>
        [v-cloak] {
            display: none;
        }
        .sty{
            color: aqua;
        }
    </style>
</head>

<body>
    <div id="app" v-cloak>
    
        <a v-bind:href="tb">淘宝</a>
        <a :href="jd">淘宝</a> //简写方式
        
        // 可以做三目运算
        <h1 :class="ifshow === true ? 'sty' : ''">Hello World!</h1>
    </div>
</body>

</html>
<script src="https://unpkg.com/vue"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            tb: "https://www.taobao.com",
            jd:"https://www.jd.com",
            
            ifshow: false
        },
    })
</script>

2、v-on:主要用来绑定事件方法。简写方式---> @

 <!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title></title>
 <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
 <div id="app">
     <p>{{num1}}</p>
     <p>最大的数:{{num1>num2 ? num1 : num2}}</p>
     <button v-on:click="num1+=2">按钮</button>
     <button v-on:click="add(50)">按钮2</button>
     <button @click="add1()">按钮3</button>
 </div>
 <script>
     let vm = new Vue({
         el: "#app",
         data:{
             num1: 20, // 变量名1 : 值1
             num2: 30, // 变量名2 : 值2
         },
         methods:{
             add(num){
                 this.num1 += num
             },
             add1(){
                 this.num1 += 5
             }
         }
     })
 </script>
</body>

</html>

复制上面代码查看。v-on绑定的事件。第一个button:可以把事件程序写在标签上,这种写法叫做内联处理器。第二。第三个button我们分别定义了方法 addadd1,这种写在methods的方式叫做:方法处理器。根据第二个button定义的方法,我们可以得知,v-on定义的事件是可以传递参数的。而且v-on还有下面一些修饰符,上面的第三个button就使用了**.once**。

使用v-on的时候后面可以跟事件修饰符,可以解决点击事件自身带的一些事件效果
    .stop   -- 阻止事件冒泡
    .prevent   -- 阻止默认事件
    .captrue   -- 添加事件侦听器使用事件捕获模式
    .self   -- 只有当事件在该元素本身(比如不是子元素)触发时触发回调函数
    .once   -- 事件只触发一次

注意:v-on同样可用于class类的判断

3、v-for和key的使用

在vue中想要进项数组的循环必须使用v-for来进行 语法:

<li v-for="(i, k) in list" :key="i">
    子节点
</li>
// i 代表数组中的每一项
// k 代表数组中的每一项的下标索
// key 节点的唯一标识,为了高效的更新虚拟DOM

例子:

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
    <div id="app">
        <ul>
            <!-- i是列表中的每一个数据 -->
            <li v-for=" i in lists">{{ i }}</li>
        </ul>
        <ul>
            <!-- k 是下标索引 -->
            <li v-for="i,k in lists">{{ k }} : {{ i }}</li>
        </ul>
        <ul>
            <li v-for="i in myObj">{{ i }}</li>
        </ul>
        <ul>
            <!-- k 是键  i 是值  -->
            <li v-for="i,k in myObj">{{ k }} : {{ i }}</li>
        </ul>
    </div>
    <script>
        let vm = new Vue({
            el:"#app",
            data:{
                lists:["123","789"],
                myObj:{
                    name: 'andy',
                    age: 24
                }    
            }
        })
    </script>
</body>

key的例子:

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app">
    <input type="text" v-model="txtVal"> <button @click="handleClick">增加</button>
    <ul>
        <!-- key用在 v-for 后面 只能传值 i  -->
        <li v-for="(i, k) in list" :key="i">
            <input type="checkbox"> {{ i }}
        </li>
    </ul>
</div>
<script>
    let vm = new Vue({
        el: '#app',
        data:{
            txtVal: '',
            list:['html', 'css', 'js', 'jq']
        },
        methods: {
            handleClick(){
                this.list.unshift(this.txtVal)
                // this.list.push(this.txtVal)
            }
        },
    })
</script>
</body>

在浏览器中运行,F12。在Elements中点开body中的ul,然后在input框内输入内容,点击增加按钮,注意观察ul中li的变化。再把:key="i" 删除掉,在进行重复操作。不带有key属性的v-for,在添加子节点时,会全部更新一遍。而带有key属性的v-for,在添加节点时,只会新增一个节点到指定的位置,而不会进行全部的更新。

总结:key的作用和Vue中虚拟DOM的Diff算法有关。Diff算法会以key作为节点唯一标识来识别节点,从而进行高效的虚拟DOM更新。

reduce()方法

语法:

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
total 初始值、或者计算后的返回值。必须
currentValue 当前的元素,必须
currentIndex 当前元素的索引值。 非必须
arr 当前元素所属的数组对象。 非必须

在上面介绍computed属性中已经出现过。

let vm = new Vue({
        el: "#ads",
        data:{
            arr: [
                { name: '入门', price: 99, count: 3 },
                { name: '底层', price: 89, count: 1 },
                { name: '从入门到放弃', price: 19, count: 5 },
            ]
        },
        computed: {
            // 直接当做普通属性调用不加括号
            // 任何data中数据变化立即重新计算
            // 计算属性会缓存
            total(){
                // 此处arr前面要使用 this.
                // pre 在这里代表初始值或者上一次的返回值
                let ret = this.arr.reduce(function (pre, current) {

                    let ret = pre + current.price * current.count;
                    return ret; 
                }, 0);
                
                return ret;
            }
            
        },
    })

v-model数据的双向绑定及其原理

v-model本质是包含了两个操作:

v-bind 绑定input元素的value属性
v-on 绑定input元素的input事件

例子:

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
    <div id="app">
        <!-- v-model表示了用户输入的这个数据-->
        <!-- <input type="text" v-bind:value="txtVal" v-on:input="handleInput"> -->
        <input type="text" :value="txtVal" @input="handleInput">
        <p>{{ txtVal }}</p>
    </div>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                txtVal: ''
            },
            methods: {
                handleInput(e){//方法都有一个事件对象 e
                    console.log(e.target.value);
                    this.txtVal = e.target.value;
                }
            },
        })
    </script>
</body>

组件间的传值

父组件向子组件传值

子组件无法访问父组件中data的数据,子组件如果想要使用父组件中的数据,需要借助props

<!DOCTYPE html>
<html>

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

<body>
    <div id="app">
        <h3>父组件</h3>
        <hr>
        <h3>子组件</h3>
        // 子组件定义属性 myname 接收data中的 name属性
        <my-cop :myname="name"></my-cop>
    </div id="app">
</body>

</html>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!-- 子组件 -->
<template id="tep">
    <div>
        // myname 属性使用在组件内h2标签处。
        <h2>{{ myname }}</h2>
        <h3>{{ myage }}</h3>
    </div>
</template>
<script>
    let vm = new Vue({
        el: "#app",
        data: { name: '张三', age: 25, sex: '男' },
        // 父组件的data 子组件无法使用
        // 要是用时、可以借助props
        components: {
            'my-cop': {
                template: '#tep',
                // props:{}
                props:{
                    // myname 与子组件中的myname对应
                    myname:{ type: String, default: '李四' },
                    myage: { type: Number, default: 18 },
                    mysex: { type: String, default: '男' },
                }
            }
        }
    })
</script>

子组件向父组件传值

子组件无法自己修改父组件的参数,触发父组件的一个行为,让父组件自己去修改。

目的:告诉父组件要去做某件事情。

语法:

this.$emit( '函数名', '需要处理的值')
参数一:代表父组件要做的事情,参数二:值
<!DOCTYPE html>
<html>

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

<body>
    <div id="app">
        <h2>{{ title }}</h2>
        <my-con :num="num" @changefn="changefn" @add="add"></my-con>
    </div>
</body>
</html>

<template id="tep">
    <div>
        <h3> {{ num }} </h3>
        <button @click="changeTitle">按钮</button>
        <button @click="handleAdd">累加</button>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    new Vue({
        el: '#app',
        data: {
            title: '这是一个标题',
            num: 1
        },
        methods: {
            // 这里才是真正修改title的函数
            changefn(e){
                this.title = e
            },
            add(e){
                this.num += e[2]
            }
        },
        components: {
            'my-con': {
                props: ['num'],
                template: "#tep",
                methods: {
                    // 触发父组件的一个行为、让父组件自己去修改
                    // 目的:要告诉父组件去做某件事

                    /* this.$emit( '函数名', {}) 
                        参数一:代表父组件要做的事情,参数二:值
                    */
                    changeTitle(){
                        this.$emit( 'changefn', '被修改了、我还是标题')
                    },
                    handleAdd(){
                        this.$emit( 'add', [1,5,9])
                    }
                },
            }
        }
    })
</script>

非父子组件的传值

如果两个组件不是父子关系,又需要传值该怎么传呢?

可以使用中央事件总线,也就是一个中介完成。

语法:组件B向组件A传值

 const bus = new Vue();
 //组件A
  mounted(){
            bus.$on('brnfn', (val)=>{
                this.word = val
            })
        }
        
 //组件B
methods: {
            brnFn(){
                bus.$emit('brnfn', '这个组件很nice')
            }
        },
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
</head>
<body>
<div id="app">
    
    <my-temp-a></my-temp-a>
    <hr>
    <my-temp-b></my-temp-b>

</div>
</body>

<template id="tempA">
    <h2>{{ word }}</h2>
</template>

<template id="tempB">
    <div>
        <h2>这是组件B</h2>
        <button @click="brnFn">B按钮</button>
    </div>
</template>

</html>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    const bus = new Vue(); // 事件巴士
    let componentA = {
        template: '#tempA',
        data() {
            return {
                word: '这是组件AA'
            }
        },
        mounted(){
            bus.$on('brnfn', (val)=>{
                this.word = val
            })
        }
    }
    let componentB = {
        template: '#tempB',
        methods: {
            brnFn(){
                bus.$emit('brnfn', '这个组件很nice')
            }
        },
    }
    new Vue({
        el:"#app",
        data:{
    
        },
        components:{
            'my-temp-a': componentA,
            'my-temp-b': componentB
        }
    })
</script>

slot 插槽的使用

插槽就是让自定义组件中的所有内容选择一个地方展示

插槽使我们封装的组件更具扩展性。

官方话语:内容发布

匿名插槽

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
</head>
<body>
<div id="app">
    <my-con>
        <h2>我是标题</h2>
        <p>文字段落</p>
    </my-con>
</div>
</body>
<template id="tmp">
    <div>
        <slot> </slot>
        <h3 style="color: red;">我是一个标题</h3>  
    </div>
</template>

</html>
<script src="https://unpkg.com/vue"></script>
<script>
    let  temp = {
        template: "#tmp",
    }
    new Vue({
        el:"#app",
        data:{
    
        },
        components:{
            "my-con": temp
        }
    })
</script>

具名插槽

当需要使用的插槽多了。我们就需要给插槽起名字了,以便于区分。

语法:

<slot name='名字'></slot>

例子:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
</head>
<body>
<div id="app">
    <my-con>
        <!-- 另外一种写法 -->
        <template slot='btn1'>
            <my-con1></my-con1>
        </template>

        <template slot='btn2'>
            <my-con2></my-con2>
        </template>
        
        <template slot='title'>
            <h4>森岛帆高</h4>
        </template>

    </my-con>
</div>
</body>

<template id="tmp">
    <div>
        <slot name='title'></slot>
        <h3>我是一个标题</h3>
        <slot :name="pageNum == 1 ? 'btn1' : 'btn2'"></slot>
    </div>
</template>
<template id="tmp1">
    <div>
       <button>按钮一</button>
    </div>
</template>

<template id="tmp2">
    <div>
        <button>按钮二</button>
    </div>
</template>

</html>
<script src="https://unpkg.com/vue"></script>
<script>
    let temp = {
        template: "#tmp",
        data(){
            return {
                pageNum: 1
            }
        }
    }
    
    let temp1 = {
        template: "#tmp1",
    }
    
    let temp2 = {
        template: "#tmp2",
    }
    new Vue({
        el:"#app",
        data:{
    
        },
        components:{
            "my-con": temp,
            "my-con1": temp1,
            "my-con2": temp2,

        }
    })
</script>

作用域插槽

默认情况下,父组件使用子组件时,插槽的数据默认是拿父组件的数据。

作用域插槽:父组件使用子组件时,插槽数据从子组件中拿到数据,而不再是父组件

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
</head>
<body>
<div id="app">
    // 组件
    <my-comp>
         // 子组件
         // slot-scope 使用了就代表是作用域插槽,继承了lists属性,可以自己自定名字,不一定要和自定义属性名字一样
        <mylist-comp slot="ls" slot-scope="lists">
            <ul>
                <li v-for="i in lists.lists">{{ i }}</li>
            </ul>
        </mylist-comp>
    </my-comp>
</div>
</body>
// 定义组件
<template id="temp">
    <div>
        <h3>我是一个标题</h3>
         // 插槽   自定义了lists属性
        <slot :lists="lists" name="ls"></slot>
    </div>
</template>
<template id="temp3">
    <div>
        <slot></slot>
    </div>
</template>

</html>
<script src="https://unpkg.com/vue"></script>
<script>

    let temp = {
        template: "#temp",
        data(){
            return {
                obj:{
                    name: '赵云',
                    age:60
                },
                lists:[1,3,5,7,9]
            }
        }
    }
    let temp3 = {
            template: "#temp3",
    }
    
    new Vue({
        el:"#app",
        data:{
            
        },
        components:{
            "my-comp": temp,
            "mylist-comp": temp3
        }
    })
</script>

Vue的初步认识到这里了。更多请参考API文档。