Vue项目数据动态过滤实践及实现思路


Posted in Javascript onSeptember 11, 2018

这个问题是在下在做一个Vue项目中遇到的实际场景,这里记录一下我遇到问题之后的思考和最后怎么解决的(老年程序员记性不好 -。-),过程中会涉及到一些Vue源码的概念比如 $mount 、 render watcher 等

问题是这样的:页面从后台拿到的数据是由 0 、 1 之类的key,而这个key代表的value比如 0-女 、 1-男 的对应关系是要从另外一个数据字典接口拿到的;类似于这样的Api:

{
 "SEX_TYPE": [
 { "paramValue": 0, "paramDesc": "女" },
 { "paramValue": 1, "paramDesc": "男" }
 ]
}

那么如果view拿到的是 0 ,就要从字典中找到它的描述 女 并且显示出来;下面故事开始了

1. 思考

有人说,这不是过滤器 filter 要做的事么,直接Vue.filter不就行了,然而问题是这个filter是要等待异步的数据字典接口返回之后才能拿到,如果在 $mount 的时候这个filter没有找到,那么就会导致错误影响之后的渲染(白屏并报undefined错);

我想到的解决方法有两个:

把接口变为同步,在 beforeCreate 或 created 钩子中同步地获取数据字典接口,保证在 $mount 的时候可以拿到注册好的filter,保证时序,但是这样会阻塞挂载,延长白屏时间,因此不推介;

把filter的注册变为异步,在获取filter之后通知 render watcher 更新自己,这样可以利用vue自己的响应式化更新视图,不会阻塞渲染,因此在下初步采用了这个方法。

2. 实现

因为filter属于 asset_types ,关于在Vue实例中asset_types的访问链有以下几个结论;具体代码实践可以参考: Codepen - filter test

1.asset_types 包括 filters 、 components 、 directives ,以下所有的 asset_types 都自行替换成前面几项

2.子组件中的 asset_types 访问不到父组件中的 asset_types ,但是可以访问到全局注册的挂载在 $root.$options.asset_types.__proto__ 上的 asset_types ,这里对应源码 src/core/util/options.js

3.全局注册方法Vue.asset_types,比如Vue.filters注册的asset_types会挂载到根实例(其他实例的 $root )的 $options.asset_types.__proto__ 上,并被以后所有创建的Vue实例继承,也就是说,以后所有创建的Vue实例都可以访问到

4.组件的slot的作用域仅限于它被定义的地方,也就是它被定义的组件中,访问不到父组件的 asset_types ,但是可以访问到全局定义的 asset_types

5.同理,因为main.js中的 new Vue() 实例是根实例,它中注册的 asset_types 会被挂载在 $root.$options.asset_types 上而不是 $root.$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.$root.$options.filters[id]
    return mth && mth(val) || val
   }
  },
  created() {
   // 把根组件中的filters响应式化
   Vue.util.defineReactive(this.$root.$options, 'filters', this.$root.$options.filters)
  },
  mounted() {
   registerFilters.call(this)
    .then(data =>
     // 这里获取到数据字典的data
    )
  }
 }
</script>

注册filter的js

