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 相关文章推荐
飞鱼(shqlsl) javascript作品集
Dec 16 Javascript
表单提交时自动复制内容到剪贴板的js代码
Mar 16 Javascript
JS 动态加载脚本的4种方法
May 05 Javascript
P3P Header解决Cookie跨域的问题
Mar 12 Javascript
jQuery蓝色风格滑动导航栏代码分享
Aug 19 Javascript
JavaScript基础篇(6)之函数表达式闭包
Dec 11 Javascript
Java  Spring 事务回滚详解
Oct 17 Javascript
JS 实现可停顿的垂直滚动实例代码
Nov 23 Javascript
Vue2.0组件间数据传递示例
Mar 07 Javascript
vue父组件通过props如何向子组件传递方法详解
Aug 16 Javascript
解决vue+webpack项目接口跨域出现的问题
Aug 10 Javascript
前端性能优化建议
Sep 17 Javascript
使用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
ThinkPHP模板IF标签用法详解
2014/07/01 PHP
PHP开发制作一个简单的活动日程表Calendar
2016/06/20 PHP
JScript内置对象Array中元素的删除方法
2007/03/08 Javascript
javascript 简练的几个函数
2009/08/29 Javascript
js getBoundingClientRect() 来获取页面元素的位置
2010/11/25 Javascript
javascript处理table表格的代码
2010/12/06 Javascript
基于jquery的放大镜效果
2012/05/30 Javascript
jQuery语法总结和注意事项小结
2012/11/11 Javascript
js中reverse函数的用法详解
2013/12/26 Javascript
如何改进javascript代码的性能
2015/04/02 Javascript
基于jquery实现一个滚动的分步注册向导-附源码
2015/08/26 Javascript
jquery实现树形菜单完整代码
2015/12/29 Javascript
jquery select2的使用心得(推荐)
2016/12/04 Javascript
JS基于对象的特性实现去除数组中重复项功能详解
2017/11/17 Javascript
jQuery.extend 与 jQuery.fn.extend的用法及区别实例分析
2018/07/25 jQuery
微信小程序iBeacon测距及稳定程序的实现解析
2019/07/31 Javascript
Vue中使用matomo进行访问流量统计的实现
2019/11/05 Javascript
nodejs使用Sequelize框架操作数据库的实现
2020/10/21 NodeJs
[01:08]DOTA2次级职业联赛 - Shield战队宣传片
2014/12/01 DOTA
[02:41]2015国际邀请赛中国区预选赛观战指南
2015/05/20 DOTA
[36:17]DOTA2上海特级锦标赛 - VGL音乐会全集
2016/03/06 DOTA
python实现颜色空间转换程序(Tkinter)
2015/12/31 Python
python解析基于xml格式的日志文件
2017/02/25 Python
Python 中迭代器与生成器实例详解
2017/03/29 Python
Python Dataframe 指定多列去重、求差集的方法
2018/07/10 Python
用Python批量把文件复制到另一个文件夹的实现方法
2019/08/16 Python
自荐信写法介绍
2014/01/25 职场文书
研修第一天随笔感言
2014/02/15 职场文书
路政管理求职信
2014/06/18 职场文书
2016新党章学习心得体会
2016/01/15 职场文书
2016学习医德医风心得体会
2016/01/25 职场文书
九年级化学教学反思
2016/02/22 职场文书
教你怎么用PyCharm为同一服务器配置多个python解释器
2021/05/31 Python
angular异步验证器防抖实例详解
2022/03/31 Javascript
Windows 64位 安装 mysql 8.0.28 图文教程
2022/04/19 MySQL
sql查询语句之平均分、最高最低分及排序语句
2022/05/30 MySQL