本文涉及到的知识点:

  1. Vue函数式组件
  2. 递归函数
  3. 深拷贝对象
  4. 正则匹配

近期在开发一个vue组织架构图组件时,为了实现高性能渲染和一些特殊用法,使用了函数式组件,要实现的效果是这样:

写一个组织架构图组件来深入认识vue函数式高阶组件
要求实现的效果有:

  1. 可以点击节点来展开/收缩其下面的子级节点;
  2. 可以很轻松地自定义每个节点HTML结构和样式,本人的想法是能够直接使用高亮显示的vue模板语法,而不是简单的拼接html字符串,类似于组件插槽的方式;
  3. 支持展开/收缩事件、能够一键展开收缩全部节点;
  4. 使用标准json格式数据;

下面进入正题。
什么是函数式高阶组件以及它的优缺点在vue官网中已经介绍的非常详细,这里只说两点:

  1. 相对于普通组件,函数式组件多了一个render函数,用于生成整个组件的虚拟dom树,render函数的参数是createElementcontextcreateElement的返回结果是虚拟节点(vnode);context是从父级组件传入的一切数据,例如props、插槽、作用域插槽、监听事件等等,下面会说如何去用它们;
  2. 函数式组件没有自己的状态,也就是没有自己的data(响应式数据),只能被动地从父级接收props,也没有this上下文;

可以像这样声明并引用一个函数式组件:

其中vueFtNode就是我们的函数式组件了,它的render函数比较庞大,所以将它单独写在了render.js中。
当然在单文件组件中也可以在template标签上面加functional声明一个函数式组件,但是这样就体会不到render函数那样纯粹的函数式的编程体验了。

然后我们返回到插件实现中,具体的HTML结构是这样:

总的来说就是用一个classvue-ftree的div包裹住整个组件结构,用class为vue-ftree-node的div渲染每一个节点,用vue-ftree-node-content渲染节点内容,然后每个节点下又包含一个vue-ftree-children,这里面又包含了当前节点的子节点,子节点依然用vue-ftree-node渲染。
这样就形成了一个简单的递归过程。

生成虚拟节点的方式是使用createElement函数,看完官网对createElement函数的介绍再来看我们的组件HTML结构,你会觉得很头大,用createElement创建一个vnode基本形式是这样:

其中children是vue-ftree下的子节点组成的数组,每个子节点同样也需要用createElement函数生成。
也就是说,要想生成上面截图中的复杂HTML结构岂不是要createElement嵌套createElement,一层又一层,像剥洋葱一样辣眼睛,等写完了满眼都是泪啊。

其实你想的一点也没错,事实就是如此残酷,不过好在有jsx这个东西,它能像写普通HTML一样生成虚拟节点,具体可以到vue官网里查看,但是需要引入一系列依赖,本着公用组件尽量少用依赖的原则,只能硬着头皮一层一层写了。

首先我们要在render函数中生成一个基本框架,一个class为vue-ftree的div作为容器节点,此节点下面包含了所有组织架构节点:


h是啥?createElement换个马甲你就不认识啦?

其中renderTree函数用于生成每个组织架构节点,renderTree中又有renderNode函数等等,这其中的弯弯绕绕我这个写插件的人都不忍再回顾,里面不光涉及到createElement的各种嵌套还有递归函数和遍历,感兴趣的朋友可以进GitHub上看源码。
GitHub:https://github.com/weitamingting/vue-furcate-tree
总之看完会掉不少头发。

讲重点:

在render函数里,要给vnode添加class,与普通组件差不多,支持字符串、数组和对象形式。所以上面生成一个class为vue-ftree的节点可以有以下几种写法:

如何给vnode添加事件监听?

我们希望给每个组织架构节点添加点击事件,而且这个点击事件需要暴露在组件外面以方便别人自由定义事件发生的事情,用法像这样:

对应的method:

vueFurcateTree组件实际上是在函数式组件vueFtNode外面又包裹的一层外壳,主要作用是隐藏函数式组件实现细节,让它能像普通组件一样被引用。

