代码

原生html渲染v-html

有时数据中携带了Html标签,输出它们时,按文本解析,如:

new Vue({
    data: {
        title:'<h3>使用<code>v-html</code>输出原生的html</h3>',
    }
}).$mount('#app-1')
<div id="app-1" >
    <div>{{title}}</div>
</div>

clipboard.png

使用原生指令v-html解析数据中标签为原生html

<div id="app-1" >
    <div v-html="title"></div>
</div>

clipboard.png

你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。

如:

new Vue({
    data: {
        title:'<h3>使用<code>v-html</code>输出原生的html</h3>',
        recieveMessage:'',
    }
}).$mount('#app-1')
<div id="app-1" >
    <div v-html="title"></div>
    <input type="text" style="width:300px" v-model="recieveMessage"/>
    <div>
        <output v-html="recieveMessage"></output>
    </div>
</div>

clipboard.png

使用时要注意这种情况。插入<script>几乎可以做任何事情,还有插入无数<img>占用带宽。

条件渲染v-if、v-show

v-if条件为truthy时,动态插入内容。
比如在晚7点到早上7点我们插入以下内容:

new Vue({
    computed: {
        isNight() {
            return new Date().getHours() < 7 || new Date().getHours() > 19
        }
    }
}).$mount('#app-2')
<div id="app-2">
    <div v-if="isNight" style="font-size:30px;color:yellow;background: black;width:300px">
        ? it's night
    </div>
</div>

也可以使用v-show="isNight"显示和隐藏内容

<div id="app-2">
    <div v-show="isNight" style="font-size:30px;color:yellow;background: black;width:300px">
        ? it's night
    </div>
</div>

两者区别为:v-show元素始终存在,通过display控制显示/隐藏。而v-if的转换要经历组件的销毁和重建。使用v-show初始会有开销,而v-if初始条件若为falsy,那么什么也不会做,节省用户的 CPU 时间。但是如果v-if频繁切换条件,那么开销又比v-show大的多。

可以用v-elsev-else-if作分支,注意先后顺序,顺序错误则无效。

<div v-else style="font-size:30px;color:orange;background: lightblue;width:300px">
    ? it's day
</div>

<template>

上面都在外包元素<div>上使用条件指令,如果不想使用可见元素包裹内容,可以使用<template>标签

<template v-if="isNight">
    <span>?</span>
    <span>it's night</span>
</template>

类似其他语言{}的作用

if(isNight){
    //...
}

key

以上,若v-if新插入部分与被移除部分有元素是相似的,那么为了效率这些元素会被复用,不会重新渲染。

new Vue({
    el:'#app-3',
    data: {
        loginType: 'username'
    },
    methods: {
        troggle(){
            this.loginType = this.loginType==='username'?'email':'username'
        }
    }
})
<div id="app-3">
    <dl>
        <dt>key</dt>
        <dd v-if="loginType === 'username'">
            <label>Username</label>
            <input placeholder="Enter your username">
        </dd>
        <dd v-else>
            <label>Email</label>
            <input placeholder="Enter your email address">
        </dd>
        <dd>
            <button @click="troggle">switch</button>
        </dd>
    </dl>
</div>

clipboard.png

inputlabel俩元素本身没有被替换,因此input中输入的内容在切换时会保留,而input的 placeholder 和label的内容被替换。

如果使用key特性对元素作唯一标识,那么该元素必备替换。

<div id="app-3">
    <dl>
        <dt>不使用key</dt>
        <dd v-if="loginType === 'username'">
            <label>Username</label>
            <input placeholder="Enter your username" key="username">
        </dd>
        <dd v-else>
            <label>Email</label>
            <input placeholder="Enter your email address" key="email">
        </dd>
        <dd>
            <button @click="troggle">switch</button>
        </dd>
    </dl>
</div>

clipboard.png

设置key后,切换内容时input的值不在保留。key主要在v-for里使用,以区分列表项,还有动画部分会大量使用,因为元素复用导致淡入淡出动画失效。

列表渲染 v-for

列表使用细节比较杂碎,按官网教程一个个试下就行了。
一般情况下用法v-for="列表项 in|of 列表|整数"

