Skip to content

Vue.js 基础

第一章:初识 Vue

一、什么是 Vue

Vue 是一款用于构建用户界面的渐进式 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

渐进式:用什么引入什么即可。

二、特点

  • 声明式

    MVVM 模型:虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。

    M:模型 (Model): data 中的数据

    V:视图 (View): 模板代码

    VM:视图模型 (ViewModel): Vue 实例

  • 组件化

  • 虚拟 DOM + 优秀的 diff 算法

三、快速入门

第一步:使用 <script src="./lib/vue.js"></script> 引入 Vue。之后会在 window 对象中注入 Vue 构造函数。

第二步:想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象。

第三步:HTML 部分片段交给 Vue 解析渲染。

例子

html
<div id="root">
    <!-- 插值语法 -->
    <!-- vue模版 -->
    <!--  {{xxx}} 中的 xxx 要写 js 表达式,且 xxx 可以自动读取到 data 中的所有属性 -->
    <!--  vm 身上所有的属性及 Vue 原型上所有属性,在 Vue 模板中都可以直接使用 -->
	<h1>hello, {{ name }}</h1>
</div>

root 容器里的代码依然符合 html 规范,只不过混入了一些特殊的 Vue 语法。root 容器里的代码被称为【Vue 模板】。

javascript
// 设置为 false 以阻止 vue 在启动时生成生产提示
Vue.config.productionTip = false; // 修改为不提示

new Vue({
   // 配置对象
   // el是element缩写,指定当前vue实例为哪一个容器服务,值通常为css选择器格式,可以用使用原生js代码
   el: '#root', // document.getElementById('root')
   data: {
	   // data用来存储数据,以后会用函数来表示
	   name:'张三',
	   age:21
   }
});

观察发现:

一旦 data 中的数据发生改变,那么页面中用到该数据的地方也会自动更新。

Vue 实例和容器是一一对应的。真实开发中只有一个 Vue 实例,并且会配合着组件一起使用。

data 中所有的属性,最后都出现在了 vm 身上。

第二章:基础语法

一、模板语法

html 中包含了一些 JS 语法代码,语法分为两种,分别为:

  • 插值语法(双大括号表达式)
  • 指令语法(以 v- 开头)

Vue 模板语法有 2 大类:

  • 插值语法

    功能:用于解析标签体内容

    写法:{{xxx}}。xxx 是 js 表达式,且可以直接读取到 data 中的所有属性。

  • 指令语法

    功能:用于解析标签(包括标签属性、标签体内容、绑定事件 ......)

    举例:v-bind:href="xxx" 或简写为 :href="xxx",xxx 同样要写 js 表达式。且可以直接读取到 data 中的所有属性。

    备注:Vue 中有很多的指令,且形式都是 v-????,此处是拿 v-bind 举个例子。

注意:js 表达式和 js 代码(语句)

表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方。

    (1). a

    (2). a + b

    (3). demo(1)

    (4). x === y ? 'a' : 'b'

    (5). [1,2,3,4].map(item => item**2)

js 代码(语句)

    (1). if(){}

    (2). for(){}

二、数据绑定

1. 基本

Vue 中有 2 种数据绑定的方式。

  • 单向绑定 (v-bind): 数据只能从 data 流向页面。

  • 双向绑定 (v-model): 数据不仅能从 data 流向页面,还可以从页面流向 data。

    v-model 的三个修饰符

        lazy:失去焦点再收集数据

        number:输入字符串转为有效的数字

        trim:输入内容首尾空格过滤

双向绑定一般都应用在表单元素上。如:input、select 等。

v-model:value 可以简写为 v-model,因为 v-model 默认收集的就是 value 值。

html
<!-- 准备好一个容器 -->
<div id="root">
	<!-- 普通写法 -->
    <!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
		 双向数据绑定:<input type="text" v-model:value="name"><br/> -->

	<!-- 简写 -->
    单向数据绑定:<input type="text" :value="name"><br/> 
    双向数据绑定:<input type="text" v-model="name"><br/>

    <!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 (只能用在表单上) -->
    <!-- <h2 v-model:x="name">你好啊</h2> -->
</div>

2. v-model 深入

1)基本使用

v-model 指令是一个语法糖,它相当于绑定了 value 属性和 input 事件。

v-model 在内部同时使用 v-bind 指令(用于绑定 value 属性)和 v-on 指令(用于监听 input 事件)来实现的。所以,v-model="something" 实际上是 v-bind:value="something" v-on:input="something = $event.target.value" 的简写。

vue
<template>
  <div>
    <h3>需求:不用v-model实现双向绑定</h3>
    <input type="text" :value="uname" @input="changeValue" />
    <p>结论:v-model 可以拆分成 :value + @input</p>
  </div>
</template>

