从零开始学习Vue(一)

256 阅读10分钟

初识Vue

Vue (/vjuː/) 是一套用于构建用户界面的渐进式框架

渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,带来更丰富的交互体验。

Vue中的MVVM

  • View层:
    视图层,在前端开发中,通常是DOM层,主要的作用是给用户展示各种信息。
  • Model层:
    数据层,数据可能是固定的死数据,更多是请求来自服务器的数据。
  • VueModel层:
    视图模型层,它是View和Model沟通的桥梁。
    一方面它实现了Data Binding,也就是数据绑定,将Model的改变实时反映到View中; 另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的Data。

Vue的生命周期

基础语法

插值操作(Mustache)

通过Mustache语法(也就是{{}})可以将data中的文本数据插入到HTML中

// html
<div id="app">
    <h2>Hello, {{massage}}</h2>
</div>

// js
let app = new Vue({
    el: "#app",
    data: {
        massage: "Vuejs"
    }
})

v-once

使用场景:

  • 希望元素和组件只渲染一次,不会随着数据的改变而改变。

使用说明:

  • 该指令后面不需要跟任何表达式

v-html

使用场景:

  • 希望数据按照HTML格式进行解析,并且显示对应的内容。

使用说明:

  • 该指令后面往往会跟上一个string类型,会将string的html解析出来并进行渲染。

v-text

使用场景:

  • v-text作用和Mustache比较相似:都是用于将数据显示在界面中。

使用说明:

  • 该指令后面往往会跟上一个string类型,会将string的html解析出来并进行渲染。

v-pre

使用场景:

  • v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法

v-cloak

使用场景:

  • 当后台请求的数据为返回时,浏览器会直接显然出未编译的Mustache标签,对用户不友好,页面渲染后v-cloak会被移除。

绑定属性(v-bind)

  • 作用:动态绑定属性
  • 语法糖::
  • 预期:any(with argument) | Object(without argument)
  • 参数:attrOrProp(optional)

v-bind用于绑定一个或多个属性值,或者向另外一个组件传递props值。

// html
<div id="app">
    <a v-bind:href="link">Vue官网</a>
    <img v-bind:src="logoURL" alt="">
    // 语法糖:简写
    <a :href="link">Vue官网</a>
    <img :src="logoURL" alt="">
</div>

// js
let app = new Vue({
    el: "#app",
    data: {
        link: "https://vuejs.org/",
        logoURL: 'https://vuejs.org/images/logo.png'
    }
})

v-bind绑定class

绑定class有两种方式:

  • 对象语法::class后面跟的是一个对象
  • 数组语法::class后面跟的是一个数组
// 对象语法
// 一、直接通过{}绑定一个类
<h2 :class="{active: isActive}">Hello Vue</h2>
// 二、绑定多个类
<h2 :class="{active: isActive, line: isLine}">Hello Vue</h2>
// 三、与普通类并不冲突,可共存
<h2 class="tittle" :class="{active: isActive}">Hello Vue</h2>
// 四、可以放在一个methods或computed中
<h2 class="tittle" :class="classes">Hello Vue</h2>

// 数组语法
// 一、直接通过[]绑定一个类
<h2 :class="['active']">Hello Vue</h2>
// 二、绑定多个类
<h2 :class="['active', 'line']">Hello Vue</h2>
// 三、与普通类并不冲突,可共存
<h2 class="tittle" :class="['active']">Hello Vue</h2>
// 四、可以放在一个methods或computed中
<h2 class="tittle" :class="classes">Hello Vue</h2>


let app = new Vue({
    el: "#app",
    data: {
        isActive: true,
        isLine: false
    },
    methods: {
        classes() {
            // 对象语法
            return {active: this.isActive, line: this.isLine}
            // 数组语法
            return ['active', 'line']
        }
    },
    computed: {
        classes() {
            // 对象语法
            return {active: this.isActive, line: this.isLine}
            // 数组语法
            return [active, 'line']
        }
    }
})

v-bind绑定style

在写CSS属性名时,例如font-size:

  • 使用驼峰式:fontSize
  • 短横线分割,用单引号括起来:'font-size'

绑定style有两种方式:

  • 对象语法::style后面跟的是一个对象
  • 数组语法::style后面跟的是一个数组
// 对象语法
<h2 :style="{color: currentColor, fontSize: fontSize + 'px'}">Hello Vue</h2>
// 数组语法
<h2 :style="[colorStyle, fontSizeStyle]">Hello Vue</h2>

let app = new Vue({
    el: '#app',
    data: {
        currentColor: 'red',
        fontSize: 50,
        colorStyle: {color: 'red'},
        fontSizeStyle: {fontSize: '50px'}
    }
})