// utils/filters
import * as Api from 'api'
/**
* 获取并注册过滤器
* 注册在$root.$options.filters上不是$root.$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.$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'))
}

这样把根组件上的filters变为响应式化的,并且在渲染的时候因为在 rootFilters 方法中访问了已经在created中被响应式化的 $root.$options.filters ,所以当异步获取的数据被赋给 $root.$options.filters 的时候,会触发这个组件render watcher的重新渲染,这时候再获取 rootFilters 方法的时候就能取到filter了;

那这里为什么不用Vue.filter方法直接注册呢,因为 Object.defineProperty 不能监听 __proto__ 上数据的变动,而全局Vue.filter是将过滤器注册在了根组件 $root.$options.asset_types.__proto__ 上,因此其变动不能被响应。

这里的代码可以进一步完善,但是这个方法存在一定的问题,首先这里使用了 Vue.util 上不稳定的方法,另外在使用中到处可见 this.$root.$options 这样访问vue实例内部属性的情况,不太文明,读起来也让人困惑。

因此在这个项目做完等待测试的时候我思考了一下,谁说过滤器就一定放在filters里面 -。-,也可以使用mixin来实现嘛

2.2 使用mixin

使用mixin要注意一点,因为vue中把data里所有以 _ 、 $ 开头的变量都作为内部保留的变量, 并不代理到当前实例上 ,因此直接 this._xx 是无法访问的,需要通过 this.$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 thisPromise = this.$data._sysParamsPromise
   return thisPromise || Api.sysParams()      // 获取数据字典的Api,返回的是promise
    .then(({ data }) => {
     this.$data._filterFunc = {}
     Object.keys(data).forEach(paramKey =>
      this.$data._filterFunc[paramKey] = val => {    // 过滤器注册到_filterFunc中
       const tar = data[paramKey].find(item => item['paramValue'] === val)
       return 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
  }
 },
 mounted() {
  this.$data._filterFunc ||
  (this.$data._sysParamsPromise = this._getSysParamsFunc())
 }
}

这里把 Api 的promise保存下来,如果其他地方还用到的话直接返回已经是 resolved 状态的promise,就不用再次去请求数据了。

那在我们的组件中怎么使用呢:

<template>
 <div>
  {{ _rootFilters( sexVal )}}
 </div>
</template>
<script type='text/javascript'>
 import * as Api from 'api'
 import sysParamsMixin from 'mixins/sysParamsMixin'
 export default {
  mixins: [sysParamsMixin],
  data() {
   return { sexVal: 1 }
  },
  mounted() {
   this._getSysParamsFunc()
    .then(data =>
     // 这里获取到数据字典的data
    )
  }
 }
</script>

这里不仅注册了过滤器,而且也暴露了数据字典,以方便某些地方的列表显示,毕竟这是实际项目中常见的场景。

参考:

  1. Vue.js 2.5.17 源码
  2. Vue 2.5.17 filter test

总结

以上所述是小编给大家介绍的Vue项目数据动态过滤实践,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript中Math对象使用说明
Jan 16 Javascript
详解new function(){}和function(){}() 区别分析
Mar 22 Javascript
JavaScript中的细节分析
Jun 30 Javascript
jquery实现微博文字输入框 输入时显示输入字数 效果实现
Jul 12 Javascript
jQuery中attr()和prop()在修改checked属性时的区别
Jul 18 Javascript
JavaScript 中有关数组对象的方法(详解)
Aug 15 Javascript
angular和BootStrap3实现购物车功能
Jan 25 Javascript
sublime text配置node.js调试(图文教程)
Nov 23 Javascript
javascript中的event loop事件循环详解
Dec 14 Javascript
VUEX 数据持久化,刷新后重新获取的例子
Nov 12 Javascript
JavaScript实现栈结构详细过程
Dec 06 Javascript
Vue全局事件总线你了解吗
Feb 24 Vue.js
使用vue.js在页面内组件监听scroll事件的方法
Sep 11 #Javascript
vue+axios+element ui 实现全局loading加载示例
Sep 11 #Javascript
Webpack之tree-starking 解析
Sep 11 #Javascript
node.js之基础加密算法模块crypto详解
Sep 11 #Javascript
Vue在页面数据渲染完成之后的调用方法
Sep 11 #Javascript
浅谈Webpack核心模块tapable解析
Sep 11 #Javascript
原生js检测页面加载完毕的实例
Sep 11 #Javascript
You might like
ionCube 一款类似zend的PHP加密/解密工具
2010/07/25 PHP
记录mysql性能查询过程的使用方法
2013/05/02 PHP
在Laravel中使用DataTables插件的方法
2018/05/29 PHP
多个Laravel项目如何共用migrations详解
2018/09/25 PHP
php装饰者模式简单应用案例分析
2019/10/23 PHP
PHP实现递归的三种方法
2020/07/04 PHP
基于PHP实现用户在线状态检测
2020/11/10 PHP
跨浏览器的事件对象介绍
2012/06/27 Javascript
Javacript实现颜色梯度变化和渐变的效果代码
2013/05/31 Javascript
javascript中parentNode,childNodes,children的应用详解
2013/12/17 Javascript
快速掌握Node.js模块封装及使用
2016/03/21 Javascript
jQuery简单动画变换效果实例分析
2016/07/04 Javascript
整理关于Bootstrap表单的慕课笔记
2017/03/29 Javascript
Vue.js仿Metronic高级表格(二)数据渲染
2017/04/19 Javascript
在 Vue 应用中使用 Netlify 表单功能的方法详解
2019/06/03 Javascript
基于Vue.js与WordPress Rest API构建单页应用详解
2019/09/16 Javascript
微信小程序吸底区域适配iPhoneX的实现
2020/04/09 Javascript
Python实现代码统计工具(终极篇)
2016/07/04 Python
python安装oracle扩展及数据库连接方法
2017/02/21 Python
详解Python 2.6 升级至 Python 2.7 的实践心得
2017/04/27 Python
Django Admin 实现外键过滤的方法
2017/09/29 Python
python numpy格式化打印的实例
2018/05/14 Python
Python3 max()函数基础用法
2019/02/19 Python
Python 3.6打包成EXE可执行程序的实现
2019/10/18 Python
pytorch 获取tensor维度信息示例
2020/01/03 Python
tensorflow2.0保存和恢复模型3种方法
2020/02/03 Python
python实现的分层随机抽样案例
2020/02/25 Python
如何在pycharm中安装第三方包
2020/10/27 Python
英国最大的经认证的有机超市:Planet Organic
2018/02/02 全球购物
安全标语口号
2014/06/09 职场文书
国际金融专业自荐信
2014/07/05 职场文书
圆明园纪录片观后感
2015/06/03 职场文书
大学生暑期实践报告
2015/07/13 职场文书
端午节将至,用Python爬取粽子数据并可视化,看看网友喜欢哪种粽子吧!
2021/06/11 Python
Golang map映射的用法
2022/04/22 Golang
Python中的socket网络模块介绍
2022/07/23 Python