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:

  1. Change the interface tosynchronousIn thebeforeCreatecreatedRetrieves the data dictionary interface synchronously in the hook$mountYou 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;
  2. Make registration of filters asynchronous and notify when a filter is acquiredrender watcherUpdate 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

  1. asset_typesincludingfilters,components,directives, all of the followingasset_typesI’m just going to replace it with the preceding terms
  2. In a child componentasset_typesCannot access the parent componentasset_types, but access to the globally registered mount is available$root.$options.asset_types.__proto__On theasset_typesHere corresponds to the source codesrc/core/util/options.js
  3. 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
  4. 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 componentasset_types, but can access globally definedasset_types
  5. The same is true for main.jsnew Vue()The instance is the root instance registered inasset_typesWill be mounted on$root.$options.asset_typesRather 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:

  1. Vue. Js 2.5.17 source code
  2. Vue source code reading series
  3. 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 ~