普通组件要加事件监听需要用到自定义事件并在组件内部合适的地方使用this.$emit触发,但是函数式组件没有this上下文,所以在函数式组件中这一方式行不通。

这里就要用到render函数的context参数了。

上面说了,context是从父级组件传入的一切数据,例如props、插槽、作用域插槽、监听事件等等,打印一下看看都有什么:

可以看到我们在vueFurcateTree上面监听的click事件函数就在listeners对象了,然后通过查阅createElement函数文档,我们只需要把这个函数传入它的数据对象中即可,例如我要在class是vue-ftree-node-content-inner的节点上监听这个点击事件,那么就可以像这样实现:

但是我们希望click事件中可以暴露出一些有用的参数,例如当前点击的节点数据,所以我们在context.listeners.click外面再包裹一层函数,像这样:

这样就可以利用闭包原理将函数体内的变量暴露到外面喽,什么是闭包?嘿嘿。

按照这个方法可以添加所有你能想到的原生事件和你自己天马行空命名的自定义事件。

最后,在开头我们说到,组件希望使用一种极其简单的方式,像写普通的html一样来制作每个组织架构节点里面显示的内容,有编辑器的高亮提示,易于阅读和编写,总之使用起来就像这样:


渲染出来的效果是这样:

传入组件的数据格式,也就是截图里的ft-data格式是这样的:


在render函数中如何拿到ft-data?很简单,context.props.ftData。然后遍历、递归、巴拉巴拉…

继续说按照模板编译的事情:

我的想法是把原来组件插槽的位置变成节点渲染模板,每个节点都按照插槽的格式来生成,模板里可以使用#{变量}的形式来访问内部变量。

例如在上面的架构图中,第一个节点里面的#{label}代表的就是'节点1'这个值,#{test.a}代表的就是'a'这个值。好吧,我知道作用域插槽也能实现,这不是为了高大上一些么,哈哈。

#{...}这是个啥?是我自己规定的一个模板标签,后面可以利用正则匹配得到变量名称从而进一步解析出变量来,跟vue的{{...}}本质上是一个东东。

context参数中已经包含了组件传入的插槽数据,看文档会发现有两个可以用的属性,一个是children,一个是slots,这两个属性乍一看返回的都是插槽内容,那么平时如此人性化的尤大大为什么会突然整出这么两个迷惑众生的属性呢?

大家都知道,插槽是可以有命名的,也就是具名插槽,使用context.slots方法取到的是一个对象,里面包含了所有具名插槽的信息,例如context.slots().test,返回的就是命名为test的插槽数据;

而children取到的是一个数组,数组包含了插槽位置的所有虚拟节点,它不分命名,只要出现在插槽位置的节点都会返回。

因为我们这里只需要拿到插槽里的节点数据不用区分命名,所以用了children属性,接着遍历children所有节点,使用正则表达式匹配到#{}里面的变量,将变量转换为真正的值,然后把转换后的children传给vnode。

有一点需要注意,因为context的children属性是一个对象数组,属于引用类型,所以每次转换children时需要深拷贝一下,否则最终会导致所有组织架构节点内容都一样。

了解了这几个属性的运用,高阶函数组件基本上就没什么难点了,因为主要讲函数式组件,所以里面的正则匹配、递归函数等知识就不说了,进项目里看代码吧。

GitHub:https://github.com/weitamingting/vue-furcate-tree

