Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d01463fc9e | ||
|
|
534e5a0f49 | ||
|
|
3b2b2adde4 | ||
|
|
e905d37dab | ||
|
|
14dbde8615 | ||
|
|
62afd07694 | ||
|
|
933862c0cd | ||
|
|
d4d87b029f | ||
|
|
ed6a7d10c2 | ||
|
|
6e36e81748 | ||
|
|
f3a50ce6f7 | ||
|
|
c8c9f7c7d9 | ||
|
|
7eeecc34d2 | ||
|
|
c8448215c7 |
@@ -6,7 +6,7 @@
|
||||
|
||||
前端界的好文精读,每周更新!
|
||||
|
||||
最新精读:<a href="./前沿技术/224.%E7%B2%BE%E8%AF%BB%E3%80%8ARecords%20%26%20Tuples%20for%20React%E3%80%8B.md">224.精读《Records & Tuples for React》</a>
|
||||
最新精读:<a href="./源码解读/227.%20%E7%B2%BE%E8%AF%BB%E3%80%8Azustand%20%E6%BA%90%E7%A0%81%E3%80%8B.md">227. 精读《zustand 源码》</a>
|
||||
|
||||
素材来源:[周刊参考池](https://github.com/ascoders/weekly/issues/2)
|
||||
|
||||
@@ -182,6 +182,8 @@
|
||||
- <a href="./前沿技术/222.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%B7%B1%E5%85%A5%E4%BA%86%E8%A7%A3%E7%8E%B0%E4%BB%A3%E6%B5%8F%E8%A7%88%E5%99%A8%E5%9B%9B%E3%80%8B.md">222.精读《深入了解现代浏览器四》</a>
|
||||
- <a href="./前沿技术/223.%E7%B2%BE%E8%AF%BB%E3%80%8ARecords%20%26%20Tuples%20%E6%8F%90%E6%A1%88%E3%80%8B.md">223.精读《Records & Tuples 提案》</a>
|
||||
- <a href="./前沿技术/224.%E7%B2%BE%E8%AF%BB%E3%80%8ARecords%20%26%20Tuples%20for%20React%E3%80%8B.md">224.精读《Records & Tuples for React》</a>
|
||||
- <a href="./前沿技术/225.%E7%B2%BE%E8%AF%BB%E3%80%8AExcel%20JS%20API%E3%80%8B.md">225.精读《Excel JS API》</a>
|
||||
- <a href="./前沿技术/226.%E7%B2%BE%E8%AF%BB%E3%80%8A2021%20%E5%89%8D%E7%AB%AF%E6%96%B0%E7%A7%80%E5%9B%9E%E9%A1%BE%E3%80%8B.md">226.精读《2021 前端新秀回顾》</a>
|
||||
|
||||
### 设计模式
|
||||
|
||||
@@ -236,6 +238,7 @@
|
||||
- <a href="./源码解读/151.%20%E7%B2%BE%E8%AF%BB%E3%80%8A%40umijs%20use-request%E3%80%8B%E6%BA%90%E7%A0%81.md">151. 精读《@umijs use-request》源码</a>
|
||||
- <a href="./源码解读/155.%20%E7%B2%BE%E8%AF%BB%E3%80%8Ause-what-changed%20%E6%BA%90%E7%A0%81%E3%80%8B.md">155. 精读《use-what-changed 源码》</a>
|
||||
- <a href="./源码解读/156.%20%E7%B2%BE%E8%AF%BB%E3%80%8Areact-intersection-observer%20%E6%BA%90%E7%A0%81%E3%80%8B.md">156. 精读《react-intersection-observer 源码》</a>
|
||||
- <a href="./源码解读/227.%20%E7%B2%BE%E8%AF%BB%E3%80%8Azustand%20%E6%BA%90%E7%A0%81%E3%80%8B.md">227. 精读《zustand 源码》</a>
|
||||
|
||||
### 商业思考
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ YUI3 的 sandbox 像极了差不多同时出现的 AMD 规范,但早期 yahoo
|
||||
|
||||
对于 js 模块化,最近出现的 `<script type="module">` 方式,虽然还没有得到浏览器原生支持,但也是我比较看好的未来趋势,这样就连 webpack 的拆包都不需要了,直接把源代码传到服务器,配合 http2.0 完美抛开预编译的枷锁。
|
||||
|
||||
上述三中方案都不依赖预编译,分别实现了 html、css、js 模块化,相信这就是未来。
|
||||
上述三种方案都不依赖预编译,分别实现了 html、css、js 模块化,相信这就是未来。
|
||||
|
||||
### 模块化标准推进速度仍然缓慢
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
### 解析阶段
|
||||
|
||||
首先 renderer process 主线程会解析 HTML 文本为 DOM(Document Object Model),只译为中文就是文档对象模型,所以首先要把文本结构化才能继续处理。不仅是浏览器,代码的解析也得首先经历 Parse 阶段。
|
||||
首先 renderer process 主线程会解析 HTML 文本为 DOM(Document Object Model),直译为中文就是文档对象模型,所以首先要把文本结构化才能继续处理。不仅是浏览器,代码的解析也得首先经历 Parse 阶段。
|
||||
|
||||
对于 HTML 的 link、img、script 标签需要加载远程资源的,浏览器会调用 network thread 优先并行处理,但遇到 script 标签就必须停下来优先执行,因为 js 代码可能会改变任何 dom 对象,这可能导致浏览器要重新解析。所以如果你的代码没有修改 dom 的副作用,可以添加 async、defer 标签,或 JS 模块的方式使浏览器不必等待 js 的执行。
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ document.body.addEventListener('touchstart', event => {
|
||||
|
||||
```css
|
||||
#area {
|
||||
touch-action: pan-x;
|
||||
touch-action: none;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
Excel 现在可利用 js 根据单元格数据生成图表、表格,或通过 js 拓展自定义函数拓展内置 Excel 表达式。
|
||||
|
||||
我们来学习一下 Excel js API 开放是如何设计的,从中学习到一些开放 API 设计经验。
|
||||
|
||||
API 文档:[Excel JavaScript API overview](https://docs.microsoft.com/en-us/office/dev/add-ins/reference/overview/excel-add-ins-reference-overview)
|
||||
|
||||
## 精读
|
||||
|
||||
Excel 将利用 JS API 开放了大量能力,包括用户能通过界面轻松做到的,也包括无法通过界面操作做到的。
|
||||
|
||||
### 为什么需要开放 JS API
|
||||
|
||||
Excel 已经具备了良好的易用性,以及 [formula](https://support.microsoft.com/en-us/office/overview-of-formulas-in-excel-ecfdc708-9162-49e8-b993-c311f47ca173) 这个强大的公式。在之前 [精读《Microsoft Power Fx》](https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/211.%E7%B2%BE%E8%AF%BB%E3%80%8AMicrosoft%20Power%20Fx%E3%80%8B.md) 提到过,formula 就是 Excel 里的 Power FX,属于画布低代码语言,不过在 Excel 里叫做 “公式” 更合适。
|
||||
|
||||
已经具备这么多能力,为何还需要 JS API 呢?一句话概括就是,在 JS API 内可以使用 formula,即 JS API 是公式能力的超集,它包含了对 Excel 工作簿的增删改查、数据的限制、RangeAreas 操作、图表、透视表,甚至可以自定义 formula 函数。
|
||||
|
||||
也就是说,JS API 让 Excel “可编程化”,即以开发者视角对 Excel 进行二次拓展,包括对公式进行二次拓展,使 Excel 覆盖更多场景。
|
||||
|
||||
### JS API 可以用在哪些地方
|
||||
|
||||
从 Excel 流程中最开始的工作薄、工作表环节,到最细节的单元格数据校验都可通过 JS API 支持,目前看来 Excel JS API 并没有设置能力边界,而且还会不断完善,将 Excel 全生命周期中一切可编程的地方开放出来。
|
||||
|
||||
首先是对工作薄、工作表的操作,以及对工作表用户操作的监听,或者对工作表进行只读设置。这一类 API 的目的是对 Excel 这个整体进行编程操作。
|
||||
|
||||
第二步就是对单元格级别进行操作,比如对单元格进行区域选中,获取选中区域,或者设置单元格属性、颜色,或者对单元格数据进行校验。自定义公式也在这个环节,因为单元格的值可以是公式,而公式可以利用 JS API 拓展。
|
||||
|
||||
最后一步是拓展行为,即在单元格基础上引入图表、透视表拓展。虽然这些功能在 UI 按钮上也可以操作出来,但 JS API 可以实现 UI 界面配置不出来的逻辑,对于非常复杂的逻辑行为,即便 UI 可以配置出来,可读性也远没有代码高。除了表格透视表外、还可以创建一些自定义形状,基本的几何图形、图片和 SVG 都支持。
|
||||
|
||||
### JS API 设计
|
||||
|
||||
比较有趣的是,Excel 并没有抽象 “单元格” 对象,即便我们所有人都认为单元格就是 Excel 的代表。
|
||||
|
||||
这么做是出于 API 设计的合理性,因为 Excel 使用 Range 概念表示连续单元格。比如:
|
||||
|
||||
```js
|
||||
Excel.run(function (context) {
|
||||
var sheet = context.workbook.worksheets.getActiveWorksheet();
|
||||
|
||||
var headers = [
|
||||
["Product", "Quantity", "Unit Price", "Totals"]
|
||||
];
|
||||
var headerRange = sheet.getRange("B2:E2");
|
||||
headerRange.values = headers;
|
||||
headerRange.format.fill.color = "#4472C4";
|
||||
headerRange.format.font.color = "white";
|
||||
|
||||
return context.sync();
|
||||
});
|
||||
```
|
||||
|
||||
可以发现,Range 让 Excel 聚焦在批量单元格 API,即把单元格看做一个范围,整体 API 都可以围绕一个范围去设计。这种设计理念的好处是,把范围局限在单格单元格,就可以覆盖 Cell 概念,而聚焦在多个单元格时,可以很方便的基于二维数据结构创建表格、折线图等分析图形,因为二维结构的数据才是结构化数据。
|
||||
|
||||
或者可以说,结构化数据是 Excel 最核心的概念,而单元格无法体现结构化。结构化数据的好处是,一张工作表就是一个可以用来分析的数据集,在其之上无论是基于单元格的条件格式,还是创建分析图表,都是一种数据二次分析行为,这都得益于结构化数据,所以 Excel JS API 必然围绕结构化数据进行抽象。
|
||||
|
||||
再从 API 语法来看,除了工作薄这个级别的 API 采用了 `Excel.createWorkbook();` 之外,其他大部分 API 都是以下形式:
|
||||
|
||||
```js
|
||||
Excel.run(function (context) {
|
||||
// var sheet = context.workbook.worksheets.getItem("Sample");
|
||||
// 对 sheet 操作 ..
|
||||
return context.sync();
|
||||
});
|
||||
```
|
||||
|
||||
最外层的函数 `Excel.run` 是注入 `context` 用的,而且也可以保证执行的时候 Excel context 已经准备好了。而 `context.sync()` 是同步操作,即使当前对 context 的操作生效。所以 Excel JS API 是命令式的,也不会做类似 MVVM 的双向绑定,所以在操作过程中数据和 Excel 状态不会发生变化,直到执行 `context.sync()`。
|
||||
|
||||
注意到这点后,就可以理解为什么要把某些代码写在 `context.sync().then` 里了,比如:
|
||||
|
||||
```js
|
||||
Excel.run(function (ctx) {
|
||||
var pivotTable = context.workbook.worksheets.getActiveWorksheet().pivotTables.getItem("Farm Sales");
|
||||
|
||||
// Get the totals for each data hierarchy from the layout.
|
||||
var range = pivotTable.layout.getDataBodyRange();
|
||||
var grandTotalRange = range.getLastRow();
|
||||
grandTotalRange.load("address");
|
||||
return context.sync().then(function () {
|
||||
// Sum the totals from the PivotTable data hierarchies and place them in a new range, outside of the PivotTable.
|
||||
var masterTotalRange = context.workbook.worksheets.getActiveWorksheet().getRange("E30");
|
||||
masterTotalRange.formulas = [["=SUM(" + grandTotalRange.address + ")"]];
|
||||
});
|
||||
}).catch(errorHandlerFunction);
|
||||
```
|
||||
|
||||
这个从透视表获取数据的例子,只有执行 `context.sync()` 后才能拿到 `grandTotalRange.address`。
|
||||
|
||||
## 总结
|
||||
|
||||
微软还在 Office 套件 Excel、Outlook、Word 中推出了 [ScriptLab](https://docs.microsoft.com/zh-cn/office/dev/add-ins/overview/explore-with-script-lab) 功能,就可以在 Excel 的 ScriptLab 里编写 Excel JS API。
|
||||
|
||||
在 Excel JS API 之上,还有一个 [通用 API](https://docs.microsoft.com/zh-cn/javascript/api/office?view=common-js-preview),定义为跨应用的通用 API,这样 Excel JS API 就可以把精力聚焦在 Excel 产品本身能力上。
|
||||
|
||||
> 讨论地址是:[精读《Excel JS API》· Issue #387 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/387)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
[2021 JavaScript Rising Stars](https://risingstars.js.org/2021/en) 每年都会对前端开源项目进行点评,其依据是去年 Star 的增幅。Star 虽然只是一个维度,但至少反应了流行度,根据这个排行榜可以大体分析出前端社区的趋势。
|
||||
|
||||
## 精读
|
||||
|
||||
该榜单包含整体榜单、前端框架、Node 框架、构建工具、Vue 生态、React 生态、CSS-In-JS、测试、移动端、桌面、静态建站、状态管理、GraphQL 共 13 个子榜单,都是前端开源最活跃的几个领域,下面分别介绍。
|
||||
|
||||
### 整体榜单
|
||||
|
||||
第一名 [zx](https://github.com/google/zx) 是一个命令行工具,它基于 Node 语法拓展了 Bash 支持,可以非常方便的进行 Node 与 Bash 之间的输入输出,就像 Node 原生就支持 Bash 一样。它解决了离不开 Bash,但 Bash 写起大段逻辑不如 Node 自然的痛点。
|
||||
|
||||
第二名 [vite](https://github.com/vitejs/vite) 是去年最闪耀的星,它是一个 bundless 概念的前端构建工具,最初服务于 vue,后来进行框架无关升级后,在 react、angular 生态都大受欢迎。它解决了 webpack 编译太慢,其他 bundless 方案不够开箱即用且存在大量兼容问题的痛点。
|
||||
|
||||
第三名 [next.js](https://github.com/vercel/next.js) 2016 年开始的项目,是一个大而全的 React 全家桶,定位就是各大厂都会自己做一套的前端一体化框架,但它更时髦,不断加入许多流行功能比如 Server Component。这和 next.js 所在的明星公司 Vercel 有关,这家公司挖了大量开源知名人物,包括 Svelte 作者与 React 团队核心成员,所以也许未来社区的新玩具会先用在 next.js 再独立开源。它给出了前端最佳实践,并解决了没有精力持续给项目进行全方位优化,或追逐不上潮流的问题,因为 next.js 本身正在成为前端潮流的发源地。
|
||||
|
||||
第四名 [react](https://github.com/facebook/react) 不用多说了,数据驱动、响应式编程、函数式的领军框架,它改变了前端开发效率。
|
||||
|
||||
第五名 [tauri](https://github.com/tauri-apps/tauri) 比 electron 更轻量的桌面应用开发框架,基于任何前端框架。它解决了前端开发者遇到桌面应用开发场景时各平台巨大的原生开发学习成本的痛点。
|
||||
|
||||
第六名 [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) 是 css 框架,它提供了大量语义化 className,提供了许多最佳实践,让你有机会把 css 打理的井然有序。它解决了前端项目 css 杂乱无章又没有人真的在意的痛点。
|
||||
|
||||
第七名 [vscode](https://github.com/microsoft/vscode) 宇宙级 IDE,它解决了程序员没有真正趁手软件写代码的痛点。
|
||||
|
||||
第八名 [Slidev](https://github.com/slidevjs/slidev) 是一个把 markdown 渲染成 PPT 的框架,基于 vite + vue 等技术栈开发。用它开发的 PPT 非常简洁美观,非常适合在公开场合分享时使用,不仅看起来赏心悦目,还可以不经意间切换到 Markdown 源码 hotfix 一下小错误,展示出你的极客精神。它解决了你真的只想展示几句话,但又要以 PPT 方式 show 出来的痛点。
|
||||
|
||||
第九名 [NocoDB](https://github.com/nocodb/nocodb) 是一个支持多种数据源的数据库 UI 管理工具。但其实它有更大的格局,即对标 [airtable](https://www.airtable.com/),即用 NocoDB 连接数据库后,一切数据可视化的操作与功能都成为了可能,且提供了大量工作常用的甘特图、电子表格等视图,并可互相转换,最终其实数据存储到连接的数据库,但你无需关心细节。它解决了基于二维表格数据开发各类生产工具需投入大量研发资源的痛点。
|
||||
|
||||
第十名 [Vue](https://github.com/vuejs/vue) 和 React 一样不多说了。
|
||||
|
||||
### 前端框架
|
||||
|
||||
第一名 [react](https://github.com/facebook/react) 在整体榜单里了。
|
||||
|
||||
第二名 [Vue](https://github.com/vuejs/vue) 也在整体榜单里了。
|
||||
|
||||
第三名 [svelte](https://github.com/sveltejs/svelte) 是一个类似 vue 的框架,但特色是极度重视编译时,而忽略运行时,即运行时除了必要逻辑外是完全不引入任何 runtime 框架的。说实话我觉得和 vue、react 相比在正儿八经项目中并没有核心优势,因为它并没有那种魔法能力,可以极大的减少大型项目体积与提升性能,反而会受制于其语法与编译时的特性产生副作用。但唯一一个好处是框架无关,即利用 svelte 编译的组件几乎没有额外运行时框架代码,可以最低成本,最大隔离性的与其他项目结合。
|
||||
|
||||
第四名 [angular](https://github.com/angular/angular) 笔者已经很久没有关注 angular 框架了,无法给出什么点评。但从 svelte 新增热度超过 angular 来看,可能大部分开发者对 angular 的态度和我一样。
|
||||
|
||||
第五名 [solid](https://github.com/solidjs/solid) 类似 svelte,提前编译,按需打包,重要的是,其类似 React `useEffect` 的 API `createEffect` 在依赖变化后,仅该函数会重新执行,而不会导致整个组件重新执行,在点对点更新上做得更极致。
|
||||
|
||||
前端框架的亮点是 svelte 与 solid 的概念,即重编译时,轻运行时,更加原子化的更新粒度,与更直接的调用原生浏览器方法带来性能提升。很难不让人觉得这是一个前端框架新趋势,但我翻了不少资料发现,这种创新带来的收益在正常项目里微乎其微,所以实际上 2021 年前端框架还是没能跳出三巨头创造新的概念,而以 svelte 与 solid 为代表的 “静态化” 框架只能算微创新。
|
||||
|
||||
### Node 框架
|
||||
|
||||
第一名 [next.js](https://github.com/vercel/next.js) 在整体榜单里了,在 Node 框架一骑绝尘。
|
||||
|
||||
第二名 [nest](https://github.com/nestjs/nest) 和 next.js 很像,据我当时的了解,是因为 next.js 起步较慢,源码还不支持 ts,所以就有了这个更时髦的新框架。但实际上 next.js 早就全部改为 ts 了,而且正如整体榜单所说,现在已经开始引领潮流了,所以不怪 nest 定位重合,只能怪 next.js 后续发力太猛了。nest 的唯一特点就是没有绑定 UI 库。
|
||||
|
||||
第三名 [Strapi](https://github.com/strapi/strapi) 专门为 API 场景服务,提供了一个 API 管理后台,解决了只需要一个便捷 API 管理,而不希望了解一个大而全的后端框架的痛点。
|
||||
|
||||
第四名 [remix](https://github.com/remix-run/remix) 其实和 next.js 定位差不多,由 react-router 作者开发,才开源不久,需要进一步观察。
|
||||
|
||||
第五名 [nuxt.js](https://github.com/nuxt/nuxt.js) 是 vue 领域的 next.js。
|
||||
|
||||
值得一提的是,svelte 也有自己的专属框架 [sveltekit](https://kit.svelte.dev/),所以 Node 后端框架之争大部分其实在打全栈的牌,毕竟 Node 的优势就是支持 js 语言,而当前端应用基于某个框架编写时,如果有一个 Node 框架可以无缝集成这个前端框架,它就比非 Node 框架更优。
|
||||
|
||||
不过大厂几乎都是前后端分离的,所以这种全栈优势框架在国内没有太多出场机会,如果你是一个个人博主,还是首推使用全栈框架建站。
|
||||
|
||||
### 构建工具
|
||||
|
||||
第一名 [vite](https://github.com/vitejs/vite) 在整体榜单里了,在构建工具里也是一骑绝尘。
|
||||
|
||||
第二名 [esbuild](https://github.com/evanw/esbuild) 是用 go 编写的构建工具,适用使用范围更广,其压缩模块在 bundless 还未成熟时就被各大构建全家桶提前集成了,而 vite 也是基于 esbuild 进行编译的,但 vite 的火热度更高,说明了整体 bundless 方案已在 2021 年成熟了。
|
||||
|
||||
第三名 [swc](https://github.com/swc-project/swc) 因采用 rust 编写而知名,类似 esbuild,但因为依托 rust 编译到 wasm 的特性,支持了在线编译器,非常方便。swc 还被大量新生代构建工具作为基建,这在 [精读《Rust 是 JS 基建的未来》](https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/218.%E7%B2%BE%E8%AF%BB%E3%80%8ARust%20%E6%98%AF%20JS%20%E5%9F%BA%E5%BB%BA%E7%9A%84%E6%9C%AA%E6%9D%A5%E3%80%8B.md) 时提到过。
|
||||
|
||||
第四名 [turborepo](https://github.com/vercel/turborepo) 是用 go 写的 monorepo 项目管理工具,是 lerna 的替代品。
|
||||
|
||||
第五名 [nx](https://github.com/nrwl/nx) 也是一个 monorepo 管理工具。
|
||||
|
||||
与框架不同,构建工具往往呈现套娃结构,不是你中有我,就是我中有你,每个热门库都重点解决某一块关键问题,不断套娃套娃,最后套成一个很棒的全家桶。
|
||||
|
||||
### Vue 生态
|
||||
|
||||
第一名 [Slidev](https://github.com/slidevjs/slidev) 在整体榜单里了。
|
||||
|
||||
第二名 [Vue Element Admin](https://github.com/PanJiaChen/vue-element-admin) 基于 vue 的管理后台,在权限验证有一些最佳实践,使用 vuex 管理状态。
|
||||
|
||||
第三名 [Headless UI](https://github.com/tailwindlabs/headlessui) 是一个完全无样式的基础组件库,支持 React 与 Vue,官网的例子都是利用 [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) 内置样式组合而成的。它解决了 UI 组件库绑定样式后,自定义样式 “实际上非常恶心” 的痛点。
|
||||
|
||||
第四名 [Naive UI](https://github.com/TuSimple/naive-ui) 是一个 Vue 组件库,没有太多特别之处,但竟然上了排行榜。看了一下 star 趋势,在 2021.6 月份 star 涨幅是之后的十倍,估计刚开源推广了一波,后续涨幅很慢了,不出意外明年会跌出这个榜单。
|
||||
|
||||
第五名 [vue-next](https://github.com/vuejs/vue-next) 即 vue3,star 数量只有 vue2 的 13%,但今年 star 增幅有 vue2 的一半。
|
||||
|
||||
vue3 还自带了状态管理库 [pinia](https://github.com/vuejs/pinia),其生态已经非常完备。
|
||||
|
||||
### React 生态
|
||||
|
||||
第一名 [next.js](https://github.com/vercel/next.js) 在整体榜单里了。
|
||||
|
||||
第二名 [Ant Design](https://github.com/ant-design/ant-design) 虽然立志成为西湖区最好的 React 组件库,但事实上已经成为了全球最好的 React 组件库。
|
||||
|
||||
第三名 [MUI](https://github.com/mui-org/material-ui) 就是大名鼎鼎的 material design UI 组件库,我对它影响最深的是按钮点击后出现的水波纹,这是 material design 的一大特色。早在 2014 年就创建了,在 Ant Design 没火的时候,是开源组件库首选。
|
||||
|
||||
第四名 [remix](https://github.com/remix-run/remix) 在 Node 框架榜单里了,和 next.js 一样,是绑定了 React 生态的 Node 框架,所以也出现在 React 生态中。
|
||||
|
||||
第五名 [react-use](https://github.com/streamich/react-use) 是很小巧的 React Hook 库,提供了如 `usePrevious`、`useDebounce` 等常用的 Hook。
|
||||
|
||||
看完整个 React 生态榜单,无论是优质生态库数量,还是去年增长的 Star 数,都比 Vue 生态更胜一筹。这背后是无副作用的纯函数与自动依赖收集的响应式视图之争,甚至在 React 生态里也有比如 mobx-react 等优质 MVVM 库,这两种编程范式都会长期并存。
|
||||
|
||||
### CSS-In-JS
|
||||
|
||||
第一名 [vanilla-extract](https://github.com/seek-oss/vanilla-extract) 作为 2021 年的黑马,主打零运行时与 TS 支持。零运行时是通过 @vanilla-extract/webpack-plugin 插件在编译时就完成内容输出。
|
||||
|
||||
第二名 [styled-components](https://github.com/styled-components/styled-components) 是推出最早,也最成熟的一个 CSS-In-JS 框架,虽然版本间出现过运行时不兼容让我放弃过,但不得不说是这个方向的鼻祖。
|
||||
|
||||
第三名 [stitches](https://github.com/modulz/stitches) 和第一名很像,也主打零运行时,不过没有提对 TS 是否友好。
|
||||
|
||||
第四名 [Twin](https://github.com/ben-rogerson/twin.macro) 基于 [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) 实现了 CSS-In-JS 版的语法,可以认为是内置了一套最佳实践的 CSS-In-JS 库,也没解决太大的痛点,只是如果你同时喜欢 Tailwind CSS 与 CSS-In-JS,可能会爱屋及乌的选择 Twin。
|
||||
|
||||
第五名 [Emotion](https://github.com/emotion-js/emotion) 也是一个相对完备的库,基本上 CSS-In-JS 各类语法都能支持。
|
||||
|
||||
相比传统 CSS-In-JS 库,第一名 vanilla-extract 的零运行时是一大亮点,是这个方向的新趋势。
|
||||
|
||||
### 测试
|
||||
|
||||
第一名 [Playwright](https://github.com/microsoft/playwright) 是一个跨浏览器跨平台的测试框架,可以利用 js 代码打开任意 url 地址截图或者对比,解决了搭建自动化测试平台需要从零开始编写底层框架的痛点。
|
||||
|
||||
第二名 [Storybook](https://github.com/storybookjs/storybook) 是非常有名的文档工具,很多开源组件、项目的文档都基于 Storybook 创建。神奇的是它还支持[单元测试](https://storybook.js.org/docs/react/writing-tests/introduction),在你访问 UI 组件时进行测试并打印出测试结果。Storybook 已经变成了一个 all-in-one 的组件开发工具。
|
||||
|
||||
第三名 [Cypress](https://github.com/cypress-io/cypress) 与 Playwright 且诞生比较早,但由于不支持多 tab 页面,且仅支持 js,所以仅在前端流行,在测试工程师角度却不如支持多语言的 Playwright 好用。
|
||||
|
||||
第四名 [Puppeteer](https://github.com/puppeteer/puppeteer) 是 2017 年谷歌推出基于 Chrome 无头浏览器的测试工具,但 2020 年微软的 Playwright 具有跨浏览器特性还是更胜一筹。
|
||||
|
||||
第五名 [Jest](https://github.com/facebook/jest) 是代码级别单测工具的佼佼者,覆盖了全框架,只要你想对代码进行单元测试,选 Jest 是不会错的。
|
||||
|
||||
测试框架围绕单测与浏览器测试这两个子领域,2021 年在浏览器测试领域出现了跨浏览器这个特色方向,在单测领域没有太大变化,顶多出了一个 [Vitest](https://github.com/vitest-dev/vitest) 让单测跑得更快,这个库在 2022 年稳定后可能会大放异彩,甚至可能因为 Vite 流行的原因取代 Jest。
|
||||
|
||||
### 移动端
|
||||
|
||||
第一名 [ReactNative](https://github.com/facebook/react-native) 是基于 React 的 Mobile Native 开发框架,笔者用过一段时间,只能说不能抱有太大期待,因为极大的局限了 web 语法,如果你觉得仅掌握前端知识就可以轻松使用,那么一定会让你失望,不要一开始就抱着这种期待。另外跨端真是非常痛,比如 `SwitchAndroid`、`SwitchIOS` 让你感受不到 Write Once, Run everywhere(虽然官方也没这么说)。
|
||||
|
||||
第二名 [Ionic](https://github.com/ionic-team/ionic-framework) 是一个跨前端框架的跨平台构建工具,解决了 ReactNative 无法 Run everywhere 的痛点,但也带来了不够灵活的问题,即无法使用平台特定特性。
|
||||
|
||||
第三名 [Expo](https://github.com/expo/expo) 是基于 ReactNative 的一站式跨端开发工具,它的 App 使用非常傻瓜化,并且内置了调试能力,可以说是把 ReactNative 要踩的坑帮你踩完了。
|
||||
|
||||
第四名 [Quasar](https://github.com/quasarframework/quasar) 可以认为是 Vue 版的 ReactNative。
|
||||
|
||||
第五名 [Flipper](https://github.com/facebook/flipper) 是一个 Native 应用调试工具,可以认为是手机应用版本的 Chrome DevTools,支持连接远程终端,解决了手机应用难以用电脑调试的痛点。
|
||||
|
||||
其实还少了 [Flutter](https://github.com/flutter/flutter) 这个优秀框架,虽然不属于前端方向,但就像前端脚手架越来越多用 Rust、Go 写一样,Native 用 Dart 也是可以接受的。
|
||||
|
||||
从前端角度看移动端,唯一需求就是 Write Once,Run Anywhere,然后再把调试体验做好一些,Native 的兼容性、拓展性做强一些,就是一个完美方案了。
|
||||
|
||||
说到跨端,基于 Flutter 的 [kraken](https://github.com/openkraken/kraken) 也绝对值得一提,它利用 Flutter 高一执行渲染层能力,并解决了 Dart 生态对前端不友好的问题,做了一个 html+css+js 到 dart 的桥接层,如果明年可以在手淘稳定覆盖大量场景,那一定是个值得考虑的方案。
|
||||
|
||||
## 总结
|
||||
|
||||
还有更多榜单就不一一总结了,如果觉得不过瘾,可以去 [2021 JavaScript Rising Stars](https://risingstars.js.org/2021/en) 翻翻这些 top star 项目的介绍和源码深入了解一下。
|
||||
|
||||
最后总结一下 2021 前端领域的几个关键特征:
|
||||
|
||||
- 编程语言全面开花。以后 JS 开发者不等于前端开发者了,因为 Go、Rust、Dart、C++ 语言都可以为前端服务,并且 2021 年是真的有不少场景做到了生产环境可用,不论我们接不接受,前端不止有 JS 一种语言了。
|
||||
- 前端开发全家桶逐渐产生技术壁垒。在前几年,抄一个前端全家桶很容易,在过程中还可以学到很多底层知识,但现在前端全家桶的积累越来越多,涉及的领域越来越广,甚至 next.js 引入的特性会超越你自己调制的全家桶,这说明全家桶的知识量已经逐渐达到个人知识广度的极限,如果你没有足够精力持续学习,跟进时代步伐的最好方式是使用一个成熟的全家桶。
|
||||
|
||||
> 讨论地址是:[精读《2021 前端新秀回顾》· Issue #390 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/390)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ RPC 主要用来做服务器之间的方法调用,影响其性能最重要因
|
||||
|
||||
gRPC 主要用于服务之间传输,这里拿 Nodejs 举例:
|
||||
|
||||
1. 定义接口。由于 gRPC 使用 protobufs,所以接口定义文件就是 `helloword.proto`:
|
||||
1. 定义接口。由于 gRPC 使用 protobufs,所以接口定义文件就是 `helloworld.proto`:
|
||||
|
||||
```protobufs
|
||||
// The greeting service definition.
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
[zustand](https://github.com/pmndrs/zustand) 是一个非常时髦的状态管理库,也是 2021 年 Star 增长最快的 React 状态管理库。它的理念非常函数式,API 设计的很优雅,值得学习。
|
||||
|
||||
## 概述
|
||||
|
||||
首先介绍 [zustand](https://github.com/pmndrs/zustand) 的使用方法。
|
||||
|
||||
### 创建 store
|
||||
|
||||
通过 `create` 函数创建 store,回调可拿到 `get` `set` 就类似 Redux 的 `getState` 与 `setState`,可以获取 store 瞬时值与修改 store。返回一个 hook 可以在 React 组件中访问 store。
|
||||
|
||||
```typescript
|
||||
import create from 'zustand'
|
||||
|
||||
const useStore = create((set, get) => ({
|
||||
bears: 0,
|
||||
increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
|
||||
removeAllBears: () => set({ bears: 0 })
|
||||
}))
|
||||
```
|
||||
|
||||
上面例子是全局唯一的 store,也可以通过 `createContext` 方式创建多实例 store,结合 Provider 使用:
|
||||
|
||||
```tsx
|
||||
import create from 'zustand'
|
||||
import createContext from 'zustand/context'
|
||||
|
||||
const { Provider, useStore } = createContext()
|
||||
|
||||
const createStore = () => create(...)
|
||||
|
||||
const App = () => (
|
||||
<Provider createStore={createStore}>
|
||||
...
|
||||
</Provider>
|
||||
)
|
||||
```
|
||||
|
||||
### 访问 store
|
||||
|
||||
通过 `useStore` 在组件中访问 store。与 redux 不同的是,无论普通数据还是函数都可以存在 store 里,且函数也通过 selector 语法获取。因为函数引用不可变,所以实际上下面第二个例子不会引发重渲染:
|
||||
|
||||
```typescript
|
||||
function BearCounter() {
|
||||
const bears = useStore(state => state.bears)
|
||||
return <h1>{bears} around here ...</h1>
|
||||
}
|
||||
|
||||
function Controls() {
|
||||
const increasePopulation = useStore(state => state.increasePopulation)
|
||||
return <button onClick={increasePopulation}>one up</button>
|
||||
}
|
||||
```
|
||||
|
||||
如果嫌访问变量需要调用多次 `useStore` 麻烦,可以自定义 compare 函数返回一个对象:
|
||||
|
||||
```typescript
|
||||
const { nuts, honey } = useStore(state => ({ nuts: state.nuts, honey: state.honey }), shallow)
|
||||
```
|
||||
|
||||
### 细粒度 memo
|
||||
|
||||
利用 `useCallback` 甚至可以跳过普通 compare,而仅关心外部 id 值的变化,如:
|
||||
|
||||
```typescript
|
||||
const fruit = useStore(useCallback(state => state.fruits[id], [id]))
|
||||
```
|
||||
|
||||
原理是 id 变化时,`useCallback` 返回值才会变化,而 `useCallback` 返回值如果不变,`useStore` 的 compare 函数引用对比就会为 `true`,非常巧妙。
|
||||
|
||||
### set 合并与覆盖
|
||||
|
||||
`set` 函数第二个参数默认为 `false`,即合并值而非覆盖整个 store,所以可以利用这个特性清空 store:
|
||||
|
||||
```typescript
|
||||
const useStore = create(set => ({
|
||||
salmon: 1,
|
||||
tuna: 2,
|
||||
deleteEverything: () => set({ }, true), // clears the entire store, actions included
|
||||
}))
|
||||
```
|
||||
|
||||
### 异步
|
||||
|
||||
所有函数都支持异步,因为修改 store 并不依赖返回值,而是调用 `set`,所以是否异步对数据流框架来说都一样。
|
||||
|
||||
### 监听指定变量
|
||||
|
||||
还是用英文比较表意,即 `subscribeWithSelector`,这个中间件可以让我们把 selector 用在 subscribe 函数上,相比于 redux 传统的 subscribe,就可以有针对性的监听了:
|
||||
|
||||
```typescript
|
||||
mport { subscribeWithSelector } from 'zustand/middleware'
|
||||
const useStore = create(subscribeWithSelector(() => ({ paw: true, snout: true, fur: true })))
|
||||
|
||||
// Listening to selected changes, in this case when "paw" changes
|
||||
const unsub2 = useStore.subscribe(state => state.paw, console.log)
|
||||
// Subscribe also exposes the previous value
|
||||
const unsub3 = useStore.subscribe(state => state.paw, (paw, previousPaw) => console.log(paw, previousPaw))
|
||||
// Subscribe also supports an optional equality function
|
||||
const unsub4 = useStore.subscribe(state => [state.paw, state.fur], console.log, { equalityFn: shallow })
|
||||
// Subscribe and fire immediately
|
||||
const unsub5 = useStore.subscribe(state => state.paw, console.log, { fireImmediately: true })
|
||||
```
|
||||
|
||||
后面还有一些结合中间件、immer、localstorage、redux like、devtools、combime store 就不细说了,都是一些细节场景。值得一提的是,所有特性都是正交的。
|
||||
|
||||
## 精读
|
||||
|
||||
其实大部分使用特性都在利用 React 语法,所以可以说 50% 的特性属于 React 通用特性,只是写在了 [zustand](https://github.com/pmndrs/zustand) 文档里,看上去像是 zustand 的特性,所以这个库真的挺会借力的。
|
||||
|
||||
### 创建 store 实例
|
||||
|
||||
任何数据流管理工具,都有一个最核心的 store 实例。对 zustand 来说,便是定义在 `vanilla.ts` 文件的 `createStore` 了。
|
||||
|
||||
`createStore` 返回一个类似 redux store 的数据管理实例,拥有四个非常常见的 API:
|
||||
|
||||
```typescript
|
||||
export type StoreApi<T extends State> = {
|
||||
setState: SetState<T>
|
||||
getState: GetState<T>
|
||||
subscribe: Subscribe<T>
|
||||
destroy: Destroy
|
||||
}
|
||||
```
|
||||
|
||||
首先 `getState` 的实现:
|
||||
|
||||
```typescript
|
||||
const getState: GetState<TState> = () => state
|
||||
```
|
||||
|
||||
就是这么简单粗暴。再看 `state`,就是一个普通对象:
|
||||
|
||||
```typescript
|
||||
let state: TState
|
||||
```
|
||||
|
||||
这就是数据流简单的一面,没有魔法,数据存储用一个普通对象,仅此而已。
|
||||
|
||||
接着看 `setState`,它做了两件事,修改 `state` 并执行 `listenser`:
|
||||
|
||||
```typescript
|
||||
const setState: SetState<TState> = (partial, replace) => {
|
||||
const nextState = typeof partial === 'function' ? partial(state) : partial
|
||||
if (nextState !== state) {
|
||||
const previousState = state
|
||||
state = replace ? (nextState as TState) : Object.assign({}, state, nextState)
|
||||
listeners.forEach((listener) => listener(state, previousState))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
修改 `state` 也非常简单,唯一重要的是 `listener(state, previousState)`,那么这些 `listeners` 是什么时候注册和声明的呢?其实 `listeners` 就是一个 Set 对象:
|
||||
|
||||
```typescript
|
||||
const listeners: Set<StateListener<TState>> = new Set()
|
||||
```
|
||||
|
||||
注册和销毁时机分别是 `subscribe` 与 `destroy` 函数调用时,这个实现很简单、高效。对应代码就不贴了,很显然,`subscribe` 时注册的监听函数会作为 `listener` 添加到 `listeners` 队列中,当发生 `setState` 时便会被调用。
|
||||
|
||||
最后我们看 `createStore` 的定义与结尾:
|
||||
|
||||
```typescript
|
||||
function createStore(createState) {
|
||||
let state: TState
|
||||
const setState = /** ... */
|
||||
const getState = /** ... */
|
||||
/** ... */
|
||||
const api = { setState, getState, subscribe, destroy }
|
||||
state = createState(setState, getState, api)
|
||||
return api
|
||||
}
|
||||
```
|
||||
|
||||
虽然这个 `state` 是个简单的对象,但回顾使用文档,我们可以在 `create` 创建 store 利用 callback 对 state 赋值,那个时候的 `set`、`get`、`api` 就是上面代码倒数第二行传入的:
|
||||
|
||||
```typescript
|
||||
import { create } from 'zustand'
|
||||
|
||||
const useStore = create((set, get) => ({
|
||||
bears: 0,
|
||||
increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
|
||||
removeAllBears: () => set({ bears: 0 })
|
||||
}))
|
||||
```
|
||||
|
||||
至此,初始化 store 的所有 API 的来龙去脉就梳理清楚了,逻辑简单清晰。
|
||||
|
||||
### create 函数的实现
|
||||
|
||||
上面我们说清楚了如何创建 store 实例,但这个实例是底层 API,使用文档介绍的 `create` 函数在 `react.ts` 文件定义,并调用了 `createStore` 创建框架无关数据流。之所 `create` 定义在 `react.ts`,是因为返回的 `useStore` 是一个 Hooks,所以本身具有 React 环境特性,因此得名。
|
||||
|
||||
该函数第一行就调用 `createStore` 创建基础 store,因为对框架来说是内部 API,所以命名也叫 api:
|
||||
|
||||
```typescript
|
||||
const api: CustomStoreApi = typeof createState === 'function' ? createStore(createState) : createState
|
||||
|
||||
const useStore: any = <StateSlice>(
|
||||
selector: StateSelector<TState, StateSlice> = api.getState as any,
|
||||
equalityFn: EqualityChecker<StateSlice> = Object.is
|
||||
) => /** ... */
|
||||
```
|
||||
|
||||
接下来所有代码都在创建 `useStore` 这个函数,我们看下其内部实现:
|
||||
|
||||
简单来说就是利用 `subscribe` 监听变化,并在需要的时候强制刷新当前组件,并传入最新的 `state` 给到 `useStore`。所以第一步当然是创建 `forceUpdate` 函数:
|
||||
|
||||
```typescript
|
||||
const [, forceUpdate] = useReducer((c) => c + 1, 0) as [never, () => void]
|
||||
```
|
||||
|
||||
然后通过调用 API 拿到 `state` 并传给 selector,并调用 `equalityFn`(这个函数可以被定制)判断状态是否发生了变化:
|
||||
|
||||
```typescript
|
||||
const state = api.getState()
|
||||
newStateSlice = selector(state)
|
||||
hasNewStateSlice = !equalityFn(
|
||||
currentSliceRef.current as StateSlice,
|
||||
newStateSlice
|
||||
)
|
||||
```
|
||||
|
||||
如果状态变化了,就更新 `currentSliceRef.current`:
|
||||
|
||||
```typescript
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (hasNewStateSlice) {
|
||||
currentSliceRef.current = newStateSlice as StateSlice
|
||||
}
|
||||
stateRef.current = state
|
||||
selectorRef.current = selector
|
||||
equalityFnRef.current = equalityFn
|
||||
erroredRef.current = false
|
||||
})
|
||||
```
|
||||
|
||||
> `useIsomorphicLayoutEffect` 是同构框架常用 API 套路,在前端环境是 `useLayoutEffect`,在 node 环境是 `useEffect`:
|
||||
|
||||
说明一下 `currentSliceRef` 与 `newStateSlice` 的功能。我们看 `useStore` 最后的返回值:
|
||||
|
||||
```typescript
|
||||
const sliceToReturn = hasNewStateSlice
|
||||
? (newStateSlice as StateSlice)
|
||||
: currentSliceRef.current
|
||||
useDebugValue(sliceToReturn)
|
||||
return sliceToReturn
|
||||
```
|
||||
|
||||
发现逻辑是这样的:如果 state 变化了,则返回新的 state,否则返回旧的,这样可以保证 compare 函数判断相等时,返回对象的引用完全相同,这个是不可变数据的核心实现。另外我们也可以学习到阅读源码的技巧,即要经常跳读。
|
||||
|
||||
那么如何在 selector 变化时更新 store 呢?中间还有一段核心代码,调用了 `subscribe`,相信你已经猜到了,下面是核心代码片段:
|
||||
|
||||
```typescript
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
const listener = () => {
|
||||
try {
|
||||
const nextState = api.getState()
|
||||
const nextStateSlice = selectorRef.current(nextState)
|
||||
if (!equalityFnRef.current(currentSliceRef.current as StateSlice, nextStateSlice)) {
|
||||
stateRef.current = nextState
|
||||
currentSliceRef.current = nextStateSlice
|
||||
forceUpdate()
|
||||
}
|
||||
} catch (error) {
|
||||
erroredRef.current = true
|
||||
forceUpdate()
|
||||
}
|
||||
}
|
||||
const unsubscribe = api.subscribe(listener)
|
||||
if (api.getState() !== stateBeforeSubscriptionRef.current) {
|
||||
listener() // state has changed before subscription
|
||||
}
|
||||
return unsubscribe
|
||||
}, [])
|
||||
```
|
||||
|
||||
这段代码要先从 `api.subscribe(listener)` 看,这使得任何 `setState` 都会触发 `listener` 的执行,而 `listener` 利用 `api.getState()` 拿到最新 `state`,并拿到上一次的 compare 函数 `equalityFnRef` 执行一下判断值前后是否发生了改变,如果改变则更新 `currentSliceRef` 并进行一次强制刷新(调用 `forceUpdate`)。
|
||||
|
||||
### context 的实现
|
||||
|
||||
注意到 context 语法,可以创建多个互不干扰的 store 实例:
|
||||
|
||||
```tsx
|
||||
import create from 'zustand'
|
||||
import createContext from 'zustand/context'
|
||||
|
||||
const { Provider, useStore } = createContext()
|
||||
|
||||
const createStore = () => create(...)
|
||||
|
||||
const App = () => (
|
||||
<Provider createStore={createStore}>
|
||||
...
|
||||
</Provider>
|
||||
)
|
||||
```
|
||||
|
||||
首先我们知道 `create` 创建的 store 是实例间互不干扰的,问题是 `create` 返回的 `useStore` 只有一个实例,也没有 `<Provider>` 声明作用域,那么如何构造上面的 API 呢?
|
||||
|
||||
首先 `Provider` 存储了 `create` 返回的 `useStore`:
|
||||
|
||||
```tsx
|
||||
const storeRef = useRef<TUseBoundStore>()
|
||||
storeRef.current = createStore()
|
||||
```
|
||||
|
||||
那么 `useStore` 本身其实并不实现数据流功能,而是将 `<Provider>` 提供的 `storeRef` 拿到并返回:
|
||||
|
||||
```typescript
|
||||
const useStore: UseContextStore<TState> = <StateSlice>(
|
||||
selector?: StateSelector<TState, StateSlice>,
|
||||
equalityFn = Object.is
|
||||
) => {
|
||||
const useProviderStore = useContext(ZustandContext)
|
||||
return useProviderStore(
|
||||
selector as StateSelector<TState, StateSlice>,
|
||||
equalityFn
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
所以核心逻辑还是是现在 `create` 函数里,`context.ts` 只是利用 ReactContext 将 `useStore` “注入” 到组件,且利用 ReactContext 特性,这个注入可以存在多个实例,且不会相互影响。
|
||||
|
||||
### 中间件
|
||||
|
||||
中间件其实不需要怎么实现。比如看这个 redux 中间件的例子:
|
||||
|
||||
```typescript
|
||||
import { redux } from 'zustand/middleware'
|
||||
const useStore = create(redux(reducer, initialState))
|
||||
```
|
||||
|
||||
可以将 zustand 用法改变为 reducer,实际上是利用了函数式理念,redux 函数本身可以拿到 `set, get, api`,如果想保持 API 不变,则原样返回 callback 就行了,如果想改变用法,则返回特定的结构,就是这么简单。
|
||||
|
||||
为了加深理解,我们看看 redux 中间件源码:
|
||||
|
||||
```typescript
|
||||
export const redux = ( reducer, initial ) => ( set, get, api ) => {
|
||||
api.dispatch = action => {
|
||||
set(state => reducer(state, action), false, action)
|
||||
return action
|
||||
}
|
||||
api.dispatchFromDevtools = true
|
||||
return { dispatch: (...a) => api.dispatch(...a), ...initial }
|
||||
}
|
||||
```
|
||||
|
||||
将 `set, get, api` 封装为 redux API:`dispatch` 本质就是调用 `set`。
|
||||
|
||||
## 总结
|
||||
|
||||
[zustand](https://github.com/pmndrs/zustand) 是一个实现精巧的 React 数据流管理工具,自身框架无关的分层合理,中间件实现巧妙,值得学习。
|
||||
|
||||
> 讨论地址是:[精读《zustand 源码》· Issue #392 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/392)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
Reference in New Issue
Block a user