Skyphobia

无责任分享一些Vue的奇技淫巧(一)

在 Vue 的世界里探索了几年,近期整理了下平时写过的妖代码,分享出来误人子弟一把。以下任何内容请在家长陪同下尝试,不要轻易在生产环境中尝试。

一、通过魔法属性 __vue__ 获取 vue 实例

Vue 会为每一个实例挂载的 DOM 对象添加一个 __vue__ 属性,这个属性对应的值就是这个实例本身。利用这个属性,我们不仅可以在生产环境进行 debug,甚至可以实现在子组件直接更改父组件状态等黑魔法。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Vue.component('demo', {
template: '<button @click="onclick">click</button>',
methods: {
onclick () {
// 通过 __vue__ 获取父级实例,并篡改 msg 数据
const $vm = document.querySelector('#parent').__vue__
$vm.msg = '__vue__'
}
}
})

new Vue({
template: `
<div id="parent">
<h1>Hello {{msg}}!</h1>
<demo></demo>
</div>
`,
el: '#app',
data: {
msg: 'Vue'
}
})

演示

至于这个“黑魔法”属性是否可以安全使用,从这个 issue 中尤大的回答来看,由于 vue 的 devtool 依赖于这个属性,未来应该也不会有变。

二、强制刷新 router-view

vue-router 的官方文档中关于动态路由匹配的说明中有这么一段话:

提醒一下,当使用路由参数时,例如从 /user/foo 导航到 /user/bar原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用

出于组件被复用的这份好意,我们不得不通过 watch $route 对象的变化来代替原本在生命周期钩子中做的一些初始化操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const Page = {
template: `
<div>
{{id}}
</div>
`,
data () {
return { id: 0 }
},
watch: {
'$route' (newVal) {
this.id = newVal.params.id
}
}
}

const routes = [{
path: '/page/:id',
component: Page
}]

const router = new VueRouter({
routes
})

new Vue({
template: `
<div>
<router-link to="/page/1">Go to One</router-link>
<router-link to="/page/2">Go to Two</router-link>
<router-view></router-view>
</div>
`,
el: '#app',
router
})

和其他不被复用的子路由组件相比,这样的代码实在是有点丑陋,初始化流程也不统一。那么有没有办法用回我们的生命周期钩子呢?

答案是 key

router-view 组件添加一个根据路由变化的 key 可以实现整个 router-view 组件的重刷:

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const Page = {
template: `<div>{{id}}</div>`,
data () {
return { id: 0 }
},
created () {
this.id = this.$route.params.id
}
}

const routes = [{
path: '/page/:id',
component: Page
}]

const router = new VueRouter({
routes
})

new Vue({
template: `
<div>
<router-link to="/page/1">Go to One</router-link>
<router-link to="/page/2">Go to Two</router-link>
<router-view :key="$route.fullPath"></router-view>
</div>
`,
el: '#app',
router
})

演示

值得注意的是,因为强制重刷导致子路由组件无法被复用,会引发页面大幅度重渲染。当页面本身具有一定复杂度的时候,这样的做法会严重影响性能,请谨慎使用!