之前基于 Vue 的项目花了十个工作日不到的时间搞定了二期迭代,又发现了一些实用的东西,于是简单记录一下。

混合

Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.

如文档介绍,混合以一种灵活的方式为组件提供分布复用功能(即将功能从组件中分离,并允许多个组件复用)。这里倾向于逻辑而非模板的复用,同时也区别于插件,更多地与业务相关。

混合的使用很简单,但在使用过程中也需要注意一些地方。一开始自己的使用如下示例:

// define a mixin object
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log(this.foo)
}
}
}
// define a component that uses this mixin
var Component = Vue.extend({
mixins: [myMixin],
data: function() {
return {
foo: 'bad code'
}
}
})
var component = new Component() // -> "bad code"

可以看到混合选项中的方法调用了组件的 data。尽管代码正常运行,但这种做法造成代码的可维护性和可读性比较糟糕。一方面,使用混合的组件必须声明 foo,增加了维护的成本;另一方面对于混合选项来说,foo 这个属性是未知的,可读性很差。我自己的解决方法则是对于组件不关心的属性直接在混合选项中声明,组件关心的属性则组件和混合选项同时声明,由组件的值覆盖后者。

组件通信

这里以 Modal 为例巴拉下自己做法转变的过程。超简化的 Modal 如下

// Modal.vue
export default {
data() {
return {
visible: false,
msg: ''
}
},
methods: {
show() {},
hide() {}
}
}

组件传递

每个页面都拥有自己的子组件 Modal 这种事情我不干,高度可定制的内容通过 partial 解决。Modal 组件放在根组件下,通过 prop 直接传给子组件调用。大概这样子:

<!-- 根组件模板 -->
<div>
<router-view :modal="$refs.modal"></router-view>
<Modal v-ref:modal></Modal>
</div>
// 子组件调用 Modal
export default {
props: {
modal: ['modal']
},
attached() {
this.modal.show('hello')
}
}

这种做法当组件层级复杂的话,需要把 Modal 一层一层传下去。尽管组件的层级关系清晰,但这是比较繁琐的事情。

自定义事件

既然不把组件从上往下传,那就可以考虑从下往上把事件抛出来。Vue 的每个实例提供了自定义事件接口,能够抛出事件并被其他组件捕获。于是基于自定义事件调起 Modal 的做法可以如下:

<!-- 根组件模板 -->
<div>
<router-view></router-view>
<Modal v-ref:modal></Modal>
</div>
// 根组件监听事件
export default {
events: {
'modal_show'(...args) {
this.$refs.modal.show(...args)
}
}
}
// 子组件调用 Modal
export default {
attached() {
this.$dispatch('modal_show', 'hello')
}
}

自定义事件使得组件之间很方便进行通信,但是滥用自定义事件的话会造成组件松散,导致难以维护。

基于 Vuex

把 Modal 的状态放进 store 之后,可以通过 action 很直观地调用 Modal。示例如下:

// store.js
const state = {
modal: {
visible: false,
msg: ''
}
}
// Modal.vue
export default {
vuex: {
getters: {
visible: state => state.modal.visible,
msg: state => state.modal.msg
}
},
methods: {
show() {},
hide() {}
}
}
// 子组件调用 Modal
export default {
vuex: {
actions: {
showModal
}
},
attached: {
this.showModal('hello')
}
}

Modal 可以看作整个应用的一个公共状态(等待用户操作),因此把它放进 store 是合理的,然后就是 Vuex 的套路了。

以上三种做法都可以达到目的,当然也有其他做法,看个人想法咯。

服务端同步数据

之前的做法是在 plugin 中捕获 action 之后发起请求,现在想想这种做法太绕啦,直接在 action 中发起请求更加简洁,然后再 dispatch 服务端响应的数据。

// actions.js
export const fetchUser = function({dispatch}) {
Vue
.http()
.post('url')
.then(res => {
let ret = res.json()
dispatch(ActionTypes.USER_UPDATE, ret.data)
})
}

当然,上面的做法相当于把服务端同步下来的数据直接放进 store 里面了。如果是组件私有的数据,则可以通过 Mixins 抽象同步数据的方法。上面也提到了,通过混合可以灵活地复用功能。以列表为例,同步服务端可以这样子处理

// mixin-list.js
export default {
data: {
return {
_store: [],
_params: {},
_link: ''
}
},
methods() {
fetchList() {
let {_store, _link, _params} = this.$data
this
.$http()
.post(_link, _params)
.then(res => {
let ret = res.json()
_store.push(ret.data)
})
}
}
}
// 组件
export default {
mixins: [mixinList],
data() {
return {
_store: [],
_params: {
auth: true
},
_link: 'url'
}
},
computed: {
list() {
return this._store.filter(item => item.valid)
}
},
attached() {
this.fetchList()
}
}