写一个组织架构图组件来深入认识vue函数式高阶组件相关推荐

  1. react组件类型及深入理解react高阶组件

    React中常见的组件类型及分类: 1.展示组件(Presentational component) 与 容器组件(Container component) 2.类组件(Class component ...

  2. 组件和高阶组件区别_为什么要对高阶组件使用代码拆分

    组件和高阶组件区别 by Nitish Phanse 由Nitish Phanse 为什么要对高阶组件使用代码拆分 (Why you should use code splitting with hi ...

  3. React 高阶组件HOC、设置displayName、高阶组件传递props

    文章目录 1. 高阶组件 使用步骤 2. 高阶组件设置displayName 3.传递props 1. 高阶组件 目的:实现状态逻辑复用.实现功能增强.返回一个增强组件. 高阶组件(HOC,Highe ...

  4. react怎么让组件强制刷新--函数式高阶组件

    export const wrapper = (innerComp)=>({updateStr, ...props})=>{const [forceUpdate,setfForceUpda ...

  5. 高阶组件HOC - 小试牛刀

    原文地址:https://github.com/SmallStoneSK/Blog/issues/6 1. 前言 老毕曾经有过一句名言,叫作"国庆七天乐,Coding最快乐~".所 ...

  6. 「react进阶」一文吃透React高阶组件(HOC)

    一 前言 React高阶组件(HOC),对于很多react开发者来说并不陌生,它是灵活使用react组件的一种技巧,高阶组件本身不是组件,它是一个参数为组件,返回值也是一个组件的函数.高阶作用用于强化 ...

  7. 【Vue】你了解高阶组件吗

    文章目录 前言 一.什么是高阶组件`(HOC)`? 二.Vue高阶组件实例 前言 高阶组件在React社区中十分火热,但是在Vue中热度并不高,本文就来了解一下Vue中的高阶组件. 一.什么是高阶组件 ...

  8. [vue] 你了解什么是高阶组件吗?可否举个例子说明下?

    [vue] 你了解什么是高阶组件吗?可否举个例子说明下? 高阶组件 高阶组件介绍 vue 高阶组件的认识,在React中组件是以复用代码实现的,而Vue中是以mixins 实现,并且官方文档中也缺少一 ...

  9. 《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——2.7 高阶组件...

    本节书摘来自华章出版社<信息物理融合系统(CPS)设计.建模与仿真--基于 Ptolemy II 平台>一书中的第2章,第2.7节,作者:[美]爱德华·阿什福德·李(Edward Ashf ...

最新文章

  1. 校招经验分享—高考结束!校招还会远么~~
  2. 科研项目:机器学习在场景中的应用!
  3. 机房几台终端电脑,本地连接中不停的出现连接和断开,网络不通,解决方法...
  4. password is not set 问题解决
  5. HDU-2444 The Accomodation of Students
  6. SpringBoot 自动配置原理
  7. 使用live555制作rtsp客户端,捕获h264等解码
  8. java+txt+词语+次数_Java练习2--读取txt文件统计考勤次数并写入一个txt文件中
  9. mysql无法连接10061错误1067_解决MySQL启动的error 2003和1067 10061错误问题
  10. springboot系列(三) 启动类中关键注解作用解析
  11. 计算机网络的硬盘组成,大卸八块!编辑为你揭秘硬盘的内部结构
  12. C++vector基础容器3.0
  13. 侧信道实验实验三 S盒CPA侧信道攻击
  14. 广东金融学院大学计算机基础,好投顾网使用说明广东金融学院专用).doc
  15. BAT自动校对时间脚本,让WINDOWS系统自动校对时间
  16. python逻辑回归模型建模步骤_从原理到代码,轻松深入逻辑回归模型!
  17. 机器学习实践系列之3 - 人脸对齐(上)
  18. Dijkstra算法、Floyd算法的区别与联系,并由此谈到greedy和DP
  19. python3 日文截图翻译和实时翻译
  20. 98 服务器系统,Windows 98

热门文章

  1. 《FPGA至简设计原理与应用》学习笔记1 —— FPGA基础
  2. Python数据分析基础: 异常值检测和处理
  3. EXCEL的基本操作(五)
  4. LabWindows/CVI 调用Microsoft Access Database数据库方法
  5. 淘宝购物车页面 PC端和移动端实战
  6. 数组添加元素的方法PHP,JavaScript如何给数组添加元素?js数组添加元素的3种方法(代码实例)...
  7. 护眼灯A级和AA级有什么区别
  8. 2018技术圈热门事件回顾,有伤痛默哀也有哭笑不得。
  9. 【图像去噪】基于二维双边高斯滤波实现图像去噪附matlab代码
  10. 《四川省建设工程安全文明施工费计价管理办法》的通知〔2017〕5(一)