Vue学习笔记


介绍


Vue.js是一套构建用户界面的框架,只关注视图层。可以减少不必要的DOM操作,提高渲染效率。

打包工具:WebPack和Gulp。

在Vue中,一个核心的概念,就是让用户不再操作DOM元素,解放了用户的双手,让程序员可以更多的时间去关注业务逻辑。

框架:一套完整的技术解决方案,对项目侵入性较大;库(插件):提供某一小功能,侵入性小。


mvvm


MVVM是前端视图层的概念,主要关注于视图层分离,把前端的视图层分为三部分,Model(数据)、View(每个页面的HTML结构)、ViewModel(调度者,数据双向绑定,可以理解为View和Model的通信桥梁)。

引入

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

实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

</head>
<body>
      <!-- Vue 实例所控制的这个元素区域,就是我们的V  -->
    <div id="vue_det">
        <h1>url : {{url}}</h1>
        <h1>{{details()}}</h1>
    </div>
    <script type="text/javascript">
        // new 出来的这个 vm 对象,就是我们 MVVM中的 VM调度者
        var vm = new Vue({
            // 表示new 的这个 Vue 实例,要控制页面上的哪个区域
            el: '#vue_det',

            // 这里的 data 就是 MVVM中的 M,专门用来保存 每个页面的数据的
            data: {
                url: "http://www.baidu.com"
            },
            methods: {
                details: function() {
                    return  this.url + " - 百度一下";
                }
            }
        })
        document.write(vm.url)
    </script>
</body>
</html>

文本插值使用双大括号: {{ }}

Vue调试

谷歌应用商店搜索Vue.js devtools


教程

https://cn.vuejs.org/v2/guide/
https://github.com/pagekit/vue-resource
http://cubic-bezier.com/#.17,.67,.83,.67

指令


指令是带有 v- 前缀的特殊属性。指令用于在表达式的值改变时,将某些行为应用到 DOM 上。


v-cloak

解决插值表达式闪烁问题,只替换占位符。

v-text

v-text默认没有闪烁问题,替换内容时会把整个元素替换。

v-html

用于输出html格式的,也会替换全部内容。

v-bind

绑定属性的指令,用于监听DOM事件。v-bind:可以省略为:。变量加常量的效果可以实现。

v-on

事件绑定机制v-on:可以省略为@

代码:

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<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>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
<div id="app">
    <!-- 使用 v-cloak 能够解决 插值表达式闪烁的问题 -->
    <p v-cloak>++++++++ {{ msg }} ----------</p>
    <!-- 默认 v-text 是没有闪烁问题的 -->
    <!-- v-text会覆盖元素中原本的内容,但是 插值表达式  只会替换自己的这个占位符,不会把 整个元素的内容清空 -->
    <h4 v-text="msg">==================</h4>

    <div>{{msg2}}</div>
    <div v-text="msg2"></div>
    <div v-html="msg2">1212112</div>

    <!-- v-bind: 是 Vue中,提供的用于绑定属性的指令 -->
    <input type="button" value="按钮" v-bind:title="mytitle + '123'">
    <!-- 注意: v-bind: 指令可以被简写为 :要绑定的属性 -->
    <!-- v-bind 中,可以写合法的JS表达式 -->

    <!-- Vue 中提供了 v-on: 事件绑定机制 -->
    <input type="button" value="按钮" @click="show">
</div>

<script>
    var vm = new Vue({
        el: '#app',
        data: {
            msg: '123',
            msg2: '<h1>哈哈,我是一个大大的H1, 我大,我骄傲</h1>',
            mytitle: '这是一个自己定义的title'
        },
        methods: { // 这个 methods属性中定义了当前Vue实例所有可用的方法
            show: function () {
                alert('Hello')
            }
        }
    })
</script>
</body>
</html>

示例之跑马灯


<!DOCTYPE html>
<html lang="en">
<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>Document</title>
    <!-- 导入Vue包 -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <input type="button" value="浪起来" @click="lang">
    <input type="button" value="低调" @click="stop">
    <h4>{{ msg }}</h4>
</div>
<script>
    // 注意:在 VM实例中,如果想要获取 data 上的数据,或者 想要调用 methods 中的 方法,必须通过 this.数据属性名  或  this.方法名 来进行访问,这里的this,就表示 我们 new 出来的  VM 实例对象
    var vm = new Vue({
        el: '#app',
        data: {
            msg: '猥琐发育,别浪~~!',
            intervalId: null // 在data上定义 定时器Id
        },
        methods: {
            lang() {
                if (this.intervalId != null) return;
                this.intervalId = setInterval(() => {
                    var start = this.msg.substring(0, 1)
                    // 获取到 后面的所有字符
                    var end = this.msg.substring(1)
                    // 重新拼接得到新的字符串,并赋值给 this.msg
                    this.msg = end + start
                }, 400)
            // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去;【好处:程序员只需要关心数据,不需要考虑如何重新渲染DOM页面】
        },
        stop() { // 停止定时器
            clearInterval(this.intervalId)
            // 每当清除了定时器之后,需要重新把 intervalId 置为 null
            this.intervalId = null;
        }
    }
    })
</script>
</body>
</html>

v-model


双向数据绑定,唯一的一个v-bind只能实现数据的单向绑定,从M自动绑定到V,无法实现数据的双向绑定。 v-model可以实现表单元素和Model中数据双向绑定。

注意:只能用到在input、select、text、checkbox、radio等表单元素中。根据表单上的值,自动更新绑定的元素的值。

v-model实现简单计算器

<!DOCTYPE html>
<html lang="en" xmlns:v-model="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title></title>
    <script src="lib/vue.js"></script>
</head>
<body>

<div id="app">
    <input type="text" v-model="n1"/>
    <select v-model="opt">
        <option value="+">+</option>
        <option value="-">-</option>
        <option value="*">*</option>
        <option value="/">/</option>
    </select>
    <input type="text" v-model="n2"/>
    <input type="button" value="=" @click="calc"/>
    <input type="text" v-model="result"/>
</div>
<script>
    var vm = new Vue({
        el: "#app",
        data: {
            n1:0,
            n2:0,
            result:0,
            opt:"+"
        },
        methods:{
            calc:function(){
                /*switch (this.opt){
                    case "+":
                       this.result = parseInt(this.n1)+parseInt(this.n2)
                        break;
                    case "-":
                        this.result = parseInt(this.n1)-parseInt(this.n2)
                        break;
                    case "*":
                        this.result = parseInt(this.n1)*parseInt(this.n2)
                        break;
                    case "/":
                        this.result = parseInt(this.n1)/parseInt(this.n2)
                        break;
                }*/
                var codeStr ="parseInt(this.n1)"+ this.opt + "parseInt(this.n2)"
                this.result = eval(codeStr)
            }
        }
    })
</script>
</body>
</html>

v-for


v-for 指令需要以site in sites形式的特殊语法, sites是源数据数组并且site是数组元素迭代的别名。

循环普通数组;循环对象数组;循环对象,包含key,value,和索引;迭代数字,从1开始。

<div id="app">
    <!--循环普通数组-->
    <p v-for="item in list">{{item}}</p>
    <p v-for="(item,i) in list">索引值是{{i}},每一项是{{item}}</p>
    <!--循环对象数组-->
    <p v-for="item in list2">{{item.id}}和{{item.name}}</p>
    <p v-for="(item,i) in list2">索引值是{{i}}</p>
    <!--循环对象-->
    <p v-for="val in user">{{val}}</p>
    <p v-for="(val,key) in user">{{val}}---{{key}}</p>
    <p v-for="(val,key,i) in user">{{val}}---{{key}}----{{i}}</p>
    <!--迭代数字-->
    <p v-for="count in 10">这是第{{count}}次</p>
</div>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            list:[1,2,3,4,5,6],
            list2:[
                {id:1,name:"a"},
                {id:2,name:"b"},
                {id:3,name:"c"},
                {id:4,name:"d"}
            ],
            user:{
                id:1,
                name:"张三",
                sex:"男"
            }
        }
    })
</script>

注意:在2.2.0+的版本里,当在组件中使用v-for时,key是必须的。key属性只能使用number或者string,必须使用v-bind属性绑定的形式,指定key的值。

<p v-for="item in list" :key="item.id">
    