<h3>以一个整数进行遍历</h3>
<span v-for='n in 10'> {{n}} </span>
<h3>票房</h3>
<ul>
    <li v-for="bo of boxOffice">
        {{bo.name}} {{bo.year}} ({{bo.sells}}$ )
    </li>
</ul>
new Vue({
    el: '#app-4',
    data: {
        query: '',
        boxOffice: [
            { name: 'Avatar', year: 2009, sells: '27.88' },
            { name: 'Frozen', year: 2013, sells: '12.765' },
            { name: 'Furious 7', year: 2015, sells: '15.15' },
            { name: 'Iron Man 3', year: 2013, sells: '12.154' },
            { name: 'Titanic', year: 1997, sells: '21.868' },
            { name: 'Spectre', year: 2015, sells: '8.722' },
            { name: 'Inception', year: 2010, sells: '8.255' },
            { name: 'Jurassic World', year: 2015, sells: '16.99' }
        ]
    }
})

clipboard.png

以上的boxOffice可以是个数组,当然我们也可以遍历一个对象的。如给电影 Avatar 加一个导演属性

{ 
    name: 'Avatar', 
    year: 2009, 
    sells: '27.88', 
    director:{firstname:'yannie',lastname:'cheung',age:'17'} 
},

在列表中嵌套一个列表显示导演信息

<li v-for="bo of boxOffice">
    {{bo.name}} {{bo.year}} ({{bo.sells}}$ )
    <ul>
        <li v-for="value of bo.director">{{value}}</li>
    </ul>
</li>

clipboard.png

在列表遍历中还可以引用列表索引,对象的遍历中引用键值与索引,索引从0开始。
列表: (列表项,索引) in 列表
对象: (值,键值,索引) in 对象
修改示例以显示这些内容

<ul>
    <li v-for="(bo,index) of boxOffice">
        {{index+1}}、{{bo.name}} {{bo.year}} ({{bo.sells}}$ )
        <ul>
            <li v-for="(value,key,index) of bo.director">{{index+1}}、{{key}}:{{value}}</li>
        </ul>
    </li>
</ul>

clipboard.png

在遍历对象时,是按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下是一致的。

v-for中的key

v-if类似的,当列表被更新时,这些已被渲染的列表元素不会重新渲染,只会改变其中的内容。我们修改以上示例,点击按钮列表会按票房重新排序,我们再为每一个li加上动画。

data(){
    //...
    isSort:false
},
//......
//点击按钮,在原顺序与票房顺序切换
computed: {
     //点击按钮,在原顺序与票房顺序切换
     sortBySells(){
         var newArr =  this.boxOffice.concat()
         if(this.isSort){
             return newArr.sort((b1,b2) => b1.sells - b2.sells)
         }else{
             return this.boxOffice
         }
     },
    //...
}
<h3>票房</h3>
<ul>
    <!-- 测试基本功能 -->
    <!-- <li v-for="(bo,index) of boxOffice"> -->
    <!-- 测试特性key -->
    <li v-for="(bo,index) in sortBySells" class="animate">
        {{index+1}}、{{bo.name}} {{bo.year}} ({{bo.sells}}$ )
        <ul>
            <li v-for="(value,key,index) of bo.director">{{index+1}}、{{key}}:{{value}}</li>
        </ul>
    </li>
</ul>
.animate {
    display: block;
    margin-bottom: 10px;
    color: #fff;
    background: #2e8b57;
    border-radius: 3px;
    -webkit-animation: animate 5s infinite;
    animation: animate 5s infinite;
}
@keyframes animate {
    0% {
        width: 350px;
    }
    to {
        width: 600px;
    }
}

clipboard.png

当列表重新排列时,动画没有中断重新开始,说明这些<li>元素没有被重新渲染,它们被复用了。

现在为列表加上key

<li v-for="(bo,index) in sortBySells" :key="bo.sells" class="animate">

clipboard.png

设置key后,某些元素的动画中断并重新开始,说明元素的确被重新渲染了。我们设置报价为key的值,如果设置index为值,列表还是会被完全的复用。

