基础视频平台树组件实践

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

前端 | 基础视频平台树组件实践.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源码...所以,对前端基本功的打磨和修炼才能使自己的前端能力有一个较大的提升,共勉!!!

相关文章
|
6月前
|
数据可视化 搜索推荐 BI
深度解析好用项目管理工具的功能优势
在选择项目管理工具时,重点在于全面的功能和高性价比。好工具应具备资源利用图(避免过度分配或闲置资源),团队协作功能(促进沟通与进度追踪),质量管理(如问题跟踪和自定义工作流),项目规划和跟踪(甘特图支持),任务管理(任务分解和依赖关系),以及费用跟踪。Zoho Projects、Microsoft Project、Jira等工具各有价格差异,例如,对于50个用户,Microsoft Project最贵,Zoho Projects最实惠,性价比高,适合中小企业。
79 2
|
6月前
|
资源调度 前端开发 JavaScript
构建高效前端项目:现代包管理器与模块化的深度解析
【2月更文挑战第21天】 在当今快速演变的前端开发领域,高效的项目管理和代码组织已成为成功交付复杂Web应用的关键。本文将深入探讨现代前端包管理器如npm, yarn和pnpm的工作原理,以及它们如何与模块化编程实践(例如CommonJS、ES6模块)协同工作以优化开发流程。我们将剖析这些工具的内部机制,了解它们如何解决依赖冲突,提高安装速度,并保证项目的健壮性。同时,本文还将介绍模块化编程的最佳实践,包括代码拆分、重用和版本控制,帮助开发者构建可维护且性能卓越的前端项目。
|
6月前
|
存储
构建二叉树的基本功能
构建二叉树的基本功能
|
6月前
|
前端开发 JavaScript 容器
第九章(应用场景篇)Qiankun微前端深度解析与实践教程
第九章(应用场景篇)Qiankun微前端深度解析与实践教程
246 0
|
6月前
|
算法 测试技术 持续交付
软件开发深度解析:从设计到单元构建
软件开发深度解析:从设计到单元构建
174 2
|
存储 分布式计算 大数据
构建与应用大数据环境:从搭建到开发与组件使用的全面指南
构建与应用大数据环境:从搭建到开发与组件使用的全面指南
304 0
|
前端开发
前端学习案例9-二叉树的概念和特性2
前端学习案例9-二叉树的概念和特性2
76 0
前端学习案例9-二叉树的概念和特性2
|
前端开发
前端学习案例4-树结构的术语
前端学习案例4-树结构的术语
53 0
前端学习案例4-树结构的术语
|
前端开发
前端学习案例5-树结构的术语1
前端学习案例5-树结构的术语1
58 0
前端学习案例5-树结构的术语1
|
前端开发
前端学习案例8-二叉树的概念和特性
前端学习案例8-二叉树的概念和特性
60 0
前端学习案例8-二叉树的概念和特性