</p>

v-ifv-show


根据表达式的值来决定是否插入元素。

v-if每次都会重新删除或者创建元素,切换消耗性能;v-show只是切换了元素display:none样式,较高的初始渲染消耗。

<div id="app">
    <input type="button" @click="flag=!flag" value="toggle"/>
    <h3 v-if="flag">这是v-if控制</h3>
    <h3 v-show="flag">这是v-show控制</h3>
</div>

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

v-else-ifv-else的使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

</head>
<body>
    <div id="app">
    <div v-if="type === 'A'">
      A
    </div>
    <div v-else-if="type === 'B'">
      B
    </div>
    <div v-else>
      Not A/B
    </div>
    </div>
    
    <script>
    new Vue({
      el: '#app',
      data: {
        type: 'C'
      }
    })
    </script>
</body>
</html>

事件修饰符


修饰符是以半角句号.指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。

.stop

阻止事件冒泡

.prevent

阻止默认事件

.capture

捕获事件

.self

只当事件在该元素本身触发时,触发回调。只会阻止自己身上冒泡行为的触发,并不会真正阻止冒泡行为。

.once

事件只触发一次

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <script src="lib/vue.js"></script>
    <style>
        .inner{
            height: 150px;
            background-color: darkcyan;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="inner" @click="div1">
            <input type="button" value="戳他" @click.stop="btn"/>
        </div>
    
        <a href="http://www.baidu.com" @click.prevent="linkClick">百度</a>
    
        <div class="inner" @click.capture="div1">
            <input type="button" value="戳他" @click="btn"/>
        </div>
    
        <div class="inner" @click.self="div1">
            <input type="button" value="戳他" @click="btn"/>
        </div>
    
        <a href="http://www.baidu.com" @click.prevent.once="linkClick">百度</a>
    </div>
    
    <script>
        var vm = new Vue({
            el: "#app",
            data: {
                div1:function(){
                    console.log("div1click")
                },
                btn:function(){
                    console.log("btnclick")
                },
                linkClick:function(){
                    console.log("btnclick")
                }
            }
        })
    </script>
</body>
</html>

样式

class样式


格式:

<div id="app">
    <!--直接绑定-->
    <h1 v-bind:class="['类名1','类名2','类名3']"></h1>
    <!--直接绑定,简写-->
    <h1 :class="['类名1','类名2','类名3']"></h1>
    <!--三元运算-->
    <h1 :class="['类名1','类名2',flag?'类名3':'']"></h1>
    <!--嵌套对象-->
    <h1 :class="['类名1','类名2',{'类名3':flag}]"></h1>
    <!--使用对象-->
    <h1 :class="{类名1:true,类名2:true,类名3:true}"></h1>
    <!--抽取对象-->
    <h1 :class="classObj"></h1>
</div>
<script>
    var vm = new Vue({
        el: "#app",
        data: {
            flag: true,
            classObj:{类名1:true,类名2:true,类名3:true}
        }
    })
</script>

代码:

<!DOCTYPE html>
<html lang="en">
<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>Document</title>
  <script src="./lib/vue-2.4.0.js"></script>
  <style>
    .red {
      color: red;
    }
    .thin {
      font-weight: 200;
    }
    .italic {
      font-style: italic;
    }
    .active {
      letter-spacing: 0.5em;
    }
  </style>
</head>

<body>
  <div id="app">
    <!-- <h1 class="red thin">这是一个很大很大的H1,大到你无法想象!!!</h1> -->

    <!-- 第一种使用方式,直接传递一个数组,注意: 这里的 class 需要使用  v-bind 做数据绑定 -->
    <!-- <h1 :class="['thin', 'italic']">这是一个很大很大的H1,大到你无法想象!!!</h1> -->

    <!-- 在数组中使用三元表达式 -->
    <!-- <h1 :class="['thin', 'italic', flag?'active':'']">这是一个很大很大的H1,大到你无法想象!!!</h1> -->

    <!-- 在数组中使用 对象来代替三元表达式,提高代码的可读性 -->
    <!-- <h1 :class="['thin', 'italic', {'active':flag} ]">这是一个很大很大的H1,大到你无法想象!!!</h1> -->

    <!-- 在为 class 使用 v-bind 绑定 对象的时候,对象的属性是类名,由于 对象的属性可带引号,也可不带引号,所以 这里我没写引号;  属性的值 是一个标识符 -->
    <h1 :class="classObj">这是一个很大很大的H1,大到你无法想象!!!</h1>
  </div>

  <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {
        flag: true,
        classObj: { red: true, thin: true, italic: false, active: false }
      },
      methods: {}
    });
  </script>
</body>
</html>

内联样式


<div id="app">
    <!--直接绑定-->
    <h1 :style="{color:'red','font-weight':200}">哈哈哈</h1>
    <!--data-->
    <h1 :style="styleObj1">哈哈哈</h1>
    <!--多个data-->
    <h1 :style="[styleObj1,styleObj2]">哈哈哈</h1>
</div>
<script>
    var vm = new Vue({
        el: "#app",
        data: {
            styleObj1 : {color:'red','font-weight':200},
            styleObj2 : {'font-style':'italic'},

        }
    })
</script>

过滤器


Vue允许你自定义过滤器,被用作一些常见的文本格式化。过滤器可以用在两个地方:双括号插值和v-bind表达式,过滤器应该被添加在js表达式的尾部,由管道符(|)提示。

过滤器中function第一个参数已经被规定死了:永远是管道符前面传递过来的数据。

全局过滤器和私有过滤器:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <p>{{ message | data1 }}</p>
    <p>{{ message | data2("嘻嘻")}}</p>
    <p>{{ message | data1 | data3}}</p>
    <p>{{ message | data4 }}</p>
</div>

<script>
    //方式一:全局过滤器
    Vue.filter('data1',function(value){
        return value+"嘻嘻"
    })
    //方式二:可以多个参数
    Vue.filter('data2',function(value,arg1){
        return value+arg1
    })
    //方式三:可以多次调用
    Vue.filter('data3',function(value){
        return value+"哈哈"
    })

    new Vue({
        el: '#app',
        data: {
            message: '哈哈哈'
        },
        //方式四:私有过滤器
        filters: {
            data4: function (value) {
                return value+"嘻嘻"
            }
        }
    })
</script>
</body>
</html>

注意:使用ES6中的字符串新方法 String.prototype.padStart(maxLength, fillString='')String.prototype.padEnd(maxLength, fillString='')来填充字符串。


自定义键盘修饰符


Vue.config.keyCodes.f2 = 113

自定义指令


和js相关的最好在inserted中执行;和样式相关的,一般在bind中执行。

Vue.directive:

Vue.directive('focus', {
  bind: function (el) { // 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,只执行一次
   
  },
  inserted: function (el) {  // inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】
   
  },
  updated: function (el) {  // 当VNode更新的时候,会执行 updated, 可能会触发多次

  }
})

全局指令和私有指令。自定义指令下的钩子函数。

https://cn.vuejs.org/v2/guide/custom-directive.html

私有指令:

var vm2 = new Vue({
  el: '#app',
  data: {
  },
  methods: {},
  filters: { // 定义私有过滤器    过滤器有两个 条件  【过滤器名称 和 处理函数】
  },
  directives: { // 自定义私有指令
  }
})

简写方式:

directives: { // 自定义私有指令
    'fontsize': function (el, binding) { // 注意:这个 function 等同于 把 代码写到了 bind 和 update 中去
      el.style.fontSize = parseInt(binding.value) + 'px'
    }
  }

vue实例的生命周期


生命周期:从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。生命周期钩子 = 生命周期函数 = 生命周期事件。

主要的生命周期函数分类:

  • 创建期间的生命周期函数:
    • beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好data和methods属性。
    • created:实例已经在内存中创建好,此时data和methods已经创建好,此时还没有开始编译模板
    • beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
    • mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
  • 运行期间的生命周期函数:当data改变时候才会执行
    • beforeUpdate:状态更新之前执行此函数, 此时data中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点
    • updated:实例更新完毕之后调用此函数,此时data中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
  • 销毁期间的生命周期函数:
    • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
    • destroyed:Vue实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

代码:

// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
  el: '#app',
  data: {
    msg: 'ok'
  },
  methods: {
    show() {
      console.log('执行了show方法')
    }
  },
  beforeCreate() { // 这是我们遇到的第一个生命周期函数,表示实例完全被创建出来之前,会执行它
    // 注意: 在 beforeCreate 生命周期函数执行的时候,data 和 methods 中的 数据都还没有没初始化
  },
  created() { // 这是遇到的第二个生命周期函数
    //  在 created 中,data 和 methods 都已经被初始化好了!
    // 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作
  },
  beforeMount() { // 这是遇到的第3个生命周期函数,表示 模板已经在内存中编辑完成了,但是尚未把 模板渲染到 页面中
    // console.log(document.getElementById('h3').innerText)//{{msg}}
    // 在 beforeMount 执行的时候,页面中的元素,还没有被真正替换过来,只是之前写的一些模板字符串
  },
  mounted() { // 这是遇到的第4个生命周期函数,表示,内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了
    // console.log(document.getElementById('h3').innerText)
    // 注意: mounted 是 实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,此时,如果没有其它操作的话,这个实例,就静静的 躺在我们的内存中,一动不动
  },
  // 接下来的是运行中的两个事件
  beforeUpdate() { // 这时候,表示 我们的界面还没有被更新【数据被更新了吗?  数据肯定被更新了】
    /* console.log('界面上元素的内容:' + document.getElementById('h3').innerText)
    console.log('data 中的 msg 数据是:' + this.msg) */
    // 得出结论: 当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时 data 数据是最新的,页面尚未和 最新的数据保持同步
  },
  updated() {
    console.log('界面上元素的内容:' + document.getElementById('h3').innerText)
    console.log('data 中的 msg 数据是:' + this.msg)
    // updated 事件执行的时候,页面和 data 数据已经保持同步了,都是最新的
  }
});

页面初始化可以在created中请求网络。


vue-resource实现网络请求


https://github.com/pagekit/vue-resource

直接在页面中,通过script标签,引入vue-resource的脚本文件,注意:引用的先后顺序是:先引用Vue的脚本文件,再引用vue-resource的脚本文件;

<script src="https://cdn.jsdelivr.net/npm/vue-resource@1.5.1"></script>

发送get请求:

getInfo() { // get 方式获取数据
  this.$http.get('url').then(res => {
    console.log(res.body);
  })
}

发送post请求:

postInfo() {
  // post 方法接收三个参数:
  // 参数1: 要请求的URL地址
  // 参数2: 要发送的数据对象
  // 参数3: 指定post提交的编码类型为 application/x-www-form-urlencoded
  this.$http.post(url, { name: 'zs' }, { emulateJSON: true }).then(res => {
    console.log(res.body);
  });
}

发送JSONP请求获取数据:

jsonpInfo() { // JSONP形式从服务器获取数据
  this.$http.jsonp(url).then(res => {
    console.log(res.body);
  });
}

JSONP的实现原理

  • 由于浏览器的安全性限制,不允许AJAX访问 协议不同、域名不同、端口号不同的数据接口,浏览器认为这种访问不安全;
  • 可以通过动态创建script标签的形式,把script标签的src属性,指向数据接口的地址,因为script标签不存在跨域限制,这种数据获取方式,称作JSONP(注意:根据JSONP的实现原理,知晓,JSONP只支持Get请求);
  • 具体实现过程:
    - 先在客户端定义一个回调方法,预定义对数据的操作;
    • 再把这个回调方法的名称,通过URL传参的形式,提交到服务器的数据接口;
    • 服务器数据接口组织好要发送给客户端的数据,再拿着客户端传递过来的回调方法名称,拼接出一个调用这个方法的字符串,发送给客户端去解析执行;
    • 客户端拿到服务器返回的字符串之后,当作Script脚本去解析执行,这样就能够拿到JSONP的数据了;

除了vue-resource之外,axios也可以实现数据请求。

全局配置

// 如果我们通过全局配置了,请求的数据接口 根域名,则 ,在每次单独发起 http 请求的时候,请求的 url 路径,应该以相对路径开头,前面不能带 /  ,否则 不会启用根路径做拼接;
Vue.http.options.root = 'http://vue.studyit.io/';
// 全局启用 emulateJSON 选项
Vue.http.options.emulateJSON = true;

练习-品牌案例


需要bootstra样式

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

<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>Document</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <link rel="stylesheet" href="./lib/bootstrap-3.3.7.css">
</head>

<body>
  <div id="app">

    <div class="panel panel-primary">
      <div class="panel-heading">
        <h3 class="panel-title">添加品牌</h3>
      </div>
      <div class="panel-body form-inline">
        <label>
          Id:
          <input type="text" class="form-control" v-model="id">
        </label>
        <label>
          Name:
          <input type="text" class="form-control" v-model="name" @keyup.f2="add">
        </label>
        <!-- 在Vue中,使用事件绑定机制,为元素指定处理函数的时候,如果加了小括号,就可以给函数传参了 -->
        <input type="button" value="添加" class="btn btn-primary" @click="add()">
        <label>
          搜索名称关键字:
          <input type="text" class="form-control" v-model="keywords" id="search" v-focus v-color="'green'">
        </label>
      </div>
    </div>

    <table class="table table-bordered table-hover table-striped">
      <thead>
        <tr>
          <th>Id</th>
          <th>Name</th>
          <th>Ctime</th>
          <th>Operation</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in search(keywords)" :key="item.id">
          <td>{{ item.id }}</td>
          <td v-text="item.name"></td>
          <td>{{ item.ctime | dateFormat() }}</td>
          <td>
            <a href="" @click.prevent="del(item.id)">删除</a>
          </td>
        </tr>
      </tbody>
    </table>
  </div>

  <script>
    Vue.filter('dateFormat', function (dateStr, pattern = "") {
      // 根据给定的时间字符串,得到特定的时间
      var dt = new Date(dateStr)
      //   yyyy-mm-dd
      var y = dt.getFullYear()
      var m = dt.getMonth() + 1
      var d = dt.getDate()
      if (pattern.toLowerCase() === 'yyyy-mm-dd') {
        return `${y}-${m}-${d}`
      } else {
        var hh = dt.getHours()
        var mm = dt.getMinutes()
        var ss = dt.getSeconds()
        return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
      }
    })
    // 自定义全局按键修饰符
    Vue.config.keyCodes.f2 = 113
    // 使用  Vue.directive() 定义全局的指令  v-focus
    // 其中:参数1 : 指令的名称,注意,在定义的时候,指令的名称前面,不需要加 v- 前缀, 
    // 但是: 在调用的时候,必须 在指令名称前 加上 v- 前缀来进行调用
    //  参数2: 是一个对象,这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作
    Vue.directive('focus', {
      bind: function (el) { // 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,只执行一次
        // 注意: 在每个 函数中,第一个参数,永远是 el ,表示 被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象
        // 在元素 刚绑定了指令的时候,还没有 插入到 DOM中去,这时候,调用 focus 方法没有作用
        //  因为,一个元素,只有插入DOM之后,才能获取焦点
        // el.focus()
      },
      inserted: function (el) {  // inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】
        el.focus()
        // 和JS行为有关的操作,最好在 inserted 中去执行,放置 JS行为不生效
      },
      updated: function (el) {  // 当VNode更新的时候,会执行 updated, 可能会触发多次

      }
    })
    // 自定义一个 设置字体颜色的 指令
    Vue.directive('color', {
      // 样式,只要通过指令绑定给了元素,不管这个元素有没有被插入到页面中去,这个元素肯定有了一个内联的样式
      // 将来元素肯定会显示到页面中,这时候,浏览器的渲染引擎必然会解析样式,应用给这个元素
      bind: function (el, binding) {
        // el.style.color = 'red'
        // console.log(binding.name)
        // 和样式相关的操作,一般都可以在 bind 执行
        // console.log(binding.value)
        // console.log(binding.expression)
        el.style.color = binding.value
      }
    })
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {
        id: '',
        name: '',
        keywords: '', // 搜索的关键字
        list: [
          { id: 1, name: '奔驰', ctime: new Date() },
          { id: 2, name: '宝马', ctime: new Date() }
        ]
      },
      methods: {
        add() { // 添加的方法
          var car = { id: this.id, name: this.name, ctime: new Date() }
          this.list.push(car)
          this.id = this.name = ''
        },
        del(id) { // 根据Id删除数据
          var index = this.list.findIndex(item => {
            if (item.id == id) {
              return true;
            }
          })
          this.list.splice(index, 1)
        },
        search(keywords) { // 根据关键字,进行数据的搜索
          /* var newList = []
          this.list.forEach(item => {
            if (item.name.indexOf(keywords) != -1) {
              newList.push(item)
            }
          })
          return newList */
          // 注意:  forEach   some   filter   findIndex   这些都属于数组的新方法,
          //  都会对数组中的每一项,进行遍历,执行相关的操作;
          return this.list.filter(item => {
            // if(item.name.indexOf(keywords) != -1)
            // 注意 : ES6中,为字符串提供了一个新方法,叫做  String.prototype.includes('要包含的字符串')
            //  如果包含,则返回 true ,否则返回 false
            //  contain
            if (item.name.includes(keywords)) {
              return item
            }
          })
          // return newList
        }
      }
    });
  </script>
