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 学习笔记(四)
Dec 31 Javascript
Jquery Select操作方法集合脚本之家特别版
May 17 Javascript
JavaScript经典效果集锦
Jul 06 Javascript
JavaScript实现QueryString获取GET参数的方法
Jul 02 Javascript
跟我学习javascript的严格模式
Nov 16 Javascript
JavaScript中数组的22种方法必学(推荐)
Jul 20 Javascript
微信小程序开发之从相册获取图片 使用相机拍照 本地图片上传
Apr 18 Javascript
使用react-router4.0实现重定向和404功能的方法
Aug 28 Javascript
Javascript实现异步编程的过程
Jun 18 Javascript
深入理解react-router 路由的实现原理
Sep 26 Javascript
解决vue 单文件组件中样式加载问题
Apr 24 Javascript
JQuery+Bootstrap 自定义全屏Loading插件的示例demo
Jul 03 jQuery
使用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
PHP下的浮点运算不准的解决方法
2016/10/27 PHP
IE 下的只读 innerHTML
2009/08/21 Javascript
jquery 实现上下滚动效果示例代码
2013/08/09 Javascript
jQuery应用之jQuery链用法实例
2015/01/19 Javascript
使用jquery操作session方法分享
2015/01/22 Javascript
jQuery选择器querySelector的使用指南
2015/01/23 Javascript
jQuery使用post方法提交数据实例
2015/03/25 Javascript
JavaScript 对象深入学习总结(经典)
2015/09/29 Javascript
完美实现bootstrap分页查询
2015/12/09 Javascript
javascript插件开发的一些感想和心得
2016/02/28 Javascript
基于JS实现无缝滚动思路及代码分享
2016/06/07 Javascript
Vue.js开发环境搭建
2016/11/10 Javascript
jQuery实现的购物车物品数量加减功能代码
2016/11/16 Javascript
js实现右键菜单功能
2016/11/28 Javascript
基于JS实现的随机数字抽签实例
2016/12/08 Javascript
JS使用面向对象技术实现的tab选项卡效果示例
2017/02/28 Javascript
Javascript实现跨域后台设置拦截的方法详解
2017/08/04 Javascript
JavaScript实现树的遍历算法示例【广度优先与深度优先】
2017/10/26 Javascript
Nodejs文件上传、监听上传进度的代码
2020/03/27 NodeJs
[06:16]《DAC最前线》之地区预选赛全面回顾
2015/01/19 DOTA
python实现支持目录FTP上传下载文件的方法
2015/06/03 Python
关于Django显示时间你应该知道的一些问题
2017/12/25 Python
python命令行解析之parse_known_args()函数和parse_args()使用区别介绍
2018/01/24 Python
python  创建一个保留重复值的列表的补码
2018/10/15 Python
celery4+django2定时任务的实现代码
2018/12/23 Python
对Django 转发和重定向的实例详解
2019/08/06 Python
美国诺德斯特龙百货官网:Nordstrom
2016/08/23 全球购物
GLAMGLOW格莱魅美国官网:美国知名的面膜品牌
2016/12/31 全球购物
澳大利亚在线购买葡萄酒:The Wine Collective
2020/02/20 全球购物
写给女生的道歉信
2014/01/14 职场文书
药学专业学生的自我评价分享
2014/02/06 职场文书
商业房地产广告语
2014/03/13 职场文书
音乐教师求职信
2014/06/28 职场文书
行政司机岗位职责
2015/04/10 职场文书
致青春观后感
2015/06/09 职场文书
2016年小学教师政治学习心得体会
2016/01/23 职场文书