Vue入门

Posted by 小拳头 on Friday, July 2, 2021

官方文档的中文介绍有简单入门的视频教程, 下载HbuilderX, 并导入官方的教程代码. 我学习的过程是先看完视频跑一边代码再根据文档梳理一次.

Hello World

app这个vue对象的data对象绑定到<div>元素中. 要注意只有当实例被创建时就已经存在于data中的property才是响应式的, 所以如果后面要用一个property, 在data中需要预先声明一个空的property.

<!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">
      {{ message }} {{name}} <!--视图-->
    </div>
    
    <script type="text/javascript">
    var app = new Vue( //创建一个vue对象
        el: '#app',
        data: {
            message: 'Hello Vue!',
            name : "Vue"
        }
    });
    </script>
</body>
</html>

如果data:obj进行了映射, 但是同时Object.freeze(obj), 那么obj中的property无法被修改. Vue有一些实例property和方法, 都带有前缀$. 比如用$watch可以观察变量前后的变化, 回调函数function会在vm.a改变后调用.

<!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 data = { a : 1 };
    var vm = new Vue({
        el   : "#app",
        data : data
    });

    //观察变量前后的变化
    vm.$watch('a', function(newVal, oldVal){
        console.log(newVal, oldVal);
    })
    //修改变量a
    vm.$data.a = "test"
    </script>
</body>
</html>

实例生命周期钩子

可以在生命周期不同的阶段注入逻辑, 不能在property或回调上使用箭头函数, 因为箭头函数没有this, 可能导致错误(向上级查找), 可能导致Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function.

<!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替换, 节点被vue节点替换
    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>

实例生命周期:

模板语法

插值(Mustache{{}})

注意差值中支持的是表达式, 不是语句或者流控制等, 所以if (ok) { return message}这种写法不会生效.

<!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>
        <p v-html="rawHtml"></p> <!--通过v-html指令把变量转换为html元素-->
        <div v-bind:class="color">test...</div> <!--通过v-bind动态绑定颜色-->
        <p>{{ number + 1 }}</p> <!--js运算-->
        <p>{{ 1 == 1 ? 'YES' : 'NO' }}</p> <!--js三元运算-->
        <p>{{ message.split('').reverse().join('') }}</p> <!--string的函数运算-->
    </div>
    <script type="text/javascript">
    var vm = new Vue({
        el : "#app",
        data : {
            msg : "hi vue",
            rawHtml : '<span style="color:red">this is 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>

指令(Directives)

带有v-前缀的特殊attribute.

  • <p v-if="seen">现在你看到我了</p>: 通过表达式seen的真假选择是否插入/移除<p>元素
  • <a v-bind:href="url">...</a>: href attribute与表达式url的值绑定
  • <a v-on:click="doSomething">...</a>: 用v-on可以监听DOM事件
<!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">...</a> <!--绑定属性-->
        <div @click="click1"> , <!--绑定点击事件-->
            <div @click.stop="click2"> <!--.stop:当前点击事件执行后就停止-->
                click me
            </div>
        </div>
    </div>
    <script type="text/javascript">
    var vm = new Vue({
        el : "#app",
        data : {
            seen : false, //是否渲染
            url : "https://cn.vuejs.org/v2/guide/syntax.html#%E6%8C%87%E4%BB%A4"
        },
        methods:{
            click1 : function () {
                console.log('click1......');
            },
            click2 : function () {
                console.log('click2......');
            }
        }
    });
    </script>
    <style type="text/css">
    </style>
</body>
</html>

缩写

直接copy文档:

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

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

<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

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

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

计算属性

computed中的内容就是计算属性, 基于响应式依赖进行缓存, 只在相关响应式依赖发生改变时它们才会重新求值. 如果不要缓存, 使用方法也可以做到同样的效果. 除了计算属性之外还有侦听属性, 和前面的vm.$watch类似, 比如<input v-model="question">, 当用户在输入框打字时, 我们就可以用watch下的question: function(newQuestion, oldQuestion) {}侦听并执行代码块内的逻辑.

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
      //可以加入setter, 否则只有getter
      //get: function() {}
      //set: function() {}
    }
  }
})

Class绑定Style

实际上还是绑定了data中的字段.

<!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">
        <!-- v-bind:class="{ active: isActive, green: isGreen}" map形式也可以, 这里的green指样式 -->
        <div 
        class="test" 
        v-bind:class="[ isActive ? 'active' : '', isGreen ? 'green' : '']" 
        style="width:200px; height:200px; text-align:center; line-height:200px;">
            hi vue
        </div>
        <!--color(属性):color(变量)-->
        <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>

条件渲染

if-else中的模板可以复用元素, 可以用两个<input>做测试, 两个<input>通过一个按键互相切换, 输入框中的值不变. 如果想每次都重新渲染, 那么在<input>中加入key即可. 而v-show无论是否隐藏都会渲染, 只是用css进行隐藏.

<!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'"> <!--根据条件渲染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>
        <!--根据条件渲染, element本身存在, 只是css渲染不同-->
        <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>

列表渲染