</body>

</html>

Vue中的动画


https://cn.vuejs.org/v2/guide/transitions.html

使用Vue官方提供的transition元素,把需要被动画控制的元素,包裹起来。v-enter和v-leave-to:是一个时间点,是进入之前,元素的起始状态,此时还没有开始进入。v-enter-active:入场动画的时间段。v-leave-active:离场动画的时间段。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <style>
        .v-enter,
        .v-leave-to{
            opacity: 0;
            transform: translateX(150px);
        }
        .v-enter-active,
        .v-leave-active{
            transition: all 0.8s ease;
        }
    </style>
</head>
<body>
<div id="app">
    <input type="button" value="toggle" @click="flag=!flag"/>
    <transition>
        <h3 v-if="flag">我是h3</h3>
    </transition>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            flag:false
        }
    })
</script>
</body>
</html>

自定义v-前缀


给transition加个name属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <style>
        .my-enter,
        .my-leave-to{
            opacity: 0;
            transform: translateX(150px);
        }
        .my-enter-active,
        .my-leave-active{
            transition: all 0.8s ease;
        }
    </style>
</head>
<body>
<div id="app">
    <input type="button" value="toggle" @click="flag=!flag"/>
    <transition name="my">
        <h3 v-if="flag">我是h3</h3>
    </transition>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            flag:false
        }
    })
</script>
</body>
</html>

使用第三方animate.css类库实现动画

官网

https://daneden.github.io/animate.css/

举个栗子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/animate.css@3.5.2/animate.min.css"/>
</head>
<body>
<div id="app">
    <input type="button" value="toggle" @click="flag=!flag"/>
    <transition enter-active-class="bounceIn" leave-active-class="bounceOut"
            :duration="{ enter: 200, leave: 400}">
        <h3 v-if="flag" class="animated">我是h3</h3>
    </transition>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            flag:false
        }
    })
</script>
</body>
</html>

动画钩子

https://cn.vuejs.org/v2/guide/transitions.html#JavaScript-%E9%92%A9%E5%AD%90

动画钩子函数的第一个参数:el,表示要执行动画的那个DOM元素,是个原生的JS DOM对象。可以认为,el是通过document.getElementById(‘’) 方式获取到的原生JS DOM对象。

  • beforeEnter 表示动画入场之前,此时,动画尚未开始,可以在beforeEnter中,设置元素开始动画之前的起始样式
  • enter表示动画开始之后的样式,这里可以设置小球完成动画之后的,结束状态
    • el.offsetWidth没有实际的作用,但是,如果不写,出不来动画效果
    • done,起始就是afterEnter这个函数,也就是说:done是afterEnter函数的引用
  • 动画完成之后,会调用afterEnter

举个例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <style>
        .ball {
            width: 15px;
            height: 15px;
            border-radius: 50%;
            background-color: red;
        }
    </style>
</head>
<body>
<div id="app">
    <input type="button" value="购物车" @click="flag=!flag">
    <!-- 1. 使用 transition 元素把 小球包裹起来 -->
    <transition
            @before-enter="beforeEnter"
            @enter="enter"
            @after-enter="afterEnter">
        <div class="ball" v-show="flag"></div>
    </transition>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            flag:false
        },
        methods: {
            beforeEnter: function (el) {
                el.style.transform = "translate(0, 0)"
            },
            enter: function (el, done) {
                el.offsetWidth
                el.style.transform = "translate(150px, 450px)"
                el.style.transition = 'all 1s ease'
                done()
            },
            afterEnter: function (el) {
                this.flag = !this.flag
            }
        }
    })
</script>
</body>
</html>

v-for的列表过渡

在实现列表过渡的时候,如果需要过渡的元素,是通过v-for循环渲染出来的,不能使用transition包裹,需要使用transitionGroup。如果要为 v-for循环创建的元素设置动画,必须为每一个元素设置 :key 属性。给transition-group添加appear属性,实现页面刚展示出来时候,入场时候的效果。通过为transition-group元素,设置tag属性,指定transition-group渲染为指定的元素,如果不指定tag属性,默认渲染为span标签。

<transition-group>组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的v-move特性,它会在元素的改变定位的过程中应用v-movev-leave-active结合使用,能够让列表的过渡更加平缓柔和。

举个例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <style>
        li {
            border: 1px dashed #999;
            margin: 5px;
            line-height: 35px;
            padding-left: 5px;
            font-size: 12px;
            width: 100%;
        }
        li:hover {
            background-color: hotpink;
            transition: all 0.8s ease;
        }
        .v-enter,
        .v-leave-to {
            opacity: 0;
            transform: translateY(80px);
        }
        .v-enter-active,
        .v-leave-active {
            transition: all 0.6s ease;
        }
        .v-move {
            transition: all 0.6s ease;
        }
        .v-leave-active {
            position: absolute;
        }
    </style>
</head>
<body>
<div id="app">

    <div>
        <label>
            Id:
            <input type="text" v-model="id">
        </label>

        <label>
            Name:
            <input type="text" v-model="name">
        </label>

        <input type="button" value="添加" @click="add">
    </div>

    <transition-group appear tag="ul">
        <li v-for="(item, i) in list" :key="item.id" @click="del(i)">
            {{item.id}} --- {{item.name}}
        </li>
    </transition-group>
</div>

<script>
    var vm = new Vue({
        el: '#app',
        data: {
            id: '',
            name: '',
            list: [
                {id: 1, name: 'Tom'},
                {id: 2, name: 'Jim'},
                {id: 3, name: 'Lucy'}
            ]
        },
        methods: {
            add: function () {
                this.list.push({id: this.id, name: this.name})
                this.id = this.name = ''
            },
            del: function (i) {
                this.list.splice(i, 1)
            }
        }
    });
</script>
</body>
</html>

组件


组件:以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可。

组件化:是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用;

模块化:是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一。


全局组件定义的三种方式


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <my-com1></my-com1>
    <mycom1></mycom1>
    <mycom2></mycom2>
    <mycom3></mycom3>
    <mycom4></mycom4>
</div>
<!-- 在被控制的 #app 外面,使用template元素,定义组件的HTML模板结构  -->
<template id="tmpl">
    <div>
        <h1>这是通过 template 元素,在外部定义的组件结构,这个方式,有代码的只能提示和高亮</h1>
        <h4>好用,不错!</h4>
    </div>
</template>

<script>
    //方式一:
     //1.1 使用 Vue.extend 来创建全局的Vue组件
     var com1 = Vue.extend({
       template: '<h3>这是使用 Vue.extend 创建的组件</h3>' // 通过 template 属性,指定了组件要展示的HTML结构
     })
     // 1.2 使用 Vue.component('组件的名称', 创建出来的组件模板对象)
      Vue.component('myCom1', com1)
     // 如果使用 Vue.component 定义全局组件的时候,组件名称使用了 驼峰命名,则在引用组件的时候,需要把大写的驼峰改为小写的字母,同时,两个单词之前,使用 - 链接;
     // 如果不使用驼峰,则直接拿名称来使用即可;
      Vue.component('mycom1', com1)

     // Vue.component 第一个参数:组件的名称,将来在引用组件的时候,就是一个 标签形式 来引入 它的;第二个参数: Vue.extend 创建的组件
     Vue.component('mycom2', Vue.extend({
         template: '<h3>这是使用 Vue.extend 创建的组件</h3>'
     }))

    //方式二:
     // 注意:不论是哪种方式创建出来的组件,组件的 template 属性指向的模板内容,必须有且只能有唯一的一个根元素
     Vue.component('mycom3', {
         template: '<div><h3>这是直接使用 Vue.component 创建出来的组件</h3><span>123</span></div>'
     })
    // 方式三:
     Vue.component('mycom4', {
         template: '#tmpl'
     })

    var vm = new Vue({
        el: '#app'
    });