计算属性

使用场景:
    在某些情况,我们需要对数据镜像一些转化后再显示

使用说明:
    计算属性是写在实例的computed中的

<div id="app">
    <h2>书籍总价值:{{totalPrice}}</h2>
</div>

let vm = new Vue({
    el: '#app',
    data: {
        books: {
            {name: 'HTML', price: 99, count: 3},
            {name: 'CSS', price: 45, count: 4}
            {name: 'JS', price: 76, count: 5},
        }
    },
    computed: {
        totalPrice() {
            return this.books.reduce((total, book) => {
                return total + book.price * book.count;
            }, 0);
        }
    }
})

计算属性的setter和getter

每个计算属性都包含一个getter和setter,当只使用getter时,可缩写为上面的例子所示。

<div id="app">
    <h2>{{fullName}}</h2>
</div>

let vm = new Vue({
    el: '#app',
    data: {
        firstName: 'apple',
        lastName: 'banana'
    },
    computed: {
        fullname {
            setter(newValue) {
                const names = newValue.split(' ');
                this.firstName = names[0];
                this.lastName = names[1];
            },
            getter() {
                return this.firstName + ' ' + this.lastName.
            }
        }
    }
})

methods和computed的区别:
计算属性会进行缓存,如果多次使用时,计算属性只会调用一次。当计算属性中涉及到的变量值发生改变时,计算属性会重新进行计算。

事件监听 v-on

  • 作用:绑定事件监听器
  • 语法糖:@
  • 预期:Function | inline Statement | Object
  • 参数:event

v-on用于监听用户交互的事件,例如:点击、拖拽、键盘等。

// 基础用法
<div id="app">
    <h2>点击次数:{{counter}}</h2>
    <button v-on:click="this.counter++">按钮1</button>
    <button v-on:click="btnClick">按钮2</button>
    // 语法糖
    <button @click="this.counter++">按钮1</button>
    <button @click="btnClick">按钮2</button>
</div>

let vm = new Vue({
   el: '#app',
   data: {
       counter: 0
   },
   methods: {
       btnClick() {
           this.counter++;
       }
   }
});

v-on参数问题

当通过methods中定义方法以供@click调用时,需要注意参数问题

  • 如果该方法不需要额外参数,那么click事件调用方法的()可以省略
    • 如果定义的方法有一个参数,那么会默认将原生事件event参数传递进去。
  • 如果需要同时传入某个参数,同时需要event时,通过$event传入事件。
<div id="app">
    <h2>点击次数:{{counter}}</h2>
    <button @click="btnClick1">按钮1</button>
    <button @click="btnClick2">按钮2</button>
    <button @click="btnClick3(2)">按钮3</button>
    <button @click="btnClick4(2, $event)">按钮4</button>
    <button @click="btnClick5">按钮5</button>
</div>

let vm = new Vue({
   el: '#app',
   data: {
       counter: 0
   },
   methods: {
       btnClick1() {
        this.counter++;
       }
       btnClick2(event) {
        console.log(event); // 默认传入原生事件event
        this.counter++;
       }
       btnClick3(count) {
        console.log(count); // 2
        this.counter = count;
       }
       btnClick4(count, event) {
        console.log(count); // 默认传入原生事件event
        this.counter = count;
       }
   }
});

v-on修饰符

Vue提供了修饰符来帮助我们方便的处理一些事件:

  • .stop → 调用event.stopPropagation()
  • .prevent → 调用event.preventDefault()
  • .{keyCode|keyAlias} → 只当事件从特定键触发时才触发回调
  • .native → 监听组件根元素的原生事件
  • .once → 只触发一次回调
<!-- 阻止冒泡 -->
<button @click.stop="btnClick"></button>

<!-- 阻止默认行为 -->
<button @click.prevent="btnClick"></button>
<!-- 阻止表单自动提交的默认行为 没有表达式 -->
<form @submit.prevent></form>

<!-- 串联修饰符 -->
<button @click.stop.prevent="btnClick"></button>

<!--只有按回车键才回调-->
<!--键修饰符,键别名-->
<input @keyup.enter="onEnter">  
<!--键修饰符,键代码-->
<input @keyup.13="onEnter">  

<!-- 点击回调只会触发一次 -->
<button @click.once="btnClick"></button>

条件判断

Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素和组件。相关指令: v-if v-else-if v-else

<div id="app">
    <div v-if="score>=90">优秀</div>
    <div v--else-if="score>=80">良好</div>
    <div v-else-if="score>=60">及格</div>
    <div v-else>不及格</div>
</div>

<!--但是不建议将复杂逻辑判断放在html中,推荐使用方法调用-->

