作者简介:
钱昱
多年前端工作经验 《JavaScript 设计模式精讲》 作者,主要分享前端方面技术博客
公众号:前端下午茶
这个问题是在下在做一个 Vue 项目中遇到的实际场景,这里记录一下我遇到问题之后的思考和最后怎么解决的(老年程序员记性不好 -。-),过程中会涉及到一些Vue源码的概念比如 $mount、 render watcher等,如果不太了解的话可以瞅瞅 Vue源码阅读系列文章 ~
问题是这样的:页面从后台拿到的数据是由 0、 1之类的key,而这个key代表的value比如 0-女、 1-男的对应关系是要从另外一个数据字典接口拿到的;类似于这样的Api:
{
"SEX_TYPE"
:
[
{
"paramValue"
:
0
,
"paramDesc"
:
"女"
},
{
"paramValue"
:
1
,
"paramDesc"
:
"男"
}
]
}
那么如果view拿到的是 0,就要从字典中找到它的描述 女并且显示出来;下面故事开始了~
- 思考
有人说,这不是过滤器 filter 要做的事么,直接 Vue.filter 不就行了,然而问题是这个filter 是要等待异步的数据字典接口返回之后才能拿到,如果在 $mount 的时候这个filter没有找到,那么就会导致错误影响之后的渲染(白屏并报 undefined错);
我想到的解决方法有两个:
把接口变为同步,在 beforeCreate或 created钩子中同步地获取数据字典接口,保证在 $mount的时候可以拿到注册好的filter,保证时序,但是这样会阻塞挂载,延长白屏时间,因此不推介;
把filter的注册变为异步,在获取filter之后通知 render watcher 更新自己,这样可以利用vue自己的响应式化更新视图,不会阻塞渲染,因此在下初步采用了这个方法。
- 实现
因为filter属于 assettypes ,关于在Vue实例中assettypes的访问链有以下几个结论;具体代码实践可以参考:Codepen - filter test
asset_types包括 filters、 components、 directives,以下所有的 asset_types都自行替换成前面几项
子组件中的 asset_types访问不到父组件中的 asset_types,但是可以访问到全局注册的挂载在 options.asset_types.proto上的 asset_types,这里对应源码 src/core/util/options.js
全局注册方法Vue.assettypes,比如Vue.filters注册的assettypes会挂载到根实例(其他实例的 options.asset_types.proto上,并被以后所有创建的Vue实例继承,也就是说,以后所有创建的Vue实例都可以访问到
组件的slot的作用域仅限于它被定义的地方,也就是它被定义的组件中,访问不到父组件的 asset_types,但是可以访问到全局定义的 asset_types
同理,因为main.js中的 newVue()实例是根实例,它中注册的 asset_types会被挂载在 options.asset_types上而不是
options.asset_types.proto上
根据以上几个结论,可以着手coding了~
2.1 使用根组件的filters
因此首先我考虑的是把要注册的filter挂载到根组件上,这样其他组件通过访问 $root可以拿到注册的filter,这里的实现:
<template>
<div>
{{ rootFilters( sexVal )}}
</div>
</template>
<script
type
'text/javascript'
import
Vue
from
'vue'
import
{
registerFilters
}
from
'utils/filters'
export
default
{
data
()
{
return
{
sexVal
:
1
// 性别
}
},
methods
:
{
/* 根组件上的过滤器 */
rootFilters
(
val
,
id
=
'SEX_TYPE'
)
{
const
mth
=
this
.
options
.
filters
[
id
]
return
mth
&&
mth
(
val
)
||
val
}
},
created
()
{
// 把根组件中的filters响应式化
Vue
.
util
.
defineReactive
(
this
.
options
,
'filters'
,
this
.
options
.
filters
)
},
mounted
()
{
registerFilters
.
call
(
this
)
.
then
(
data
=>
// 这里获取到数据字典的data
)
}
}
</script>
注册filter的js
// utils/filters
import
as
Api
from
'api'
/**
-
获取并注册过滤器
-
注册在
options.filters上不是
options.filters.proto上
-
注意这里的this是vue实例,需要用call或apply调用
-
@returns {Promise}
*/
export
function
registerFilters
()
{
return
Api
.
sysParams
()
// 获取数据字典的Api,返回的是promise
.
then
(({
data
})
=>
{
Object
.
keys
(
data
).
forEach
(
T
=>
this
.
root
.
$options
.
filters
,
T
,
val
=>
{
const
tar
=
data
[
T
].
find
(
item
=>
item
[
'paramValue'
]
===
val
)
return
tar
[
'paramDesc'
]
||
''
})
)
return
data
})
.
catch
(
err
=>
console
.
error
(
err
,
' in utils/filters.js'
))
}
这样把根组件上的filters变为响应式化的,并且在渲染的时候因为在 rootFilters方法中访问了已经在created中被响应式化的 options.filters,所以当异步获取的数据被赋给
options.filters的时候,会触发这个组件render watcher的重新渲染,这时候再获取 rootFilters方法的时候就能取到filter了;
那这里为什么不用Vue.filter方法直接注册呢,因为 Object.defineProperty不能监听 proto上数据的变动,而全局Vue.filter是将过滤器注册在了根组件 options.asset_types.proto上,因此其变动不能被响应。
这里的代码可以进一步完善,但是这个方法存在一定的问题,首先这里使用了 Vue.util上不稳定的方法,另外在使用中到处可见 this.options这样访问vue实例内部属性的情况,不太文明,读起来也让人困惑。
因此在这个项目做完等待测试的时候我思考了一下,谁说过滤器就一定放在filters里面 -。-,也可以使用mixin来实现嘛
2.2 使用 mixin
使用mixin要注意一点,因为vue中把data里所有以 _、 data._xx来访问。
// mixins/sysParamsMixin.js
import
as
Api
from
'api'
export
default
{
data
()
{
return
{
_filterFunc
:
null
,
// 过滤器函数
_sysParams
:
null
,
// 获取数据字典
_sysParamsPromise
:
null
// 获取sysParams之后返回的Promise
}
},
methods
:
{
/* 注册过滤器到_filterFunc中 */
_getSysParamsFunc
()
{
const
{
$data
}
=
this
return
$data
.
_sysParamsPromise
||
(
$data
.
_sysParamsPromise
=
Api
.
sysParams
()
.
then
(({
data
})
=>
{
this
.
$data
.
_sysParams
=
data
this
.
$data
.
_filterFunc
=
{}
Object
.
keys
(
data
).
forEach
(
paramKey
=>
this
.
$data
.
_filterFunc
[
paramKey
]
=
val
=>
{
const
tar
=
data
[
paramKey
].
find
(
item
=>
item
[
'paramValue'
]
===
val
)
return
tar
&&
tar
[
'paramDesc'
]
||
''
})
return
data
})
.
catch
(
err
=>
console
.
error
(
err
,
' in src/mixins/sysParamsMixin.js'
)))
},
/* 按照键值获取单个过滤器 */
_rootFilters
(
val
,
id
=
'SEX_TYPE'
)
{
const
func
=
this
.
$data
.
_filterFunc
const
mth
=
func
&&
func
[
id
]
return
mth
&&
mth
(
val
)
||
val
},
/* 获取数据字典 */
_getSysParams
()
{
return
this
.
$data
.
_sysParams
}
}
}
这里把 Api的 promise 保存下来,如果其他地方还用到的话直接返回已经是 resolved状态的 promise,就不用再次去请求数据了。另外为了在其他实例中也可以方便的访问,这里挂载在根组件上。
那在我们的根组件中怎么使用呢:
// src/main.js
import
sysParamsMixin from
'mixins/sysParamsMixin'
new
Vue
({
el
:
'#app'
,
mixins
:
[
sysParamsMixin
],
render
:
h
=>
h
(
App
),
})
在需要用过滤器的组件中:
<template>
<div>
{{ $root._rootFilters( sexVal )}}
</div>
</template>
<script
type
'text/javascript'
export
default
{
data
()
{
return
{
sexVal
:
1
}
},
mounted
()
{
this
.
$root
.
_getSysParamsFunc
()
.
then
(
data
=>
// 这里获取到数据字典的data
)
}
}
</script>
这里不仅注册了过滤器,而且也暴露了数据字典,以方便某些地方的列表显示,毕竟这是实际项目中常见的场景。
当然如果使用 vuex 更好,不过这里的场景个人觉得没必要用 vuex,如果还有更好的方法欢迎讨论一下下啊~
作者简介:
钱昱
多年前端工作经验 《JavaScript 设计模式精讲》 作者,主要分享前端方面技术博客
公众号:前端下午茶
本文已经获得钱昱老师授权转发,其他人若有兴趣转载,请直接联系作者授权。
网友评论