</script>
</body>
</html>

注意:不论是哪种方式创建出来的组件,组件的template属性指向的模板内容,必须有且只能有唯一的一个根元素。


私有组件定义


通过components定义私有组件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <login></login>
</div>
<template id="tmpl2">
    <h3>这是私有组件</h3>
</template>
<script>
    var vm = new Vue({
        el: '#app',
        components: { // 定义实例内部私有组件的
            login: {
                //方式一:
//                template: '<h3>这是私有组件</h3>',
                //方式二:
                template: '#tmpl2'
            }
        }
    });
</script>
</body>
</html>

组件中的data和methods


组件可以有自己的data数据,实例中的data可以为一个对象,但是组件中的data必须是一个方法,这个方法内部,还必须返回一个对象才行。组件中 的data数据,使用方式,和实例中的data使用方式完全一样。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <counter></counter>
    <hr>
    <counter></counter>
</div>
<template id="tmpl">
    <div>
        <input type="button" value="+1" @click="increment">
        <h3>{{count}}</h3>
    </div>
</template>
<script>
    // 这是一个计数器的组件, 身上有个按钮,每当点击按钮,让 data 中的 count 值 +1
    Vue.component('counter', {
        template: '#tmpl',
        data: function () {
            // return dataObj
            return { count: 0 }
        },
        methods: {
            increment() {
                this.count++
            }
        }
    })
    var vm = new Vue({
        el: '#app'
    });
</script>
</body>
</html>

组件切换


v-if方式切换:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <a href="" @click.prevent="flag=true">登录</a>
    <a href="" @click.prevent="flag=false">注册</a>

    <login v-if="flag"></login>
    <register v-else="flag"></register>
</div>
<script>
    Vue.component('login', {
        template: '<h3>登录组件</h3>'
    })
    Vue.component('register', {
        template: '<h3>注册组件</h3>'
    })
    var vm = new Vue({
        el: '#app',
        data: {
            flag: false
        }
    });
</script>
</body>
</html>

使用component元素实现:component是一个占位符,:is属性可以用来指定要展示的组件的名称

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <a href="" @click.prevent="comName='login'">登录</a>
    <a href="" @click.prevent="comName='register'">注册</a>
    <component :is="comName"></component>
</div>
<script>
    // 组件名称是 字符串
    Vue.component('login', {
        template: '<h3>登录组件</h3>'
    })
    Vue.component('register', {
        template: '<h3>注册组件</h3>'
    })
    var vm = new Vue({
        el: '#app',
        data: {
            comName: 'login' // 当前 component 中的 :is 绑定的组件的名称
        }
    });
</script>
</body>
</html>

组件切换动画

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/animate.css@3.5.2/animate.min.css"/>
    <style>
        .v-enter,
        .v-leave-to {
            opacity: 0;
            transform: translateX(150px);
        }
        .v-enter-active,
        .v-leave-active {
            transition: all 0.5s ease;
        }
    </style>
</head>
<body>
<div id="app">
    <a href="" @click.prevent="comName='login'">登录</a>
    <a href="" @click.prevent="comName='register'">注册</a>
    <!-- 通过 mode 属性,设置组件切换时候的模式 -->
    <transition mode="out-in">
        <component :is="comName"></component>
    </transition>
</div>
<script>
    // 组件名称是 字符串
    Vue.component('login', {
        template: '<h3>登录组件</h3>'
    })
    Vue.component('register', {
        template: '<h3>注册组件</h3>'
    })
    var vm = new Vue({
        el: '#app',
        data: {
            comName: 'login' // 当前 component 中的 :is 绑定的组件的名称
        }
    });
</script>
</body>
</html>

父组件向子组件传值


组件中,默认无法访问到父组件中的data上的数据和methods中的方法。父组件可以在引用子组件的时候,通过属性绑定v-bind:的形式, 把需要传递给子组件的数据,以属性绑定的形式,传递到子组件内部,供子组件使用。组件中的所有props中的数据,都是通过父组件传递给子组件的。

注意:props中的数据,都是只读的,无法重新赋值。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <com1 v-bind:parentmsg="msg"></com1>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            msg: '这是父组件中的数据'
        },
        components: {
            com1: {
                template: '<h1 @click="change">这是子组件 --- {{ parentmsg }}</h1>',
                props: ['parentmsg']// 把父组件传递过来的 parentmsg 属性,先在 props 数组中,定义一下,这样,才能使用这个数据
            }
        }
    });
</script>
</body>
</html>

父组件向子组件传方法

父组件向子组件传递方法,使用的是事件绑定机制:v-on, 当我们自定义了一个事件属性之后,那么子组件就能够通过某些方式,来调用传递进去的 这个方法了。子组件通过$emit调用父组件方法。

子组件调用父组件的方法,并传值:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <com2 @func="show"></com2>
</div>

<template id="tmpl">
    <div>
        <h1>这是子组件</h1>
        <input type="button" value="子组件按钮-触发父组件传递过来的方法" @click="myclick">
    </div>
</template>

<script>
    var com2 = {
        template: '#tmpl',
        data() {
            return {
                sonmsg: { name: '子组件data', age: 16 }
            }
         },
        methods: {
            myclick() {
                this.$emit('func', this.sonmsg)
            }
        }
    }
    var vm = new Vue({
        el: '#app',
        data: {
            datamsgFormSon: null
        },
        methods: {
            show(data) {
                this.datamsgFormSon = data
                console.log(this.datamsgFormSon)
            }
        },
        components: {
            com2
        }
    });
</script>
</body>
</html>

组件案例-评论功能实现

存放在localStorage中。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
</head>
<body>
<div id="app">
    <cmt-box @func="loadComments"></cmt-box>

    <ul class="list-group">
        <li class="list-group-item" v-for="item in list" :key="item.id">
            <span class="badge">评论人: {{ item.user }}</span>
            {{ item.content }}
        </li>
    </ul>
</div>
<template id="tmpl">
    <div>
        <div class="form-group">
            <label>评论人:</label>
            <input type="text" class="form-control" v-model="user">
        </div>
        <div class="form-group">
            <label>评论内容:</label>
            <textarea class="form-control" v-model="content"></textarea>
        </div>
        <div class="form-group">
            <input type="button" value="发表评论" class="btn btn-primary" @click="postComment">
        </div>
    </div>
</template>
<script>
    var commentBox = {
        data() {
            return {
                user: '',
                content: ''
            }
        },
        template: '#tmpl',
        methods: {
            postComment() {
                var comment = { id: Date.now(), user: this.user, content: this.content }
                // 从 localStorage 中获取所有的评论
                var list = JSON.parse(localStorage.getItem('cmts') || '[]')
                list.unshift(comment)
                // 重新保存最新的 评论数据
                localStorage.setItem('cmts', JSON.stringify(list))
                this.user = this.content = ''
                this.$emit('func')
            }
        }
    }
    var vm = new Vue({
        el: '#app',
        data: {
            list: []
        },
        created(){
            this.loadComments()
        },
        methods: {
            loadComments() { // 从本地的 localStorage 中,加载评论列表
                var list = JSON.parse(localStorage.getItem('cmts') || '[]')
                this.list = list
            }
        },
        components: {
            'cmt-box': commentBox
        }
    });
</script>
</body>
</html>

通过refs获取元素和组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <input type="button" value="获取元素" @click="getElement" ref="mybtn">
    <h3 ref="myh3">哈哈哈, 今天天气太好了!!!</h3>
    <login ref="mylogin"></login>
</div>

<script>

    var login = {
        template: '<h1>登录组件</h1>',
        data() {
            return {
                msg: 'son msg'
            }
        },
        methods: {
            show() {
                console.log('调用了子组件的方法')
            }
        }
    }
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {
            getElement() {
                console.log(this.$refs.myh3.innerText)
                console.log(this.$refs.mylogin.msg)
                this.$refs.mylogin.show()
            }
        },
        components: {
            login
        }
    });
