Routing parameter decoupling
Generally use route parameters inside components, most people will do this:
export default {
methods: {
getParamsId() {
return this.$route.params.id
}
}
}
Use in a component makes it highly coupled with its corresponding route, so that the component can only be used on some specific URLs, limiting its flexibility. $route
The correct way to do this is through decoupling props
const router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
props: true
}]
})
After setting the attribute of the route to, the parameters can be received in the component through props
true
props
params
export default {
props: ['id'],
methods: {
getParamsId() {
return this.id
}
}
}
In addition, you can also use the function pattern to return props
const router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
props: (route) => ({
id: route.query.id
})
}]
})
functional component
A functional component is stateless, it cannot be instantiated, it does not have any lifecycle and methods. Creating functional components is also as simple as adding a declaration to the template. It is generally suitable for components that only depend on changes in external data. Because of their light weight, rendering performance will be improved. functional
Everything a component needs is passed through parameters. It is a context object, see documentation for specific properties. Here is an object containing all bound properties. context
props
functional component
<template functional>
<div class="list">
<div class="item" v-for="item in props.list" :key="item.id" @click="props.itemClick(item)">
<p>{{item.title}}</p>
<p>{{item.content}}</p>
</div>
</div>
</template>
parent component use
<template>
<div>
<List :list="list" :itemClick="item => (currentItem = item)" />
</div>
</template>
import List from '@/components/List.vue'
export default {
components: {
List
},
data() {
return {
list: [{
title: 'title',
content: 'content'
}],
currentItem: ''
}
}
}
Documentation: cn.vuejs.org/v2/guide/re…
style penetration
It is very common to modify the styles of third-party components in development, but due to the style isolation of attributes, it may be necessary to remove or create a new one . These practices will bring side effects (component style pollution, not elegant enough), and style penetration will only take effect when used in the CSS preprocessor. scoped
scoped
style
We can use or work around this: >>>
/deep/
<style scoped>
外层 >>> .el-checkbox {
display: block;
font-size: 26px;
.el-checkbox__label {
font-size: 16px;
}
}
</style>
<style scoped>
/deep/ .el-checkbox {
display: block;
font-size: 26px;
.el-checkbox__label {
font-size: 16px;
}
}
</style>
Advanced use of watch
execute immediately
watch
It is triggered when the listener property changes. Sometimes, we want to execute it immediately after the component is created. watch
The possible way to think of it is to call it once in the life cycle, but this way of writing is not elegant, maybe we can use such a method create
export default {
data() {
return {
name: 'Joe'
}
},
watch: {
name: {
handler: 'sayName',
immediate: true
}
},
methods: {
sayName() {
console.log(this.name)
}
}
}
Deep listening
When listening to an object, it cannot be triggered when the properties inside the object are changed , we can set deep monitoring for it watch
export default {
data: {
studen: {
name: 'Joe',
skill: {
run: {
speed: 'fast'
}
}
}
},
watch: {
studen: {
handler: 'sayName',
deep: true
}
},
methods: {
sayName() {
console.log(this.studen)
}
}
}
Trigger listener to execute multiple methods
Use arrays to set multiple items in the form of strings, functions, objects
export default {
data: {
name: 'Joe'
},
watch: {
name: [
'sayName1',
function(newVal, oldVal) {
this.sayName2()
},
{
handler: 'sayName3',
immaediate: true
}
]
},
methods: {
sayName1() {
console.log('sayName1==>', this.name)
},
sayName2() {
console.log('sayName2==>', this.name)
},
sayName3() {
console.log('sayName3==>', this.name)
}
}
}
watch listens to multiple variables
watch itself cannot listen to multiple variables. But we can return the object by calculating the properties of multiple variables that need to be monitored, and then monitor this object to achieve "listening to multiple variables"
export default {
data() {
return {
msg1: 'apple',
msg2: 'banana'
}
},
compouted: {
msgObj() {
const { msg1, msg2 } = this
return {
msg1,
msg2
}
}
},
watch: {
msgObj: {
handler(newVal, oldVal) {
if (newVal.msg1 != oldVal.msg1) {
console.log('msg1 is change')
}
if (newVal.msg2 != oldVal.msg2) {
console.log('msg2 is change')
}
},
deep: true
}
}
}
Event parameter $event
$event
It is a special variable of the event object. In some scenarios, it can provide us with more available parameters for implementing complex functions.
Primitive case
Behaves the same as the default event object in native events
<template>
<div>
<input type="text" @input="inputHandler('hello', $event)" />
</div>
</template>
export default {
methods: {
inputHandler(msg, e) {
console.log(e.target.value)
}
}
}
custom event
Behaves in custom events to capture values thrown from child components
my-item.vue
:
export default {
methods: {
customEvent() {
this.$emit('custom-event', 'some value')
}
}
}
复制代码
App.vue
<template>
<div>
<my-item v-for="(item, index) in list" @custom-event="customEvent(index, $event)">
</my-list>
</div>
</template>
export default {
methods: {
customEvent(index, e) {
console.log(e) // 'some value'
}
}
}
Documentation: cn.vuejs.org/v2/guide/ev…
cn.vuejs.org/v2/guide/co…
Two-way binding of custom components
Component options: model
Allows a custom component to customize props and events when using v-model. By default, v-model on a component will use value as prop and input as event, but some input types such as radio buttons and checkbox buttons may want to use the value prop for different purposes. Use the model option to avoid conflicts in these situations.
input
By default, it is used as the update event of the two-way binding, and the value of the binding can be updated by $emit
<my-switch v-model="val"></my-switch>
export default {
props: {
value: {
type: Boolean,
default: false
}
},
methods: {
switchChange(val) {
this.$emit('input', val)
}
}
}
Modify component options, customize bound variables and events model
<my-switch v-model="num" value="some value"></my-switch>
export default {
model: {
prop: 'num',
event: 'update'
},
props: {
value: {
type: String,
default: ''
},
num: {
type: Number,
default: 0
}
},
methods: {
numChange() {
this.$emit('update', this.num++)
}
}
}
Documentation: cn.vuejs.org/v2/api/#mod…
Monitor component life cycle
Usually we use the monitoring component life cycle , and the parent component receives events to notify $emit
Subassembly
export default {
mounted() {
this.$emit('listenMounted')
}
}
parent component
<template>
<div>
<List @listenMounted="listenMounted" />
</div>
</template>
In fact, there is a simple method, you can use it to monitor the component life cycle, and you don't need to make any changes in the component. Similarly, , , etc. can also use this method. @hook
created
updated
<template>
<List @hook:mounted="listenMounted" />
</template>
Programmatic event listeners
For example, if you define a timer when the page is mounted, you need to clear the timer when the page is destroyed. This looks fine. But if you take a closer look , the only function is to be able to get the timer serial number inside , and there is no use other than that. this.timer
beforeDestroy
export default {
mounted() {
this.timer = setInterval(() => {
console.log(Date.now())
}, 1000)
},
beforeDestroy() {
clearInterval(this.timer)
}
}
It is best if only the lifecycle hooks can access it. This isn't a serious problem, but it can be considered a clutter.
We can solve this problem by listening for page lifecycle destruction: $on
$once
export default {
mounted() {
this.creatInterval('hello')
this.creatInterval('world')
},
creatInterval(msg) {
let timer = setInterval(() => {
console.log(msg)
}, 1000)
this.$once('hook:beforeDestroy', function() {
clearInterval(timer)
})
}
}
After using this method, even if we create multiple timers at the same time, it will not affect the effect. Because they are automatically cleared programmatically after the page is destroyed.
Manually mount components
In some requirements, manually mounting components can make our implementation more elegant. For example, a popup window component, the most ideal use is through an imperative call, like . Instead of passing state toggles in the template, this implementation is really bad. elementUI
this.$message
Let's start with the simplest example:
import Vue from 'vue'
import Message from './Message.vue'
// 构造子类
let MessageConstructor = Vue.extend(Message)
// 实例化组件
let messageInstance = new MessageConstructor()
// $mount可以传入选择器字符串,表示挂载到该选择器
// 如果不传入选择器,将渲染为文档之外的的元素,你可以想象成 document.createElement()在内存中生成dom
messageInstance.$mount()
// messageInstance.$el获取的是dom元素
document.body.appendChild(messageInstance.$el)
The following implements a simple popup window component message
Message/index.vue
<template>
<div class="wrap">
<div class="message" :class="item.type" v-for="item in notices" :key="item._name">
<div class="content">{{item.content}}</div>
</div>
</div>
</template>
// 默认选项
const DefaultOptions = {
duration: 1500,
type: 'info',
content: '这是一条提示信息!',
}
let mid = 0
export default {
data() {
return {
notices: []
}
},
methods: {
add(notice = {}) {
// name标识 用于移除弹窗
let _name = this.getName()
// 合并选项
notice = Object.assign({
_name
}, DefaultOptions, notice)
this.notices.push(notice)
setTimeout(() => {
this.removeNotice(_name)
}, notice.duration)
},
getName() {
return 'msg_' + (mid++)
},
removeNotice(_name) {
let index = this.notices.findIndex(item => item._name === _name)
this.notices.splice(index, 1)
}
}
}
.wrap {
position: fixed;
top: 50px;
left: 50%;
display: flex;
flex-direction: column;
align-items: center;
transform: translateX(-50%);
}
.message {
--borderWidth: 3px;
min-width: 240px;
max-width: 500px;
margin-bottom: 10px;
border-radius: 3px;
box-shadow: 0 0 8px #ddd;
overflow: hidden;
}
.content {
padding: 8px;
line-height: 1.3;
}
.message.info {
border-left: var(--borderWidth) solid #909399;
background: #F4F4F5;
}
.message.success {
border-left: var(--borderWidth) solid #67C23A;
background: #F0F9EB;
}
.message.error {
border-left: var(--borderWidth) solid #F56C6C;
background: #FEF0F0;
}
.message.warning {
border-left: var(--borderWidth) solid #E6A23C;
background: #FDF6EC;
}
Message/index.js
import Vue from 'vue'
import Index from './index.vue'
let messageInstance = null
let MessageConstructor = Vue.extend(Index)
let init = () => {
messageInstance = new MessageConstructor()
messageInstance.$mount()
document.body.appendChild(messageInstance.$el)
}
let caller = (options) => {
if (!messageInstance) {
init(options)
}
messageInstance.add(options)
}
export default {
// 返回 install 函数 用于 Vue.use 注册
install(vue) {
vue.prototype.$message = caller
}
}
main.js
import Message from '@/components/Message/index.js'
Vue.use(Message)
use
this.$message({
type: 'success',
content: '成功信息提示',
duration: 3000
})
Collect and organize: 10 Vue development tips