美文网首页Web前端On the Road(成为大牛)VUE我爱编程
Vue.js学习笔记-基础部分+完整实现代码

Vue.js学习笔记-基础部分+完整实现代码

作者: 冥冥2017 | 来源:发表于2017-04-27 16:40 被阅读1737次

    下载安装搭建环境

    • 可以选npm安装,或者简单下载一个开发版的vue.js文件
    • 浏览器打开加载有vue的文档时,控制台提示可以安装vue控制台插件,于是去chrome商店安装,Firefox的插件是通过模拟chrome插件来辅助安装的。但是第一次点击Vue Devtools提示“vue.js is not detected”,咦,原来我忘记勾选“允许访问文件地址”,勾选后就可以正常运行了。

    第一个demo

    Hello Vue!
    关键词:模板语法、声明式

    这里有个小bug,JSFiddle Hello World例子中,并没有为new Vue赋值变量,于是教程中接下来说的,“修改app.message”就没法实现,认真看教程中代码可以看到它为其赋值命名为app,下一个例子命名为app-2,命名后就可以在控制台采用“名称.属性”的形式访问修改内容了。

    第二个demo

    新指令:v-bind
    用法

    • html——v-bind:title="message"
    • js——data:{message:'内容'}

    新指令:v-if
    用法:根据值的真假决定是否渲染DOM内容
    原理:Vue可以绑定DOM结构到数据


    新指令:v-for
    用法

    • html——<li v-for="todo in todos">{{todo.text}}</li>
    • js——data:{todos:[{text:"内容1"},{text:"内容2"},{text:"内容3"}]}

    原理:v-for是一个包装过的for in循环,对应的data内需要有一个数组


    新指令:v-on
    用法

    • html——绑定事件----v-on:click="事件名字"
    • js——methods:{事件名字:function(){处理内容}}

    新指令:v-model
    用法:

    • html——<input v-model="message">
    • js——data:{message:"hello vue"}

    原理:v-model可实现表单输入和应用状态的双向绑定,修改表单内容,message对应值也会修改。

    组件化应用构建

    • 注册组件
    Vue.component('todo-item',{
          template:'<li>这是一个待办项</li>'
    })
    
    • 使用
    <ol>
        <todo-item></todo-item>
    </ol>
    

    但是这样实现,会为每个元素绑定相同文本,Vue可以将数据从父作用域传到子组件。

    • 注册组件
    Vue.component('todo-item', {
      props: ['todo'],
      template: '<li>{{ todo.text }}</li>'
    })
    
    • 创建元素并v-bind绑定数据到一个对象
    <div id="app-7">
      <ol>
        <todo-item v-for="item in groceryList" v-bind:todo="item"></todo-item>
      </ol>
    </div>
    
    • 创建对象
    var app7 = new Vue({
      el: '#app-7',
      data: {
        groceryList: [
          { text: '蔬菜' },
          { text: '奶酪' },
          { text: '随便其他什么人吃的东西' }
        ]
      }
    })
    
    • props是作为一个接口存在的。
    • Vue组件非常类似于自定义元素,但是有一些纯自定义元素不具备的重要功能,包括跨组件数据流,自定义事件通信和构建工具集成。

    Vue实例

    • Vue 构造器
    • Vue.extend({})可以扩展Vue构造器
    • 每个Vue实例都会代理其data对象里面的所有属性,即实例.属性===data.属性
    • 实例引用内部属性和方法需要加前缀$
    vm.$el===document.getElementById('exzample');
    
    • 实例声明周期与钩子
      created是实例被创建后调用,还有mounted、updated、destroyed,钩子的this指向调用它的实例

    模板语法

    • 文本:双大括号的文本插值(默认是响应式的,对应属性的值变化,此处内容会更新)
    <span>Message:{{ msg}}</span>
    

    也可以通过v-once执行一次性插值,不再更新内容,但是这会影响这个节点的所有数据绑定

    <span v-once>This will never change:{{message}}</span>
    
    • 纯HTML:用v-html可以输出真正的html
    <div id="app-8" v-html='rawHtml'>   </div>
    var app8=new Vue({
            el:'#app-8',
            data:{
                rawHtml:'<li>纯html</li>'
            }
        })
    //输出:● 纯html
    

    但是要注意只对可信内容使用html插值,不然容易导致xss攻击

    • 属性:html属性不能用双大括号语法,需要用v-bind。
    <div id="app-2">
        <span v-bind:title="message">悬停几秒看信息</span>
    </div>
    
    • 所有的数据绑定都可以用js表达式
    {{ number + 1 }}
    {{ ok ? 'YES' : 'NO' }}
    {{ message.split('').reverse().join('') }}
    <div v-bind:id="'list-' + id"></div>
    

    然而,语句和流控制是不行滴,三元表达式是可以的

    {{var a = 1 }}
    {{if(ok){return message}}}
    
    • 指令:带有v-前缀的特殊属性,它的值预期是单一js表达式,指定的职责是,当表达式的值改变时相应的将某些行为应用到DOM上。
    • 部分指令接收一个参数,在指令后以冒号指明,比如v-bind:href="url"
      还比如v-on:click="方法名"绑定一个事件
    • 修饰符,以半角句号指明的特殊后缀,指出一个指令应该以特殊方式绑定,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():
    <form v-on:submit.prevent="onSubmit"></form>
    
    • 过滤器:大括号语法和v-bind表达式,用于文本转换
    <div id="app-9">{{message|format}}</div>
    var app9=new Vue({
            el:'#app-9',
            data:{
                message:'hello,Filters!'
            },
            filters:{
                format:function(value){
                    value=value.toLowerCase()
                return value.charAt(0).toUpperCase()+value.slice(1)
                }
            }
        })
    

    过滤器可以串联,可以接受参数

    • 缩写:v-bind缩写为空,后接冒号
    <a v-bind:href="url"></a>
    <a :href="url"></a>
    

    v-on缩写为@,并省略了后面的冒号

    <a v-on:click="doSomething"></a>
    <a @click="doSomething></a>
    

    计算属性

    • 声明一个计算属性,其后的函数,将会用作调用属性时的getter
      computed:{内容}——computed是关键字
    • computed计算属性 vs methods函数调用:
      计算属性基于依赖进行缓存,只要message不变,多次访问都是返回之前的结果,而函数调用是只要发生重新渲染就会执行该函数——例如,在计算缓存里返回Date.now(),不再更新,因为Date.now()不是响应式依赖。
    • computed vs watch
      watch用来观测,但是有些情况用computed更方便。
    • computed不止有getter,还有setter,需要显式设置:
    var app11=new Vue({
            el:'#app-11',
            data:{
                firstName:'Foo',
                lastName:'Bar'
            },
            computed:{
                fullName:{
                    get:function(){
                        return this.firstName+' '+this.lastName
                    },
                    set:function(newValue){
                        var names = newValue.split(' ')
                        this.firstName=names[0]
                        this.lastName=names[names.length-1]
                    }   
                }
                
            }
        })
    

    class和style绑定

    对class的增强绑定:

    • 传入一个对象:
    <div id="app-01" v-bind:class="{active:isActive}"></div>
    var app01=new Vue({
            el:'#app-01',
            data:{
                isActive:true
            }
        })
    //div class="active"
    
    • v-bind:class可与普通class属性共存,并可以传入更多属性来动态切换:
    <div class="static"
         v-bind:class="{ active: isActive, 'text-danger': hasError }">
    </div>
    
    • 还可以绑定为一个对象:
    <div id="app-02" v-bind:class='classObject'></div>
    var app02=new Vue({
            el:'#app-02',
            data:{
                classObject:{
                active:true,
                'text-danger':false
                }
            }
            
        })
    
    • 还可以结合计算属性,加入函数
    computed: {
      classObject: function () {
        return {
          active: this.isActive && !this.error,
          'text-danger': this.error && this.error.type === 'fatal',
        }
      }
    }
    

    总结:只要冒号后的数据形式为‘名称:布尔值’就可以

    • 可以传入数组
      数组语法涉及逻辑判断很少,可以理解为对象语法的组合版。
    <div v-bind:class="[activeClass, errorClass]">
    data: {
      activeClass: 'active',
      errorClass: 'text-danger'
    }
    <div v-bind:class="[{ active: isActive }, errorClass]">
    
    • 可以用在组件上
    <my-component v-bind:class="{ active: isActive }"></my-component>
    

    和内置元素的使用差不多

    对style样式增强绑定:
    由于默认格式里就包括一对引号,要注意单双引号的区分,不然会出错

    <div id='app-03' v-bind:style='{color:activeColor,fontSize:fontSize + "px"}'>
    <p>1111</p>
    </div>
    var app03=new Vue({
            el:'#app-03',
            data:{
                activeColor:'red',
                fontSize:30
            }
        })
    

    条件渲染

    • v-if/v-else
    <div id="app-04">
        <p v-if="ok">Yes</p>
        <p v-else>No</p>
    </div>
    var app04=new Vue({
            el:'#app-04',
            data:{
                ok:false
            }
        })
    
    • template可以作为一个包装元素,包裹多个需要同一个v-if的元素
    <div id="app-04">
        <template v-if="ok">
        <h1>Title</h1>
        <p>Para</p>
        </template>
        <p v-else>No</p>
    </div>
    
    • 还有一个v-else-if作为链式使用判断
    • template包装起来,用v-if和v-else判断的组件,默认是复用的,不想复用,给各自添加一个不同名称的key就可以了。
      复用的如下:
    <div id="app-05">
    <template v-if="loginType==='username'">
        <label for="">Username</label>
        <input placeholder="Enter your username">
    </template> 
    <template v-else>
        <label for="">Email</label>
        <input placeholder="Enter your email address">
    </template>
    <button v-on:click="toggle">Toggle login type</button>
    </div>
    var app05=new Vue({
            el:'#app-05',
            data:{
                loginType:'username'
            },
            methods:{
                toggle:function(){
                    this.loginType==='username'?this.loginType='email':this.loginType='username'
                }
            }
    
        })
    

    不复用的如下:

    <div id="app-05">
    <template v-if="loginType==='username'">
        <label>Username</label>
        <input placeholder="Enter your username" key="username">
    </template> 
    <template v-else>
        <label>Email</label>
        <input placeholder="Enter your email address" key="email">
    </template>
    <button v-on:click="toggle">Toggle login type</button>
    </div>
    
    • v-show始终都会渲染,简单的切换display属性,不支持template语法,也不支持v-else
    <h1 v-show='ok'>222</h1>
    

    v-show vs v-if
    v-show适合频繁切换,v-if适合条件不太改变的情况

    列表渲染

    • 基本用法:v-for = "item in items",items是源数组,item是数组元素迭代的别名
    <ul id="app-06">
        <li v-for="item in items">{{item.message}}</li>
    </ul>
    var app06=new Vue({
            el:'#app-06',
            data:{
                items:[
                {message:'Foo'},
                {message:'Bar'}
                ]
            }
        })
    
    • v-for支持索引参数,但是是从0开始滴(改为{{index+1}}就是从1开始了),并且拥有对父作用域的完全访问权限,可以引用其他属性,比如例子中的parentMessage
    <ul id="app-06">
        <li v-for="(item,index) in items">{{parentMessage}}-{{index}}-{{item.message}}</li>
    </ul>
    
    • 还可以用of替代in作为分隔符,更接近js迭代器
    • 支持template组合语句单元
    <ul id="app-07">
        <template v-for="(item,index) in items">
            <span>组合列表{{index+1}}</span>
            <li>{{item.message}}</li>       
        </template>
    </ul>
    
    • 对象迭代
      v-for = "value in object"——object是一个拥有多个属性的对象,不再是数组。
      不同于数组迭代,对象迭代是三个参数,分别为迭代内容,迭代键值和参数:(item,key ,index)顺序固定
    <ul id="app-08">
        <li v-for="(item,key,index) in object">
            {{item}}-{{key}}-{{index}}
        </li>
    </ul>
    
    • v-for也可做简单的数字循环:
    <ul id='app-09'>
      <li v-for="n in 10">{{n}}</li>
    </ul>
    

    但是同样需要建立Vue对象,

    var app09=new Vue({
            el:'#app-09'
        })
    
    • ‘就地复用’的选择:与v-if相同,添加唯一key属性值可以不复用,v-for中最好绑定一个唯一id
    <ul id="app-10" >
    <li v-for="(item,index) in items"  :key='item.id=index+3'>{{item.message}}-{{index}}-{{item.id}}</li>
    </ul>
    

    这里我绑了元素的index在id上

    • 包含一组观察数组的变异方法:
    • push()——数组末端添加新项(返回新长度)
    • pop()——删除数组最后一项(返回元素值)
    • shift()——删除数组第一项(返回元素值)
    • unshift()——数组头部添加新项(返回新长度)
    • splice()——添加或删除项目
    • sort()
    • reverse()
    • 包含几个非变异数组方法(变异是指改变原数组):
    • filter()
    • concat()
    • slice()
    • 数组操作方法的局限:
    • 不能通过索引值直接设置一个项:
    vm.items[indexOfItem] = newValue
    
    • 但是可以用set方法设置:
    Vue.set(example1.items,indexOfItem,newValue)
    
    • 或者用万能的splice:
    example1.items.splice(indexOfItem,1,newValue)
    

    注意这里的第二个参数为"1",表示原地替换原元素

    • 不能直接修改数组长度:
    vm.items.length = newLength
    

    但是依旧可以用万能的splice:

    example1.items.splice(newLength)
    第二个参数为删除个数,不填第二个参数时表示删除到末尾
    

    此处翻阅了犀牛书和高程,高程中没提到splice()省略第二个参数的情况,犀牛书提到了,省略第二个参数,从起始点到结尾的所有元素都将被删除。查了ecma-262的文档,原文是:

    5  If the number of actual arguments is 0, then
         a. Let insertCount be 0.
         b. Let actualDeleteCount be 0.
    6  Else if the number of actual arguments is 1, then 
         a. Let insertCount be 0. 
         b. Let actualDeleteCount be len ‑ actualStart.
    
    • 显示过滤、排序结果
    • 使用计算属性computed
    <ul id="app-11">
        <li v-for="n in evenNumbers">{{n}}</li>
    </ul>
    var app11=new Vue({
            el:'#app-11',
            data:{
                numbers:[1,2,3,4,5]
            },
            computed:{
                evenNumbers:function(){
                    return this.numbers.filter(function(number){
                        return number%2 === 0
                    })
                }
            }
        })
    
    • 使用方法,methods——区别很小,引用时需要传入参数
    <ul id="app-11">
        <li v-for="n in evenNumbers(numbers)">{{n}}</li>
    </ul>
    var app11=new Vue({
            el:'#app-11',
            data:{
                numbers:[1,2,3,4,5]
            },
            methods:{
                evenNumbers:function(){
                    return this.numbers.filter(function(number){
                        return number%2 === 0
                    })
                }
            }
        })
    

    事件处理器

    • 最简单的把处理代码写在v-on:click里:
    <div id="app-12">
        <button v-on:click="counter+=1">增加1</button>
        <p>这个按钮被点击了{{counter}}次</p>
    </div>
    var app12=new Vue({
            el:"#app-12",
            data:{
                counter:0
            }
        })
    
    • 命名一个方法,把方法名写在v-on:click后
    • 可以传参数
    <div id="app-13">
        <button v-on:click="say('hi')">1</button>
        <button v-on:click="say('what')">2</button>
    </div>
    var app13=new Vue({
            el:'#app-13',
            data:{
                message:'new event!'
            },
            methods:{
                say:function(m){
                    alert(m)
                }
            }
        })
    
    • 可以访问原生DOM事件,用特殊变量$event传入
    <div id="app-14">
        <button v-on:click="mod('event is modified',$event)">submit</button>
    </div>
    var app14=new Vue({
            el:'#app-14',
            methods:{
                mod:function(message,e){
                    if(e)e.preventDefault()
                    alert(message)
                }
            }
        })
    

    代码中点击button会触发一个click事件,把event作为参数传入,就可以对这个事件进行操作

    • 事件修饰符
      从方法中拆分出了事件处理过程给指令,表达更直观。
    • .stop——阻止冒泡
    • .prevent——?
    • .capture——捕获事件
    • .self——该元素本身(而不是子元素)时触发回调
    • .once——点击事件只会触发一次
    • 按键修饰符:用于监听键盘事件
    <input type="text" id="app-1" v-on:keyup.13="submit()">
    var app1=new Vue({
       el:'#app-1',
       methods:{
           submit:function(){
               alert('success')
           }
       }
    })
    

    keyup.13是"enter"
    还有别名:

    <input type="text" v-on:keyup.13="submit">
    <input type="text" v-on:keyup.enter="submit">
    <input type="text" @keyup.enter="submit">
    
    • .enter
    • .tab
    • .delete
    • .esc
    • .space
    • .up
    • .down
    • .left
    • .right
    • .ctrl
    • .alt
    • .shift
    • .meta
    • 还可以自定义别名:
    Vue.config.keyCodes.f2=113
    

    表单控件绑定

    v-model本质上是语法糖,监听输入事件以更新数据

    • 基础用法:
    <div id="app-2">
        <input type="text" v-model="message" placeholder="'edit me">
        <p>Message is:{{message}}</p>   
    </div>
    var app2=new Vue({
        el:'#app-2',
        data:{
            message:''
        }   
    })
    

    多行文本同理

    • 单选框很方便
    <div id="app-3">
        <input type="checkbox" id="chechbox" v-model="checked">
        <label for="checkbox">{{checked}}</label>
    </div>
    var app3=new Vue({
        el:'#app-3',
        data:{
            checked:false
        }
    })
    

    这里,checked预设值如果设为空,则开始时label没有内容,如果设为true,则选框会默认选上,所以最好只设为false

    • 复选框可共用v-model
    <div id="app-4">
        <input type="checkbox" value="Jack" v-model="checkedNames" id="jack">
        <label for="jack">jack</label>
        <input type="checkbox" value="John" v-model="checkedNames" id="john">
        <label for="john">john</label>
        <input type="checkbox" value="Mike" v-model="checkedNames" id="mike" >
        <label for="mike">mike</label>
        <br>
        <span>checked names: {{ checkedNames }}</span>
    </div>
    var app4=new Vue({
        el:'#app-4',
        data:{
            checkedNames:[]
        }
    })
    
    • 单选按钮
    • 单选列表
    <div id="app-6">
        <select name="" id="" v-model="selected">
            <option>A</option>
            <option>B</option>
            <option>C</option>
        </select>
        <span>selected:{{selected}}</span>
    </div>
    var app06=new Vue({
        el:'#app-6',
        data:{
            selected:""
        }
    })
    

    此处遇到一小问题,建立option时,emmet的补全加了属性value='',如果不改动的话,默认option的值是为空的,选中无效,需要删除这个属性或者显式填值

    • 多选列表,在select处添加属性,multiple="multiple"就可以了。多选时按住ctrl或command键才可以多选
    • 动态选项值
    <div id="app-6">
        <select v-model="selected">
            <option v-for="option in options" v-bind:value="option.value">{{option.text}}</option>
        </select>
        <span>selected:{{selected}}</span>
    </div>
    var app06=new Vue({
        el:'#app-6',
        data:{
            selected:'A',
            options:[
            {text:'One',value:'A'},
            {text:'Two',value:'B'},
            {text:'Tree',value:'C'}
            ]
        }
    })
    

    此例子将选项内容与选项值区分成两组数据,而selected显示的是value的内容,同上一个小问题相同,option不显式设置值时,option标签内容即为值,显式设置后,value值会覆盖标签内容被发送。

    • 动态value值
      v-model绑定的value通常是静态字符串,通过v-bind可以动态绑定变量到value上,语法是v-bind:true-value/v-bind:false-value
    <div id="app-7">
        <input id="bindvalue" type="checkbox" v-model="toggle" v-bind:true-value="a" v-bind:false-value='b'>
        <label for="bindvalue">{{toggle}}</label>
    </div>
    var app07=new Vue({
        el:'#app-7',
        data:{
            toggle:'false',
                a:'true',
                b:'false'       
        }
    })
    

    例子中绑定了true-value到a变量,false-value到b变量
    单选框同理:

    <input type="radio" v-model='pick' v-bind:value="a">
    
    • 修饰符
    • .lazy——默认情况下v-model载input事件中同步输入框的值与数据,添加一个.lazy修饰符后,转变为在change事件中同步,问题是,change事件是神马?存疑
    <input v-model.lazy="msg">
    
    • .number自动将输入值转为Number类型
    <input v-model.number='age' type='number'>
    

    这个例子玄机很多:
    输入字母e/E情况特殊,会被理解为指数标志,不同设定效果不同:
    1. 只设置type为number:字母输入无效;e后为有效数字时会正常显示,比如:

    输入:123abc456,
    框内显示为:123456,值显示为123456
    、、、
    输入:123e12,
    框内显示为:123e12  值显示为:123e12
    

    但是超过一定数值会显示为空:

    (很大很大一个数字)显示为:
    123e1234 值显示为:
    

    然而负指数会正确显示:

    123e-1234 值显示为 123e-1234
    
       2. 只设置v-model为number时:
    
    输入:123abc456,
    框内显示为123abc456,失去焦点时更新为123,
    值显示为123
    、、、
    输入:abc123,
    框内显示为abc123,失去焦点没变化,
    值显示为abc123,说明判定失效啦
    、、、
    123e12 值显示为:123000000000000
    ——同时输入框内会被更新成123000000000000
    

    指数e超过一定数值会显示为Infinity

    123e1234值显示为:Infinity
    ——同时输入框内会被更新成Infinity
    

    在一个范围内的负指数会被格式化后显示,
    超过一定值的负指数会被显示为0:

    123e-123  值显示为:1.23e-121
    123e-1234 值显示为:0
    ——同时输入框内会被更新
    
       3. 设置type为number并且v-model为number时:
    
    输入123abc456,
    框内显示为:123456,值显示为123456
    、、、
    输入abc123,
    框内显示为:123,值显示为123
    、、、
    123e12 值显示为:123000000000000
    ——同时输入框内会被更新成123000000000000
    指数大于20位会转化成指数形式
    

    超过一定数值显示为空:

    123e1234 值显示为:
    

    在一个范围内的负指数会被格式化后显示,
    超过一定值的负指数会被显示为0:

    123e-123  值显示为:1.23e-121
    123e-1234 值显示为:0
    ——同时输入框内会被更新
    

    总结:控制数字输入的主要依赖type="number",v-model.number是将输入值转化为Number类型,但是表现不太稳定。

    • .trim自动过滤用户输入的首尾空格:
    空空abc空空def空空  ——>  abc空空def
    

    组件

    • 基础语法:
    <div id="app-01">
        <my-component></my-component>
    </div>
    Vue.component('my-component',{
        template:'<div>A custom component!</div>'
    })
    var app01=new Vue({
        el:'#app-01'
    })
    

    要确保,先注册组件,再初始化实例,调换前后顺序会出错

    • 局部注册:把组件用components注册在实例中,注意components为复数形式,好处是限制了作用域
    <div id="app-01">
        <my-component></my-component>
    </div>
    var app01=new Vue({
        el:'#app-01',
        components:{
            'my-component':{
                template:'<div>A custom component!</div>'
            }
        }
    })
    

    注意此处相当于先实例再在实例中注册组件,因此可以判断,注册组件行为不能落后于实例化,但是可以包含在实例的同时进行。

    • DOM模板解析——当把模板加载到一些已存在的元素上时,可能会受到HTML的一些限制,因为像一些ul/ol/table/select限制了能被它包裹的元素,而一些如option的元素只能出现在某些元素内部,因此,有时自定义组件会失效而出错:
    <table>
      <my-row>...</my-row>
    </table>
    

    需要使用特殊的is属性:

    <table id="app-01">
        <tr is="my-component"></tr>
    </table>
    

    这相当于把组件伪装成一个tr ,查看浏览器文档结构解析为:

    <table id="app-01">
            <tbody>
            <div>A custom component</div>——(组件内容)
            </tbody>
    </table>
    

    tbody问题
    此处出现的tbody是html解析自动补全的,tbody功能为,划分表格结构,默认情况下是一个tbody,也可手动建立多个并行的tbody,这影响表格分别渲染及切分(很长的表格换页的时候断在哪里就是切分)

    • 限制不适用情况没看懂(存疑):
      • <script type="text/x-template">----这个解决了,后面有解释
      • JavaScript内联模版字符串
      • .vue 组件
    • 组件关联构造器的data必须是函数,否则会报错,深层次的原因是,每个模板都会引用data,如果data是定值,则每个模板引用的都是同一个,如果data是函数,则返回不同,每个模板才会区别开
    <div id="app-02">
        <simple-counter></simple-counter>
        <simple-counter></simple-counter>
        <simple-counter></simple-counter>
    </div>
    如果是:
    var data={counter:0}
    var app02=new Vue({
        el:'#app-02',
        components:{
            'simple-counter':{
                template:'<button v-on:click="counter +=1">{{counter}}</button>',
                data:function(){
                        return data
                    }
            }
            
        }
    则修改一个模板,其他模板数字也被修改,因为data的返回值是data自身,而所有模板都会引用data,都有修改权。
    改一下:
    var app02=new Vue({
        el:'#app-02',
        components:{
            'simple-counter':{
                template:'<button v-on:click="counter +=1">{{counter}}</button>',
                data:function(){
                        return {counter:0}
                    }
            }
            
        }
    每个模板可以有独立的值了,因为data作为一个函数返回的是不同的counter,不会被共用
    

    模板内部的data与外部是冲突的

    构成组件
    <div id="app-03">
        <child message="hello"></child>
        <div>{{value}}</div>
    </div>
    var app03=new Vue({
        el:'#app-03',
        components:{
            'child':{
                template:'<span>{{message}}</span>',
                props:['message']
            }
        },
        data:{
            value:'yyy'
        }
    })
    

    此例子中,props请求的message通过组件元素传入,即特性传入,props与外部的data不冲突

    • 特性命名问题:
    • 矛盾点一:html的特性不区分大小写
    • 矛盾点二:Vue中除了模板命名,其他命名不允许出现小横杠 ‘-’
      于是当两个单词串联成一个名字时,怎么区分呢?
      vue貌似做了一个命名自动转换,在js文件内,命名为驼峰式,camerCase,进入html文件,自动转换成短横线隔开式,kebab-case,对应的是同一个变量
    props:['myMessage']——js内
    <child-11 my-message="hello"></child-11>——html内
    
    • 也可以用v-bind动态绑定prop到一个model
    <div id="app-03">
        <input type="text" v-model='parentMsg'>
        <child-11 v-bind:my-message="parentMsg"></child-11>
    </div>
    var app03=new Vue({
        el:'#app-03',
        components:{
            'child-11':{
                template:'<span>{{myMessage}}</span>',
                props:['myMessage']
            }
        },
        data:{
            parentMsg:''
        }
    })
    
    • 字面量语法vs动态语法:
    <comp some-prop='1'></comp>
    由于这是一个字面prop,传递了一个字符串'1',它的值是字符串‘1’而不是number
    <comp v-bind:some-prop='1'></comp>
    这样会把值当做一个javascript表达式计算,才是number
    (存疑)
    - prop是单向绑定:父组件属性变化时,将传到给子组件,但不会反过来。同时,每次父组件更新时,子组件的所有prop都会更新为最新值,这意味着,不应该在子组件内部改变prop。但是还要用怎么办呢?
      - 定义一个局部变量,用prop初始化
      - 定义一个计算属性,处理prop值
    然而,js中,对象和数组是引用类型,指向同一个内存空间,如果prop是对象或者数组,在子组件内部改变也会影响父组件的状态。
    - Prop验证
    

    <div id="app-04">
    <child prop-c="111"></child>

    <child prop-c="333" prop-d="222"></child>
    </div>
    var app04=new Vue({
    el:'#app-04',
    components:{
    'child':{
    template:'<span>{{propC}}-{{propD}}-{{propE}}</span>',
    props:{
    propC:{
    type:String,
    required:true
    },
    propD:[String,Number],
    propE:{
    type:[String,Number],
    default:100
    }
    }
    }
    }
    })

    有几种验证:
     - 指定类型验证:
    

    单个的,propA:Number;
    多个的,propB:[String,Number]

     - 是否必须存在的验证:
    

    propC:{
    type:String,
    required:true
    }

     - 带默认值的验证:
    

    propD:{
    type:Number,
    default:100
    }

    默认值可以是函数:
    

    propE:{
    type:Object,
    default:function(){
    return { message:'hello'}

     - 还可以自定义验证函数:
    

    propF:{
    validator:function(value){
    return value>0
    }
    这个验证失败时是控制台报错

    
    - type的类型是以下原生构造器:
     - String
     - Number
     - Boolean
     - Function
     - Object
     - Array
    
    #####自定义事件
    

    <div id="app-05">
    <p>{{total}}</p>
    <button-counter v-on:increment="incrementTotal"></button-counter>
    <button-counter v-on:increment="incrementTotal"></button-counter>
    </div>
    var app05=new Vue({
    el:'#app-05',
    components:{
    'button-counter':{
    template:'<button v-on:click="increment">{{counter}}</button>',
    data:function(){
    return{
    counter:0
    }
    },
    methods:{
    increment:function(){
    this.counter+=1
    this.$emit('increment')
    }
    }
    }
    },
    data:{
    total:0
    },
    methods:{
    incrementTotal:function(){
    this.total+=1
    }
    }
    })

    这个例子中,创建了父元素和内部的子组件,子组件包含一个点击事件,v-on:click=‘increment’,点击后改变counter数值,同时为了上传事件,在函数increment内部自定义了一个事件,并在点击时触发此事件,$emit,也叫increment,并在子元素上绑定了这个事件,v-on:increment,然后对此在父元素上绑定了一个函数,incrementTotal,从而达到计算总数的效果。
    我觉得这个名字区分一下比较好,子组件的方法不必和触发事件同一个名字,所以改一下:
    

    <div id="app-05">
    <p>{{total}}</p>
    <button-counter v-on:user-defined="incrementTotal"></button-counter>
    <button-counter v-on:user-defined="incrementTotal"></button-counter>
    </div>
    var app05=new Vue({
    el:'#app-05',
    components:{
    'button-counter':{
    template:'<button v-on:click="increment">{{counter}}</button>',
    data:function(){
    return{
    counter:0
    }
    },
    methods:{
    increment:function(){
    this.counter+=1
    this.$emit('user-defined')
    }
    }
    }
    },
    data:{
    total:0
    },
    methods:{
    incrementTotal:function(){
    this.total+=1
    }
    }
    })

    - 给组件绑定原生事件
    用.native修饰v-on
    

    <new-component v-on:click.native="doSomeThing"></new-component>

    - v-on与$on的不同:
    v-on可以被父组件用来监听子组件的事件
    (存疑):$on的用法
    - v-model的使用:
     - ref与$.refs——注册引用信息和使用:
       - 在元素上注册:<p ref="a">hello</p>
    使用注册:vm.$refs.a就是p这个DOM节点
       - 在组件上注册:<input ref="b">
    使用注册:this.$refs.b就是指向了组件实例,this.$refs.b.value指向的是组件input.value
      - mounted ——生命周期钩子,当el被新创建的vm.$el替换,并挂载到实例上之后调用该钩子
     - NumberObject.toFixed(num)——把Number四舍五入为指定小数位数的数字
    

    <script src="https://cdn.rawgit.com/chrisvfritz/5f0a639590d6e648933416f90ba7ae4e/raw/98739fb8ac6779cb2da11aaa9ab6032e52f3be00/currency-validator.js"></script>
    //载入一个验证插件
    <div id="app-07">
    <currency-input label="Price" v-model="price"></currency-input>
    <currency-input label="Shipping" v-model="shipping"></currency-input>
    <currency-input label="Handling" v-model="handling"></currency-input>
    <currency-input label="Discount" v-model="discount"></currency-input>
    //此处有一个合计
    <p>Total: ${{total}}</p>
    </div>
    //组件部分
    Vue.component('currency-input',{
    template:'<div>
    //如果有label,则显示,否则不显示
    <label v-if="label">{{label}}</label>
    $<input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)"
    v-on:focus="selectAll" v-on:blur="formatValue"></div>',
    props:{
    //对参数进行验证
    value:{
    type:Number,
    default:0
    },
    label:{
    type:String,
    default:''
    }
    },
    //生命周期钩子,当组件被挂载到实例时会触发
    mounted:function(){
    this.formatValue()
    },
    methods:{
    updateValue:function(value){
    var result=currencyValidator.parse(value,this.value)
    if(result.warning){
    this.$refs.input.value=result.value
    }
    this.$emit('input',result.value)
    },
    formatValue:function(){
    this.$refs.input.value =currencyValidator.format(this.value)
    },
    //做setTimeout是由于safari此处有bug,不考虑兼容的话不必如此
    selectAll:function(event){
    setTimeout(function(){
    event.target.select()
    },0)
    }
    }
    })

    - 非父子组件间的通信(简单场景)——创建一个空的Vue实例作为连接:
    

    var bus = new Vue()
    //在A组件内
    bus.$emit('id-selected', 1)
    //在B组件内
    bus.$on('id-selected', function (id) {
    // ...
    })

    - slot 内容分发
     - 单个slot:子组件只有一个没有名字的slot时,父组件的整个内容片断会插入到slot所在的DOM位置
     - 具名slot:
    

    子模板
    <div class="container">
    <header>
    <slot name="header"></slot>
    </header>
    <main>
    <slot></slot>
    </main>
    <footer>
    <slot name="footer"></slot>
    </footer>
    </div>
    父模板
    <app-layout>
    <h1 slot="header">这里可能是一个页面标题</h1>
    <p>主要内容的一个段落。</p>
    <p>另一个主要段落。</p>
    <p slot="footer">这里有一些联系信息</p>
    </app-layout>
    渲染结果
    <div class="container">
    <header>
    <h1>这里可能是一个页面标题</h1>
    </header>
    <main>
    <p>主要内容的一个段落。</p>
    <p>另一个主要段落。</p>
    </main>
    <footer>
    <p>这里有一些联系信息</p>
    </footer>
    </div>

     - 作用域插槽:子组件中把数据传递到插槽slot,父元素用一个template scope="props" 获取数据,然后就可以使用了
    

    Vue.component('child',{
    template:'<div><slot text="hello from child"></slot></div>'
    })
    var app03=new Vue({
    el:'#app-03',
    components:{
    'parent-component':{
    template:'<div>
    <child>
    <template scope="props">
    <span>hello from parent</span>
    <span>{{props.text}}</span>
    </template>
    </child></div>'
    }
    }
    })
    //显示为:
    hello from parent hello from child

     - 更复杂的列表组件的例子:
    

    //需要把mylist传递进parent-list
    <parent-list id="app-04" v-bind:mylist="mylist"></parent-list>
    Vue.component('child-list',{
    props:['mylist'],
    template:'<ul><slot name="item" v-for="item in mylist" v-bind:text="item.text"></slot></ul>'
    })
    var app04=new Vue({
    el:'#app-04',
    data:{
    mylist:[
    {text:'蔬菜'},
    {text:'水果'},
    {text:'肉'}
    ]
    },
    components:{
    'parent-list':{
    props:['mylist'],
    //此处也需要把mylist传递进child-list
    template:'<child-list v-bind:mylist="mylist">
    <template slot="item" scope="props">
    <li>{{props.text}}</li>
    </template>
    </child-list>'
    }
    }
    })
    显示为:

    • 蔬菜
    • 水果
    - 渲染一个元组件为动态组件,根据is的值来决定渲染哪个组件
    

    <component id="app-06" v-bind:is="currentView"></component>
    var app06=new Vue({
    el:'#app-06',
    data:{
    currentView:'posts'
    },
    components:{
    'home':{
    template:'<span>home</span>'
    },
    'posts':{
    template:'<span>posts</span>'
    },
    'archive':{
    template:'<span>archive</span>'
    }
    }
    })
    //输入为posts

    - keep-alive保留状态或避免重新渲染
    

    <keep-alive><component id="app-06" v-bind:is="currentView"></component></keep-alive>

    - 可复用组件,三个接口:
     - props
     - events
     - slots
    
    

    <my-component
    :foo="baz"
    :bar="qux"
    @event-a="doThis"
    @event-b="doThat"

    <img slot="icon" scr="...">
    <p slot="main-text">Hello!</p>
    </my-component>

    - 子组件索引:使用ref指定一个索引id
    

    <div id="parent">
    <user-profile ref="profile"></user-profile>
    </div>
    var parent = new Vue({el:'#parent'})
    var child = parent.$refs.profile

    当ref与v-for一起使用时,ref是一个数组或对象,包含相应的子组件,是非响应式的,应避免在模板或计算属性中使用
    - 异步组件,接受一个工厂函数,可以动态解析组件的定义:
    
    • 可以搭配webpack的代码分割功能
    • 可以使用webpack2 +es2015返回一个promise resolve函数
    • 组件命名约定,三种都可以:kebab-case,camelCase,TitleCase
      但是,html模板中,只能使用kebab-case
      字符串模板中,三种都可以
    • 递归组件,组件可以递归的调用自己
      (这里的雷是,不能在实例内部创建组件,因为递归时会查找不到子组件,需要定义全局组件)
    <div id="app-07">
      <recursion-component :count="0"></recursion-component>
    </div>
    Vue.component('recursion-component',{
      props:['count'],
      name:'count',
      template:'<div><span>{{count}}</span><recursion-component :count="count+1" v-if="count<10"></recursion-component></div>'
    })
    var app07=new Vue({
      el:'#app-07'
    })
    //输出是:0 1 2 3 4 5 6 7 8 9 10
    
    • 组件间的循环引用——注册为全局组件时没有问题,但是使用webpack或者browerify用requiring/importing组件的话,会报错,需要用生命周期钩子中注册它的优先级:
    beforeCreate:function(){
    this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue')
    }
    

    此处(存疑,以后用了webpack再说)
    文件目录树的例子:

    <div id="app-08">
      <tree-folder :folder="folder"></tree-folder>
    </div>
    Vue.component('tree-folder',{
      template:'<p><span>{{folder.name}}</span><tree-folder-contents :children="folder.children"/></p>',
      props:['folder']
    })
    Vue.component('tree-folder-contents',{
      template:'<ul><li v-for="child in children">\
      <tree-folder v-if="child.children" :folder="child"/>\
      <span v-else>{{child.name}}</span>\
      </li></ul>',
      props:['children']
    })
    var app08=new Vue({
      el:'#app-08',
      data:{
        folder:{
          name:'总目录',
          children:[
            {name:"二层目录1"},
            {name:"二层目录2",
              children:[
              {name:"三层目录1"},
              {name:"三层目录1"}
              ]
            },
            {name:'二层目录3'}
          ]
        }
      }
    })
    //输出:
    总目录
      - 二层目录1
      - 二层目录2
         - 三层目录1
         - 三层目录2
      - 二层目录3
    
    • 内联模板:一个组件有inline-template属性时,组件会把它的内容当做它的模板,而不是当做分发内容。应对于很简单的模板,因为有作用域问题
    <parent-com><span>啦啦啦</span></parent-com>
    \\js部分
    Vue.component('parent-com',{
      template:'<div><child-com><span>this is part of parents</span></child-com></div>'
    })
    \\默认情况下,“啦啦啦”作为分发内容,不会显示,而是显示this is part of parents.
    \\如果改为:
    <parent-com inline-template><span>啦啦啦</span></parent-com>
    \\则显示为“啦啦啦”,原设定的template被覆盖了
    
    • 另一种定义模板的方式:x-template
    <div id="app-10">
      <hello-world></hello-world>
    </div>
    \\先单独一个js,类型为type="text/x-template",并且需要有id
    <script type="text/x-template" id="hello-world-template">
      <p>Hello hello hello</p>
    </script>
    \\然后在另一个js里创建组件,只不过模板可以直接引用这个id:
    Vue.component('hello-world', {
      template: '#hello-world-template'
    })
    \\别忘了创建实例(掩面)
    var app10=new Vue({
      el:'#app-10'
    })
    
    • 对低开销的静态组件使用v-once只渲染一次,下次时直接应用缓存
    Vue.component('terms-of-service', {
      template: '\
        <div v-once>\
          <h1>Terms of Service</h1>\
          ... a lot of static content ...\
        </div>\
      '
    })
    

    基础部分结束

    相关文章

      网友评论

        本文标题:Vue.js学习笔记-基础部分+完整实现代码

        本文链接:https://www.haomeiwen.com/subject/szbpattx.html