对遍历列表、对象的处理

在以上key的使用中已经看到我们对列表进行的排序是在计算属性中进行的,这种方法的形式一般为这样:
v-for="项 in 计算属性"

虽然处理列表首选计算属性,但也可以使用方法,特别是在计算属性不适用的情况下。
v-for="项 in 方法(参数)"
改写以上示例,使用方法对列表进行排序,只为演示,最优还是使用计算属性

methods: {
    //点击按钮,在原顺序与票房顺序切换
    sort(isSort) {
        var newArr = this.boxOffice.concat()
        return isSort ? newArr.sort((b1, b2) => b1.sells - b2.sells) : this.boxOffice
    }
}  
<li v-for="(bo,index) in sort(isSort)" :key="bo.sells" class="animate">

效果与使用计算属性一样。

变异方法和非变异方法:以上对列表使用了sort()方法,这个方法会改变原始数组,所以我们用concat()复制出一份,不改变原数据。这里sort()为变异方法,concat()为非变异方法。

filter()是一个非变异方法,比如我们过滤出2010年前的票房大于10亿的电影

computed: {
    //...
    //2010之前票房大于10亿的电影,调用两个方法
    filterBoxOffice() {
        return this.highSells(this.beforeYear(this.boxOffice))
    },
    //...
},
methods: {
    //...
    beforeYear(list) {
        return list.filter(bo => bo.year <= 2010)
    },
    highSells(list) {
        return list.filter(bo => bo.sells <= 10.0)
    },
}
<ul>
    <li v-for="bo in beforeYear(highSells(boxOffice))">
        <!-- <li v-for="bo in filterBoxOffice"> -->
        {{bo.name}} {{bo.year}} ({{bo.sells}}$ )
    </li>
</ul>

clipboard.png

如上,可以使用一个计算属性返回过滤结果,也可以直接连续调用两个方法。

又比如,我们根据文本输入来进行过滤。

<h3>过滤</h3>
<input type="text" v-model="query" />
<ul>
    <li v-for="bo in queryBo">
        {{bo.name}} {{bo.year}} ({{bo.sells}}$ )
    </li>
</ul>

由于在filter()里不能使用this引用 vue 实例,因此在外部先把它赋给变量 vm

queryBo() {
    var vm = this
    var noblank = this.boxOffice.filter(item => Number(vm.query) === item.year)
    return this.query ? noblank : this.boxOffice
}

clipboard.png

Vue.set

以下两种情形的数据变化,vue无法再视图中作出响应

当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength

如添加以下代码,点击按钮修改

modifyItem(){
    this.boxOffice[0] = { name: 'The Last Jedi', year: 201
![clipboard.png](/img/bV8201)
7, sells: '13.32', director: { firstname: 'yannie', lastname: 'cheung', age: '17' } }
    this.boxOffice.length = 5
}
<button @click="modifyItem" style="margin-left:20px">vue.set test</button>

clipboard.png

没有任何响应,但事实上值已经更新,

clipboard.png

不过设置某索引对应对象的属性,时会有响应的

modifyItem(){
    //无响应
    this.boxOffice[0] = { name: 'The Last Jedi', year: 2017, sells: '13.32', director: { firstname: 'yannie', lastname: 'cheung', age: '17' } }
    this.boxOffice.length = 5
    //有响应
    this.boxOffice[1].name = 'somename'
}

clipboard.png

索引值和长度的改变是因为其他改变(this.boxOffice[1].name = 'somename')触发了响应,而并非他们自身是响应式的。

为了解决这个问题提供了两种:使用变异方法splice()Vue.set

//设置
modifyItem(){
    //无响应
    // this.boxOffice[0] = { name: 'The Last Jedi', year: 2017, sells: '13.32', director: { firstname: 'yannie', lastname: 'cheung', age: '17' } }
    // this.boxOffice.length = 5
    
    //无响应的解决方法
    // this.$set 等价于 Vue.set
    Vue.set(this.boxOffice, 0, { name: 'The Last Jedi', year: 2017, sells: '13.32', director: { firstname: 'yannie', lastname: 'cheung', age: '17' }})
    // this.boxOffice.splice(0, 1, { name: 'The Last Jedi', year: 2017, sells: '13.32', director: { firstname: 'yannie', lastname: 'cheung', age: '17' }})
    //修改长度
    this.boxOffice.splice(5)
    
    //有响应
    // this.boxOffice[1].name = 'somename'
}