<script>
export default {
  data () {
    return {
      uname: 'zhangsan'
    }
  },
  methods: {
    changeValue (e) {
      // 把输入框的值,赋值给uname
      this.uname = e.target.value
    }
  }
}
</script>

<style lang="less" scoped></style>
2)父子使用 - inpute

子组件有 inpute 输入文本框,在父组件引入子组件。把输入框数据定义在父组件,子组件的输入框内容改变,父组件 data 中的数据就改变。

父组件:

vue
<template>
  <div>
    <h3>需求:实现输入框组件的v-model</h3>
    <!-- <MyInput :value="age" @input="changeAge"></MyInput> -->
    <MyInput v-model="age"></MyInput>
    <p>结论:组件的v-model需要子组件的配合。</p>
    <p>结论:子组件需要接收value属性,并触发input事件</p>
    <hr />

    <p>总结:v-model ==== :value + @input</p>
    <p>总结:父传子,为了把数据传给子组件,设置表单元素的默认值</p>
    <p>总结:子传父,为了把表单元素的值,传给父组件,修改data数据</p>
  </div>
</template>

<script>
import MyInput from './components/MyInput.vue'
export default {
  data () {
    return {
      age: 20
    }
  },
  components: {
    MyInput
  },
  methods: {
    changeAge (val) {
      this.age = val
    }
  }
}
</script>

<style lang="less" scoped></style>

子组件:

vue
<template>
  <input
    type="text"
    :value="value"
    @input="$emit('input', $event.target.value)"
  />
</template>

<script>
export default {
  name: 'MyInput',
  props: ['value']
}
</script>

<style lang="less" scoped></style>
3)父子使用 - select

父组件:

vue
<template>
<h3>需求:实现下拉框组件的v-model</h3>
<!-- <MySelect :value="address" @input="changeSelect"></MySelect> -->
<MySelect v-model="address"></MySelect>
<p>下拉框的v-model相当于 :value + @input</p>
</template>

<script>
import MySelect from './components/MySelect.vue'
export default {
  data () {
    return {
      address: '上海',
    }
  },
  components: {
    MySelect
  },
  methods: {
    changeSelect (val) {
      this.address = val
    }
  }
}
</script>

子组件:

vue
<template>
  <select :value="value" @change="$emit('input', $event.target.value)">
    <option value="北京">北京市</option>
    <option value="上海">上海市</option>
    <option value="广州">广州市</option>
    <option value="深圳">深圳市</option>
  </select>
</template>

<script>
export default {
  name: 'MySelect',
  props: ['value']
}
</script>

<style lang="less" scoped></style>
4)sync 修饰符的原理

子组件是模态框,父组件有个按钮控制模态框显示与隐藏,使用 sync 修饰符实现。

vue
<template>
  <div>
    <button @click="visible = true">删除</button>
    <MyDialog :visible.sync="visible"></MyDialog>
    <!-- <MyDialog :visible="visible" @update:visible="xx"></MyDialog> -->
    <!-- 
      :属性.sync="变量"
      等同于 :属性="变量"  +  @update:属性="xxx"
    -->
  </div>
</template>

<script>
import MyDialog from './components/MyDialog.vue'
export default {
  data () {
    return {
      visible: false // 控制弹出框是否显示
    }
  },
  components: {
    MyDialog
  }
}
</script>

<style></style>

子组件:

vue
<template>
  <div class="dialog" v-show="visible">
    <div class="dialog-header">
      <h3>友情提示</h3>
      <span class="close" @click="close">✖️</span>
    </div>
    <div class="dialog-content">我是文本内容</div>
    <div class="dialog-footer">
      <button>取消</button>
      <button>确认</button>
    </div>
  </div>
</template>

<script>
export default {
  props: ['visible'],
  methods: {
    close () {
      this.$emit('update:visible', false)
    }
  }
}
</script>

<style scoped>
// 省略 css
</style>

三、el 与 data 的两种写法

el 有 2 种写法

  • new Vue 时候配置 el 属性。
  • 先创建 Vue 实例,随后再通过 vm.$mount('#root') 指定 el 的值。

data 有 2 种写法

  • 对象式
  • 函数式(以后学习到组件时,data 必须使用函数式,否则会报错)

一个重要的原则:有 Vue 管理的函数,一定不要写箭头函数,一旦写了箭头函数,this 就不再是 Vue 实例了。

javascript
// el的两种写法
const v = new Vue({
	// el:'#root', // 第一种写法
	data:{
		name:'张三'
	}
})
v.$mount('#root') // 第二种写法

// data的两种写法
new Vue({
	el:'#root',
	// data的第一种写法:对象式
	/* data:{
			name:'张三'
	} */

	// data的第二种写法:函数式
	data(){
		console.log('@@@',this) // 此处的this是Vue实例对象
		return {
			name:'张三'
		}
	}
})

四、数据代理

1. Object.defineproperty

