Vue.js起步
数据到视图映射
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<fieldset>
<legend>数据驱动视图</legend>
{{ message }}
</fieldset>
</div>
<script>
const App = {
data() {
return {
message: 'Hello World'
}
}
}
Vue.createApp(App).mount('#app')
</script>
</body>
</html>
修改数据
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<fieldset>
<legend>数据驱动视图</legend>
<button @click="add">Click Me</button>
{{ counter }}
</fieldset>
</div>
<script>
const App = {
data() {
return {
counter: 0
}
},
methods: {
add() {
this.counter++
}
},
}
Vue.createApp(App).mount('#app')
</script>
</body>
</html>
属性绑定
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>

<body>
<div id="app">
<fieldset>
<legend>属性的绑定</legend>
<a :href="url" :title="message">百度</a>
{{ message }}
</fieldset>
</div>
<script>
const App = {
data() {
return {
url: 'https://baidu.com',
message: 'Hello World'
}
}
}
Vue.createApp(App).mount('#app')
</script>
</body>
</html>
双向绑定
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<fieldset>
<legend>双向绑定</legend>
<input type="text" v-model="search">
{{search}}
</fieldset>
</div>
<script>
const App = {
data() {
return {
search: "hello"
}
}
}
Vue.createApp(App).mount('#app')
</script>
</body>
</html>
条件渲染
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<fieldset>
<legend>条件渲染</legend>
<p v-if="isShow"> 子组件</p>
<button @click="isShow = !isShow">Click</button>
</fieldset>
</div>
<script>
const App = {
data() {
return {
isShow: true
}
},
// methods: {
// toggle() {
// this.isShow = !this.isShow
// }
// },
}
Vue.createApp(App).mount('#app')
</script>
</body>
</html>
循环渲染
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<fieldset>
<legend>列表渲染</legend>
<ul>
<li v-for="value in items"> {{value}}</li>
</ul>
<button @click="add">Click</button>
</fieldset>
</div>
<script>
const App = {
data() {
return {
items: ['item0', 'item1', 'item2']
}
},
methods: {
add() {
this.items.push('item' + this.items.length)
}
},
}
Vue.createApp(App).mount('#app')
</script>
</body>
</html>
自定义组件
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<fieldset>
<legend>自定义组件</legend>
<todo-item></todo-item>
<button @click="add">Click</button>
</fieldset>
</div>
<script>
const App = {
data() {
return {
}
}
}
const app = Vue.createApp(App)
app.component('todo-item', {
data() {
return { message: 'hello' }
},
template: "<div>{{message}} </div>"
})
app.mount('#app')
</script>
</body>
</html>
组件实例、生命周期
1、Vue有哪些⽣命周期钩⼦?
- beforeCreate 在实例初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用
- created
在实例创建完成后被立即调用。在这一步,实例已完成以下的配置︰数据观测(data observer),property和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,
$el property目前尚不可用 - beforeMount 在挂载开始之前被调用∶相关的render函数首次被调用
- mounted
实例被挂载后调用,这时
Vue.createApp({}).mount()被新创建的vm.$el替换了。如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.$el也在文档内。 - beforeUpdate 数据更新时调用,发生在虚拟DOM打补丁之前
- updated 由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子
- beforeUnmount 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的
- unmounted 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载
2、 如果需要发送Ajax请求,最好放在哪个钩⼦内?
一般情况下,都放在mounted中,保证逻辑的统一性。因为生命周期是同步执行的,ajax是异步执行的。
服务端渲染不支持mounted方法,所以在服务端渲染的情况下统一放在created中。
3、 ⽗⼦组件嵌套时,⽗组件视图和⼦组件视图渲染完成谁先谁后?
不确定
4、 ⽗⼦组件嵌套时,如果希望在所有组件视图都渲染完成后再执⾏操
作,该如何做?
mounted() {
this.$nextTick(function () {
// 仅在渲染整个视图之后运⾏的代码
});
}
应用实例与组件实例
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<fieldset>
<legend>组件实例</legend>
{{ message }}
<button @click="change">Click</button>
</fieldset>
</div>
<script>
const App = {
data() {
return {
message: 'Hello Vue!!'
}
},
methods: {
change() {
console.log(this)
this.message += '!'
}
},
}
const app = Vue.createApp(App)
console.log(app)
const vm = app.mount('#app')
console.log(vm)
</script>
</body>
</html>
问题:左侧代码中的this是什么?
vm对象
生命周期
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
<div id="app">
<fieldset>
<legend>生命周期钩子</legend>
<span id="msg">{{ message }}</span>
<button @click="change">Click</button>
</fieldset>
</div>
<script>
const App = {
data() {
return { message: 'Hello Vue'}
},
methods: {
change() {
this.message += '!'
}
},
beforeCreate() {
console.log('beforeCreate', this.message, this.$el)
},
created() {
console.log('created', this.message, this.$el)
},
beforeMount() {
console.log('beforeMount', this.message, this.$el)
},
mounted() {
console.log('mounted', this.message, this.$el)
},
beforeUpdate() {
console.log('beforeUpdate', this.message, document.querySelector('#msg').innerText)
},
updated() {
console.log('updated', this.message, document.querySelector('#msg').innerText)
}
}
Vue.createApp(App).mount('#app')
</script>
</body>
</html>
组件销毁生命周期代码演示
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<fieldset>
<legend>生命周期钩子</legend>
<child v-show="isShow"></child>
<button @click="isShow = !isShow">Click</button>
</fieldset>
</div>
<script>
const App = {
data() {
return { isShow: true }
},
beforeCreate() {
console.log('root beforeCreate', this.message, this.$el)
},
created() {
console.log('root created', this.message, this.$el)
},
beforeMount() {
console.log('root beforeMount', this.message, this.$el)
},
mounted() {
console.log('root mounted', this.message, this.$el)
}
}
const app = Vue.createApp(App)
app.component('child', {
template: '<div>{{text}}</div>',
data() {
return {text: 'I am child'}
},
created() {
console.log('child created')
},
mounted() {
console.log('child mounted')
},
beforeUnmount() {
console.log('child beforeUnmount')
},
unmounted() {
console.log('child unmounted')
},
})
app.mount('#app')
</script>
</body>
</html>
data、methods、computed、watch
指令
data methods
computed
methods和computed的区别
- 调用方式不同。computed直接以对象属性方式调用,不需要加括号,而methods必须要函数执行才可以得到结果。
- 绑定方式不同。methods与compute纯get方式都是单向绑定,不可以更改输入框中的值。compute的get与set方式是真正的双向绑定。
- 是否存在缓存。methods没有缓存,调用相同的值计算还是会重新计算。competed有缓存,在值不变的情况下不会再次计算,而是直接使用缓存中的值。
watch
小结
- v-text 和 v-html 有什么区别
- data 为什么要是函数
- 计算属性缓存是什么,如果不想缓存该怎么做
- watch、计算属性有什么区别
响应式原理
什么是响应式
数据响应式
范例
var obj = {}
var age
Object.defineProperty(obj, 'age', {
get: function(){
console.log('get age...')
return age
},
set: function(val){
console.log('set age...')
age = val
}
})
obj.age = 100 // 'set age...'
console.log(obj.age) // 'get age...', 100
Object.defineProperty实现响应式
function observe(data) {
if(!data || typeof data !== 'object') return
for(var key in data) {
let val = data[key]
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
track(data, key)
return val
},
set: function(newVal) {
trigger(data, key, newVal)
val = newVal
}
})
if(typeof val === 'object'){
observe(val)
}
}
}
function track(data, key) {
console.log('get data ', key)
}
function trigger(data, key, value) {
console.log('set data', key, ":", value)
}
var data = {
name: 'hunger',
friends: [1, 2, 3]
}
observe(data)
console.log(data.name)
data.name = 'valley'
data.friends[0] = 4
data.friends[3] = 5 // 非响应式
data.age = 6 //非响应式
Proxy和Reflect
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, prop) {
console.log('get...', prop)
//return target[prop]
return Reflect.get(...arguments)
},
set(target, key, value) {
console.log('set...', key, value)
//target[key] = value
return Reflect.set(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
Proxy实现响应式
function reactive(obj) {
const handler = {
get(target, prop, receiver) {
track(target, prop)
const value = Reflect.get(...arguments)
if (typeof value === 'object') {
return reactive(value)
} else {
return value
}
},
set(target, key, value, receiver) {
trigger(target, key, value)
return Reflect.set(...arguments)
}
}
return new Proxy(obj, handler)
}
function track(data, key) {
console.log('get data ', key)
}
function trigger(data, key, value) {
console.log('set data', key, ":", value)
}
const dinner = {
meal: 'tacos'
}
const proxy = reactive(dinner)
proxy.meal = 'apple'
proxy.list = []
proxy.list.push(1) //响应式
小结
- Vue3和Vue2的响应式原理分别是什么,二者有什么差异
- 手写reactive 实现track trigger
- 用Proxy和Object.defineProperty相比有什么优点和缺点
条件、列表、事件、组件、双向绑定、单向数据流
v-if和v-show的区别
<div id="app">
<button @click="isShow=!isShow">Toggle</button>
<p v-if="isShow">v-if content</p>
<p v-show="isShow">v-show content</p>
<child v-if="isShow" title="v-if"></child>
<child v-show="isShow" title="v-show"></child>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data() {
return { isShow: true }
}
})
app.component('child', {
props: ['title'],
template: `<div>component {{title}}</div>`,
created() {
console.log('child created', this.title)
},
unmounted() {
console.log('child unmounted', this.title)
}
})
app.mount('#app')
</script>
v-for列表渲染
<div id="app">
<div v-for="(item,index) in items" >
<child></child>
<button @click="remove(index)">delete</button>
</div>
<button @click=add>add</button>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
//[{id:1}, {id:2}, {id:3}]
//[child1, child2, child3]
const app = Vue.createApp({
data() { return { items: [] } },
methods:{
remove(index) {
console.log('remove ', index)
this.items.splice(index,1)
},
add() { this.items.push({id: Math.random()}) }
}
})
app.component('child', {
data() { return { counter: Math.random() } },
template: `<span>{{counter}} </span>` ,
unmounted() {
console.log(this.counter + ' unmounted')
},
})
app.mount('#app')
</script>
事件处理
<div id="app">
<span v-on:click="sayHello">click</span>
<!-- <span @click="sayHello">click</span>
<span @click="sayHello('hunger')">click</span>
<span @click="sayHello($event), sayHi('hunger')">click</span>
<span @click.once="sayHello(name)">click</span> -->
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
data() {
return {
name: 'hunger'
}
},
methods: {
sayHello(name) {
console.log(name)
},
sayHi(name) {
console.log(name)
}
},
}).mount('#app')
</script>
v-model
<div id="app">
<fieldset>
<legend> value 和 input </legend>
<!-- value 和 input -->
<input v-model="message" /> {{ message }}
</fieldset>
<fieldset>
<legend> value 和 change </legend>
<!-- value 和 change -->
<textarea v-model.lazy="message"></textarea> {{ message }}
</fieldset>
<fieldset>
<legend> 复选框 </legend>
<input type="checkbox" v-model="checked" /> {{checked}}
</fieldset>
<fieldset>
<legend> 多个复选框 </legend>
<input type="checkbox" value="a" v-model="list" />
<input type="checkbox" value="b" v-model="list" /> {{list}}
</fieldset>
<fieldset>
<legend> 单选框 </legend>
<input type="radio" value="a" v-model="theme" />
<input type="radio" value="b" v-model="theme" /> {{theme}}
</fieldset>
<fieldset>
<legend> select </legend>
<select v-model="selected" multiple>
<option value="AA">A</option>
<option value="BB">B</option>
<option value="CC">C</option>
</select>
<br />
<span>Selected: {{ selected }}</span>
</fieldset>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
data() {
return {
message: 'aa',
checked: false,
list: [],
theme: '',
selected: []
}
}
}).mount('#app')
</script>
组件基础
<div id="app">
<font-size step="1" :val="fontSize" @plus="fontSize += $event"
@minus="fontSize -= $event"></font-size>
<font-size step="3" :val="fontSize" @plus="fontSize += $event"
@minus="fontSize -= $event"></font-size>
<p :style="{fontSize:fontSize+'px'}">Hello {{fontSize}}</p>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data() { return { fontSize: 18 } }
})
app.component('font-size', {
props: ['val', 'step'],
template: `
<div>step: {{step}}
<button @click="onPlus">+</button>
<button @click="$emit('minus', step)">-</button>
</div>`,
methods: {
onPlus() { this.$emit('plus', parseInt(this.step)) }
}
})
app.mount('#app')
</script>
组件的v-model双向绑定
- 父组件通过v-model="属性"把属性传递给子组件。
- 子组件内有一个modelvalue的ProP,接收父组件传递的数据。
- 子组件通过触发update:modelValue修改父组件绑定的属性
<div id="app">
<font-size step="1" v-model="fontSize"></font-size>
<font-size step="4" v-model="fontSize"></font-size>
<p :style="{fontSize:fontSize+'px'}">Hello {{fontSize}}</p>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data() { return { fontSize: 16 } }
})
app.component('font-size', {
props: ['modelValue', 'step'],
template: `
<div>step: {{modelValue}}
<button @click="$emit('update:modelValue', +step+modelValue)">+</button>
<button @click="$emit('update:modelValue', modelValue-step)">-</button>
</div>`
})
app.mount('#app')
</script>
单向数据流
- 什么是单向数据流?
很多框架都使用单向数据流,指的是父组件能直接传递数据给子组件,子组件不能随意修改父组件状态。 - 为什么要单向?
单向数据流的目的是让数据的传递变得简单、可控、可追溯。假设都是用双向数据流,父组件维护一个状态,并且传递给所有的子组件。当其中一个子组件通过双向数据流直接修改父组件的这个状态时,其他子组件也会被修改。当这个结果产生时我们无法得知是拿个子组件修改了数据,在项目庞大时数据的管理和追湖变得更复杂。 - 如果要双向如何实现?
一般来说,父组件可用通过设置子组件的props直接传递数据给子组件。子组件想传递数据给父组件时,可以在内部mit一个自定义事件,父组件可在子组件上绑定该事件的监听,来处理子组件mit的事件和数据。 在Vue里,v-models实现的所谓双向绑定,本质上就这种方式。
小结
1、列表循环时key的作用?
- 简单通俗地讲,没有key时,状态默认绑定的是位置,有key时,状态根据key的属性值绑定到了响应的数组元素。
- key是为了更高效的更新虚拟DOM
- 推荐使用数组内的字段(保证唯一性)作为key的唯一标识,不建议直接使用index 2、@click="handler"和@click="handler()"哪个正确?
3、父子组件如何传递数据?
4、单向数据流指的是什么?有什么好处? 5、v-model是如何实现的
Vue3组件注册、Props、Attribute、自定义事件
组件注册
Props
非Prop的属性
自定义事件
v-model语法糖
小结
- 在Vue中组件的全局注册和局部注册有什么区别,如何局部注册组件?\
- 如何传递一个字符串类型的prop给子组件?数字类型呢?如何动态给prop赋值?\
- 对于组件来说非prop的attribute怎么处理?\
- v-model:foo="bar”与:foo="bar"有什么区别?如何实现v-model:foo="bar” ?\
- Vue中的插槽是什么?\
- 如何实现多层级嵌套的父子组件数据传递?\
- keep-alive有什么作用
插槽、具名插槽、作用域插槽
slot插槽
具名插槽
作用域插槽
keep-alive与生命周期
小结
- Vue中的插槽是什么?
- 具名插槽怎么用?
- 作用域插槽是什么?