</script>
</body>
</html>

路由

后端路由:对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源;

前端路由:对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现;在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由。

网站介绍

https://router.vuejs.org/zh/

基本使用

router-view是vue-router提供的元素,专门用来当作占位符的,匹配到的组件,就会展示到router-view中去。

routes:路由匹配规则,每个路由规则,都是一个对象,有两个必须的属性: 属性1是path,表示监听哪个路由链接地址;属性2是component,表示,如果路由是前面匹配到的path,则展示component属性对应的那个组件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="./lib/vue-router-3.0.1.js"></script>
</head>
<body>
<div id="app">
    <a href="#/login">登录</a>
    <a href="#/register">注册</a>
    <router-view></router-view>
</div>
<script>
    var login = {
        template: '<h1>登录组件</h1>'
    }
    var register = {
        template: '<h1>注册组件</h1>'
    }
    var routerObj = new VueRouter({
        routes: [
            {path: '/login', component: login},
            {path: '/register', component: register}
        ]
    })
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        router: routerObj // 将路由规则对象,注册到 vm 实例上,用来监听 URL 地址的变化,然后展示对应的组件
    });
</script>
</body>
</html>
<router-link to="/login" tag="span">登录</router-link>
<router-link to="/register">注册</router-link>

重定向

routes: [ // 路由匹配规则 
    { path: '/', redirect: '/login' }, // 这里的 redirect 和 Node 中的 redirect 完全是两码事
    { path: '/login', component: login },
    { path: '/register', component: register }
]

选中路由高亮

方式一:router-link标签中,默认情况

<style>
    .router-link-active{
        color: red;
        font-weight: 800;
        font-style: italic;
        font-size: 18px;
        text-decoration: underline;
    }
</style>

方式二:修改router-link-active为myactive,然后再定义样式

var routerObj = new VueRouter({
      linkActiveClass: 'myactive'
})

路由传参

方式一:query方式传递参数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="./lib/vue-router-3.0.1.js"></script>
</head>
<body>
<div id="app">
    <router-link to="/login?id=10&name=zs">登录</router-link>
    <router-view></router-view>
</div>
<script>
    var login = {
        template: '<h1>登录组件 --- {{ $route.query.id }} --- {{ $route.query.name }}</h1>',
        created(){ // 组件的生命周期钩子函数
             console.log(this.$route.query.id)
        }
    }
    var register = {
        template: '<h1>注册组件</h1>'
    }
    var routerObj = new VueRouter({
        routes: [
            {path: '/login', component: login}
        ]
    })
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        router: routerObj // 将路由规则对象,注册到 vm 实例上,用来监听 URL 地址的变化,然后展示对应的组件
    });
</script>
</body>
</html>

方式二:params方式传递参数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="./lib/vue-router-3.0.1.js"></script>
</head>
<body>
<div id="app">
    <router-link to="/login/10/zs">登录</router-link>
    <router-view></router-view>
</div>
<script>
    var login = {
        template: '<h1>登录组件 --- {{ $route.params.id }} --- {{ $route.params.name }}</h1>',
        created(){ // 组件的生命周期钩子函数
             console.log(this.$route.params.id)
        }
    }
    var register = {
        template: '<h1>注册组件</h1>'
    }
    var routerObj = new VueRouter({
        routes: [
            {path: '/login/:id/:name', component: login}
        ]
    })
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        router: routerObj // 将路由规则对象,注册到 vm 实例上,用来监听 URL 地址的变化,然后展示对应的组件
    });
</script>
</body>
</html>

路由嵌套

使用children属性,实现子路由。同时,子路由的path前面,不要带/,否则永远以根路径开始请求,这样不方便我们用户去理解URL地址。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="./lib/vue-router-3.0.1.js"></script>
</head>
<body>
<div id="app">
    <router-link to="/account">Account</router-link>
    <router-view></router-view>
</div>

<template id="tmpl">
    <div>
        <h1>这是 Account 组件</h1>
        <router-link to="/account/login">登录</router-link>
        <router-link to="/account/register">注册</router-link>
        <router-view></router-view>
    </div>
</template>
<script>
    var account = {
        template: '#tmpl'
    }
    var login = {
        template: '<h3>登录</h3>'
    }
    var register = {
        template: '<h3>注册</h3>'
    }
    var router = new VueRouter({
        routes: [
            {
                path: '/account',
                component: account,
                children: [
                    { path: 'login', component: login },
                    { path: 'register', component: register }
                ]
            }
        ]
    })
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        router
    });
</script>
</body>
</html>

命名视图实现经典布局

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="./lib/vue-router-3.0.1.js"></script>
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
        }
        .header {
            background-color: orange;
            height: 80px;
        }
        h1 {
            margin: 0;
            padding: 0;
            font-size: 16px;
        }
        .container {
            display: flex;
            height: 600px;
        }
        .left {
            background-color: lightgreen;
            flex: 2;
        }
        .main {
            background-color: lightpink;
            flex: 8;
        }
    </style>
</head>

<body>
<div id="app">
    <router-view></router-view>
    <div class="container">
        <router-view name="left"></router-view>
        <router-view name="main"></router-view>
    </div>
</div>
<script>
    var header = {
        template: '<h1 class="header">Header头部区域</h1>'
    }
    var leftBox = {
        template: '<h1 class="left">Left侧边栏区域</h1>'
    }
    var mainBox = {
        template: '<h1 class="main">mainBox主体区域</h1>'
    }
    var router = new VueRouter({
        routes: [
            {
                path: '/', components: {
                'default': header,
                'left': leftBox,
                'main': mainBox
                }
            }
        ]
    })
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        router
    });
</script>
</body>
</html>

watch

可以监视 data 中指定数据的变化,然后触发这个 watch 中对应的 function 处理函数。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <input type="text" v-model="firstname"> +
    <input type="text" v-model="lastname"> =
    <input type="text" v-model="fullname">
</div>
<script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
        el: '#app',
        data: {
            firstname: '',
            lastname: '',
            fullname: ''
        },
        methods: {},
        watch: {
            'firstname': function (newVal, oldVal) {
                // this.fullname = this.firstname + '-' + this.lastname
                this.fullname = newVal + '-' + this.lastname
            },
            'lastname': function (newVal) {
                this.fullname = this.firstname + '-' + newVal
            }
        }
    });
</script>
</body>
</html>

watch监视路由地址的改变

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="./lib/vue-router-3.0.1.js"></script>
</head>
<body>
<div id="app">
    <router-link to="/login">登录</router-link>
    <router-link to="/register">注册</router-link>
    <router-view></router-view>
</div>
<script>
    var login = {
        template: '<h3>这是登录子组件</h3>'
    }
    var register = {
        template: '<h3>这是注册子组件</h3>'
    }
    var router = new VueRouter({
        routes: [
            { path: '/', redirect: '/login' },
            { path: '/login', component: login },
            { path: '/register', component: register }
        ]
    })
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        // router: router
        router,
        watch: {
            '$route.path': function (newVal, oldVal) {
                // console.log(newVal + ' --- ' + oldVal)
                if (newVal === '/login') {
                    console.log('欢迎进入登录页面')
                } else if (newVal === '/register') {
                    console.log('欢迎进入注册页面')
                }
            }
        }
    });
</script>
</body>
</html>

computed计算属性

计算属性关键词:computed。computed是基于它的依赖缓存,只有相关依赖发生改变时才会重新取值。而使用methods,在重新渲染的时候,函数总会重新调用执行。

在computed中,可以计算属性,本质是一个方法,只不过,我们在使用这些计算属性的时候,是把它们的名称,直接当作属性来使用的,并不会把计算属性,当作方法去调用。

可以说使用 computed 性能会更好,但是如果你不希望缓存,你可以使用 methods 属性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <input type="text" v-model="firstname"> +
    <input type="text" v-model="middlename"> +
    <input type="text" v-model="lastname"> =
    <input type="text" v-model="fullname">
    <p>{{ fullname }}</p>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            firstname: '',
            lastname: '',
            middlename: ''
        },
        methods: {},
        computed: {
            'fullname': function () {
                console.log('ok')
                return this.firstname + '-' + this.middlename + '-' + this.lastname
            }
        }
    });
