<!-- 插值 {{}} -->
<span>{{ message }}</span>
<!-- 使用表达式 -->
<span>{{ number + 1 }}</span>
<span>{{ ok ? 'YES' : 'NO' }}</span>
<span>{{ message.split('').reverse().join('') }}</span>
XSS
风险。v-bind:
简写:
style
对象属性使用驼峰式写法<template>
<p :class="{ black: isBlack, yellow: isYellow }">使用 class 对象</p>
<p :class="[black, yellow]">使用 class 数组</p>
<p :style="styleData">使用 style</p>
</template>
export default {
data() {
return {
isBlack: true,
isYellow: true,
black: 'black',
yellow: 'yellow',
styleData: {
fontSize: '40px',
color: 'red',
backgroundColor: '#ccc'
}
};
}
}
v-if
、v-else
的用法:可以使用变量,也可使用 ===
表达式。
<template>
<div>
<p v-if="type === 'a'">A</p>
<p v-else-if="type === 'b'">B</p>
<p v-else>other</p>
<p v-show="type === 'a'">A by v-show</p>
<p v-show="type === 'b'">B by v-show</p>
</div>
</template>
export default {
data() {
return {
type: 'a'
};
}
}
v-if
和 v-show
的区别?
v-if
控制dom
节点的渲染和销毁,适用于切换不频繁的场景;v-show
是通过display: none;
来控制显示隐藏,适用于切换频繁的场景。v-for
遍历,需要写key
(尽量不要是random
和index
,需要是和业务相关的id
);v-for
和v-if
不能同时使用。<template>
<div>
<p>遍历数组</p>
<ul>
<li v-for="(item, index) in listArr" :key="item.id">
{{index}} - {{item.id}} - {{item.title}}
</li>
</ul>
<p>遍历对象</p>
<ul >
<li v-for="(val, key, index) in listObj" :key="key">
{{index}} - {{key}} - {{val.title}}
</li>
</ul>
</div>
</template>
export default {
data() {
return {
flag: false,
listArr: [
{ id: 'a', title: '标题1' }, // 数据结构中,最好有 id ,方便使用 key
{ id: 'b', title: '标题2' },
{ id: 'c', title: '标题3' }
],
listObj: {
a: { title: '标题1' },
b: { title: '标题2' },
c: { title: '标题3' },
}
}
}
}
computed
有缓存,data
不变则不会重新计算。watch
是浅度监听, watch
监听引用类型,拿不到 oldval
。watch
如何深度监听?
<template>
<div>
<input v-model="name"/>
<input v-model="info.city"/>
</div>
</template>
<script>
export default {
data() {
return {
name: '章三',
info: {
city: '北京'
}
}
},
watch: {
name(val, oldVal) {
console.log('watch name', val, oldVal) // 值类型,可正常拿到 oldVal 和 val
},
info: {
handler(val, oldVal) {
console.log('watch info', val, oldVal) // 引用类型,拿不到 oldVal 因为指针相同,此时已经指向了新的 val
},
deep: true // 深度监听
}
}
}
</script>
event
参数;event
参数传递($event
)。<template>
<div>
<p>{{num}}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
</div>
</template>
export default {
data() {
return {
num: 0
}
},
methods: {
increment1(event) {
// eslint-disable-next-line
console.log('event', event, event.__proto__.constructor) // 是原生的 event 对象
// eslint-disable-next-line
console.log(event.target)
// eslint-disable-next-line
console.log(event.currentTarget) // 注意,事件是被注册到当前元素的,和 React 不一样
this.num++
// 1. event 是原生的
// 2. 事件被挂载到当前元素
// 和 DOM 事件一样
},
increment2(val, event) {
// eslint-disable-next-line
console.log(event.target)
this.num = this.num + val
},
loadHandler() {
// do some thing
}
},
mounted() {
window.addEventListener('load', this.loadHandler)
},
beforeDestroy() {
//【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
// 自己绑定的事件,需要自己销毁!!!
window.removeEventListener('load', this.loadHandler)
}
}
vue
中事件被绑定到哪里?
event
是原生的;修饰符可以串联;可以只有修饰符。
v-on:click.stop
阻止单击事件继续传播v-on:submit.prevent
提交事件不再重载页面v-on:click.capture
内部元素触发的事件先在此处理,然后才交由内部元素进行处理v-on:click.self
只当在 event.target
是当前元素自身时触发处理函数<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a >
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a >
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
v-model
textarea
、checkbox
、radio
、select
lazy
(防抖,输完才会变化)、number
(转换成数字)、trim
(去掉两端空格)<template>
<div>
<p>输入框: {{name}}</p>
<input type="text" v-model.trim="name"/>
<input type="text" v-model.lazy="name"/>
<input type="text" v-model.number="age"/>
<p>多行文本: {{desc}}</p>
<textarea v-model="desc"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->
<p>复选框 {{checked}}</p>
<input type="checkbox" v-model="checked"/>
<p>多个复选框 {{checkedNames}}</p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<p>单选 {{gender}}</p>
<input type="radio" id="male" value="male" v-model="gender"/>
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model="gender"/>
<label for="female">女</label>
<p>下拉列表选择 {{selected}}</p>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表选择(多选) {{selectedList}}</p>
<select v-model="selectedList" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
export default {
data() {
return {
name: '章三',
age: 18,
desc: '自我介绍',
checked: true,
checkedNames: [],
gender: 'male',
selected: '',
selectedList: []
};
}
}
// event.js
import Vue from 'vue';
export default new Vue();
<!-- 子组件 List.vue -->
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">
{{item.title}}
<button @click="deleteItem(item.id)">删除</button>
</li>
</ul>
</div>
</template>
// 子组件 List.vue
import event from './event';
export default {
// props: ['list']
props: {
// prop 类型和默认值
list: {
type: Array,
default() {
return [];
}
}
},
data() {
return {
};
},
methods: {
deleteItem(id) {
this.$emit('delete', id);
},
addTitleHandler(title) {
console.log('on add title', title);
}
},
created() {
console.log('list created');
},
mounted() {
console.log('list mounted');
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler);
},
beforeUpdate() {
console.log('list before update');
},
updated() {
console.log('list updated');
},
beforeDestroy() {
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler);
}
}
<!-- 子组件 Input.vue -->
<template>
<div>
<input type="text" v-model="title"/>
<button @click="addTitle">add</button>
</div>
</template>
// 子组件 Input.vue
import event from './event';
export default {
data() {
return {
title: ''
};
},
methods: {
addTitle() {
// 调用父组件的事件
this.$emit('add', this.title);
// 调用自定义事件
event.$emit('onAddTitle', this.title);
this.title = '';
}
}
}
<!-- 父组件 Index.vue -->
<template>
<div>
<Input @add="addHandler"/>
<List :list="list" @delete="deleteHandler"/>
</div>
</template>
// 父组件 Index.vue
import Input from './Input';
import List from './List';
export default {
components: {
Input,
List
},
data() {
return {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
}
]
};
},
methods: {
addHandler(title) {
this.list.push({
id: `id-${Date.now()}`,
title
});
},
deleteHandler(id) {
this.list = this.list.filter(item => item.id !== id);
}
},
created() {
console.log('index created');
},
mounted() {
console.log('index mounted');
},
beforeUpdate() {
console.log('index before update');
},
updated() {
console.log('index updated');
},
}
beforeCreate
、created
(vue
实例初始化完成)beforeMount
、mounted
(dom
渲染完成,一般进行ajax
请求)beforeUpdate
(data
改变,dom
渲染之前)、updated
(data
改变,vdom
重新渲染完成)beforeDestroy
(解除绑定,销毁子组件,解绑事件监听器)、destroyed
以上述“vue组件如何通讯”的代码作为例子讲解。
父组件初始化完成
子组件初始化完成
子组件挂载完成
父组件挂载完成
父组件开始更新
子组件开始更新
子组件更新完成
父组件更新完成
父组件开始销毁
子组件开始销毁
子组件销毁完成
父组件销毁完成
index created
list created
list mounted
index mounted
index before update
list before update
list updated
index updated
<template>
<!-- 例如:vue 颜色选择 -->
<input type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
>
<!--
1. 上面的 input 使用了 :value 而不是 v-model
2. 上面的 change1 和 model.event 要对应起来
3. text1 属性对应起来
-->
</template>
export default {
model: {
prop: 'text1', // 对应 props text1
event: 'change1'
},
props: {
text1: String,
default() {
return '';
}
}
}
<!-- 使用自定义 v-model -->
<template>
<p>{{ name }}</p>
<CustomVModel v-model="name"/>
</template>
由于vue
是异步渲染,data
改变之后,dom
不会立刻渲染,$nextTick
会在dom
渲染之后被触发,以获取最新的dom
节点。
<template>
<div id="app">
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{item}}
</li>
</ul>
<button @click="addItem">添加一项</button>
</div>
</template>
export default {
name: 'app',
data() {
return {
list: ['a', 'b', 'c'];
};
},
methods: {
addItem() {
this.list.push(`${Date.now()}`);
this.list.push(`${Date.now()}`);
this.list.push(`${Date.now()}`);
// 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
// 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1;
console.log( ulElem.childNodes.length );
})
}
}
}
父组件想往子组件内部插一些东西。
<!-- SlotDemo.vue -->
<template>
<a :href="url">
<slot>
默认内容,即父组件没设置内容时,这里显示
</slot>
</a>
</template>
// SlotDemo.vue
export default {
props: ['url'],
data() {
return {};
}
}
<!-- Index.vue -->
<template>
<SlotDemo :url="website.url">
{{website.title}}
</SlotDemo>
</template>
// Index.vue
import SlotDemo from './SlotDemo';
export default {
components: {
SlotDemo
},
data() {
return {
name: '章三',
website: {
url: 'http://imooc.com/',
title: 'imooc',
subTitle: '程序员的梦工厂'
},
showFormDemo: false
}
}
}
把子组件的data
扔出来,让父组件获取到。
子组件的<slot>
指定:slotData
将数据暴露出去;父组件通过v-slot
定义一个名字xxx
,然后通过xxx.slotData
获取到子组件暴露出去的数据。
<!-- ScopedSlotDemo.vue -->
<template>
<a :href="url">
<slot :slotData="website">
{{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 -->
</slot>
</a>
</template>
// ScopedSlotDemo.vue
export default {
props: ['url'],
data() {
return {
website: {
url: 'http://wangEditor.com/',
title: 'wangEditor',
subTitle: '轻量级富文本编辑器'
}
};
}
}
<!-- Index.vue -->
<template>
<ScopedSlotDemo :url="website.url">
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
</ScopedSlotDemo>
</template>
// Index.vue
import ScopedSlotDemo from './ScopedSlotDemo';
export default {
components: {
ScopedSlotDemo
},
data() {
return {
name: '章三',
website: {
url: 'http://imooc.com/',
title: 'imooc',
subTitle: '程序员的梦工厂'
},
showFormDemo: false
}
}
}
<!-- NamedSlotDemo.vue -->
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
// NamedSlotDemo.vue
export default {
props: {},
data() {
return {};
}
}
<!-- Index.vue -->
<template>
<NamedSlotDemo>
<template v-slot:header>
<h1>将插入 header slot 中</h1>
</template>
<p>将插入到 main slot 中,即未命名的 slot</p>
<template v-slot:footer>
<p>将插入 footer slot 中</p>
</template>
</NamedSlotDemo>
</template>
// Index.vue
import NamedSlotDemo from './NamedSlotDemo';
export default {
components: {
NamedSlotDemo
},
data() {
return {};
}
}
用法::is="组件名字"
。
需要根据数据,动态渲染组件。即组件类型不确定。
<!-- NextTick.vue -->
<template>
<div id="app">
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{item}}
</li>
</ul>
<button @click="addItem">添加一项</button>
</div>
</template>
// NextTick.vue
export default {
name: 'app',
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
// 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1
// eslint-disable-next-line
console.log( ulElem.childNodes.length )
})
}
}
}
<!-- Index.vue -->
<template>
<component :is="NextTickName"/>
</template>
// Index.vue
import NextTick from './NextTick'
export default {
components: {
NextTick
},
data() {
return {
NextTickName: 'NextTick' // 对应组件的名字
};
}
}
用法:import()
函数。
按需加载,异步加载大组件。
<!-- FormDemo.vue -->
<template>
<div>
<p>输入框: {{name}}</p>
<input type="text" v-model.trim="name"/>
<input type="text" v-model.lazy="name"/>
<input type="text" v-model.number="age"/>
<p>多行文本: {{desc}}</p>
<textarea v-model="desc"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->
<p>复选框 {{checked}}</p>
<input type="checkbox" v-model="checked"/>
<p>多个复选框 {{checkedNames}}</p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<p>单选 {{gender}}</p>
<input type="radio" id="male" value="male" v-model="gender"/>
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model="gender"/>
<label for="female">女</label>
<p>下拉列表选择 {{selected}}</p>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表选择(多选) {{selectedList}}</p>
<select v-model="selectedList" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
// FormDemo.vue
export default {
data() {
return {
name: '章三',
age: 18,
desc: '自我介绍',
checked: true,
checkedNames: [],
gender: 'male',
selected: '',
selectedList: []
}
}
}
<!-- Index.vue -->
<template>
<FormDemo v-if="showFormDemo"/>
<button @click="showFormDemo = true">show form demo</button>
</template>
// Index.vue
export default {
components: {
FormDemo: () => import('../BaseUse/FormDemo'),
},
data() {
return {
showFormDemo: false
};
}
}
vue
缓存组件。频繁切换,不需要重复渲染的时候使用,比如切换tab
页。
<!-- KeepAlive.vue -->
<template>
<div>
<button @click="changeState('A')">A</button>
<button @click="changeState('B')">B</button>
<button @click="changeState('C')">C</button>
<keep-alive> <!-- tab 切换 -->
<KeepAliveStageA v-if="state === 'A'"/> <!-- v-show -->
<KeepAliveStageB v-if="state === 'B'"/>
<KeepAliveStageC v-if="state === 'C'"/>
</keep-alive>
</div>
</template>
// KeepAlive.vue
import KeepAliveStageA from './KeepAliveStateA'
import KeepAliveStageB from './KeepAliveStateB'
import KeepAliveStageC from './KeepAliveStateC'
export default {
components: {
KeepAliveStageA,
KeepAliveStageB,
KeepAliveStageC
},
data() {
return {
state: 'A'
}
},
methods: {
changeState(state) {
this.state = state
}
}
}
<!-- KeepAliveStateA.vue -->
<template>
<p>state A</p>
</template>
// KeepAliveStateA.vue
export default {
mounted() {
// eslint-disable-next-line
console.log('A mounted')
},
destroyed() {
// eslint-disable-next-line
console.log('A destroyed')
}
}
<!-- KeepAliveStateB.vue -->
<template>
<p>state B</p>
</template>
// KeepAliveStateB.vue
export default {
mounted() {
// eslint-disable-next-line
console.log('B mounted')
},
destroyed() {
// eslint-disable-next-line
console.log('B destroyed')
}
}
<!-- KeepAliveStateC.vue -->
<template>
<p>state C</p>
</template>
// KeepAliveStateC.vue
export default {
mounted() {
// eslint-disable-next-line
console.log('C mounted')
},
destroyed() {
// eslint-disable-next-line
console.log('C destroyed')
}
}
<!-- Index.vue -->
<template>
<<KeepAlive />
</template>
// Index.vue
import KeepAlive from './KeepAlive';
export default {
components: {
KeepAlive
},
data() {
return {
};
}
}
多个组件有相同的逻辑,抽离出来,就使用mixin
。
但是它会有一些问题,vue3
提出来的composition API
可以解决这些问题。
// mixin.js
export default {
data() {
return {
city: '北京'
}
},
methods: {
showName() {
// eslint-disable-next-line
console.log(this.name)
}
},
mounted() {
// eslint-disable-next-line
console.log('mixin mounted', this.name)
}
}
<!-- MixinDemo.vue -->
<template>
<div>
<p>{{name}} {{major}} {{city}}</p>
<button @click="showName">显示姓名</button>
</div>
</template>
// MixinDemo.vue
import myMixin from './mixin'
export default {
mixins: [myMixin], // 可以添加多个,会自动合并起来
data() {
return {
name: '章三',
major: 'web 前端'
}
},
methods: {
},
mounted() {
// eslint-disable-next-line
console.log('component mounted', this.name)
}
}
mixin
的问题:
mixin
可能造成命名冲突mixin
组件可能会出现多对多的关系,复杂度较高vuex
是vue
的状态管理库,方便组件之间的通讯。
出处:https://coding.imooc.com/lesson/419.html
state
:状态树getter
:派生状态(也叫做store
的计算属性)mutation
:更改 store
中状态的唯一方法是提交 mutation
action
:处理异步操作module
:由于单一状态比较臃肿,所以vuex
支持将将 store
分割成模块。dispatch
Action
通过 store.dispatch
方法触发。commit
mutation
更改store
中的状态。store.commit
。mapState
mapState
辅助函数帮助我们生成计算属性。mapGetters
store
中的 getter
映射到局部计算属性。mapActions
action
,可以使用 mapActions
辅助函数将组件的 methods
映射为 store.dispatch
调用(需要先在根节点注入 store
)。mapMutations
mutation
,可以使用 mapMutations
辅助函数将组件中的 methods
映射为 store.commit
调用(需要在根节点注入 store
)。hash
模式:默认,如http://www.abc.com#/user/10
h5 history
模式:需要server
端支持,如http://www.abc.com/user/10
const User = {
// 获取参数如 10 、20
template: '<div>User {{ $route.params.id }}</div>'
};
const router = new VueRouter({
routes: [
// 动态路径参数:以冒号开头。
// 能命中 '/user/10'、'/user/20' 等格式的路由
{
path: '/user/:id',
componnet: User
}
]
});
export default new VueRouter({
routes: [
{
path: '/',
component: () => import('./../components/Navigator')
},
{
path: '/feedback',
component: () => import('./../components/FeedBack')
}
]
});