docs: Historical Debt of API (#45263)

* docs: Historical Debt of API

* docs: update doc
This commit is contained in:
二货爱吃白萝卜 2023-10-11 13:47:11 +08:00 committed by GitHub
parent 92c9c03858
commit 7dda41df03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 166 additions and 0 deletions

View File

@ -0,0 +1,83 @@
---
title: Historical Debt of API
date: 2023-10-11
author: zombieJ
---
You may have received this warning when upgrading Ant Design:
```text
Warning: [antd: XXX] `old prop` is deprecated. Please use `new prop` instead.
```
This is because antd has some historical debt in API design. For example, in antd v3 and before, the code of TreeSelect was directly copied from Select and extended on this basis. There are differences in their search styles:
<img alt="Select" height="162" src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*uDbxSKTLU8YAAAAAAAAAAAAADrJ8AQ/original" />
<img alt="TreeSelect" height="316" src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ggTeQqbnFVkAAAAAAAAAAAAADrJ8AQ/original" />
And in the maintenance process, developers want to control the content of the search box. Unfortunately, this requirement was PR by different developers at different times. So two different properties were added, one called `inputValue` and the other called `searchValue`:
```tsx
// Select in combobox mode, the search box is the input box, `inputValue` looks reasonable
<Select inputValue="search" />
// TreeSelect's search box is in the popup layer, `searchValue` is also reasonable
<TreeSelect searchValue="search" />
```
In multiple mode, the Select component will clear the search box content after selecting the content. But in some scenarios, developers want to keep it. So TreeSelect and Select added the `autoClearSearchValue` property.
Wait! Select is called `inputValue`, why should it be called `autoClearSearchValue`? Obviously it should be called `autoClearInputValue`. If we continue to grow other similar API styles on the existing API. You will find that the component's props are getting more and more split. This will also cause bad smells in code maintenance. For example, in the above example, we later extracted the Select component into a unified UI layer and merged it into the `rc-select` component. `rc-tree-select` only needs to implement the content of the popup layer, and the structure and style of the input box can be completely reused with Select. But because the APIs of the two are inconsistent, we need to do extra processing, so we need to refactor these API debts and unify them during the iteration process. (In v4, we merged it into `searchValue` and unified the design)
However there is no silver bullet in the world, we can't design a perfect API at the beginning. Some APIs seem very reasonable at the beginning of the design, but as the iteration progresses, they will be found to be more or less inappropriate. For example, the popup layer was named dropdown in the early days, which corresponds to the popup content of Dropdown and Select components. But for Tooltip, dropdown is obviously not suitable. From a unified perspective, popup will be more suitable.
### Deprecated Warning
We gradually unified the API naming rules during the maintenance process ([API Naming rules](https://github.com/ant-design/ant-design/wiki/API-Naming-rules)). When adding new features, we prefer to find similar APIs from existing ones. For existing APIs, deprecated warnings are gradually added. In order to maintain compatibility, our strategy is that the deprecated warnings provided by each version will continue to be compatible with a major version, and it will be removed in the next major version. For example, in v4, deprecated warnings are added, so they can still be used in v5, but they will be removed in v6. In this way, developers have enough time to migrate.
But from the developer's point of view, this is not reasonable. Developers only upgraded antd, but they have to suffer from the intrusion of console because of the API design mistakes of the component library. If a few usage warnings are mixed into the deprecated warnings, developers often have difficulty finding them. This situation is particularly prominent in major version upgrades. The business may not give you enough time to do the upgrade migration, so you have to use compatible packages and other technical means to make it run first. For long deprecated warnings, developers have to choose to temporarily (or permanently) ignore them. For this situation, usage warnings will be more important, so we propose the [Warning Filter RFC](https://github.com/ant-design/ant-design/discussions/44551).
#### Warning Filter
You can aggregate the deprecated information through the `warning` property of ConfigProvider:
```tsx
<ConfigProvider warning={{ strict: false }} />
```
After aggregation, the original flattened deprecated information will be merged into an array and displayed in the console. And it will not affect the usage warnings:
![Merged Message](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*MG-rQ4NSbbcAAAAAAAAAAAAADrJ8AQ/original)
#### Extension Problem
As mentioned above, there is no silver bullet in API design. In order to prevent breaking change, we generally will not change the existing API implementation. But for some conventions, this will cause trouble. For example, the `ref` component is a typical convention. As long as it is a React developer, you can understand that you can get the DOM node through `ref` and do some basic operations such as `focus`. But for composite components, the calling method and DOM may not be unified. For example, the `ref` of the Table component should obviously be the outermost div, but the `scrollTo` method should correspond to the scroll container (if it is VirtualTable, it should be handled by the internal `rc-virtual-list`). In antd mobile, `ref` is designed as a composite structure, and the DOM node is always returned through `nativeElement`:
```tsx
export interface SampleRef {
nativeElement: HTMLElement;
focus(): void;
blur(): void;
}
```
But in antd, because we did not make a convention for `ref` early, we encountered difficulties in implementing the method. But fortunately, there is Proxy support, we can intercept `ref` through Proxy and return the results we want:
```tsx
useImperativeHandle(
ref,
() =>
new Proxy(divRef.current, {
get(target, key) {
// ...
},
}),
);
```
We can continue to be compatible with previous usage in this way. It is still a DOM node, but it also supports the definition call of SampleRef.
## Summary
API design is a difficult problem. As the iteration of technology stack and components themselves. Some designs will gradually decay, and API upgrades themselves are also painful for developers. We hope that through this article, you can understand our design ideas and some problems in the upgrade process. If you have any suggestions or ideas, welcome to discuss on Github.

View File

@ -0,0 +1,83 @@
---
title: API 的历史债务
date: 2023-10-11
author: zombieJ
---
在升级 Ant Design 的过程中,你或许收到过这种警告:
```text
Warning: [antd: XXX] `old prop` is deprecated. Please use `new prop` instead.
```
这是因为 antd 在开发过程中,有一些 API 设计的不合理导致的历史债务。举个例子,在 antd v3 及以前TreeSelect 的代码是从 Select 中直接复制出来并在此基础上做的拓展。他们的搜索样式存在差异:
<img alt="Select" height="162" src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*uDbxSKTLU8YAAAAAAAAAAAAADrJ8AQ/original" />
<img alt="TreeSelect" height="316" src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ggTeQqbnFVkAAAAAAAAAAAAADrJ8AQ/original" />
而后的维护过程中,开发者希望可以受控控制搜索框的内容。而不巧的是,这个需求是不同的机缘巧合被提出并由不同的开发者贡献了代码。于是两者添加了不同的属性,一个叫做 `inputValue` 而另一个叫做 `searchValue`
```tsx
// Select 在 combobox 模式下,搜索框就是输入框,`inputValue` 看起来很合理
<Select inputValue="search" />
// TreeSelect 的搜索框在弹出层,`searchValue` 也很合理
<TreeSelect searchValue="search" />
```
在多选模式下,类 Select 组件在选择内容后会清除搜索框内容。但是有些场景下,开发者希望能够保留。因而 TreeSelect 和 Select 又添加了 `autoClearSearchValue` 属性。
等等Select 明明叫 `inputValue`,为什么要叫 `autoClearSearchValue`?明显应该叫做 `autoClearInputValue` 呐。如果我们在现有的 API 上继续生长其他的同类 API 风格。你会发现组件的 prop 变得越来越分裂。这也会导致代码维护出现坏味道。例如上面这个例子,在之后我们对类 Select 组件抽成了统一的 UI 层并将其合并到 `rc-select` 组件中。`rc-tree-select` 只需要实现弹出层的内容,而输入框的结构和样式可以和 Select 完全复用。但是由于两者的 API 不一致,导致我们需要额外的处理,所以我们在迭代过程中需要对这些 API 债务进行重构并将其统一起来。(在 v4 中,我们将其合并为了 `searchValue` 并且对设计也进行了统一)
然而世上没有银弹,我们无法在一开始就设计出完美的 API。有一些 API 在设计之初显得非常合理,而随着迭代又会发现或多或少不合时宜。比如说弹出层早期起名为 dropdown这对应了 Dropdown 以及类 Select 组件的弹出内容。但是对于 Tooltip 而言dropdown 显然是不适合的。从统一的角度看popup 会更适合。
### 废弃警告
在维护过程中,我们逐渐统一了 API 命名规范([API Naming rules](https://github.com/ant-design/ant-design/wiki/API-Naming-rules))。在添加新的 feature 时,优先从现存的 API 中寻找接近。对于现存的 API逐步添加废弃警告。为了保持兼容我们的策略是每个版本提供的废弃警告会继续兼容一个大版本而在下下个大版本中移除它。例如在 v4 中添加了废弃警告,那么在 v5 中仍然可以使用,但是在 v6 中将会被移除。以此确保开发者有足够的时间进行迁移。
但是从开发者角度看,这也并不合理。开发者本身只是对 antd 进行了升级,却要因为组件库 API 设计的失误而遭受 console 的侵扰。如果在废弃警告中混入几个使用警告,开发者往往很难发现它们。这种情况在大版本升级中尤为显著,业务可能并没有给你足够的时间去做升级迁移,因而不得不使用兼容包以及其他的一些技术手段让它先跑起来。而对于冗长的废弃警告,开发者不得不选择暂时(或者永远)无视它们。针对这种情况,使用警告会更为重要,因而我们提出了 [Warning Filter RFC](https://github.com/ant-design/ant-design/discussions/44551)。
#### 警告过滤
通过 ConfigProvider 的 `warning` 属性,可以将废弃信息进行聚合:
```tsx
<ConfigProvider warning={{ strict: false }} />
```
聚合后,原本打平的废弃信息会合并为一个数组在 console 中展示。而对于使用警告则不会影响:
![Merged Message](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*MG-rQ4NSbbcAAAAAAAAAAAAADrJ8AQ/original)
### 拓展问题
如上所述API 设计不存在银弹。为了防止 breaking change我们一般不会改动现有的 API 实现。但是对于一些约定的内容,这就会造成麻烦。比如说 `ref` 组件是很典型的约定,只要是 React 的开发者就能明白,通过 `ref` 可以获取 DOM 节点以及做一些诸如 `focus` 的基本操作。但是对于复合组件而言,调用方法和 DOM 不一定能够统一。比如说 Table 组件的 `ref` 显然应该是最外层的 div但是对于 `scrollTo` 方法则应该对应到滚动容器上(如果是 VirtualTable 则应该交由内部的 `rc-virtual-list` 进行处理)。在 antd mobile 中 `ref` 被设计为复合结构DOM 节点总是通过 `nativeElement` 返回:
```tsx
export interface SampleRef {
nativeElement: HTMLElement;
focus(): void;
blur(): void;
}
```
而在 antd 中,由于我们早期没有对 `ref` 进行约定,导致在实现方法是就遇到了难题。不过好在有 Proxy 支持,我们可以通过 Proxy 对 `ref` 进行拦截并返回我们想要的结果:
```tsx
useImperativeHandle(
ref,
() =>
new Proxy(divRef.current, {
get(target, key) {
// ...
},
}),
);
```
通过这种方式,我们可以继续兼容之前的使用。它仍然是一个 DOM 节点,但是同样也支持了 SampleRef 的定义调用。
## 总结
API 设计是个难题,随着技术栈以及组件本身的迭代。一些设计会逐渐腐朽,而 API 升级本身对于开发者也是痛苦的。我们希望通过这篇文章,让开发者能够理解我们的设计思路以及在升级过程中的一些问题。如果你有任何的建议或者想法,欢迎在 Github 中讨论。