Vue.js#

Vue.js 是一个用于创建用户界面的开源 JavaScript 框架,也是一个创建单页应用的 Web 应用框架,能够简化 Web 开发。 Vue 所关注的核心是 MVC 模式中的视图层,同时,它也能方便地获取数据更新,并通过组件内部特定的方法实现视图与模型的交互。

Vue 主要特性包括:

  • 组件:组件是基础 HTML 元素的扩展,可以方便地自定义其数据行为

  • 模板:使用基于 HTML 模板语法,可以将 DOM 元素与底层 Vue 实例中的数据绑定。满足响应式设计。

  • 响应式设计:在视图与对应的模型绑定后,Vue 自动观测模型的变动,并重新绘图。

  • 过渡效果:Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。

  • 单文件组件:为了更好地适应复杂的项目,Vue 支持以 .vue 为扩展名的文件来定义一个完整组件,用以替代使用 Vue.component 注册组件的方式。

安装与部署#

如果只是在一个页面中使用 Vue.js,那么普通的 JS 导入就可以直接使用了。 在项目中安装 Vue.js 只需要下载 vue.js 然后,用代码引入就可以了。

<script src="vue.js" type="text/javascript" charset="utf-8"></script>

当然,如果项目更加复杂,一般通过单文件组件(后面会讲)的方式在构建应用,通常首先需要安装 npm、vue-cli、webpack 三个工具。 然后在命令行中键入 vue ui 创建一个项目。

创建第一个 Vue 应用#

在 JS 中用 new Vue() 即可创建一个 Vue 应用,它接收参数为 JSON 格式的对象。 众所周知,JSON 格式的对象是由很多键值对组成的,它的键一般都是字符串,而值的变化就比较多,可以是字符串,列表,也可以是函数。

这个 JSON 格式的对象负责完成 Vue.js 与 DOM 元素的数据绑定,采用 声明式渲染 的方式完成页面渲染。

JSON 对象中,除了 el 的值是一个字符串外,其他键(如 datamethods )的值都是一个对象。

代码清单 1:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
    <div id="app">                  <!-- 一般通过 id 索引模块 -->
        {{ message }} {{name}}      <!-- HTML 声明式模板语法,文本插值,双大括号 -->
    </div>

    <script type="text/javascript">
    var app = new Vue({
        el: '#app',                 // el:用于和对应的 DOM 元素一一对应(绑定)
        data: {                     // data:为相应 DOM 元素下的变量赋值
            message: 'Hello Vue!',
            name: 'vue'
        }
    });
    </script>

</body>
</html>

数据与方法#

在上一节,学习了如何修改 HTML 模板中的变量值,这一章学习如何修改 Vue 应用中的变量值。

在 Vue 实例中, $ 表示该实例的属性或方法,访问 Vue 实例或 JS 对象的属性或方法用点操作符 .。 因为,在 JS 脚本中单大括号表示 {对象},因此,下例中修改变量 a 的值时使用了两次点操作符,在默认情况下 $data 可以省略不写。 注意,vm.$watch 是 Vue 实例的方法,用于观测变量的值是否发生改变。

代码清单 2:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    {{a}}
</div>

<script type="text/javascript">

    var test = { a : 1 }; // 声明变量
    var vm = new Vue({
        el   : "#app",
        data : test // data 是 Vue 实例的属性
        // data : { a : 3 } // data : test 的等价形式
    });

    // 观察 a 的变化,如果 a 发生了变化,就执行function
    vm.$watch('a', function(to, from) {
        console.log(to, from);
    })

    // vm.$data.a = "test...." // 访问 vue 实例 $data 属性下的 a 变量
    vm.a = "123"    // vm.$data.a 的等价形式,$data 可以省略不写

</script>

</body>
</html>

生命周期#

Vue 实例的生命周期如下图,要理解这个图现在还有些困难,随着学习的深入,后面可以回过头来回顾。

在 Vue 的生命周期中,他会做很多事情,图中的绿色部分时 Vue 的内部实现,红色部分使我们需要关注的。

红色部分表示钩子函数,也是我们在开发中 可以重写 的部分,在 Vue 运行到相应阶段的时候,会自动回调。