小案例

通过点击按钮切换使用账号登陆还是邮箱地址登陆

<div id="app">
    <div v-if="type === "userName">
        <label for="userName">用户名登陆:</label>
        <input placeholder="请输入用户名">
    </div>
    <div v--else>
        <label for="email">邮箱登陆:</label>
        <input placeholder="请输入邮箱">
    </div>
    <botton @click="switchType">切换</button>
</div>

let vm = new Vue({
   el: '#app',
   data: {
       type: 'userName'
   },
   methods: {
       switchType() {
        this.type = this.type='userName' ? 'email' : 'userName';
       }
   }
});

问题: 你会发现当我们在有输入内容的情况下,切换了类型,文字依然显示之前输入的内容,但是按道理讲,我们应该切换到另一个input元素中并且没有输入内容。

解答: Vue在进行DOM渲染时,出于性能考虑,会尽可能服用已经存在的元素,而不是重新创建新的元素。在上面的案例中,Vue内部会发现原来的input元素不再使用(v-if为false会销毁),就将它直接作为input来使用了。

解决方案: 给对应的input添加key并保证key的唯一性。

<div id="app">
    <div v-if="type === "userName">
        <label for="userName">用户名登陆:</label>
        <input placeholder="请输入用户名" key="userName">
    </div>
    <div v--else>
        <label for="email">邮箱登陆:</label>
        <input placeholder="请输入邮箱" key="email">
    </div>
    <botton @click="switchType">切换</button>
</div>

v-show

v-show和v-if的用法相似,都决定一个元素是否渲染。

区别:
v-if当条件为false时,不会有对应的元素在DOM中;
v-show当条件为false时,仅将元素的display属性设置为none。

使用场景:
当需要在显示与隐藏之间切换很频繁时,用v-show;
当只有一次切换时,使用v-if。

v-for

当我们有一组数据需要渲染时,就可以使用v-for来完成。

<div id="app">
    <ul>
        <!--不需要索引index-->
        <li v-for="item in movies">{{item}}</li>
        <!--需要使用索引index-->
        <li v-for="(item, index) in movies">{{index+1}}.{{item}}</li>
    </ul>
</div>

let vm = new Vue({
   el: '#app',
   data: {
       movies: ['火影忍者','海贼王','进击的巨人']
   }
});

v-for同样可以遍历对象

<div id="app">
    <ul>
        <li v-for="(value, key, index) in info">
            {{value}} - {{key}} - {{index}} // apple - name - 0
        </li>
    </ul>
</div>

let vm = new Vue({
   el: '#app',
   data: {
       info: {
           name: 'apple',
           age: 18,
           height: 1.88
       }
   }
});

组件的key属性

官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。
原因:和Vue的虚拟DOM的Diff算法有关系。

当某一层有很多相同的节点,我们希望插入一个新的节点,例如我们希望在B和C之间插入一个F,Diff算法默认执行起来是将C更新成F,后面依次更新,这样的效率太低。

所以我们需要使用key来给每个节点做一个唯一标识。Diff算法就可以正确识别此节点,找到正确的位置插入新的节点。

key的作用主要是为了高效的更新虚拟DOM。

检查数组更新

因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。
Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发试图的更新。

methods: {
    updateData() {
        <!--1. push方法 可同时传入多个参数,用','隔开-->
        <!--在数组末尾插入元素-->
        this.names.push('apple');
        
        <!--2. pop方法-->
        <!--弹出数组最后一个元素-->
        this.names.pop();
        
        <!--3. unshift方法 可同时传入多个参数,用','隔开-->
        <!--在数组顶部插入元素-->
        this.names.unshift('apple')
        
        <!--4. shift-->
        <!--弹出数组第一个元素-->
        this.names.shift()
        
        <!--5. splice-->
        <!--可以对数组进行删除,更新,替换操作-->
        this.names.splice(1,)
        
        <!--6. sort-->
        <!--对数组进行排序, 可以传入比较函数-->
        this.names.sort()
        
        <!--7. reverse-->
        <!--数组反转-->
        this.names.reverse()
        
        <!--不会修改页面-->
        this.names[0] = 'apple';
        <!--替换↓-->
        this.names.splice(0, 1, 'apple');
        Vue.set(this.names, 0, 'apple');
    }
}

表单绑定v-model

Vue中使用v-model指令来实现表单元素和数据的双向绑定
v-model其实时一个语法糖,它的背后本质上是包含两个操作:

  1. v-bind绑定一个value属性
  2. v-on指令给当前元素绑定input事件
