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 相关文章推荐
js函数般调用正则
Apr 08 Javascript
基于jQuery替换table中的内容并显示进度条的代码
Aug 02 Javascript
跨浏览器的事件对象介绍
Jun 27 Javascript
创建你的第一个AngularJS应用的方法
Jun 16 Javascript
JavaScript小技巧整理篇(非常全)
Jan 26 Javascript
浅析JS动态创建元素【两种方法】
Apr 20 Javascript
深入理解JavaScript 函数
Jun 06 Javascript
详解BootStrap中Affix控件的使用及保持布局的美观的方法
Jul 08 Javascript
微信小程序 loading(加载中提示框)实例
Oct 28 Javascript
JS回调函数基本定义与用法实例分析
May 24 Javascript
jquery实现吸顶导航效果
Jan 08 jQuery
Vue实现手机号、验证码登录(60s禁用倒计时)
Dec 19 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
亚洲咖啡有什么?亚洲咖啡产地介绍 亚洲咖啡有什么特点?
2021/03/05 新手入门
扩展你的 PHP 之入门篇
2006/12/04 PHP
使用php批量删除数据库下所有前缀为prefix_的表
2014/06/09 PHP
PHP实现根据银行卡号判断银行
2015/04/29 PHP
PHP获取星期几的常用方法小结
2018/12/18 PHP
PHP的mysqli_stat()函数讲解
2019/01/23 PHP
jquery mobile实现拨打电话功能的几种方法
2013/08/05 Javascript
JS实现鼠标箭头变成一个燃烧烛光效果的方法
2015/02/28 Javascript
AngularJS控制器之间的通信方式详解
2016/11/03 Javascript
javascript ES6 新增了let命令使用介绍
2017/07/07 Javascript
推荐15个最好用的JavaScript代码压缩工具
2019/02/13 Javascript
对于防止按钮重复点击的尝试详解
2019/04/22 Javascript
JavaScript怎样在删除前添加确认弹出框?
2019/05/27 Javascript
node中实现删除目录的几种方法
2019/06/24 Javascript
Node.js API详解之 dns模块用法实例分析
2020/05/15 Javascript
vue-cli 3如何使用vue-bootstrap-datetimepicker日期插件
2021/02/20 Vue.js
python根据经纬度计算距离示例
2014/02/16 Python
Python格式化css文件的方法
2015/03/10 Python
Python在图片中添加文字的两种方法
2017/04/29 Python
Python3实现的Mysql数据库操作封装类
2018/06/06 Python
wxPython修改文本框颜色过程解析
2020/02/14 Python
python GUI库图形界面开发之PyQt5信号与槽机制、自定义信号基础介绍
2020/02/25 Python
Python2手动安装更新pip过程实例解析
2020/07/16 Python
Python实现自动签到脚本的示例代码
2020/08/19 Python
一款纯css3实现的竖形二级导航的实例教程
2014/12/11 HTML / CSS
菲律宾旅游网站:Expedia菲律宾
2017/10/11 全球购物
Bose加拿大官方网站:美国知名音响品牌
2019/03/21 全球购物
2014全国两会学习心得体会2000字
2014/03/10 职场文书
县委党的群众路线教育实践活动工作情况报告
2014/10/25 职场文书
行政主管岗位职责
2015/02/03 职场文书
运动会800米赞词
2015/07/22 职场文书
2015中秋节晚会开场白
2015/07/30 职场文书
分享CSS盒子模型隐藏的几种方式
2022/02/28 HTML / CSS
2021年国漫热度排行前十,完美世界上榜,第四是美国动画作品
2022/03/18 国漫
使用Python开发冰球小游戏
2022/04/30 Python