一、关于Vue.js

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

总体来说作为一门前端框架,Vue.js本身可以最大程度减少用户操作Dom的工作

Vue.js可以将简洁的模板语法来声明式的将数据渲染到页面中,且声明的数据是双向的。而且对于一些特殊的标签可以通过绑定元素特性。例如

<div id="app">
    姓氏:<input type="text" v-model = 'firstname'> 
    名字:<input type="text" v-model = 'lastname'>
    <p>{{firstname+lastname}}</p>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
    const app = new Vue({
        el:'#app',  //绑定id=app的元素   
        data: {     //声明数据
            firstname:'',
            lastname:''
        }
    })

解释一下上例,功能:输入姓氏和名字来合成名字。

首先为两个Input绑定了v-model,因为数据是双向的,默认值为Vue构造函数中声明的“”,但是当修改文本框中的值时,app.firstnameapp.lastname也会对应发生改变。

三、 指令

在上例中使用的v-model就是一个指令,它可以用于绑定<input>、<select>、<textarea>、components标签的数据。指令一般都以v-开头。

3.1 用v-on指令绑定监听

基本格式:(事件方法内容可以直接为Js语句也可以是方法名)简写格式为@

v-on: 监听方式='事件方法'

例如:功能:输入价格,可以利用按钮来增减数量,并显示价格。通过v-on指令来绑定click事件,

方法应当挂载在methods下。

<div id="app">
    <div>
        <label for="">价格</label>
        <input type="text" v-model='price'>
    </div>
    <div>
        数量:<button v-on:click='jian'>-</button><span>{{ number }}</span><button v-on:click='jia'>+</button>  
    </div>
    <div>
        <strong>{{ price*number }}</strong>
    </div>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
    new Vue({
        el:'#app',
        data:{
            price:0,
            number:0
        },
        methods:{
            jian:function(){
                --this.number
            },
            jia:function(){
                ++this.number
            }
        }
    })
</script>

3.2 条件与循环

  • v-if作为条件指令,在引号内,值可以为绑定的数据,也可以直接填写truefalse,

    当然还包括v-elsev-else-if进行配套使用

<div id="app3">
     <span v-if='true'>NiKon</span>
     <span v-if='nonSeen'>Canen</span>
 </div>
new Vue({
    el:"#app3",
    data:{
        nonSeen: true
    }
})