javascript
let number = 18
let person = {
    name: '张三',
    sex: '男',
}

Object.defineProperty(person, 'age', {
    // value:18,
    // enumerable:true, //控制属性是否可以枚举,默认值是false
    // writable:true, //控制属性是否可以被修改,默认值是false
    // configurable:true //控制属性是否可以被删除,默认值是false

    // 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
    get() {
        console.log('有人读取age属性了')
        return number
    },

    // 当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
    set(value) {
        console.log('有人修改了age属性,且值是', value)
        number = value
    }

})

// console.log(Object.keys(person))

console.log(person)

2. Vue 中的数据代理

数据代理

通过一个对象代理对另一个对象中属性的操作 (读/写) 。

javascript
let obj = {x:100}
let obj2 = {y:200}

Object.defineProperty(obj2,'x',{
    get(){
        return obj.x
    },
    set(value){
        obj.x = value
    }
})

Vue 中的数据代理

1)是什么?通过 vm 对象来代理 data 对象中属性的操作 (读/写) 。

2)Vue 中数据代理的好处:更加方便的操作 data 中的数据。

3)基本原理

      通过 Object.defineProperty() 把 data 对象中所有属性添加到 vm 上。

      为每一个添加到 vm 上的属性,都指定一个 getter/setter。

      在 getter/setter 内部去操作 (读/写) data 中对应的属性。

五、事件处理

1. 事件的基本使用

1)使用 v-on:xxx@xxx 绑定事件,其中 xxx 是事件名。

2)事件的回调需要配置在 methods 对象中,最终会在 vm 上。

3)methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向默认是 vm 或组件实例对象。methods 中配置的函数,不要用箭头函数!!!否则 this 就不是 vm 了。

4)@click="demo"@click="demo($event)" 效果一致,但后者可以传参。

2. 修饰符

prevent:阻止默认事件 (常用)。

stop:阻止事件冒泡 (常用)。

once:事件只触发一次 (常用)。

capture:使用事件的捕获模式。

     ① 冒泡是从里往外冒,捕获是从外往里捕。

     ② 当捕获存在时,先从外到里的捕获,剩下的从里到外的冒泡输出。

self:只有 event.target 是当前操作的元素时才触发事件。

passive:事件的默认行为立即执行,无需等待时间回调执行完毕。

事件修饰符可以连写,也就是说,一个事件可以绑定多个事件修饰符。

html
<!-- 准备好一个容器 -->
<div id="root">
    <h2>欢迎来到{{name}}学习</h2>
    <!-- 阻止默认事件(常用) -->
    <a href="http://www.baidu.com" @click.prevent="showInfo">点我提示信息</a>

    <!-- 阻止事件冒泡(常用) -->
    <div class="demo1" @click="showInfo">
        <button @click.stop="showInfo">点我提示信息</button>
        <!-- 修饰符可以连续写 -->
        <!-- <a href="http://www.baidu.com" @click.prevent.stop="showInfo">点我提示信息</a> -->
    </div>

    <!-- 事件只触发一次(常用) -->
    <button @click.once="showInfo">点我提示信息</button>

    <!-- 使用事件的捕获模式 -->
    <div class="box1" @click.capture="showMsg(1)">
        div1
        <div class="box2" @click="showMsg(2)">
            div2
        </div>
    </div>

    <!-- 只有event.target是当前操作的元素时才触发事件; -->
    <div class="demo1" @click.self="showInfo">
        <button @click="showInfo">点我提示信息</button>
    </div>

    <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
    <ul @wheel.passive="demo" class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>

</div>

3. 键盘事件

Vue 中常用的按键别名(修饰符)

    回车 => enter

    删除 => delete (捕获"删除"和"退格"键)

    退出 => esc

    空格 => space

    换行 => tab (特殊,必须配合 keydown 去使用)

    上 => up

    下 => down

    左 => left

    右 => right

Vue 未提供别名的按键,可以使用按键原始的 key 去绑定,但是注意要转为 kebab-case (短横线命名)。也可以使用 keyCode 去指定具体的按键 (不推荐) 。

系统修饰符 (用法特殊): ctrl、alt、shift、meta

  • 配合 keyup 使用:按下修饰按键的同时,再按下其他键,随后释放其他键,事件才触发。

    比如:@keyup.ctrl 表示 ctrl + 其他按键都可以触发事件。如果不想其他按键都可以触发事件,可以 @keyup.ctrl.y 表示只有 ctrl + y 可以触发事件。

  • 配合 keydown 使用:正常触发事件。

定制按键别名

Vue.config.keyCodes.自定义键名 = 键码

六、计算属性

1)定义:要用的属性不存在,要通过已有属性计算得来。

2)原理:底层借助了 Object.defineproperty 方法提供的 getter 和 setter。

3)get 函数什么时候执?

      初次读取时会执行一次。

      当依赖的数据发生改变时会被再次调用。

