This problem is in the next Vue project encountered in the actual scene, here is a record of the problem I encountered after thinking and how to solve the final (old programmers have a bad memory -. -), the process will involve some of the Vue source concepts such as $mount, render Watcher, etc., if you do not understand the Vue source to read a series of articles ~
The problem is that the page gets data from the background with keys like 0 and 1, and the values represented by keys like 0- female and 1- male are retrieved from another data dictionary interface. An Api like this:
{
"SEX_TYPE": [{"paramValue": 0."paramDesc": "Female" },
{ "paramValue": 1."paramDesc": "Male"}}]Copy the code
So if the view gets 0, it’s going to look up its description in the dictionary and display it; The story begins
1. Think about
$mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount = $mount This will result in an error affecting the render (white screen and undefined error);
I came up with two solutions:
- Change the interface tosynchronousIn the
beforeCreate
或created
Retrieves the data dictionary interface synchronously in the hook$mount
You can get the registered filter to ensure the timing, but this will block the mount and prolong the white screen time, so it is not recommended; - Make registration of filters asynchronous and notify when a filter is acquired
render watcher
Update yourself so that you can take advantage of Vue’s own reactive update view without blocking rendering, so I initially use this approach.
2. Implement
Because filters belong to asset_types, there are several conclusions about the access chain of asset_types in Vue instances; For details, see codepen-filter test
asset_types
includingfilters
,components
,directives
, all of the followingasset_types
I’m just going to replace it with the preceding terms- In a child component
asset_types
Cannot access the parent componentasset_types
, but access to the globally registered mount is available$root.$options.asset_types.__proto__
On theasset_types
Here corresponds to the source codesrc/core/util/options.js - The global registration method vue.asset_types, such as the asset_types registered by vue.filters, will be mounted to the root instance (of other instances)
$root
)$options.asset_types.__proto__
Is inherited by all Vue instances created later, that is, all Vue instances created later are accessible - A component’s slot is only scoped where it is defined, that is, in the component it is defined in, and cannot be accessed by the parent component
asset_types
, but can access globally definedasset_types
- The same is true for main.js
new Vue()
The instance is the root instance registered inasset_types
Will be mounted on$root.$options.asset_types
Rather than on$root.$options.asset_types.__proto__
上
According to the above conclusions, coding can be started
2.1 Filters using the root component
So the first thing I want to do is to mount the registered filter to the root component, so that other components can get the registered filter by accessing $root.
<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 / / gender}},methods: {
/* Filter on the root component */
rootFilters(val, id = 'SEX_TYPE') {
const mth = this.$root.$options.filters[id]
return mth && mth(val) || val
}
},
created() {
// Make the filters in the root component reactive
Vue.util.defineReactive(this.$root.$options, 'filters'.this.$root.$options.filters)
},
mounted() {
registerFilters.call(this)
.then(data= >
// Get the data from the data dictionary)}}</script>
Copy the code
Register the js of filter
// utils/filters
import * as Api from 'api'
Filters are registered on $root.$options.filters.__proto__ Call * @returns {Promise} */ with call or apply
export function registerFilters() {
return Api.sysParams() // Get the data dictionary Api, return promise
.then(({ data }) = > {
Object.keys(data).forEach(T= >
this.$set(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'))}Copy the code
This makes the filters on the root component reactive, and at rendering time because $root.$options.filters is accessed in the rootFilters method that is already reactive in created. Filters ($root.$options.filters) ¶ When asynchronous filters are assigned to $root.$options.filters, the component render Watcher will be triggered.
Object defineProperty can’t listen for changes to __proto__ data. The global Vue. Filter registers the filter with the root component $root.$options.asset_types.__proto__, so its changes cannot be responded to.
The code here could be improved, but there are a few problems with this method. First of all, it uses an unstable method on Vue. Util, and the use of this.
So when the project was finished waiting to be tested I thought, who says filters must be placed in filters -. You can also use mixins to do this
2.2 use a mixin
This._xx cannot be accessed directly. This.$data._xx can be accessed directly.
// mixins/sysParamsMixin.js
import * as Api from 'api'
export default {
data() {
return {
_filterFunc: null.// Filter function
_sysParams: null.// Get the data dictionary
_sysParamsPromise: null // Get the Promise returned after sysParams}},methods: {
/* Register the filter in _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')))},/* Get a single filter by key */
_rootFilters(val, id = 'SEX_TYPE') {
const func = this.$data._filterFunc
const mth = func && func[id]
return mth && mth(val) || val
},
/* Get the data dictionary */
_getSysParams() {
return this.$data._sysParams
}
}
}
Copy the code
So I’m going to save the Api promise and if it’s used elsewhere just return the Resolved promise so I don’t have to request the data again. It is also mounted on the root component for easy access in other instances.
How to use this in our root component:
// src/main.js
import sysParamsMixin from 'mixins/sysParamsMixin'
new Vue({
el: '#app'.mixins: [sysParamsMixin],
render: h= > h(App),
})
Copy the code
In components that require filters:
<template>
<div>
{{ $root._rootFilters( sexVal )}}
</div>
</template>
<script type='text/javascript'>
export default {
data() {
return { sexVal: 1 }
},
mounted() {
this.$root._getSysParamsFunc()
.then(data= >
// Get the data from the data dictionary)}}</script>
Copy the code
Not only are the filters registered, but the data dictionary is also exposed to facilitate listing in some places, which is a common scenario in real projects after all.
Of course, it would be better to use VUex, but I don’t think it is necessary to use VUex in this scene. If there is a better method, we can discuss it
Online posts are mostly different in depth, even some inconsistent, the following article is a summary of the learning process, if you find mistakes, welcome to comment out ~
Reference:
- Vue. Js 2.5.17 source code
- Vue source code reading series
- Vue 2.5.17 filter test
PS: Welcome to pay attention to my official account [front-end afternoon tea], come on together
In addition, you can join the “front-end afternoon tea Exchange Group” wechat group, long press to identify the following TWO-DIMENSIONAL code to add my friend, remarks add group, I pull you into the group ~