1.UE4-Slate源码学习(六)slate渲染Part2-Paint控件绘制
2.我对 Element 的插槽源t插槽原 Table 做了封装
3.ant-design-vue中table自定义列
4.element-tabs组件 源码阅读
5.vue3源码分析——实现slots
UE4-Slate源码学习(六)slate渲染Part2-Paint控件绘制
上一篇文章介绍了绘制一个SWindow的初期步骤,即计算整个UI树的插槽源t插槽原控件大小,为绘制做准备。插槽源t插槽原文章随后深入探讨了绘制流程的插槽源t插槽原第二步,即执行FSlateApplication::PrivateDrawWindows()后,插槽源t插槽原开始调用SWidget::Paint()函数,插槽源t插槽原工匠家源码开发每个控件随后实现其虚函数OnPaint()。插槽源t插槽原
在这一过程中,插槽源t插槽原绘制参数被封装在FPaintArgs中,插槽源t插槽原作为Paint和OnPaint过程中的插槽源t插槽原关键引用参数。FSlateRHIRenderer与FSlateDrawBuffer是插槽源t插槽原继承自FSlateRenderer的类,作为FSlateApplicationBase的插槽源t插槽原全局变量,在构造时创建。插槽源t插槽原在绘制过程中,插槽源t插槽原通过GetDrawBuffer()函数可获取到FSlateDrawBuffer对象。插槽源t插槽原
FSlateDrawBuffer实现了Slate的绘制缓冲区,内部封装了FSlateWindowElementList数组,用于存储多个SWindow下的绘制元素列表。每个SWindow通过AddWindowElementList()返回一个元素列表。
FSlateWindowElementList负载了SWindow内的所有图元信息,内部封装了FSlateDrawElement的数组,包含Cached和Uncached元素,以及SWindow的指针和用于渲染的批处理数据FSlateBatchData。
FSlateDrawElement是构建Slate渲染界面的基本块,封装了UI树节点控件需要渲染的相关信息,如渲染变换、位置、大小、层级ID、绘制效果等,以及后续渲染阶段需要的相关数据。
在Paint流程中,处理当前传入的SWindow和ChildWindows,首先判断窗口是否可见和是否最小化,然后从参数封装的OutDrawBuffer中获取WindowElementList。调用SWindow的PaintWindow()函数开始绘制窗口,并最终返回所有子控件计算完的最大层级。接着,子窗口递归绘制。
PaintWindow()函数在绘制窗口时,首先调用SetHittestArea()设置点击区域,多店铺系统源码HittestGrid会判断窗口大小是否改变,若不变则仅更新窗口在屏幕中的位置。构造FPaintArgs参数后,将其封装到FSlateInvalidationContext中。
FSlateInvalidationRoot类的PaintInvalidationRoot()函数可以作为控件树的根节点或叶子节点(SInvalidationPanel),构建快速路径避免每次绘制都计算大小和Paint函数,有利于优化。本篇文章主要分析正常慢速路径调用流程,优化相关将另文分析。
PaintSlowPath()函数从SWindow开始调用Paint()函数,并定义LayerId从0开始作为参数,进行实际的绘制相关计算。
Paint()函数首先处理裁剪、透明度混合、坐标转换等代码。若SWidget包含NeedsTick掩码,则调用Tick函数,我们在日常开发中通过蓝图或lua使用Tick函数时即调用到这里,通过SObjectWidget::Tick调用到UUserWidget::NativeTick供实现Tick。构造FSlateWidgetPersistentState PersistentState作为SWidget的变量,表示Paint时的状态。
PersistentState.CachedElementHandle将当前SWidget存储到FSlateWindowElementList中的WidgetDrawStack数组中。
更新FPaintArgs中的父节点参数和继承可点击测试参数,判断点击测试状态,然后将当前SWidget添加到点击测试中。调用虚函数OnPaint,由控件自己实现。
OnPaint()函数参数包括绘制参数引用、几何体、裁剪矩形、缓冲元素列表、层级、控件风格、父节点状态等。最后处理重绘标签、延迟绘制相关内容、UpdateWidgetProxy()根据缓存句柄更新快速路径中需要处理标记设置为Volatile不稳定状态的SWidget。
虚函数OnPaint()由子类自己实现,本文列举了SImage、SButton、徐琳娇指标源码SCompoundWidget和SConstraintCanvas的OnPaint()示例代码学习。
在SImage中,简单判断Brush是否存在以及BrushDrawType的类型,然后调用FSlateDrawElement::MakeBox将控件添加到缓冲区元素列表中。
SButton继承自SCompoundWidget,GetBorder()根据当前按钮状态返回ui中设置的Enabled、Press、Hover、Disabled等状态的Brush。
SCompoundWidget作为合成节点,有且只能有一个子节点,且在Paint时强制将子节点的LayerId+1,同时SCompoundWidget可以单独设置混合颜色和透明度,影响子节点。
SConstraintCanvas作为SWidget的基类对应UMG中常用的UCanvasPanel,通过ArrangeLayeredChildren()对孩子进行层级排序,并根据孩子的层级是否相同存储bool值在ChildLayers中。遍历所有孩子,判断是否开启新层级,递归调用Paint函数,最后返回最大层级。
SConstraintCanvas::ArrangeLayeredChildren函数中,获取设置bExplicitChildZOrder,表示可以将同层一次渲染,有利于提高渲染器批处理。对所有孩子排序,排序规则为FSortSlotsByZOrder。遍历所有孩子,判断可见性掩码、计算偏移、锚点、位置、拉伸缩放等,封装成FArrangedWidget存储到ArrangedChildren中,用于OnPaint时有序遍历。判断每个孩子ZOrder是否相同,相同则bNewLayer为false,大于LastZOrder则将bNewLayer设置为true,最终存储到ArrangedChildLayers中,用于OnPaint函数判断是智能还款网站源码否将layerId+1。
FSlateDrawElement::MakeBox()函数在OnPaint之后调用,将绘制控件的相关信息通过创建FSlateDrawElement绘制元素对象,添加到SWindow管理的FSlateWindowElementList元素列表中。创建Payload用于存储贴图等相关信息,根据控件Paint过程中的参数调用Element.Init初始化绘制元素,得到为该控件绘制创建的FSlateDrawElement对象。
总结整个Slate绘制流程的第二步,我们没有分析快速处理和优化细节,而是按照正常绘制流程分析代码。通过从PaintWindow开始遍历整个控件树,处理每个空间节点的Paint、OnPaint函数,最终目的是给每个控件创建一个FSlateDrawElement对象,存储渲染线程绘制所需的相关信息,并添加到FSlateWindowElementList中。理解了整个调用流程,整个过程较为清晰,本文基于UE4版本4..2。
我对 Element 的 Table 做了封装
ElementUI 的 Table 组件在使用过程中存在一些不便之处,尤其是在列数较多时,需要在 template 中书写大量的 el-table-column,增加了维护成本。为了解决这个问题,我对其进行了封装,创建了TableView组件。
TableView 是一个以 Table 为主要内容的页面容器,适用于管理后台项目中的各种列表页面。这类页面通常包含搜索、表格、页脚、工具栏和表格自定义等部分,具有明显的页面特征和较高的可复用性。TableView 允许你传入一个 columns 数组来渲染表格中的所有列,并且支持通过 render 函数或插槽自定义每一列。此外,它还内置了分页逻辑,并允许用户自定义展示哪些列,但搜索表单部分需要使用者自行完成并通过插槽嵌入TableView。
TableView 的结构包括 internals 和 slots 两部分。internals 是仿租机源码组件内置的功能,包括 Table 部分的列表渲染、footer 部分的分页,以及 customer 部分的列表自定义。slots 当前包含的插槽有:search(搜索)、table(表格)、footer(页脚)和 toolbar(工具栏)。
要使用TableView,你只需要传入一个 columns 数组即可渲染表格。每一列的对象(column 对象)具有完整的属性,你可以灵活地控制每一列的展示。TableView 支持通过 render 函数和插槽两种方式自定义列,其中 render 函数的优先级高于插槽。
TableView 要求使用者在 getListMethod 中调用接口并回填数据,getListMethod 有两种写法。第一种是写一个返回 Promise 的函数,Promise 需 resolve 接口返回的数据;第二种是 getListMethod 也可以使用如下写法:TableView 内部在调用 getListMethod 的时候会传两个参数,第一个是请求参数,第二个则是一个回调函数,用于设置数据。
TableView 需要使用者自行整理搜索条件,然后手动调用内部方法 search。有两种方式调用 search:通过作用域插槽和通过 ref。
TableView 内置了自定义列的组件,支持用户勾选想展示的列,隐藏多余的列。要使用此功能,需要设置 useColumnCustomer 为 true,并给组件传递 columnCustomMethod 方法。你需要通过 columnCustomMethod 更新 columns,把自定义展示的列的 show 设为 true,其他列的 show 设为 false。当然,你还可以在 columnCustomMethod 中保存用户自定义的数据,下次渲染列表时根据保存的数据把 columns 对应的列的 show 设为 true。
关于TableView的更多介绍,请前往 GitHub 查看源码和完整文档。如果你有建议或想法,也欢迎 star 一下。此外,我还把 Table 封装为独立的组件,如果你只想使用 Table 部分,请直接查看 src/Table.vue 文件。
ant-design-vue中table自定义列
在开发项目中使用ant-vue的a-table组件时,常需显示序号列或在列中显示、超链接或按钮等UI元素。为实现这一需求,可利用`customCell`和`customRender`功能。`customRender`可生成复杂数据的渲染函数,如以下表格数据展示示例。
槽作用与scopedSlots的含义在文档中经常提及,如在`customRender`描述中,`slot-scope`表示生成支持作用域插槽的渲染函数。若配置不当,如直接将`customRender`定义为函数,而非接受参数的函数,会导致运行错误。正确配置应使用作用域插槽,其优先级高于静态插槽。
`customCell`影响vnode的属性,允许自定义列样式等信息。通过修改返回值,可改变列的字体大小、颜色或显示内容。
`customRender`同样影响列显示信息,且更灵活,能返回类似`customCell`的属性信息。它能作为插槽或函数使用,且优先级低于`customCell`。作为插槽使用时,优先显示作用域插槽内容;作为函数使用时,执行`isInvalidRenderCellText`函数判断渲染值。
理解这些功能后,可灵活运用`customRender`和`customCell`实现自定义列信息。例如,改变列字体或颜色时,优先考虑使用`customCell`。示例代码如下所示,可根据不同业务需求选择使用。
了解以上特性后,对于面试题中关于自定义列最终渲染内容的提问,需要分析所给代码片段,基于理解的特性进行解答。如需获取更多示例代码或Demo源码,可通过扫描指定二维码,关注公众号[小院不小]并回复“ant-table”获取。
element-tabs组件 源码阅读
在深入分析element-tabs组件源码的过程中,需要把握两个基本前提:首先,对API有着深入的理解;其次,带着具体问题进行阅读,以便更高效地获取所需信息。遵循两个基本原则:不要过于纠结于那些无关紧要的细节,而应首先明确自己的实现思路,然后再深入阅读源码。接下来,我们将针对几个关键点进行详细探讨。
首先,我们关注于元素切换时的滑动效果。通过观察源码,可以发现这种效果实现的关键在于tabs内部的计算逻辑。在`/tabs/src/tab-nav.vue`文件中,使用jsx语法实现的逻辑中,通过判断`type`的类型来决定是否调用`tab-bar`。`tab-bar`内部通过计算属性来计算`nav-bar`的宽度,这一计算依赖于`tabs.vue`通过`props`传入的`panes`数据。这表明`nav-bar`的宽度是由`panes`数组驱动的,从而实现了动态调整和滑动效果。
接下来,我们探讨`border-card`中的边框显示机制。通过观察源码,发现`tabs.scss`中`nav-wrap`的样式设置为`overflow: hidden`。这个设置与边框显示之间的关系在于,通过改变当前选中的`tab`的`border-bottom-color`为`#fff`,来实现边框的动态显示效果。具体来说,当激活某个`tab`时,通过调整CSS样式使得边框底边颜色变白,从而达到视觉上的边框显现效果。实现的细节在于通过设置`nav`的盒子位置下移动1px,并且使激活的`tab`的`border-bottom`颜色为白色,以此达成效果。
再者,`tab-position`共有四个位置调节选项:`top`、`right`、`bottom`和`left`。通过分析源码可以发现,`top`是常规布局,而`left`与`right`是基于`BFC`的两侧布局,`bottom`则通过改变插槽子节点的位置来实现常规布局。具体实现细节在于`el-tabs__content`的代码中,针对`is-left`和`is-right`的SCSS代码,以及`is-top`和`is-bottom`的区别仅在于`tabs.vue`里的放置位置。这意味着`left`和`bottom`的布局是基于`BFC`的两侧等高布局,而`top`和`bottom`则只是常规流体布局,只是位置不同。
对于`stretch`功能的实现细节,通过分析源码可以得出当`stretch`设置为`false`时,`tab`的显示形式为`inline-block`;当设置为`true`时,父级变为`flex`布局,而子`tab`具有`flex:1`的属性。这表明`stretch`功能通过调整显示模式和布局方式,实现了`tab`的弹性扩展。
在业务逻辑方面,`tabs`组件的逻辑主要体现在计算`tabs`插槽里的`tab-pane`组件,并将其解析为对应的组件数组`panes`。渲染分为两部分:一方面,通过`tabs`组件将`panes`传给`tab-nav`渲染`tab-header`,另一方面,直接渲染`$slots.default`对应的`tab-pane`组件。`tabs`组件的选中状态由`currentName`控制。`tab-header`通过`inject`获取`tabs`实例的`setCurrentName`方法,从而操作选中的`tab`;而`tab-pane`则是通过`$parents.currentName`实时控制当前`pane`是否展示。
对于动态新增`tab`的细节,`tabs.vue`在`mounted`时会调用`calcPaneInstances`函数来获取对应的`panes`。`calcPaneInstances`的主要作用是通过`slots.default`获取对应的组件实例。`panes`在两个关键位置被使用:在`tab-nav`组件中构造`tab-header`,以及在不考虑切换影响的内容渲染中。当动态增加`tab-pane`时,虽然`panes`不会响应变化,但通过在`tabs.vue`的虚拟DOM补丁更新后执行`updated`钩子,可以自动更新`panes`。
此外,`tabs`插槽可以插入不受切换影响的内容,这一特性在`tabs.vue`中的渲染函数中体现。这里,全插槽内容都会被渲染,而`tab-pane`会根据`currentName`来决定是否展示。由此产生的效果是,插槽内容与`tab-pane`的选择逻辑完全分离,使得插槽内容不受切换状态影响。
当点击单个`tab`时,`tabs.vue`组件内部会通过`props`传递`handleTabClick`函数到`tabNav`组件。`nav`组件将该函数绑定到`click`事件上。当`click`事件触发时,如果不考虑`tab`是否为`disabled`状态,会触发`setCurrentName`函数。这个函数通过`beforeLeave`起到作用,以确保在切换到下一个`tab`之前进行适当的过渡。在`setCurrentName`中使用了两次`$nextTick`,其目的是确保在更新视图时子组件的`$nextTick`操作不会影响父组件的更新流程。
最后,源码中展示了`props`值`activeName`的使用,其功能与`value`类似,用于绑定选中的`tab`。源码中还提到了组件名称的获取方式,`props`值`vnode.tag`实际指向的是注册组件时返回的`vue-component+[name]`,而通过`vnode.componentOptions.Ctor.options.tag`可以获取正常组件名。如果在`options`中未声明`name`,那么组件名将基于注册组件时的名称。
通过这次深入阅读,我们不仅掌握了`element-tabs`组件的核心工作原理和实现细节,还学会了如何更有效地阅读和理解复杂的前端组件源码。在阅读过程中,耐心地记录问题、适时放松心情,都能帮助我们更好地理解代码,从而提升技术能力。
vue3源码分析——实现slots
Vue3源码深入解析:揭秘插槽实现机制
插槽在Vue3中扮演着关键角色,它们是组件化开发中的重要特性。让我们通过源码探究,如何在模板中运用和实现各种类型的插槽:普通插槽、具名插槽以及作用域插槽。首先,理解模板中的插槽调用方式是关键,它会转化为render函数中的h函数,生成vnode对象,再通过特定属性(如default)访问。
为了深入理解,让我们从基础用法开始。在组件实例中, slots的default属性就像一个容器,存储用户未传递的插槽内容。为了测试,先准备DOM环境,然后进行实际操作。
通过测试用例,我们可以发现问题并进行编码解决。具名插槽的特性在于支持多个插槽,并且可以为每个插槽指定特定的名字。实现时,只需在renderSlot方法中传入相应名称即可。
作用域插槽则更为灵活,它允许在slot内部传递数据,且数据仅限于该slot范围内。通过测试用例,我们发现如何在代码层面处理数据共享问题,以确保插槽的局部性。
至此,通过一步步的编码实现和测试用例分析,我们已经掌握了插槽的完整工作原理。无论是普通插槽的简单调用,还是具名插槽的命名处理,以及作用域插槽的数据传递,都得到了全面的掌握。整个开发流程顺畅,测试用例也完美通过。