4)优势:与 methods 实现相比,内部有缓存机制(复用)。效率更高,调试方便。

计算属性最终会出现在 vm 上,直接读取使用即可。

如果计算属性要被修改,那必须写 set 函数去响应修改,且 set 中要引起计算时依赖的数据发生改变。

javascript
computed:{
    fullName:{
        // get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
        // get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
        get(){
            console.log('get被调用了')
            // console.log(this) //此处的this是vm
            return this.firstName + '-' + this.lastName
        },
        // set什么时候调用? 当fullName被修改时。
        set(value){
        	console.log('set',value)
            const arr = value.split('-')
            this.firstName = arr[0]
            this.lastName = arr[1]
        }
    }
}

// 完整写法
/* fullName:{
	get(){
		console.log('get被调用了')
		return this.firstName + '-' + this.lastName
	},
	set(value){
		console.log('set',value)
		const arr = value.split('-')
		this.firstName = arr[0]
		this.lastName = arr[1]
	}
} */

// 简写
// 注意:如果计算属性要被修改,那必须写set函数去响应修改,且必须使用完整写法
fullName(){
    console.log('get被调用了')
    return this.firstName + '-' + this.lastName
}

七、监视属性 / 侦听器

监视属性 watch。

1)当被监视的属性变化时,回调函数自动调用,进行相关操作。

2)监视的属性必须存在,才能进行监视。

3)监视的两种写法

      ① new Vue 时传入 watch 配置。

      ② 通过 vm.$watch 监视。

javascript
// 第一种写法
watch:{
    isHot:{
        immediate:true, // 初始化时让handler调用一下
        // handler什么时候调用?当isHot发生改变时。
        handler(newValue,oldValue){
            console.log('isHot被修改了',newValue,oldValue)
    }
}

// 第二种写法
vm.$watch('isHot',{
	immediate:true, // 初始化时让handler调用一下
    // handler什么时候调用?当isHot发生改变时。
    handler(newValue,oldValue){
    	console.log('isHot被修改了',newValue,oldValue)
    }
})

深度监视

1)Vue 中的 watch 默认不监测对象内部值的改变(一层)。

2)配置 deep:true 可以监测对象内部值改变(多层)。

Vue 自身可以监测对象内部值的改变,但 Vue 提供的 watch 默认不可以!

使用 watch 时根据数据的具体结构,决定是否采用深度监视。

javascript
watch:{
    // 正常写法
    /* isHot:{
		// immediate:true, // 初始化时让handler调用一下
		// deep:true,// 深度监视
		handler(newValue,oldValue){
			console.log('isHot被修改了',newValue,oldValue)
		}
	}, */
    // 简写
    /* isHot(newValue,oldValue){
		console.log('isHot被修改了',newValue,oldValue,this)
	} */
}

computed / watch 区别

1)computed 能完成的功能,watch 都可以完成。大多数情况下,computed 实现简单。

2)watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作。

两个重要的小原则

1)被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或组件实例对象。

2)所有不被 Vue 所管理的函数(定时器的回调函数、ajax 的回调函数、Promise 的回调函数),最好写成箭头函数,这样的 this 的指向才是 vm 或组件实例对象。

八、绑定样式

class 样式

写法::class="xxx",其中 xxx 可以是字符串、对象、数组。

  • 字符串写法适用于:类名不确定,要动态获取。
  • 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
  • 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。

style 样式

  • :style="{fontSize: xxx+"px"}",其中 xxx 是动态值。
  • :style="[styleObj1, styleObj2]",其中 styleObj1、styleObj2 是样式对象。
html
<!-- 准备好一个容器 -->
<div id="root">
    <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
    <div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>

    <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
    <div class="basic" :class="classArr">{{name}}</div> <br/><br/>

    <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
    <div class="basic" :class="classObj">{{name}}</div> <br/><br/>

    <!-- 绑定style样式--对象写法 -->
    <div class="basic" :style="styleObj1">{{name}}</div> <br/><br/>
    <!-- 绑定style样式--数组写法 -->
    <div class="basic" :style="styleArr">{{name}}</div>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false

    const vm = new Vue({
        el: '#root',
        data: {
            name: '张三',
            mood: 'normal',
            classArr: ['header1', 'header2', 'header3'],
            classObj: {
                header1: false,
                header2: false,
            },
            styleObj1: {
                fontSize: '40px',
                color: 'red',
            },
            styleObj2: {
                backgroundColor: 'orange'
            },
            styleArr: [{
                fontSize: '40px',
                color: 'blue',
            }, {
                backgroundColor: 'gray'
            }]
        },
        methods: {
            changeMood() {
                const arr = ['happy', 'sad', 'normal']
                const index = Math.floor(Math.random() * 3)
                this.mood = arr[index]
            }
        },
    })
