一、 生命周期
| 生命周期钩子 | 组件状态 | 最佳实践 |
|---|---|---|
| beforeCreate | 实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods上的方法和数据 | 常用于初始化非响应式变量 |
| created | 实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到ref属性内容为空数组 | 常用于简单的ajax请求,页面的初始化 |
| beforeMount | 在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 | -- |
| mounted | 实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问 | 常用于获取VNode信息和操作,ajax请求 |
| beforeupdate | 响应式数据更新时调用,发生在虚拟DOM打补丁之前 | 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器 |
| updated | 虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作 | 避免在这个钩子函数中操作数据,可能陷入死循环 |
| beforeDestroy | 实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例 | 常用于销毁定时器、解绑全局事件、销毁插件对象等操作 |
| destroyed | 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 | -- |
注意:
- created阶段的ajax请求与mounted请求的区别:前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态
mounted不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick- vue2.0之后主动调用$destroy()不会移除dom节点,作者不推荐直接destroy这种做法,如果实在需要这样用可以在这个生命周期钩子中手动移除dom节点
<template>
<div>
<h3>单组件</h3>
<el-button @click="value += 1">更新 {{value}}</el-button>
<el-button @click="handleDestroy">销毁</el-button>
</div>
</template>
<script>
export default {
data() {
return {
value: 1
}
},
beforeCreate() {
this.compName = 'hello'
console.log(`--${this.compName}--beforeCreate`)
},
created() {
console.log(`--${this.compName}--created`)
},
beforeMount() {
console.log(`--${this.compName}--beforeMount`)
},
mounted() {
console.log(`--${this.compName}--mounted`)
},
beforeUpdate() {
console.log(`--${this.compName}--beforeUpdate`)
},
updated() {
console.log(`--${this.compName}--updated`)
},
beforeDestroy() {
console.log(`--${this.compName}--beforeDestroy`)
},
destroyed() {
console.log(`--${this.compName}--destroyed`)
},
methods: {
handleDestroy() {
this.$destroy()
}
}
}
</script>
二、自定义组件的v-model
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突。
子组件:
<template>
<label>
自定义input:
<input
v-bind="$attrs"
:value="value"
@input="onInput($event.target.value)"
>
</label>
</template>
<script>
export default {
props: ['value'],
watch: {
value(newVal, oldVal) {
console.log('newVal type = ' + typeof newVal)
console.log('oldVal type = ' + typeof oldVal)
console.log('watch-child-value', newVal)
}
},
methods: {
onInput(value) {
console.log('onInput')
this.$emit('input', value)
}
}
}
</script>
父组件:
<template>
<child-component v-model="currValue" />
</template>
<script>
import childComponent from './child-component'
export default {
components: { childComponent },
data() {
return {
currValue: ''
}
},
watch: {
currValue(val) {
console.log('watch-value:', val)
console.log('watch-value type:', typeof val)
// this.currValue = parseInt(val) // 这是一串死循环代码
}
}
}
</script>
注意:
- 理论上,input event -> parent value change ->child value change -> input event,是一个死循环。当child value change时,会判定前后的值是否相等,如果相等则不会触发input event。
- 为了避免上面的死循环,不能在上面的过程中修改value值。
三、自定义插槽
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 <slot> 元素作为承载分发内容的出口。插槽
定义插槽
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
使用插槽
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
定义插槽参数
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
使用插槽参数
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
四、动态组件 & 异步组件
<component v-bind:is="currentTabComponent"></component>
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
<script>
import childComponent from './child-component.vue'
export default {
components: { asyncComponent: () => import('./async-component.vue'), childComponent },
}
</script>
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
五、Vue通信
-
父子组件通信
1. 通过事件:v-on指令和$emit 2. 通过属性:props 3. 语法糖:v-model 4. 直接访问实例:$parent和$refs 5. 访问内置属性:$attrs和$listeners 6. 注入:provide和inject -
事件总线:EventBus
-
状态管理库:VueX
-
定义全局变量
Vue.prototype.GLOBAL = {} export const Global = {} -
通过本地存储:cookie / localStorage / sessionStorage
-
路由传参
六、路由传参
- 路径传参
// 跳转
this.$router.push({ path: `/details/${id}`,})
// 获取参数
this.$route.params.id
- 通过路由属性中的name来确定匹配的路由,通过params来传递参数
// 跳转
const id = '12345'
this.$router.push({ name: 'userDetail', params: { id }})
// 获取参数
this.$route.params.id
- 使用path来匹配路由,然后通过query来传递参数
// 跳转
const id = '12345'
this.$router.push({ name: 'userDetail', query: { id }})
// 获取参数
this.$route.query.id
七、自定义指令
参考自定义指令
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})