<div id="app">
    <input type="text" v-model="message">
    <h2>{{message}}</h2>
    <!--等同于↓-->
    <input type="text" :value="message" @input="message = $event.target.value">
    
    <!--绑定textarea元素-->
    <textarea type="text" v-model="textMessage"></textarea>
    <h2>{{textMessage}}</h2>
</div>

const vm = new Vue({
   el: "#app",
   data: {
       message: 'hello',
       textMessage: ''
   }
});

v-model绑定radio

<div id="app">
    <label for="male">
      <input type="radio" :value="type" id="male" v-model="gender">男
    </label>
    <label for="female">
      <input type="radio" value="female" id="female" v-model="gender">女
    </label>
</div>

const vm = new Vue({
   el: "#app",
   data: {
        type: 'male',
        gender: 'male'
   }
});

v-model绑定checkbox

<div id="app">
    <!--单个复选框-->
    <label for="check">
      <input type="checkbox" id="check" v-model="checked">同意协议
    </label>
    
    <!--多个复选框-->
    <label for="篮球">
      <input type="checkbox" id="篮球" v-model="hobbies">篮球
    </label>
    <label for="足球">
      <input type="checkbox" id="足球" v-model="hobbies">足球
    </label>
    <label for="台球">
      <input type="checkbox" id="台球" v-model="hobbies">台球
    </label>
    
</div>

const vm = new Vue({
   el: "#app",
   data: {
        checked: false,
        hobbies: []
   }
});

v-model绑定select

<div id="app">
    <!--单个下拉框-->
    <select v-model="fruit">
        <option value="apple">苹果</option>
        <option value="banana">香蕉</option>
        <option value="orange">橘子</option>
    </select>
    
    <!--多个下拉框-->
    <select v-model="fruits" multiple>
        <option value="apple">苹果</option>
        <option value="banana">香蕉</option>
        <option value="orange">橘子</option>
    </select>
    
</div>

const vm = new Vue({
   el: "#app",
   data: {
        fruit: '',
        fruits: []
   }
});

修饰符

<div id="app">
    <!--1. lazy修饰符-->
    <input type="text" v-model.lazy="message">
    <p>{{message}}</p>
    
    <!--2. number修饰符-->
    <input type="number" v-model.number="age">
    <p>{{age}} - {{typeof age}}</p>
    
    <!--3. trim修饰符-->
    <input type="text" v-model.trim="message">
    <p>{{message}}</p>
</div>

案例

// HTML
<div id="app" v-cloak>
  <div v-if="list.length">
    <table>
      <thead>
        <tr>
          <th></th>
          <th>名称</th>
          <th>出版日期</th>
          <th>价格</th>
          <th>购买数量</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(item, index) in list" :key="item.id">
          <td>{{index + 1}}</td>
          <td>{{item.name}}</td>
          <td>{{item.date}}</td>
          <td>{{item.price | showPrice}}</td>
          <td>
            <button @click="decrement(index)" :disable="item.count === 1">-</button>
            {{item.count}}
            <button @click="increment(index)">+</button>
          </td>
          <td>
            <button @click="handleRemove(index)">移除</button>
          </td>
        </tr>
      </tbody>
    </table>
    <div>总价:{{totalPrice | showPrice}}</div>
  </div>
  <div v-else>
    购物车为空
  </div>
</div>

// CSS
table {
    border: 1px solid #e9e9e9;
    border-collapse: collapse;
    border-spacing: 0;
}

th,td {
    padding: 8px 16px;
    border: 1px solid #e9e9e9;
    text-align: left;
}

th {
    background-color: #f7f7f7;
    color: #5c6b77;
    font-weight: 600;
}

// JS
const app = new Vue({
    el: '#app',
    data: {
        list:[{
            name: 'HTML入门到放弃',
            id: '01',
            date: '2020-06-18',
            price: 11,
            count: 1
        },{
            name: 'CSS入门到放弃',
            id: '02',
            date: '2020-06-18',
            price: 12,
            count: 1
        },{
            name: 'JS入门到放弃',
            id: '03',
            date: '2020-06-18',
            price: 13,
            count: 1
        },{
            name: 'Vue入门到放弃',
            id: '041',
            date: '2020-06-18',
            price: 14,
            count: 1
        }]
    },
    methods: {
        decrement(index) {
            this.list[i].count--;
        },
        increment(index) {
            this.list[i].count++;
        },
        handleRemove(index) {
            this.list.splice(index, 1);
        }
    },
    <!--过滤器-->
    filters: {
        showPrice() {
            return '$' + value.toFix(2);
        }
    },
    computed: {
        totolPrice() {
            return this.list.reduce((total, next) = > {
                return total + next.price * next.count;
            }, 0);
        }
    }
});