</script>

九、条件渲染

v-if

(1). v-if="表达式"

(2). v-else-if="表达式"

(3). v-else

适用于:切换频率较低的场景。

特点:不展示的 DOM 元素直接被移除。

注意:v-if 可以和 v-else-if、v-else 一起使用,但要求结构不能被"打断"。

vue
<template>
  <div>
    <p v-if="condition === 'A'">Condition is A</p>
    <p v-else-if="condition === 'B'">Condition is B</p>
    <p v-else>Condition is neither A nor B</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      condition: 'A',
    };
  },
};
</script>

v-show

写法:v-show="表达式"

适用于:切换频率较高的场景

特点:不展示的 DOM 元素未被移除,仅仅是使用样式隐藏掉。给元素添加了 display:none 属性。

使用 v-if 的时候,元素可能无法获取到,而使用 v-show 一定可以获取到。

如果有一片 HTML 的显示要控制是否显示,可以使用 template 标签来包裹,在 template 标签上使用条件渲染。在实际到浏览器后,template 标签会不在,不影响结构。

十、列表渲染

1. 基本列表

v-for 指令

1)用于展示列表数据。

2)语法:v-for="(item, index) in xxx" :key="yyy" 或者 v-for="(value, key, index) in xxx" :key="yyy"

3)可遍历:数组、对象、字符串、指定次数。

注意:在 v-for 中 in 与 of 在使用中没有区别,都可以遍历数组和对象。在遍历数组时,key 与 index 的值相同,一般会略去 key 只写 (item,index)

2. key 的原理

面试题:react、vue 中的 key 有什么作用?(key 的内部原理)

1)虚拟 DOM 中 key 的作用?

      key 是虚拟 DOM 对象的标识,当数据发生变化时,vue 会根据【新数据】生成【新的虚拟DOM】,随后 Vue 进行【新虚拟DOM】与【旧虚拟DOM】的差异比较。

2)对比规则

      (1). 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key。

          ①. 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM。

          ②. 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM。

      (2). 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key。

             创建新的真实 DOM,随后渲染到页面。

3)用 index 作为 key 可能引发的问题

      (1). 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 更新 ==> 界面效果没问题,但效率低。

      (2). 如果结构中还包含输入类的 DOM,会产生错误 DOM 更新 ==> 界面有问题。

4)开发中如何选择 key?

      (1). 最好使用每条数据的唯一标识作为 key,比如 id、手机号码、身份证号、学号等唯一值。

      (2). 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。

3. 列表过滤

javascript
// 用watch实现
new Vue({
    el:'#root',
    data:{
        keyWord:'',
        persons:[
            {id:'001',name:'马冬梅',age:19,sex:'女'},
            {id:'002',name:'周冬雨',age:20,sex:'女'},
            {id:'003',name:'周杰伦',age:21,sex:'男'},
            {id:'004',name:'温兆伦',age:22,sex:'男'}
        ],
        filPerons:[]
    },
    watch:{
        keyWord:{
            immediate:true,
            handler(val){
                this.filPerons = this.persons.filter((p)=>{
                    return p.name.indexOf(val) !== -1
                })
            }
        }
    }
}) 

// 用computed实现
new Vue({
    el:'#root',
    data:{
        keyWord:'',
        persons:[
            {id:'001',name:'马冬梅',age:19,sex:'女'},
            {id:'002',name:'周冬雨',age:20,sex:'女'},
            {id:'003',name:'周杰伦',age:21,sex:'男'},
            {id:'004',name:'温兆伦',age:22,sex:'男'}
        ]
    },
    computed:{
        filPerons(){
            return this.persons.filter((p)=>{
                return p.name.indexOf(this.keyWord) !== -1
            })
        }
    }
})

当 watch 与 computed 都能实现功能的时候,优先使用 computed。

4. 列表排序

html
<div id="root">
    <h2>人员列表</h2>
    <!-- v-model双向绑定 -->
    <input type="text" placeholder="请输入名字" v-model="keyword"/>
    <button @click="sortType = 2">年龄升序</button>
    <button @click="sortType = 1">年龄降序</button>
    <button @click="sortType = 0">原顺序</button>
    <ul v-for="p in filPersons" :key="p.id">
        <li>{{p.name}}-{{ p.age }}- {{ p.sex }}</li>
    </ul>
</div>
javascript
new Vue({
	el:'#root',
	data: {
		keyword : '',
		sortType: 0, // 0代表原顺序 1代表降序 2代表升序
		persons: [
			{id: "001", name:'周冬雨', age:20, sex:'女'},
			{id: "002", name:'马冬梅', age:19, sex:'女'},
			{id: "003", name:'周杰伦', age:21, sex:'男'},
			{id: "004", name:'温兆伦', age:22, sex: '男'},
		],
	},
	computed:{
		filPersons(){
			const arr = this.persons.filter(p => p.name.indexOf(this.keyword) !== -1);
			// 判断是否需要排序
			if(!this.sortType) return arr;
			// sort回调回收到前后两个数据项,a和b
			// sort会改变的原数组
			return arr.sort((p1, p2) => this.sortType === 1 ? p2.age-p1.age : p1.age - p2.age);
		}
	}
});

