NG包网,欧博包杀,亚星包杀, 利博包杀,列表渲染可以不加 Key 么?
问个问题,React 迭代组件之后,需不需要传 key?如果不传 key,会有什么影响?
function SomeCompo(){
return someList.map(el=><Compo prop={el} key={el.id}/>)
}
这是个很常见的用法对吧?熟悉 React 的朋友会很快回答,不传 key 后果就是每次渲染都是个全新的 Compo
React 的 key,实际上在 DDD 开发模型中,属于实体唯一标识,也是 diff 两大设计原则之一:
两个不同类型的元素会产生出不同的树;
开发者可以通过keyprop 来暗示哪些子元素在不同的渲染下能保持稳定;
换句话说,就是没有节点移动和替换,你优化 React 的 list 渲染,没有节点交换操作的情况下,你也必须(隐式)传递 key(形成类似 Vue 那种,只能感应添加删除,不能感应移动的方案)
ng 也有类似的 api,叫做 TrackBy
但是,当你看到 Angular 官方文档的示例代码时,你会看到大量不加 TrackBy 的官方示例:

列表渲染可以不加 key 吗?
如果你只写 React 或者 Vue,结论是不行
但是如果你用了 Angular,回答就是 —— 可以
列表渲染不加 key 的前提,是需要支持节点的移动和替换操作
这部分内容在 Vue 文档中的解释更加直观:
当 Vue 正在更新使用
v-for渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
看到了加粗的那部分文字么?好!
来看看 Angular 的结构型指令,ngForOf:

angular/ng_for_of.ts at master · angular/angular (github.com)
viewContainer.move(view, currentIndex)

是的,节点的移动和替换操作,当这个操作出现时,key 就不再是必须,而熟悉 diff 的朋友们知道,React 和 Vue 中不存在节点的移动和替换操作(除了 portal 和 teleport)
而 Angular 中:
所有视图节点操作,都是移动,替换和删除操作
当然,ng 的 TrackBy 官方也有说明何时使用,其实就是在数组被全量更新的时候,Vue React 中,只有当数组被全量更新,才会有新的检查甚至渲染发生(Vue3 proxy 和 React refresh 用法除外),所以数组更新必然是全量的,即数据和引用都发生改变,而 ng 的 TrackBy,当且仅当数组引用更新,才会使用,这也是 cdk table dataSource 结构的来源
言归正传,我们继续
严格来说,Angular 甚至:
不存在组件,只存在指令
比如,如果我们要为图片加上 click 打印,如果是 Vue 或者 React,你可能要封住一个叫做 Image 的组件 或者 v-imgClick 指令了
但是 ng:
@Directive({
selector: 'img'
})
class ImageClick{
@HostListener('click') onMouseEnter() {
console.log('clicked')
}
}
// ng
<img/>
// react
// <Image/>
// vue
// <img v-imgClick/>
ng 实现附加逻辑
再进一步,如果我想要将 img 节点,插入 p 标签,并加入 textContent:‘img 插入 p 标签辣!’,该怎么做?
@Component({
selector: "img",
// 实际开发是原生 custom element
// selector: "app-some-element"
template: `<p>插入 p 标签辣!</p>`
})
export class NoImage {}
// 使用
<img/>

ng 实现添加替换逻辑
不难发现 ng 组件的定义 —— ng 的组件,就是指令,不过是可以添加视图,替换视图的指令而已
你甚至直接在元数据中加入: encapsulation:ViewEncapsulation.ShadowDom ,它就是个基于 shadow dom 而非 VirtualDom 的组件,除此之外还有 Emulate 仿真模式,这些样式封装能力,用比较新潮的说法,配合 Module 资源功能封装(领域),以及多 module 构建目标,即实现了真正的 —— “微前端”
于是,当别人问你,Angular 如何实现 React 和 Vue 的 Portal 或者 Teleport 时,你可以这么回答:
Angular 的每个组件,都是 Portal/Teleport
这里就引出了另一个用法,类似 React 的 renderProps,Vue 实现此功能必须借助 render 或者 jsx,且所有优化全部失效,但是 Angular 就很方便,比如对 MatTable 的封装:

<ng-container *ngTemplateOutlet=”someRef”></ng-container>
<ng-template #someRef></ng-template>
你就能写出类似 React 的 renderProps 风格的 table,类似 antd dataSource (注意要用 cdk DataSource 类型,不然需要 trackBy)
注意,这个不是 Vue 具名插槽的概念,Vue 具名插槽对应 ng select 插槽,而 ng templateOutlet 对应 React render props —— <SomeCompo outlet={<div>{/* … */}</div>}/>,Vue 模板没有这个功能
看到这里,你就会发现一个 Angular 一个原理性的特点 ——
Angular 以 HTML 为准,遵循 HTML 标准
在 HTML 中,写 script
这一点与 React (在 script 中写 HTML/jsx )正好相反,而正因为 Angular 这种,优先保证视图一致的思维,加上事件驱动模型(zone,也与 React 数据驱动一体两面,背后是可变不可变的差异),可以很好保证用户体验和产品质量,这些体现在:
- CDK 有统一弹出层管理,即打开 3D 视图,z-index 层永远只有一个
- CDK 有弹出层策略模式,即弹出框,选择框等内容,总会根据策略和可视检测,响应式地竟可能显示在视图中,不会出现类似 element UI 或者 antd 的选项框遮盖,React material UI 有策略模式,但是不是实时的
- Material 库保证了可访问性(a11y MatFormFieldControl),屏幕阅读器和辅助功能总能正确运行
- Material 库实现了完整的焦点管理,即动态内容的输入项自动聚焦,符合 W3C 标准,配合 a11y,用户不依靠鼠标,可以完整访问应用除画板外的所有功能
- 配合 a11y 和 辅助指令,生成 XLIFF 翻译文件,构建多语言版本包,配合标准化多语言流程。很抱歉,这种多语言,React Vue 生态目前没有看到稳定的可用支持,对应 React Vue 的运行时多语言方案,是 Angular 的 language service
- 在服务端渲染时,可以不进行组件的完整挂载,仅生成原生或 selector 标识 tag
- Angular 中,配合 HTTPCLient,普遍使用 swagger rpc 或者 grpc 工具,实现远程方法调用,避免与 http 直接接触
- Module 结构的多构建目标,以及 Component 的多封装方式支持(ViewEncapsulation),配合 @optional @self provideIn: root 服务,我们可以动态进行多项目构建,发布,持续集成,或者将此成为 —— 微前端?
- 更多 …
这一系列的实现,我们认为当你采用 Angular 构建项目后,能够很快达到最基本的软件质量要求
之前会有人讨论,前端有没有复杂项目?
其实前端有复杂项目,而且每个项目都可以复杂,关键看你有没有质量要求
如果本身不关注残疾人,认为所有使用者都只用鼠标访问网站且仅仅使用键盘输入 input 类标签,仅仅把多语言看成键值对,又或者认为弹框选择框仅仅是 z-index 更高的元素,那的确没有复杂项目
但是你要明白,从万维网的定义看,前端就是一个无比复杂的领域
想要和 ng 一起体会复杂业务的魅力么?要不先来个 MatFormFieldControl 体会一下?
Custom form field control | Angular Material
顺便回应一下,React 搭上什么周边可以媲美 Angular,答案是什么周边都不行,因为它有很多缺失
比如 xliff(react-i18n-next 在尝试解决这个问题)整体生态对很多业务问题解决不完整,焦点管理,a11y 都没有被正视起来,虽然补足了没有 service 或者叫 SOA 的缺陷(Hooks),但是 ng 还有更上层的 Module
React 有 useTransition,但是 Angular 有事件驱动搭配 webworker
两者最初都想做一个模式,比如 React 的 concurrent 和 Angular plateform/webworker,都不了了之,看来用户想要在 setTimeout 0 之后拿到视图的需求,还是很旺盛的嘛
可以说 React 在架构上和 Angular 有非常大的一段距离需要跟进,但是不影响 React 的灵活,以及小而美和函数式带来的一致性(注意,是视图部分的函数式,类似将 rx 也写到了视图里,这点个人很喜欢)
但是 React 的美,都是形式上和扩展性上的美,业务上它真的很难控制,个人观点是,想要 React 更上一层楼,Angular 的学习也是必要的,否者很多业务问题你没有指导,也没看过更优或者更具建设性的解法
否者你会陷入天天纠结怎么保证回调不变性的怪圈,实际上这是个非常容易解决的问题,不过这个属于下一章内容了

发表回复