实际开发中,一般的工作流程是:

  • 根组件在 created 阶段请求网络数据;

  • 将数据保存在 Vue 实例的 data 部分;

  • 通过父子组件之间的通信,子组件将数据展示在 DOM 上。

../_images/vue-lifecycle.svg

代码清单 3:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    {{msg}}
</div>
<script type="text/javascript">

    var vm = new Vue({

        el : "#app",

        data : {
            msg : "hi vue",
        },

        // 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
        beforeCreate : function(){
            console.log('beforeCreate');
        },

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

        // 在挂载开始之前被调用:相关的渲染函数首次被调用
        beforeMount : function(){
            console.log('beforeMount');
        },

        // el 被新创建的 vm.$el 替换, 挂载成功
        mounted : function(){
            console.log('mounted');
        },

        // 数据更新时调用
        beforeUpdate : function(){
            console.log('beforeUpdate');
        },

        // 组件 DOM 已经更新, 组件更新完毕
        updated : function(){
            console.log('updated');
        }
    });

    setTimeout(function(){
        vm.msg = "change ......";
    }, 3000);

</script>
</body>
</html>

模板语法-插值#

双大括号可以实现文本插值,如果是 HTML 代码的话,那么无法进行解析, 这时候可以借助 Vue 提供的 v-html 命令,将插值解析成 HTML 代码。

v-bind:class="表达式",暂时可以忽略,后面会讲。

代码清单 4:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    {{msg}}
    <p>Using mustaches: {{ rawHtml }}</p>   <!-- 用 HTML 模板语法声明的变量 -->
    <p v-html="rawHtml"></p>                <!-- 用指令声明的变量(凡是带有 v- 开头的都是 Vue 指令) -->
    <div v-bind:class="color">test...</div>
    <p>{{ number + 1 }}</p>
    <p>{{ ok ? 'YES' : 'NO' }}</p>
    <p>{{ message.split('').reverse().join('') }}</p>
</div>
<script type="text/javascript">

    var vm = new Vue({

        el : "#app",

        data : {
            msg : "hi vue",
            rawHtml : '<span style="color:red">This should be red</span>',
            color : 'blue',
            number : 10,
            ok : 1,
            message : "vue"
        }
    });
    vm.msg = "hi....";

</script>

<style type="text/css">
    .red {
        color: red;
    }

    .blue {
        color: blue;
        font-size: 100px;
    }
</style>
</body>
</html>

模板语法-指令#

如下代码清单 5 所示,展示了一些比较常用的指令:

  • v-if="表达式"

  • v-on:事件名="表达式"

  • v-bind:属性名="表达式"

v-if 中的表达式结果为真的时候,Vue 会渲染当前的 DOM 元素,如果为假,该元素将不会出现在网页上。 v-ifv-show 的不同之处就在于 v-show 不管表达式是真还是假,都会出现在网页上,只不过为假的时候, display=none

v-on 中的事件名可以是鼠标单击、双击、键盘按下、抬起等浏览器自动监听的时间,也可以是自定义事件, 比如我们在父子组件通信的时候,子组件向父组件通过 $emit("事件名", 变量名) 发送的事件。 v-on 中的表达式可以是一个函数名,事件发生时触发这个函数,也可以是普通的表达式语句表示做出什么动作。

v-bind 是用的比较多的一个指令了,因此也有语法糖的写法形式,就是省略 v-bind,直接用冒号代替。 当属性名为普通的属性(如 hrefsrc )时,我们可以在 Vue 实例的 data 选项中 给表达式中的变量 赋初值。 当属性名是 classstyle 时,我们就可以动态地改变样式了,见下一节 classstyle 绑定。

代码清单 5:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    <p v-if="seen">现在你看到我了</p>
    <a v-bind:href="url">可以更换的动态URL</a>
    <div v-on:click="click1">   <!-- 因为下面的 click.stop 这里的 click1 不会触发了 -->
        <div v-on:click.stop="click2">
        <!-- <div @click.stop="click2"> 和上面一行效果一样 -->
            click me
        </div>
    </div>