十一、Vue 监视数据的原理

1)vue 会监视 data 中所有层次的数据,如果数据发生更改,会重新渲染页面。

2)如何监测对象中的数据发生改变(数据劫持)了?

      通过 setter 实现监视(其实和前面学习的数据代理一致,只不过 new vue 传入的 data 对象会创建一个与原来数据一样的对象,在这个对象的每个属性上添加 getter 与 setter 方法,间接操作原 data,在 setter 方法中会有渲染页面的代码),且要在 new Vue 时就要传入要监测的数据。

      对象中后追加的属性,Vue 默认不做响应式处理。如需给后添加的属性做响应式,请使用如下 API。

      Vue.set(target, propertyName/index, value) 或 vm.$set(target, propertyName/index, value)

3)如何监测数组中的数据?

      修改 data 中的数组,发现页面没有更新。发现没有给数组添加 setter 实现监视,那怎么办?

      通过包裹数组更新元素的方法实现。本质就是做了两件事:

          ① 调用原生对应的方法对数组进行更新。

          ② 重新解析模板,进而更新页面。

4)在 Vue 修改数组中的某个元素一定要用如下方法。

      ① 使用这些 API: push()、pop()、shift()、unshift()、splice()、sort()、reverse()

      ② Vue.set() 或 vm.$set()

      特别注意: Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象添加属性。

十二、收集表单数据

<input type="text"/>,则 v-model 收集的是 value 的值,用户输入的就是 value 值。

<input type="radio"/>,则 v-model 收集的是 value 值,且要给标签配置 value 值。否则收集到的是 null。

<input type="checkbox"/>

  • 没有配置 input 的 value 属性,那么收集的就是 checked (勾选 or 未勾选,是布尔值)

  • 配置 input 的 value 属性:

    (1). v-model 的初始值是非数组,那么收集的就是 checked (勾选 or 未勾选,是布尔值) 。

    (2). v-model 的初始值是数组,那么收集的就是 value 组成的数组。

html
<!-- 准备好一个容器 -->
<div id="root">
    <form @submit.prevent="demo">
        账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
        密码:<input type="password" v-model="userInfo.password"> <br/><br/>
        年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
        性别:
        男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
        女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
        爱好:
        学习<input type="checkbox" v-model="userInfo.hobby" value="study">
        打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
        吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
        <br/><br/>
        所属校区
        <select v-model="userInfo.city">
            <option value="">请选择校区</option>
            <option value="beijing">北京</option>
            <option value="shanghai">上海</option>
            <option value="shenzhen">深圳</option>
            <option value="wuhan">武汉</option>
        </select>
        <br/><br/>
        其他信息:
        <textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
        <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
        <button>提交</button>
    </form>
</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el:'#root',
        data:{
            userInfo:{
                account:'',
                password:'',
                age:18,
                sex:'female',
                hobby:[],
                city:'beijing',
                other:'',
                agree:''
            }
        },
        methods: {
            demo(){
                console.log(JSON.stringify(this.userInfo))
            }
        }
    })
</script>

十三、过滤器

1)定义:对要显示的数据进行特定格式化后再显示 (适用于一些简单逻辑的处理) 。

2)语法

       注册过滤器: Vue.filter(name,callback)new Vue({filters:{}})

       使用过滤器: v-bind:属性= "xxx | 过滤器名" 或者 {{ xxx | 过滤器名 }}

3)备注

      过滤器也可以接收额外参数、多个过滤器也可以串联。

      并没有改变原本的数据,是产生新的对应的数据。

javascript
// 全局过滤器
Vue.filter('mySlice',function(value){
	return value.slice(0,4)
})

// 局部过滤器
filters:{
    timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
        // console.log('@',value)
        return dayjs(value).format(str)
    }
}

十四、内置指令

以前学习过的指令。

v-bind : 单向绑定解析表达式,可简写为 :xxx

v-model : 双向数据绑定

v-on : 绑定事件监听,可简写为 @

v-for : 遍历数组/对象/字符串

v-show : 条件渲染 (动态控制节点是否展示)

v-if : 条件渲染 (动态控制节点是否存在)

v-else-if : 条件渲染 (动态控制节点是否存在)

v-else : 条件渲染 (动态控制节点是否存在)

1. v-text

1)作用:向其所在的节点中渲染文本内容。

2)与插值语法的区别:v-text 会替换掉节点中的内容,而 {{xxx}} 则不会。

2. v-html

1)作用:向指定节点中渲染包含 html 结构的内容。