<li v-for="(item,index) in items" :key="index">就是迭代的语法, in可以用of替换. <li v-for="value, key in object">可以用来迭代对象中的内容. 对象遍历的是Object.keys()的结果. v-for采用就地更新策略, 所以即使遍历对象顺序变了, Vue也不会移动Dom元素, 如果要更新这个元素, 就需要用v-bind:key="index"来制定一个唯一的key确定节点身份, 其中v-bind可以省略.

可以用计算属性的list.filter进行对迭代结果进行过滤, 如果计算属性不适用, 也可以用方法包裹一个过滤.

<!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>
            <!--列表渲染数组元素与索引-->
            <!--:key等同v-bind:key, 让vue根据index排序, 否则list顺序改变是不会渲染新顺序的-->
            <li v-for="(item,index) in items" :key="index">
            {{index}}{{ item.message }}
            </li>
        </ul>
        <ul>
            <li v-for="value, key in object">
                {{key}} : {{ value }}
            </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>

官方文档不推荐在一个element上同时用v-forv-if, 因为v-for的优先级更高, 所以其实每一次的迭代都运行v-if, 所以直接把v-if放在外层元素(template)才能真的跳过循环的执行.

事件处理

通过v-on绑定事件, 类似于中断器. 除了点击事件, 双击事件, 还可以处理系统修饰键, 比如v-on:keyup.enter拿到回车事件, .left鼠标左键事件等等.

<!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 /> <!--直接绑定js指令-->
            <button v-on:dblclick="greet('abc', $event)">Greet</button> <!--绑定greet函数,dblclick为双击-->
        </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>

输入绑定

可以对输入内容进行双向绑定. 要注意v-model不会在输入法组合文字过程中得到更新, 输入法输入需要用input事件处理.

<!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"> <!--双向绑定message单行文本-->
            <p>Message is: {{ message }}</p>
            <textarea v-model="message2" placeholder="add multiple lines"></textarea> <!--双向绑定message多行文本-->
            <p style="white-space: pre-line;">{{ message2 }}</p>
            <br />
            
            <!--复选框绑定到list-->
            <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);
                var postObj = { //通过变量收集所有data的值
                    msg1 : this.message1,
                    msg2 : this.message2,
                    checkval : this.checkedNames
                };
                console.log(postObj)
            }
        }
    });
    </script>
    <style type="text/css">
    </style>
</body>
</html>

v-model有一些修饰符, 比如:

  • v-model.lazy: 输入框在change事件之后同步, 而不是input
  • v-model.number: 自动将用户的输入值转为数值类型
  • v-model.trim: 减掉输入的首尾空白字符

组件(Components)

注意这里的data是一个函数而不是一个字段, 保证每个实例可以维护一份被返回对象的独立的拷贝, 否则用到这个Component的地方都会使用同一个对象, 绑定的template中必须有根节点, 否则会报every component must have a single root element错误.

<!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">
        <!--数据封闭在组件内部, 互相没有影响-->
        <button-counter title="title1 : " @clicknow="clicknow">
            <h2>---------------</h2>
        </button-counter>
        <button-counter title="title2 : "></button-counter>
    </div>
    <script type="text/javascript">
    Vue.component('button-counter', { //创建一个组件
        props: ['title'], //props定义组件属性
        data: function () {
            return {
                count: 0
            }
        },
        //组件template必须有根节点, <slot></slot>是插槽, 可以放入任意html标签, 这里是<h2>---------------</h2>
        template: '<div><h1>hi...</h1><button v-on:click="clickfun">{{title}} You clicked me {{ count }} times.</button><slot></slot></div>',
        methods:{ //组件脚本
            clickfun : function () {
                this.count ++;
                this.$emit('clicknow', this.count); //触发事件(事件名称, 可携带的参数)
            }
        }
    })
    var vm = new Vue({
        el : "#app",
        data : {
            
        },
        methods:{
            clicknow : function (e) {
                console.log(e);
            }
        }
    });
    </script>
    <style type="text/css">
    </style>
</body>
</html>

可以局部绑定, 把template声明到Vue实例的components中. props中可以注册一些attributes, 这些中可以注册一些attributes就成为了组件的property.

<!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">
        <button-counter title="This is title"></button-counter>
        <test></test>
    </div>
    <script type="text/javascript">
    Vue.component('button-counter', {
        props: ['title'],
        data: function () {
            return {}
        },
        template: '<div><h1>hi...{{ title }}</h1></div>',
        methods:{
            
        }
    })
    var vm = new Vue({
        el : "#app",
        data : {
            
        },
        methods:{
            clicknow : function (e) {
                console.log(e);
            }
        },
        components:{
            test : {
                template:"<h2>h2...</h2>" //局部注册
            }
        }
    });
    </script>
    <style type="text/css">

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

部分HTML元素对内部元素的类型有限制, 所以我们自定义的component如果放在里面, 可能会导致渲染错误.

单文件组件

简单来说, 就是一个.vue文件作为一个单文件组件, 在页面文件中通过import 文件名 from '路径/文件名.vue', 再用

export default {
    name: 'app',
    components: {
        文件名
    }
}

注册这个组件, 在template中就可以使用这个组件了. 单文件组件包含<template>/<script>/<style>三种组件.

参考

  1. Vue getting started

comments powered by Disqus