基础视频平台树组件实践

简介: 树组件是项目中最常见的一种组件,其综合了数据结构的处理、拖拽、点击等前端常见面试题的综合,以下是本次实现过程中遇到的问题汇总

前端 | 基础视频平台树组件实践.png

前言

树组件是项目中最常见的一种组件,其综合了数据结构的处理、拖拽、点击等前端常见面试题的综合,以下是本次实现过程中遇到的问题汇总

问题目录

  • json数据处理
  • $set分析
  • 深拷贝

踩坑案例

json数据处理

[bug描述] 项目中树组件数据所需为三级结构,前两级结构数据与第三级结构数据是不同的接口,商讨过后,决定二级结构后获取三级结构数据,将三级结构数据中的字段获取塞入原数据中,在数据量较大时层级较多时会产生较大延时,影响体验
[bug分析] 大数据量多层级性能较差,需优化
[解决方案] 抽象成两个json数据的映射关系处理
接口1中获取的数据json1如下:

    [
        {
            "name": "组1",
            "id": "g100001",
            "type": "group",
            "children": [
                {
                    "name": "摄像机",
                    "id": "d100001",
                    "type": "device",   
                },
                {
                    "name": "NVR",
                    "id": "d100002",
                    "type": "device",   
                },
                {
                    "name": "摄像机",
                    "id": "d100003",
                    "type": "device",   
                }
            ]
        }
    ]

接口二中获取的数据json2如下:

    [
        {
            "ID": "10001",
            "DeviceID": "10000101001"
        },
        {
            "ID": "10002",
            "DeviceID": "10000101002"
        },
        {
            "ID": "10003",
            "DeviceID": "10000101003"
        }
    ]

最终要将json1中的类型为device的id对应获取到的json2数据组合成最终的数据json如下:

    [
        {
            "name": "组1",
            "id": "g100001",
            "type": "group",
            "children": [
                {
                    "name": "摄像机",
                    "id": "d100001",
                    "type": "device", 
                    ”children": [
                        {
                            "name": ID,
                            "id": DeviceID,
                            "type": "channel"
                        },
                        {
                            "name": ID,
                            "id": DeviceID,
                            "type": "channel"
                        },
                        {
                            "name": ID,
                            "id": DeviceID,
                            "type": "channel"
                        }
                    ]   
                }
            ]
        }
    ]

抽象转化 => [
{
"children":[
{...}
],
"字段1":"",
"字段2":""
}
]
对于多个json数据的映射组合,常见的方法有:1、硬解;2、转AST;3、动态规划
硬解在本场景下时空复杂度还可控制,倘若出现多层结构的深度递归去解会出现爆栈和性能损耗;转ast可以将所有类似结构进行通解,但是需要去写解释执行器也会占据时间和空间,另外ast更适合数据结构变化非常大的,比如像jsx这种要转换成js就需要ast的引入,本场景多层级结构类似;动态规划是对深度递归的一种优化,将每一个问题转化为子问题,从子问题反推,上边的抽象转化是最小子问题,只需将最小子问题进行反向推导,就可以减少内存使用,提升效率

$set使用及对Watcher的理解

[bug描述] 由于三层数据是由另外一个接口获取的,其获取后需要重新塞入元数据中,然而再次塞入后却没有被监听到,在视图上没有显示
[bug分析] 没有defineReactive,也就无法被dep收集,Watcher就无法监听
[解决方案] 使用$set方法
Vue中的双向数据绑定是通过defineReactive方法实现,其基本是Object.defineProperty的使用

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

对于一般对象的属性新增,defineReactive是不能获取到的,因而需要使用$set方法

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

只有在defineReactive中才能被dep收集到,才能通过Watcher进行监听

深拷贝导致的数据不能及时更新

[bug描述] 树组件重命名后,新的名字未能及时响应到页面上,却在第二轮渲染到了页面上
[bug分析] lodash深拷贝和点击发送事件的回调发生时间未知,因而在defineReactive中的值获取到的时间也是未定的,通常点击事件的回调会阻塞js线程,因而在点击后虽然defineReactive改变了,传到子组件中的数据由于有深拷贝也需要时间响应,然而响应完后,页面已经完成,因此在这一个时间周期中,响应后的数据被放到了下一次的渲染中,当再次点击后才会显示
[解决方案] 去除深拷贝,点击后的数据直接传到子组件中,不需要深拷贝执行
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

  • 加载渲染过程:

父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

  • 子组件更新过程:

父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

  • 父组件更新过程:

父 beforeUpdate -> 父 updated

  • 销毁过程:

父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

总结

树组件是一个比较复杂的组件,同时也是前端基本功的一个很好的测试案例,反复琢磨树组件的基础功能实现会发现面试中常见题目都有体现,其实面试中的常见题目都是日常工作中的抽象化的考察,除了个别的完全不靠谱的题目,大部分题目还是在日常开发中由很好的体现的,比如本次踩坑实践中就有:1、算法考察:动态规划;2、Object基础api考察;3、深拷贝浅拷贝问题;4、EventLoop问题;5、Vue源码...所以,对前端基本功的打磨和修炼才能使自己的前端能力有一个较大的提升,共勉!!!

相关文章
|
8月前
|
存储
构建二叉树的基本功能
构建二叉树的基本功能
|
2月前
|
存储 前端开发 JavaScript
前端中对象的深度应用与最佳实践
前端对象应用涉及在网页开发中使用JavaScript等技术创建和操作对象,以实现动态交互效果。通过定义属性和方法,对象可以封装数据和功能,提升代码的组织性和复用性,是现代Web开发的核心技术之一。
|
2月前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
45 1
|
4月前
|
JavaScript
从零开始写一套广告组件【一】搭建基础框架并配置UI组件库
其实这个从零有点歧义,因为本质上是要基于`tdesign-vue-next`来进行二次封装为一套广告UI组件库,现在让我们在一起快乐的搭建自己的广告UI库之前,先对以下内容做出共识:
105 0
从零开始写一套广告组件【一】搭建基础框架并配置UI组件库
|
7月前
|
UED
带您一步步构建一个具有复杂布局的电商详情页,涵盖页面结构规划、样式设计以及交互效果的实现
【6月更文挑战第14天】构建复杂布局的电商详情页涉及页面结构规划、样式设计和交互效果实现。首先,规划页面结构,包括顶部导航栏、商品图片展示区、商品信息区、用户评价区和相关商品推荐区。接着,进行样式设计,注重色彩搭配、字体选择、布局与间距以及图片处理。例如,使用固定顶部导航栏,轮播图展示商品图片,分块展示商品信息和评价,以及设计相关商品推荐区。最后,实现交互效果,如图片放大、添加到购物车按钮、滚动监听和评论互动,提升用户体验。实际开发时需根据需求和规范进行调整,保证跨设备兼容性。
128 1
|
存储 分布式计算 大数据
构建与应用大数据环境:从搭建到开发与组件使用的全面指南
构建与应用大数据环境:从搭建到开发与组件使用的全面指南
356 0
|
前端开发
前端学习案例1-组件优化1
前端学习案例1-组件优化1
80 0
前端学习案例1-组件优化1
|
前端开发
前端学习案例1-组件优化1
前端学习案例1-组件优化1
68 0
前端学习案例1-组件优化1
|
前端开发
前端学习案例2-组件优化2
前端学习案例2-组件优化2
83 0
前端学习案例2-组件优化2
构建系统发育树简述
构建系统发育树简述
189 0

热门文章

最新文章