2)与插值语法的区别

      ① v-html 会替换掉节点中所有的内容,而 {{xxx}} 则不会。

      ② v-html 可以识别 html 结构,而 v-text 则不会。

3)严重注意:v-html 有安全性问题!!!

      ① 在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。

      ② 一定要在可信的内容上使用 v-html,永远不要用在用户提交的内容上!

3. v-cloak

v-cloak 指令 (没有值) 。

1)本质上是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性。

2)使用 css 配合 v-cloak 可以解决网速慢时页面展示出 {{xxx}} 的问题。

例子:

css
[v-cloak] {
  display: none;
}
html
<div v-cloak>
  {{ message }}
</div>

message 还未被编译时,包含 v-cloak 指令的 div 元素会被隐藏。当 Vue 实例结束编译,v-cloak 指令被移除后,div 元素就会显示出来。

4. v-once

v-once 表示元素和所有的子元素在初始渲染后,将被视为静态内容。这意味着,如果数据发生变化,这些元素和子元素不会再被重新渲染。

使用 v-once 可以有助于提高 Vue.js 应用的性能,因为静态内容只需要被渲染一次,而不需要在每次数据变化时都重新渲染。

5. v-pre

1)用于跳过这个元素和它的子元素的编译过程。

2)可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

十五、自定义指令

1. 定义语法

① 局部指令

javascript
new Vue({
  el: '#app',
  data: {
    color: 'red'
  },
  directives: {
    'color': {
      inserted: function (el, binding) {
        el.style.color = binding.value;
      }
    }
  }
});
html
<div id="app" v-color="red">Hello, world!</div>

② 全局指令

Vue.directive(指令名,配置对象)Vue.directive(指令名,回调函数)

javascript
// 使用配置对象
Vue.directive('color', {
  inserted: function (el, binding) {
    el.style.color = binding.value;
  }
});

// 使用回调函数
// 回调函数何时执行?① 指令与元素成功绑定时(一上来)。② 指令所在的模板被重新解析时。 <==> bind & updata
Vue.directive('color', function (el, binding) {
  el.style.color = binding.value;
});

2. 配置对象中常用的 3 个回调

     ① bind:指令与元素成功绑定时调用。

     ② inserted:指令所在元素被插入页面时调用。

     ③ updata:指令所在模板结构被重新解析时调用。

3. 备注

     ① 指令定义时不加 v-,但使用时要加 v-。

     ② 指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase 命名。

html
<div id="root">
    <h2>{{name}}</h2>
    <h2>当前的n值是:<span v-text="n"></span> </h2>
    <!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
    <h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
    <button @click="n++">点我n+1</button>
    <hr/>
    <input type="text" v-fbind:value="n">
</div>
<script type="text/javascript">
    Vue.config.productionTip = false

    // 定义全局指令
    /* Vue.directive('fbind',{
    	// 指令与元素成功绑定时(一上来)
    	bind(element,binding){
    		element.value = binding.value
    	},
    	// 指令所在元素被插入页面时
    	inserted(element,binding){
    		element.focus()
    	},
    	// 指令所在的模板被重新解析时
    	update(element,binding){
    		element.value = binding.value
    	}
    }) */

    new Vue({
        el: '#root',
        data: {
            name: '张三',
            n: 1
        },
        directives: {
            // big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
            /* 'big-number'(element,binding){
            	// console.log('big')
            	element.innerText = binding.value * 10
            }, */
            big(element, binding) {
                console.log('big', this) // 注意此处的this是window
                element.innerText = binding.value * 10
            },
            fbind: {
                // 指令与元素成功绑定时(一上来)
                bind(element, binding) {
                    element.value = binding.value
                },
                // 指令所在元素被插入页面时
                inserted(element, binding) {
                    element.focus()
                },
                // 指令所在的模板被重新解析时
                update(element, binding) {
                    element.value = binding.value
                }
            }
        }
    })
</script>

十六、生命周期

生命周期又名生命周期回调函数、生命周期函数、生命周期钩子。

1)是什么?Vue 在关键时刻帮我们调用的一些特殊名称的函数。

2)生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。

3)生命周期函数中 this 指向是 vm 或组件实例对象。

Vue 实例从创建到销毁的过程中,会经历一系列的生命周期阶段。在这些阶段中,Vue 提供了一些生命周期钩子函数。以下是 Vue 的生命周期钩子:

  1. beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  2. created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
  3. beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。
  4. mounted:el 被新创建的 vm.elrootmountedvm.el 也在文档内。
  5. beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  6. updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
  7. beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  8. destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器都会被移除,所有的子实例也都会被销毁。

常用的生命周期钩子:

1)mounted:发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化工作】。

2)beforeDestroy:清楚定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

关于销毁 Vue 实例:

1)销毁后借助 Vue 开发者工具看不到任何信息。