</script>
</body>
</html>

watchcomputedmethods之间的对比

  1. computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;
  2. methods方法表示一个具体的操作,主要书写业务逻辑;
  3. watch一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是computedmethods的结合体;

webpack

webpack可以解决各个包之间的复杂依赖关系。webpack是基于Node.js开发出来的一个前端项目自动化构建工具,可以完美实现资源的合并、打包、压缩、混淆等诸多功能。

使用Gulp,是基于task任务的;使用Webpack,是基于整个项目进行构建的。

官网

http://webpack.github.io/

webpack安装的两种方式

运行npm i webpack -g全局安装webpack,这样就能在全局使用webpack的命令

在项目根目录中运行npm i webpack --save-dev安装到项目依赖中

配置文件打包命令

创建webpack.config.js文件:

// 导入处理路径的模块
var path = require('path');
// 导出一个配置对象,将来webpack在启动的时候,会默认来查找webpack.config.js,并读取这个文件中导出的配置对象,来进行打包处理
module.exports = {
    entry: path.join(__dirname, 'src/js/main.js'), // 项目入口文件
    output: { // 配置输出选项
        path: path.join(__dirname, 'dist'), // 配置输出的路径
        filename: 'bundle.js' // 配置输出的文件名
    }
}

最后控制台执行webpack。

webpack-dev-server实时打包构建

使用webpack-dev-server来实现代码实时打包编译,当修改代码之后,会自动进行打包构建。

运行cnpm i webpack-dev-server --save-dev安装到开发依赖

package.json文件中,在scripts节点下新增"dev": "webpack-dev-server"指令,然后执行npm run dev,发现可以进行实时打包。

但是dist目录下并没有生成bundle.js文件,这是因为webpack-dev-server将打包好的文件放在了内存中。把bundle.js放在内存中的好处是:由于需要实时打包编译,所以放在内存中速度会非常快。

这个时候访问webpack-dev-server启动的http://localhost:8080/网站,发现是一个文件夹的面板,需要点击到src目录下,才能打开我们的index首页,此时引用不到bundle.js文件,需要修改index.html中script的src属性为:<script src="../bundle.js"></script>

为了直接访问到index首页,可以指定启动的根目录:

"dev": "webpack-dev-server --contentBase src"

同时修改index页面中script的src属性为<script src="bundle.js"></script>

配置package.json

修改package.json的script节点如下,其中--open表示自动打开浏览器,--port 4321表示打开的端口号为4321,--hot表示启用浏览器热更新,--contentBase src表示直接访问到index首页:

"dev": "webpack-dev-server --hot --port 4321 --open --contentBase src"

配置webpack.config.js

webpack.config.js文件,新增devServer节点如下:

devServer:{
    hot:true,
    open:true,
    port:4321,
    contentBase:'src'
}

在头部引入webpack模块:

var webpack = require('webpack');

plugins节点下新增:

plugins:[
    new webpack.HotModuleReplacementPlugin()
]

Vue2.5开发去哪儿网App

TODOList


指令,组件化,父子组件的传值,的使用:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>TODOList</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <input type="text" v-model="inputValue"/>
    <button v-on:click="handleBtnClick">提交</button>
    <ul>
        <todo-item v-bind:content="item"
                   v-bind:index = "index"
                   v-for="(item, index) in list"
                   @delete="handleItemDelete">
        </todo-item>
    </ul>
</div>

<script>
    //全局组件
    // Vue.component("TodoItem", {
    //     props: ['content'],
    //     template: "<li>{{content}}</li>"
    // })
    //局部组件
    var TodoItem = {
        props: ['content','index'],
        template: "<li @click='handleItemDelete'>{{content}}</li>",
        methods: {
            handleItemDelete: function (){
                this.$emit("delete", this.index);
            }
        }
    }
    var app = new Vue({
        el: "#app",
        //局部组件-注册
        components: {
            TodoItem: TodoItem
        },
        data: {
            list: [],
            inputValue: ""
        },
        methods: {
            handleBtnClick: function () {
                this.list.push(this.inputValue)
                this.inputValue = ''
            },
            handleItemDelete: function (index){
                this.list.splice(index, 1)
            }
        }
    })
</script>
</body>
</html>

setter和getter


setter和getter:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    {{fullName}}
</div>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            firstName: "Dell",
            lastName: "Lee"
        },
        computed: {
            fullName: {
                get: function (){
                    return this.firstName + " " + this.lastName
                },
                set: function (value){
                    var arr = value.splice(" ");
                    this.firstName = arr[0];
                    this.lastName = arr[1];
                }
            }
       }
    })
</script>
</body>
</html>

样式的绑定


点击颜色切换。

class方式一:定义class的名字是activated,但是class是否有名字是根据isActivated:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <style>
        .activated {
            color: red;
        }
    </style>
</head>
<body>
<div id="app">
    <div @click="handleDivClick" :class="{activated: isActivated}">
        Hello World
    </div>
</div>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            isActivated: false
        },
        methods: {
            handleDivClick: function (){
                this.isActivated = !this.isActivated
            }
        }
    })
</script>
</body>
</html>

class方式二:定义class的名字的key是activated,切换名字的value是否为activated和空值:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <style>
        .activated {
            color: red;
        }
    </style>
</head>
<body>
<div id="app">
    <div @click="handleDivClick" :class="[activated]">
        Hello World
    </div>
</div>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            activated: ""
        },
        methods: {
            handleDivClick: function (){
                this.activated = this.activated === "activated" ? "" : "activated"
            }
        }
    })
</script>
</body>
</html>

内联style样式,方式一:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <div @click="handleDivClick" :style="styleObj">
        Hello World
    </div>
</div>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            styleObj: {
                color: "black"
            }
        },
        methods: {
            handleDivClick: function () {
                this.styleObj.color = this.styleObj.color === "black" ? "red" : "black"
            }
        }
    })
</script>
</body>
</html>

内联style样式,方式二:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <div @click="handleDivClick" :style=[styleObj]>
        Hello World
    </div>
</div>

<script>
    var vm = new Vue({
        el: "#app",
        data: {
            styleObj: {
                color: "black"
            }
        },
        methods: {
            handleDivClick: function () {
                this.styleObj.color = this.styleObj.color === "black" ? "red" : "black"
            }
        }
    })
</script>
</body>
</html>

组件化中的is


组件化中的is使用:当某些标签下必须要固定标签,这时候使用组件化,可以用is:

比如table下的tr,ul下的li,ol下的li,select下的option,等等

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <table>
        <tbody>
            <tr is="row"></tr>
            <tr is="row"></tr>
            <tr is="row"></tr>
        </tbody>
    </table>
</div>

<script>
    Vue.component(
        "row",{
            template: "<tr><td>this is a row</td></tr>"
        }
    )
    var vm = new Vue({
        el: "#app"
    })
</script>
</body>
</html>

组件化中的refs


子组件通过change通知父组件发生改变,利用refs获取document:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <counter ref="one" @change="handleChange"></counter>
    <counter ref="two" @change="handleChange"></counter>
    <div>{{total}}</div>
</div>

<script>
    Vue.component(
        "counter",{
            template: "<div @click='handlerClick'>{{number}}</div>",
            data: function (){
                return {number: 0}
            },
            methods: {
                handlerClick: function (){
                    this.number ++
                    this.$emit('change')
                }
            }
        }
    )
    var vm = new Vue({
        el: "#app",
        data: {
            total: 0
        },
        methods: {
            handleChange: function (){
                this.total = this.$refs.one.number + this.$refs.two.number
            }
        }
    })
</script>
</body>
</html>

父子组件之间传值


父组件通过属性形式,向子组件传值,子组件通过props接收。

单向数据流:父组件向子组件传值的参数,子组件不要修改,可以让子组件用变量接收后,子组件修改变量。

子组件向父组件传值,通过emit事件的形式。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <counter :count="3" @inc="handleIncrease"></counter>
    <counter :count="2" @inc="handleIncrease"></counter>
    <div>{{total}}</div>
</div>