结果:NiKon Canen

  • v-for作为循环指令。在要要遍历的标签中绑定该指令,值的格式为‘xx[,index] in xxs’`
<div id="app4">
    <ul>
        <li v-for='todo,index in todos'>
            {{ index+'·'+todo.text }}
        </li>
    </ul>
</div>
new Vue({
    el:'#app4',
    data: {
        todos:[
            {
                index:1,
                text:'wowo'
            },
            {
                index:2,
                text:'wowo'
            },
            {
                index:3,
                text:'wowo'
            }
        ]
    }
})

结果:

0·wowo

1·wowo

2·wowo

3.3 v-model指令

用于作为表单控件的双向绑定的指令,格式:v-model="xxx"

3.4 v-bind属性渲染指令

对于原有的HTML标签属性可以通过v-bind来渲染,为单向数据绑定。简写格式为:

<input type="text" v-bind: value="xxx">
或者
<input type="text" :class="{xxx:true of false}">

3.5 v-once只渲染一次指令

数据不再绑定,仅在在第一次加载页面时进行渲染。

3.6 v-text和v-cloak指令

在vue管理的模板入口节点作用v-cloak

这两条指令是为了解决模板语法在页面加载时会出现页面抖动。

<h1 v-text="message" ><h1>
<style>
    [v-cloak]{
        display:none;
    }
</style>
<div v-cloak>
    <h1>{{ message }}</h1>
</div>

一般v-text使用较少

3.7 v-show显示标签指令

这条指令是在渲染层上将改标签隐藏。

<h1 v-show="false">hehe</h1>

v-if不同的是,该指令是将改标签的样式设置为display: none,而v-if是直接将该标签从Dom树中移除。

3.8 v-pre跳过编译指令

跳过不需要Vue编译的节点和其子节点,显示其原始内容。可以加快编译。😂

四、 自定义指令

如果到了不得不自己去操作Dom元素时,且没有Vue内置指令来实现需求时。可以使用自定义指令来解决需求。

4.1 定义自定义指令

自定义指令分为全局和局部,即可以定义为在所有组件下都可以使用的自定义指令,也可以定义为只能在当前组件下使用的自定义指令。

4.1.1 全局自定义指令
Vue.directive('指令名',{
    钩子函数
})
4.1.2 局部自定义指令
new Vue({
    ...,
    directives:{
        指令名:{
            钩子函数
        }
    },
    ...
})

4.2 钩子函数

钩子函数就是当自定义指令在何时出发什么样的动作

形式都为钩子名:function([el[,binding]])

其中,el是当前绑定指令的Dom元素,而binding是指令对象其中value属性包含了指令被赋的值。

Vue官方文档中给出的钩子函数有5个:

  • bind -在指令被绑定时执行一次,无法读取被绑定的Dom元素的父节点el.parentNode
  • inserted -和bind一样是在指令被绑定时执行一次,但是在bind后面执行。并且可以读取被绑定的Dom元素的父节点
  • update -当指令所在的被Vue管理的整个模板更新时执行,binding.value是更新后的值
  • componendUpdated -和update一样,但是binding.value是更新前的值
  • unbind -当指令被解绑时执行,例如清除定时器(移除Dom时自动解绑)

4.3 复现一个v-show

<div id="app">
    <input type="text" v-focus>
    <div style="width: 100px;height: 100px ;background-color:#005151" v-myshow = "show" ></div>
</div>
4.3.1 全局定义方式
Vue.directive('myshow',{
    bind:function(el,binding){
        if(binding.value){
            el.style.display = "block"
        }else{
            el.style = "none"
        }
    },
    // inserted:function(){
    //     console.log("inserted  is  called")
    // },
    update:function(el,binding){
        if(binding.value){
            el.style.display = "block"
        }else{
            el.style.display= "none"
        }
    },
    // componentUpdated:function(){
    //     console.log("componentUpdated  is  called")
    // },
    // unbind:function(){
    //     console.log("unbind  is  called")
    // }
})

大多数情况都是bind & update钩子函数配合使用的比较多,且大多情况下两者的代码是一样的,所以可以使用简写方式

Vue.directive('myshow',function(el,binding){
        if(binding.value){
            el.style.display = "block"
        }else{
            el.style.display= "none"
        }
})
4.3.2 局部定义
app = new Vue({
    el:'#app',
    data:{
        show: true
    },
    directives:{
        myshow:function(el,binding){
        if(binding.value){
            el.style.display = "block"
        }else{
            el.style.display= "none"
        }
        }
    }

五、 计算属性和侦听器

5.1 计算属性computed

计算属性要在computed对象中定义。

计算属性的本质就是方法,在使用时就像data中定义的属性那样去使用。

每一个计算属性都必须有一个getter,还可以根据需求来加入一个setter。

  1. 只包含getter的计算属性的一般使用方法
computed:{
    remaingCount:{
        get(){
                  return this.todoList.filter(item=>!item.completed).length || 0
              }
    }
}
  1. 对于只包含getter的计算属性还有一种简写方法
computed:{
    remaingCount(){
        return this.todoList.filter(item=>!item.completed).length || 0
    }
},
  1. 同时存在getter和setter的标准计算属性定义方式
computed:{
    toggleAll:{
        get(){
            return this.todoList.every(item => item.completed)
        },
        set([value]){
            let boolean = !this.toggleAll
            this.todoList.forEach(element => {
                element.completed = boolean
            });
        }
    }
}

5.2 侦听器watch

侦听器要在watch对象中定义。

侦听器的作用是每当侦听对象发生改变时就作出反应。

同情况下计算属性要比侦听器合适,但是还有一些自定义业务需求需要使用侦听器。

  • hanlder:function(val,oldval) 是在触发监听对象时去执行的那个自己定义的方法
  • deep:boolean 当监听对象为一个引用类型的对象时,只能监听到对象整体,无法监听到对象内的个体。当设置为true时即可同时监听到对象内的个体。
  • immediate:boolean 在监听开始前就会调用该监听器下的hanlder方法
watch:{
    todoList:{
        handler(val,oldVal){
            window.localStorage.setItem("todoList",JSON.stringify(val))
        },
        deep: true  //深度监听
    },
          //只包含handler时的简写方法
    toggleAll(){
        console.log('计算属性也可以被监听哦')
    }
},

六、 组件

Vue中,可以将单独功能的模块可以拆分为一个一个的组件。一个项目的组件构成通常为一个树形结构。一个典型的TodoMVC案例可以拆封为这样一个组件示例图:

img

6.0 组件的使用方式

定义好的属性,只需要像使用html标签一样使用,只不过把标签名换为组建名就可以了

6.1 组件定义方式

组件定义方式和自定义指令一样,有全局和局部之分。

  • 全局定义
Vue.component('myComponent',{
    template:`<p>hello</p>`,
    data:function(){
        return {
            ...,
            ...
        }
    }
})
  • 局部定义
new Vue({
    el:'#app',
    template:`<app></app>`
    components:{
        app:{
            template = `<header></header> .. `,
            components:{
            header:{
             template:`<p>hello i am header</p>`,
                data:function(){
                    return {
                            ...,
                        ...
                    }
                },
                //当然组件中也可以放子组件
                components:{
                    ....
                }
            },
            ...其他组件
            }
        }
    }
})

在Vue根实例中创建的这些组件的父子节点可能不同,但是他们的根组件都是这个Vue实例。因为这个Vue实例本身也是一个组件。

6.2 组件间的通信

6.2.1 直接父子通信
  • 父传子通信(prop down):

要将父组件中的属性值传送给其子组件

  1. 需要在父组件中的模板中调用子组件的部分加上v-bind属性(如传送name属性给其子组件son)

    const Father = {
        template: `<son v-bind:name="name"></son>`,
        data(){
            return {
                name:"jack"
            }
        }
        componends:{
            son
        }
    }
  2. 在子组件体中,使用props数组属性将name接收过来,之后就可以像自己的data定义的属性一样去用了。

    const son = {
        template:`<p> my father is {{ name }}</p>`,
        props:["name"]
    }

注意:Vue不允许在子组件去修改基本类型的*父组件传过来的值。但是很不幸。因为传过来的数据如果是引用l类型的话,你可以修改成功,且不会报错,但是请不要这样去做!,如有子元素要修改穿过来的值的需求,请使用子传夫通信方法

  • 子传父通信(Event up)

父组件的值就要在父组件中进行,所以子组件可以把修改好的父组件数据整一个副本(不要修改原数据),然后通过订阅事件的方法来传给父组件。

  1. 再子组件的方法中生成数据副本

    const son = {
        template:`<button @click = "handleUpdateName"> my father is {{ name }}</button>`,
        props:["name"],
        methods:{
            handleUpateName(){
                let value = 'maojiankai'
                this.$emit('UpdatedName',value) // 向父组件提交UpdatedName事件
            }
        }
    }
  2. 在父组件中订阅对应事件并处理数据

    const Father = {
        template: `<son v-bind:name="name" @UpatedName="handleName"></son>`, //订阅UpdatedName事件
        data(){
            return {
                name:"jack"
            }
        },
        componends:{
            son
        },
        methods:{
            handleName(name){ //对应的处理
                this.name = name 
            }
        }
    }
  • 非父子关系的组件通信

    • 简单环境中 Event Bus
    1. 设组件a,b非父子关系但他们同属于一个Vue根实例,所以在Vue的根实例之前创建一个名字为bus的空Vue实例
    var bus = new Vue()
    1. 在组件A中向bus提交自定义事件
    bus.$emit('Edited',value)
    1. 在组件B的创建钩子函数中,订阅bus中组件A提交的事件
    bus.$on('Edited',function(e){
        ...
    })
    • 复杂环境中 VueX(状态管理模式)

七、插槽

7.1 关于插槽solt

在父组件调用子组件时,会使用子组件的模板HTML内容,但是如果父组件想要加入一些自己的内容到使用子组件的的模板HTML内容可能就是个问题了,尤其是当父组件多次引用子组件时,想要加入不同得内容到子组件模板当中去。

为此,Vue提供了一个插槽—slot标签。在子组件得template中加入一个slot标签后,父组件只需在引用子组件的标签内加入自己想加入的内容即可。

需要说明的是,如果想要插槽具有默认的内容,只需要在加入的slot标签内添加你需要的标签即可。

<div id='app'>
        <cnp><button>hehe</button></cnp>
        <cnp></cnp>
    </div>
<template id="cnp">
    <div>
        <h2>我是组件</h2>
        <p>123456789</p>
        <slot ><button>footer</button></slot>
    </div>
</template>
const app = new Vue({
    el:'#app',
    components:{
        cnp:{
            template:'#cnp'
        }
    }
})

7.2 具名插槽

如果子组件存在多个不同的插槽,当你在调用时只想改动其中的一小部分插槽,其他的插槽都保留其原始值时,就需要用到Vue提供的具名插槽了。

具名插槽的定义是在插槽的name属性上设置插槽的名称

<template>
    <slot name="left"><span>left</span></slot>
    <slot name="center"><span>center</span></slot>
    <slot name="right"><span>right</span></slot>
    <slot><span>defalut</span></slot>
</template>

具名插槽的使用需要在先使用设置了v-slot:name指令的template标签包裹要改动的内容

<div id='app'>
    <cnp>
        <template v-slot:center>
            <h1 >123</h1>
        </template>
    </cnp>
    <cnp></cnp>
</div>

7.3 作用域插槽

首先是Vue的编译作用域。

摘自Vue官网:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

其实很意思很简单,比如:

<div id="app">
    <cnp>
        <button v-show='show'>hello</button>
    </cnp>
</div>

<!-- 子组件模板 -->
<template id='cnp'>
    <div>
        <h1>我是模板</h1>
        <slot v-show='show'></slot>
    </div>
</template>

<!-- 入口Vue实例 -->
<script>
    const app = new Vue({
        el:'#app',
        data:{
            show:false
        },
        components:{
            cnp:{
                template:'#cnp',
                data(){
                    return {
                        show:true
                    }
                }
            }
        }
    })
</script>

通过不断调试Vue实例和组件cnp中的show,可以看出。模板tempate属于组件cnp,所以模板中的show是组件cnp中的show,而在实际调用中的show是属于Vue实例中的show。

这说明Vue的是具有编译作用域的,且父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

虽然Vue的编译作用域是合理且安全的。但是影响到了一种插槽需求,就是当引用该子组件时,需要用到一些将子组件的一些数据以不同的形式在组件的插槽内显示。

所以,Vue提供了作用域插槽。

定义v-bind:属性名='表达式'

<template id="cnp">    <div>        <h2>我是组件</h2>        <p>123456789</p>        <!-- 定义一个随意名称的属性 -->        <slot :data='pLanguages'></slot>    </div></template>

使用v-solt:name或者default='数据名' (name是具名时用于替换指定插槽,default是默认插槽)

   <div id='app'>
       <cnp>
           <!-- 完整的 -->
           <template v-slot:default='slot'>
               <span>{{ slot.data.join(' - ') }}</span>
           </template>
       </cnp>
</div>

如果像上例一样使用的是默认插槽,还可以使用简写方式

简写时请勿与具名插槽混用

  <div id='app'>
       <cnp>
           <!-- 简写的 -->
           <template v-slot='slot'>
               <span>{{ slot.data.join(' - ') }}</span>
           </template>
       </cnp>
</div>xxxxxxxxxx   <div id='app'>       <cnp>           <!-- 简写的 -->           <template v-slot='slot'>               <span>{{ slot.data.join(' - ') }}</span>           </template>       </cnp></div>   <div id='app'>       <cnp>           <!-- 简写的 -->           <template v-slot='slot'>               <span>{{ slot.data.join(' - ') }}</span>           </template>       </cnp></div>

如果是多个插槽,则需要使用完整的使用格式。

Example

比如现在要来实现将子组件中提供的编程语言数组在插槽中以两种不同形式显示

<div id='app'>
    <cnp>
        <template v-slot:default='slot'>
            <span>{{ slot.data.join(' - ') }}</span>
        </template>
    </cnp>
    <cnp>
            <template v-slot='slot'>
                    <span>{{ slot.data.join(' * ') }}</span>
                </template>
    </cnp>
</div>

<template id="cnp">
    <div>
        <h2>我是组件</h2>
        <p>123456789</p>
        <slot :data='pLanguages'></slot>
    </div>
</template>    

<script>

    const app = new Vue({
        el:'#app',
        components:{
            cnp:{
                template:'#cnp',
                data(){
                    return {
                        pLanguages:['Java','C++','JavaScript','Python','Php']
                    }
                }
            }
        }
    })
</script>

一个好奇的人