2)销毁后自定义事件会失效,但原生 DOM 事件依然有效。

3)一般不会再 beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了(你都要销毁了,别在这里更新了,做一些收尾操作得了)。

除了现在学习的 8 个 4 对生命周期钩子外,其实还有 3 个隐藏的,需要学习组件后才可以了解。

第三章:组件基础

一、认识组件

以前开发时,HTML、CSS、JavaScript 关系混乱。

组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。

在实际应用中,组件常常被组织成层层嵌套的树状结构。

二、使用组件方式

1. 非单文件组件

使用组件的三大步骤

① 定义组件(创建组件)。

② 注册组件。

③ 使用组件(写组件标签)。

如何定义一个组件?

使用 Vue.extend(options) 创建,其中 options 和 new Vue(options) 时传入的那个 options 几乎一样,但也有点区别。

① el 不要写,为什么?最终所有的组件都要经过一个 vm 管理,由 vm 中的 el 决定服务哪个容器。

② data 必须写成函数,为什么?避免组件被复用时,数据存在引用关系。

③ 使用 template 可以配置组件结构。

④ components 可以写在 options 里面,表示这个组件下有子组件(组件嵌套)。

如何注册组件?

① 局部注册:靠 new Vue 的时候传入 components 选项。

② 全局注册:靠 Vue.component('组件名',组件)

1)关于组件名

一个单词组成

    第一种写法 (首字母小写): school

    第二种写法 (首字母大写): School

多个单词组成

    第一种写法(kebab-case命名): my-school

    第二种写法(CamelCase命名): MySchool (需要脚手架支持)

备注

    组件名尽可能回避 HTML 中已有的元素名称,例如: h2、H2 都不行

    可以使用 name 配置项指定组件在开发者工具中呈现的名字

3)关于组件标签

第一种写法: <school></school>

第二种写法: <school/>

    备注: 不使用脚手架时,<school/> 会导致后续组价不能渲染

4)一个简写方式

const school = Vue.extend(options) 可简写为: const school = options

为什么可以这样?因为在 components 里,他会检查组件在定义的时候有没有写 Vue.extend(options),如果没有就会补上,如果有,就拿来直接用。

2. 单文件组件

[1] 使用步骤

Vue 的单文件组件(SFC)是一个 .vue 文件,它包含一个 Vue 组件的模板、脚本和样式。以下是一个基本的单文件组件的例子:

vue
<template>
  <div class="message">
    {{ message }}
  </div>
</template>

<script>
  export default {
    name: "APP",
    data() {
      return {
        message: 'Hello, Vue!'
      }
    }
  }
</script>

<style scoped>
  .message {
    color: blue;
  }
</style>

在这个例子中:

  • <template> 部分是组件的 HTML 模板。这里定义了组件的结构和内容。
  • <script> 部分是组件的 JavaScript 代码。这里定义了组件的数据和方法。
  • <style> 部分是组件的 CSS 样式。这里定义了组件的外观。scoped 属性表示这些样式只应用于当前组件。

要使用这个组件,需要在其他组件或应用中导入它:

javascript
import MyComponent from './MyComponent.vue'

然后,就可以在模板中使用这个组件:

vue
<template>
  <my-component></my-component>
</template>

<script>
import MyComponent from './MyComponent.vue'

export default {
  components: {
    MyComponent
  }
}
</script>

注意:需要一个能够处理 .vue 文件的工具,如 Vite 或 webpack(配合 vue-loader)。开发时大多数会使用 Vue CLI(其实使用的是 webpack)。

[2] scoped 样式

1)作用:让样式在局部生效,防止冲突。

2)写法:<style lang="css/less ……" scoped>

一般在 src/App.vue 里的 style 标签里写全局都使用的样式。这样在组件里都可以使用这里的样式了。

三、原理探究

1. Vue Component

1)当定义一个组件 (Vue.extend) 时,返回一个特殊的 Vue 实例(VueComponent 构造函数)。

javascript
function extend(extendOptions){
    // ......
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // ......
    return Sub
}

特别注意:每次调用 Vue.extend 时,返回的都是一个全新的 VueComponent!!!

2)当使用组件时 (<school/><school></school>),Vue 解析时会帮我们创建 school 组件的实例对象,即 Vue 帮我们执行 new VueComponent(options)

3)关于 this 指向

  • 组件配置中

    data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 均是【VueComponent 实例对象】。

  • new Vue(options) 配置中

    data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 均是【Vue 实例对象】。

4)VueComponent 的实例对象,以后简称 vc (也可称之为:组件实例对象) 。

2. 内置关系

1)一个重要的内置关系:VueComponent.prototype._proto_ === Vue.prototype。

2)为什么要有这个关系:让组件实例对象 (vc) 可以访问到 Vue 原型上的属性、方法。

Released under the MIT License.