<script>
    var counter = {
        props: ['count'],
        data: function (){
            return{number: this.count}
        },
        template: '<div @click="handleClick">{{number}}</div>',
        methods: {
            handleClick: function (){
                this.number = this.number + 2;
                this.$emit('inc',2)
            }
        }
    }
    var vm = new Vue({
        el: "#app",
        components: {
          counter: counter
        },
        data: {
            total: 5
        },
        methods: {
            handleIncrease: function (step){
                this.total += step
            }
        }
    })
</script>
</body>
</html>

子组件props


约束父组件传递来的content必须为String:

props: {
    content: String
}

另一种写法:

props: {
    content: {
        type: String
    }
}

约束父组件传递来的content可以为数字或者字符串:

props: {
    content: [Number, String] 
}

约束父组件必须传content:

props: {
    content: {
        type: String,
        required: true
    }
}

设置父组件传递的content有默认值:

props: {
    content: {
        type: String,
        required: false,
        default: 'default value'
    }
}

约束父组件传递来的content长度大于5:

props: {
    content: {
        type: String,
        validator: function (value){
            return (value.length > 5)
        }
    }
}

组件绑定原生事件


在子组件中实现父组件中的点击事件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <child @click="handlerClick"></child>
</div>

<script>
    Vue.component('child', {
        template: '<div @click="handlerChildClick">Child</div>',
        methods: {
            handlerChildClick: function () {
                this.$emit('click')
            }
        }
    })
    var vm = new Vue({
        el: "#app",
        methods: {
            handlerClick: function () {
                alert("click")
            }
        }
    })
</script>
</body>
</html>

组件绑定原生事件,另有一种native写法:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <child @click.native="handlerClick"></child>
</div>

<script>
    Vue.component('child', {
        template: '<div>Child</div>'
    })
    var vm = new Vue({
        el: "#app",
        methods: {
            handlerClick: function () {
                alert("click")
            }
        }
    })
</script>
</body>
</html>

非父子组件传值


Bus/事件总线/发布订阅模式/观察者模式:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <child content="Dell"></child>
    <child content="Lee"></child>
</div>

<script>
    Vue.prototype.bus = new Vue()

    Vue.component('child', {
        data: function (){
          return {
              selfContent: this.content
          }
        },
        props: {
            content: String
        },
        template: '<div @click="handleClick">{{selfContent}}</div>',
        methods: {
            handleClick:function (){
                this.bus.$emit('change', this.selfContent)
            }
        },
        mounted: function (){
            var this_ = this
            this.bus.$on('change',function (msg){
                this_.selfContent = msg
            })
        }
    })
    var vm = new Vue({
        el: "#app"
    })
</script>
</body>
</html>

插槽slot


向子组件中插入标签:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <body-content>
        <div class="header" slot="header">header</div>
        <div class="footer" slot="footer">footer</div>
    </body-content>
</div>

<script>
    Vue.component('body-content', {
        template: '<div><slot name="header"></slot><div class="content">content</div><slot name="footer"></slot></div>'
    })
    var vm = new Vue({
        el: "#app"
    })
</script>
</body>
</html>

作用域插槽slot-scope:

父组件向子组件插入标签,子组件向父组件传值item:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <body-content>
        <template slot-scope="props">
            <li>{{props.item}}</li>
        </template>
    </body-content>
</div>

<script>
    Vue.component('body-content', {
        data: function (){
          return {list:[1,2,3,4]}
        },
        template: '<div><ul><slot v-for="item of list" :item=item></slot></ul></div>'
    })
    var vm = new Vue({
        el: "#app"
    })
</script>
</body>
</html>

项目准备


命令行工具:

node -v

npm -v

git --version

npm install --global vue-cli

vue init webpack 项目文件夹

//运行项目-项目目录下
npm run dev
npm run start

代码结构介绍:

package.json---------------------项目依赖
package-lock.json----------------依赖具体版本
index.html-----------------------首页模板文件
.postcssrc.js--------------------postcssrc配置项
.eslintrc.js---------------------规范
.eslintignore--------------------eslint检测忽略
.editorconfig--------------------编译器语法
.babelrc-------------------------babelrc配置
static---------------------------静态资源
node_modules---------------------依赖包
src-main.js----------------------入口
src-App.vue----------------------根组件-单文件组件
src-router-----------------------路由
src-components-------------------小组件
src-assets-----------------------项目图片资源
config-index.js------------------基础配置
config-dev.env.js----------------开发环境配置
config-prod.env.js---------------线上环境配置
build文件------------------------打包配置

fastclick-解决移动端三秒延迟:

npm install fastclick --save

stylus和stylus-loader:

npm install stylus --save

npm install stylus-loader --save

轮播插件:

npm install vue-awesome-swiper@2.6.7 --save

axios:

npm install axios --save

better-scroll:

npm install better-scroll --save

vuex:

npm install vuex --save

babel-polyfill:

npm install babel-polyfill --save

打包:

npm run build

京东金融

项目准备


nvm:

//mac
https://github.com/nvm-sh/nvm
//windows
https://github.com/coreybutler/nvm-windows

git:

https://www.bootcss.com/p/git-guide/

webpack:

https://webpack.docschina.org/
https://www.webpackjs.com/

babel:

https://www.babeljs.cn/

atom:

https://atom.io/

eslint:

http://eslint.cn/docs/user-guide/configuring

es6:

http://es6-features.org/#Constants

sass:

https://sass.bootcss.com/

vue:

https://cn.vuejs.org

项目


swiper:

npm i vue-awesome-swiper

打包:

npm run build

js压缩:

npm i -D uglifyjs-webpack-plugin

css压缩:

//css-loader
minimize: true

线上调试

source map

构建工具


webpack.config.js文件中:

入口:

entry: {
      app: './app/js/main.js'
}

配置解析构建:

module: {
  loaders: [
    {
      test: /\.html$/,
      loader: 'html-loader'
    }, {
      test: /\.vue$/,
      loader: 'vue-loader',
    }, {
      test: /\.scss$/,
      loader: 'style-loader!css-loader!sass-loader'
    }
  ]
},

插件:

plugins:[]

输出:

output: {
  filename: '[name].min.js',
  path: path.resolve(__dirname, 'dist')
}

devServer(端口等配置):

devServer: {
  contentBase: './dist',
  hot: true,
  compress: true,
  port: 9000,
  clientLogLevel: "none",
  quiet: true
},

配置:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

resolve:

resolve: {
  extensions: [
    '.js', '.vue', '.json'
  ],
  alias: {
    'vue$': 'vue/dist/vue.esm.js'
  }
},

头条

初始化项目


脚手架:

npm install --global @vue/cli

创建项目:

vue create toutiao-vue

Vue CLI v4.5.13
? Please pick a preset: Manually select features

? Check the features needed for your project:
 (*) Choose Vue version
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 (*) Router
 (*) Vuex
 (*) CSS Pre-processors
>(*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing
 
? Choose a version of Vue.js that you want to start the project with 2.x
 
? Use history mode for router? (Requires proper server setup for index fallback in production) No
 
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
 
? Pick a linter / formatter config: Standard

? Pick additional lint features:
 (*) Lint on save
>(*) Lint and fix on commit (requires Git)

? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

? Save this as a preset for future projects? (y/N)n

运行项目:

npm run serve

vant:

http://github.com/youzan/vant
https://youzan.github.io/vant/#/zh-CN/

Rem 布局适配:

npm i amfe-flexible
//main.js
import 'amfe-flexible'

//-D 是 --save-dev 的简写
npm install postcss-pxtorem -D
npm install postcss-pxtorem@5.1.1 -D
//项目根目录中创建 `.postcssrc.js` 文件
module.exports = {
  plugins: {
    'autoprefixer': {
      browsers: ['Android >= 4.0', 'iOS >= 8']
    },
    'postcss-pxtorem': {
      rootValue: 37.5,
      propList: ['*']
    }
  }
}

注意:该工具的rem转换,行内样式不会转,例如 <div style="width: 200px;"></div>

网络封装:

npm i axios

Element

介绍


Element:是饿了么公司前端开发团队提供的一套基于 Vue 的网站组件库,用于快速构建网页。

Element 提供了很多组件(组成网页的部件)供我们使用。例如 超链接、按钮、图片、表格等等~

官网网址是

https://element.eleme.cn/#/zh-CN


文章作者:
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 !
  目录