此时,点击按钮设置新状态,立刻作出响应。

clipboard.png

对于对象而言,属性的添加或删除无法响应,如

//添加对象属性
modifyObject(){
    this.boxOffice[0].director.sex = 'male'
}

可以使用Vue.set解决这个问题

clipboard.png

或者将对象原先属性和新增属性合并成新对象再赋给该对象,这使我们可以添加多个属性,如:

//methods
//添加对象属性
modifyObject(){
    // 无响应
    // this.boxOffice[0].director.sex = 'male'
    //解决方法
    Vue.set(this.boxOffice[0].director, 'sex', 'male')
    
    this.boxOffice[0].director = Object.assign({}, this.boxOffice[0].director,{
        aliasname:'kanzaki urumi',
        graduation: 'NUIST'
    })
}

clipboard.png

template

同 v-if,包裹多个元素,下节有使用到。

v-if、v-for优先级

v-for 的优先级比 v-if 高。就是说可以向下面这样,先展开所有列表项,之后 v-if 将作用于每个列表项。

为影片添加是否上映属性run

boxOffice: [
    { name: 'Avatar', year: 2009, sells: '27.88',run:true, director: { firstname: 'yannie', lastname: 'cheung', age: '17' } },
    { name: 'Frozen', year: 2013, sells: '12.765',run:false },
    { name: 'Furious 7', year: 2015, sells: '15.15',run:true },
    { name: 'Iron Man 3', year: 2013, sells: '12.154',run:false },
    { name: 'Titanic', year: 1997, sells: '21.868',run:false },
    { name: 'Spectre', year: 2015, sells: '8.722',run:true },
    { name: 'Inception', year: 2010, sells: '8.255',run:false },
    { name: 'Jurassic World', year: 2015, sells: '16.99',run:false }
]
<h3>v-for|if 的优先级</h3>
<ul>
    <li v-for="bo in boxOffice" v-if="bo.run">
        {{bo.name}} {{bo.year}} ({{bo.sells}}$ )
    </li>
</ul>
v>

clipboard.png

如果想设置在某条件下循环是否执行,那么可以在外层加上 v-if

<h3>v-for|if 的优先级</h3>
<ul>
    <template v-if="boxOffice.length">
        <li v-for="bo in boxOffice" v-if="bo.run">
            {{bo.name}} {{bo.year}} ({{bo.sells}}$ )
        </li>
    </template>
    <li v-else>没有影片</li>
</ul>

组件上的v-for

和在原生元素上使用一样,只是必须使用 key

new Vue({
    data: {
        boxOffice: [
            { id:1,name: 'Avatar', year: 2009, sells: '27.88', run: true, director: { firstname: 'yannie', lastname: 'cheung', age: '17' } },
            { id:2,name: 'Frozen', year: 2013, sells: '12.765', run: false },
            { id:3,name: 'Furious 7', year: 2015, sells: '15.15', run: true },
            { id:4,name: 'Iron Man 3', year: 2013, sells: '12.154', run: false },
            { id:5,name: 'Titanic', year: 1997, sells: '21.868', run: false },
            { id:6,name: 'Spectre', year: 2015, sells: '8.722', run: true },
            { id:7,name: 'Inception', year: 2010, sells: '8.255', run: false },
            { id:8,name: 'Jurassic World', year: 2015, sells: '16.99', run: false }
        ]
    },
    components: {
        'child': {
            props:['bo'],
            template: '<li>{{bo.id}}、{{bo.name}}</li>'
        }
    }
}).$mount('#app-5')
<div id="app-5">
    <child v-for="bo in boxOffice" :bo="bo" :key="bo.id"></child>
</div>

更多内容参见组件与单文件组件部分


yanniecheung
70 声望6 粉丝