</div>
<script type="text/javascript">

    var vm = new Vue({

        el : "#app",

        data : {
            seen : true,
            url : "https://cn.vuejs.org/v2/guide/syntax.html"
        },

        methods:{
            click1 : function () {
                console.log('click1......');
            },
            click2 : function () {
                console.log('click2......');
            }
        }
    });

</script>
</body>
</html>

class 与 style 绑定#

class内联样式 是 HTML 元素的常用属性,通过 v-bind 可以将两者进行绑定。 有了这个绑定,我们后面可以通过 class 来动态地修改 HTML 元素的样式了。

代码清单 6:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    <div class="test"
        v-bind:class="[ isActive ? 'active' : '', isGreen ? 'green' : '']"
        style="width:200px; height:200px; text-align:center; line-height:200px;">
            hi vue
    </div>

    <div :style="{color:color, fontSize:size, background: isRed ? '#FF0000' : ''}">
        hi vue
    </div>
</div>
<script type="text/javascript">

    var vm = new Vue({

        el : "#app",

        data : {
            isActive : true,
            isGreen : true,
            color : "#FFFFFF",
            size : '50px',
            isRed : true
        }
    });

</script>

<style>
    .test{font-size:30px;}
    .green{color:#00FF00;}
    .active{background:#FF0000;}
</style>
</body>
</html>

条件渲染#

v-if 是 Vue 的一个指令,我们上面已经用过了,因此这一章比较容易理解。

代码清单 7:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    <div v-if="type === 'A'"> <!-- if 和  else-if 语法,选择某一个 div 进行渲染 -->
        A
    </div>
    <div v-else-if="type === 'B'">
        B
    </div>
    <div v-else-if="type === 'C'">
        C
    </div>
    <div v-else>
        Not A/B/C
    </div>
    <h1 v-show="ok">Hello!</h1>
</div>

<script type="text/javascript">

    var vm = new Vue({
        el : "#app",
        data : {
            type : "B",
            ok : true
        }
    });

</script>

<style type="text/css">

</style>
</body>
</html>

列表渲染#

列表渲染指的是有序列表或无序列表的渲染。通常用 v-for 来操作列表中的每个元素。语法为 v-for="表达式"

说到表达式,必然后变量和关键字,那么一般常用的表达式是 v-for="item,index in items"。 这其中只有 in 是关键字,其他都是变量,可以在 data 选项中赋初值,在 methods 中定义函数进行修改(一般与 @click 搭配,有事件触发函数)。

需要注意的是,如果 items 是数组,第一个元素 item 表示数组的值,第二个返回值 index 表示数组的索引; 如果 items 是对象,第一个元素 item 表示对象的值,第二个返回值 index 表示对象的键。

代码清单 8:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    <ul>
        <li v-for="(item, listindex) in items" :key="listindex">
            {{ item }} : {{ listindex }} : {{ item.message }}
        </li>
    </ul>
    <ul>
        <li v-for="(value, key, objindex) in object" :key="objindex">
            {{ key }} : {{ value }} : {{objindex}}
        </li>
    </ul>
</div>

<script type="text/javascript">
    var vm = new Vue({
        el : "#app",
        data : {
            items : [
                { message: 'Foo' },
                { message: 'Bar' }
            ],
            object: {
                title: 'How to do lists in Vue',
                author: 'Jane Doe',
                publishedAt: '2016-04-10'
            }
        }
    });
</script>
</body>
</html>

事件绑定#

v-on 用来监听 DOM 事件,比如鼠标点击( @click ),键盘抬起( @keyup ),Enter 键抬起( @keyup.enter )。

语法 v-on:click="表达式"。这里的表达式,既可以是一个函数名,也可以是一个逻辑表达式。

实际开发中,因为 v-on 比较常用,语法糖的写法是用 @ 符号代替 v-on:

代码清单 9:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    <div id="example-1">
        <button v-on:click="counter += 1"> 数值 :  {{ counter }} </button><br />
        <button v-on:dblclick="greet('abc', $event)">Greet</button>
    </div>
</div>
<script type="text/javascript">
    var vm = new Vue({
        el : "#app",
        data : {
            counter: 0,
            name : "vue"
        },
        methods:{
            greet : function (str, e) {
                alert(str);
                console.log(e);
            }
        }
    });
</script>
<style type="text/css">

</style>
</body>
</html>

表单输入绑定#

我们之前都是通过在后台修改数据,来让前端页面的内容得到修改,这时如果反过来,在前端页面修改值, 并不会修改后台中的 data 中变量的值,这是因为只有单向绑定。

而用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。 使得前端页面的修改可以在后台收到改动,后台的改动也会在前端页面中展示出来。

尽管有些神奇,但 v-model 本质上不过是语法糖,它整合了 :value@input 两个事件,是一个缩写版本。 但是双向绑定只在表单中实现了,如果想要自己在其他元素中实现双向绑定,则需要自己实现上面两个完整的事件。

代码清单 10:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    <div id="example-1">
        <input v-model="message" placeholder="edit me">
        <p>Message is: {{ message }}</p>
        <textarea v-model="message2" placeholder="add multiple lines"></textarea>
        <p style="white-space: pre-line;">{{ message2 }}</p>
        <br />

        <div style="margin-top:20px;">
            <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
            <label for="jack">Jack</label>

            <input type="checkbox" id="john" value="John" v-model="checkedNames">
            <label for="john">John</label>

            <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
            <label for="mike">Mike</label>
            <br>
            <span>Checked names: {{ checkedNames }}</span>
        </div>

        <div style="margin-top:20px;">
            <input type="radio" id="one" value="One" v-model="picked">
            <label for="one">One</label>
            <br>

            <input type="radio" id="two" value="Two" v-model="picked">
            <label for="two">Two</label>
            <br>
            <span>Picked: {{ picked }}</span>
        </div>
        <button type="button" @click="submit">提交</button>
    </div>

</div>

<script type="text/javascript">

    var vm = new Vue({

        el : "#app",

        data : {
            message : "test",
            message2 : "hi",
            checkedNames : ['Jack', 'John'],
            picked : "Two"
        },

        methods: {
            submit : function () {
                console.log(this.message);
            }
        }
    });

</script>

<style type="text/css">
</style>
</body>
</html>

父子组件通信#

组件是可复用的 Vue 实例,可以通过 Vue.component('组件名', JSON 对象) 创建组件。

JSON 对象 的一般格式为:

{
    template: `<div>某些 HTML 代码</div>`,
    ...
}

关于组件,首先需要了解什么是全局组件和局部组件,然后还需要知道什么是父组件,什么是子组件。 子组件指的就是局部组件,而相应地,父组件就是包含局部注册的组件,简而言之,在组件中注册组件即构成父子关系。 可以推断, new Vue() 创建出来的就是根组件,它是所有组件的父组件。

  • 全局组件:用 Vue.component('组件名', JSON 对象) 创建的组件;

  • 局部组件:使用 component 属性注册的组件。

这里仅说明一下概念,更详细的内容参考 组件注册

根据我们现有的编程基础,可以断定,父组件相比子组件而言,拥有更大的作用域和生命周期。

需要明白的是,组件之间有父子关系,父组件和子组件之间的通信因此成为了很关键的一环。

在真实业务中,一般是根组件向服务器发送请求,保存在 data 中,然后子组件获取父组件的 data,进行展示。

子组件中的 data() 必须是函数,而不能是属性,这是因为每个子组件都希望有自己的数据空间,而不被共享。

父组件向子组件通信:在子组件中用 props: ['子组件变量名'] 接收消息。 在父组件模板中的通过属性 v-bind:子组件变量名="父组件变量名" 中转消息。 在父组件中通过 data 初始化父组件变量的值来 发送消息, 又因为 data 中的变量值可以通过 methodscomputed 方法进行修改,从而实现对网页内容的实时渲染。

子组件向父组件通信:在子组件的某个方法中用 $emit('子组件事件名', 子组件变量名) 发送消息。 在父组件模板中用 @子组件事件名='父组件事件名' 监听子组件的事件,触发父组件事件。 消息本体 就是 子组件变量名 这个参数保存了数据,因此实现消息的传递。 这样当父组件拿到变量后,可以保存到自己的 data 部分,就可以 实现持久化 了。

代码清单 11:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>

<!-- HTML 代码中的变量只能在 根组件 中注册,不能在 子组件 中注册 -->
<div id="app">
    <buttoncounter :ctitle="ptitle" @cclick="pclick"> <!-- 子组件向父组件发送事件 -->
        <h2>上面这个按钮接收由子组件发送过来的 cclick 事件,触发父组件的 pclick 事件</h2>
    </buttoncounter>
    <buttoncounter :ctitle="ptitle"></buttoncounter>
</div>

<script type="text/javascript">

    // 实现子组件
    const buttoncounter = {

        // 在子组件中写 props 列表/对象,获取父组件相关变量的值
        props: ['ctitle'],

        // 定义子组件的模板,在模板中使用变量
        template:
            `<div>
                <button v-on:click="clickfunc">
                    {{ctitle}} 子组件在统计你点击了 {{ count }} 次.
                </button>
                <slot></slot>
            </div>`,

        // 子组件中的变量注册
        // data() 必须为函数,因为每个组件都希望有自己的变量且互不干扰
        data() {
            return {
                count: 0
            }
        },

        // 定义子组件的方法
        methods:{
            clickfunc() { // ES6 的写法
                this.count ++; // 你会发现子组件的 count 在增长
                this.$emit('cclick', this.count); // 发出一个 cclick 事件,参数为 this.count
            }
        }
    }

    // Vue 实例(根组件、父组件)
    var vm = new Vue({
        el : "#app",
        data : {
            ptitle: '父组件赋予的标题:'
        },
        methods:{
            pclick(e) { // ES6 写法
                console.log(e);
            }
        },

        // 局部组件注册,形成父子关系
        components: {
            buttoncounter // ES6 简写形式,全写是 buttoncounter: buttoncounter
        }
    });

</script>
<style type="text/css">

</style>
</body>
</html>

如果父组件想直接访问子组件的方法或属性可以用 $children[i].func()$refs.name

如果子组件想访问父组件的方法或属性用 $parent.func()$root.func()

实际开发中,用 $children$root 都比较少,因为 $children 在增加或删除子组件时会发生索引错误。

单文件组件#

到目前为止,我们学完了 Vue 主要的基础内容。基于组件的开发方式更适用于大项目。

首先,安装准备环境:

  1. 安装 npm:npm -v

  2. 由于网络原因 安装 cnpm:npm install -g cnpm --registry=https://registry.npm.taobao.org

  3. 安装 vue-cli:cnpm install -g @vue/cli

  4. 安装 webpack:cnpm install -g webpack

然后,在命令行中使用 vue ui 创建一个 Vue 项目,包管理器选择 npm 其他保持默认即可。

创建完成后,关闭浏览器,用 IDE 打开项目。可以看到, public 是项目开发完成后部署的文件。 src 是源代码文件,我们将在这里完成开发工作。

  1. src/App.vue 是项目的入口文件,在 scriptimport 自定义的组件;

  2. script 中使用 export default 注册组件;

    • name: 给组件起个名字;

    • props 在子组件中声明需要向父组件请求的数据;

    • data() {}template 中的变量赋予初值

    • methods: {} 定义函数方法实现

    • mounted() {} 自动调用函数(因为有些函数不需要监听鼠标或键盘事件)

  3. template 中使用已经注册的组件,即可完成整个开发流程。

以上,基础知识全部更新完毕。

进阶阅读#

实际项目开发中,我们可能需要 频繁使用 一些更加高级的功能,比如:

  • 插槽 占位,后面根据内容自定义补充到这个位置;

  • 使用 Vue Router 实现前端路由;

  • 使用 Vuex 让多个组件可以共享某些信息,比如用户的登录状态等等;

  • 使用 Axios 处理并发的网络请求,借助 Promise 对象 良好的封装实现异步通信。

未来有更多的知识等待探索。比如,如何更加优雅地组织代码,如何尽量减少第三方库混在业务逻辑中。

当你检查是否已经掌握了上面的知识,可以通过阅读我的 代码仓库笔记 检查一下,或者也可以当做复习用。