Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaaa28953b | ||
|
|
9609101870 | ||
|
|
afb601a303 | ||
|
|
c72477ed7d | ||
|
|
dc09550e0c | ||
|
|
dc66e76dd1 | ||
|
|
cecef87d13 | ||
|
|
d0ca3a348f | ||
|
|
de4e309f39 | ||
|
|
ea56ac10ce | ||
|
|
90660c6d34 | ||
|
|
fcf04083a5 | ||
|
|
a1ea12a736 | ||
|
|
02a795bb8a | ||
|
|
6bffed8eb5 | ||
|
|
ddd5ceb74a | ||
|
|
3c6cee2f49 | ||
|
|
fc2f0c6f68 | ||
|
|
0dcf208315 | ||
|
|
7730834e83 | ||
|
|
91875ab232 | ||
|
|
d81e6d7813 | ||
|
|
3e299d85e9 | ||
|
|
455e1693f4 | ||
|
|
2a17e62107 | ||
|
|
0a090cf0a7 | ||
|
|
c13f0af435 | ||
|
|
8ee7995ddc | ||
|
|
0e8c749e68 | ||
|
|
ee0450ffb2 | ||
|
|
e0d8be6916 | ||
|
|
7c4d37a428 | ||
|
|
eeaebad903 | ||
|
|
c624e7e607 | ||
|
|
0f724a20cf | ||
|
|
c7922c4a0e | ||
|
|
9d98f72fd3 | ||
|
|
db9a256efa | ||
|
|
aedfdd4b91 | ||
|
|
99d5c51d42 | ||
|
|
086b733288 | ||
|
|
6f9ccaef9e | ||
|
|
dba92f52b5 | ||
|
|
61512fba55 | ||
|
|
542faafe04 | ||
|
|
70468eae8a | ||
|
|
9e0c2027d2 | ||
|
|
6ac4e965bb | ||
|
|
5edddb5890 | ||
|
|
706ac612ff | ||
|
|
444a56e333 | ||
|
|
2e966d717d | ||
|
|
237d856cfe | ||
|
|
d12c076774 | ||
|
|
3e3cc53715 | ||
|
|
ea4d30385a | ||
|
|
1b03871be8 | ||
|
|
42e068aab7 | ||
|
|
280efe4545 | ||
|
|
9b54c138a7 | ||
|
|
c253b7d60f | ||
|
|
367bba2692 | ||
|
|
e888c4e22c | ||
|
|
8a3425b5e3 | ||
|
|
9081bde65e | ||
|
|
4f783678da | ||
|
|
299a60a881 | ||
|
|
7e651219b0 | ||
|
|
e1e7bc6f57 | ||
|
|
beed8434ec | ||
|
|
ab13166c0a | ||
|
|
71a48cde3b | ||
|
|
c603ac0d1d | ||
|
|
316d3d98aa | ||
|
|
0465cf43db | ||
|
|
65de19ddb6 | ||
|
|
9c0f11cc55 | ||
|
|
754ceb3d5a | ||
|
|
dcc5247ba0 | ||
|
|
02de198a8b | ||
|
|
b7cfc4c6e3 | ||
|
|
46a094e711 | ||
|
|
b5cd1b2379 | ||
|
|
cfe94e6aa7 | ||
|
|
3362f25775 | ||
|
|
87f9704831 | ||
|
|
6c7084dfe1 | ||
|
|
164590159d | ||
|
|
b8ab46d26b | ||
|
|
183b4fc41a | ||
|
|
2b7ab34978 | ||
|
|
2ca7132569 | ||
|
|
aac9aa34f8 | ||
|
|
ad8a645462 | ||
|
|
2dbe4d6368 | ||
|
|
d76253ef75 | ||
|
|
46b68f1933 | ||
|
|
8aab27bc3f | ||
|
|
22f118e1b7 | ||
|
|
922eb6dc59 | ||
|
|
20526273d3 | ||
|
|
f3222c646b | ||
|
|
9ae28c5e85 | ||
|
|
e72318adb4 | ||
|
|
2682dc0504 | ||
|
|
8b4611b47a | ||
|
|
25938c60a6 | ||
|
|
9920f0dbb7 | ||
|
|
90f7d89db7 | ||
|
|
6ddbdbbbb2 | ||
|
|
3c8c9fd5ef | ||
|
|
91a9bf82fd | ||
|
|
4603ac77d8 | ||
|
|
998940af1b | ||
|
|
24ed724eab | ||
|
|
33b71628ab | ||
|
|
2dbf1429ba | ||
|
|
9867c1ee9d | ||
|
|
e8a0539984 | ||
|
|
685e8ba32a | ||
|
|
6c9493df7b | ||
|
|
1994438792 | ||
|
|
60be3e354b | ||
|
|
9d0bb5c996 | ||
|
|
304dc5fc36 | ||
|
|
819b6d7452 | ||
|
|
b066c8a5d7 | ||
|
|
80fb60c6fb | ||
|
|
6a2031899d | ||
|
|
aedcc2fd56 | ||
|
|
757eb403ac | ||
|
|
87c2745395 | ||
|
|
0d3d8ef54c | ||
|
|
53d5ee1960 | ||
|
|
92a8dfc55c | ||
|
|
c59f46cc1d | ||
|
|
ef3904a2f6 | ||
|
|
994c5844d9 | ||
|
|
3881e51b08 | ||
|
|
815ae1367a | ||
|
|
84ed47dbdf | ||
|
|
252980724a | ||
|
|
225aab476d | ||
|
|
f065539266 | ||
|
|
f8d81fc3f5 | ||
|
|
43e264cc07 | ||
|
|
957737959f | ||
|
|
000e6511e6 | ||
|
|
a652284bbc | ||
|
|
1b88e5ac06 | ||
|
|
79ba9ec6a1 | ||
|
|
a4acf09d8b | ||
|
|
872ef2e346 | ||
|
|
8795ae0aaa | ||
|
|
23c4190d7e | ||
|
|
75d0109102 | ||
|
|
893bb2d97a | ||
|
|
0c2c8c6c03 | ||
|
|
97a313ca1e | ||
|
|
3fd57e26b8 | ||
|
|
aee6f6d6bb | ||
|
|
00fd6e541a | ||
|
|
6cc1af4ca9 | ||
|
|
d8f2e0977d | ||
|
|
f4e75f5e21 |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 818 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 556 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 414 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 312 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 发布辅助脚本
|
||||
* @author 黄子毅
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
|
||||
const dirs = ['前沿技术', '设计模式', '编译原理', '源码解读', '商业思考', '算法']
|
||||
|
||||
dirs.forEach(dir => {
|
||||
const readDir = fs.readdirSync(`./${dir}`);
|
||||
|
||||
console.log(`### ${dir}\n`)
|
||||
|
||||
readDir.sort((left, right) => left.split('.')[0] - right.split('.')[0]).forEach(dirName=>{
|
||||
console.log(`- <a href="./${dir}/${dirName}">${dirName.replace('.md', '')}</a>`)
|
||||
})
|
||||
|
||||
console.log('')
|
||||
})
|
||||
|
||||
@@ -1,27 +1,241 @@
|
||||
# 前端精读
|
||||
|
||||
<a href="https://travis-ci.org/dt-fe/weekly">
|
||||
<img src="https://travis-ci.org/dt-fe/weekly.svg?branch=v2" alt="CircleCI Status">
|
||||
<a href="https://travis-ci.org/ascoders/weekly">
|
||||
<img src="https://travis-ci.org/ascoders/weekly.svg?branch=v2" alt="CircleCI Status">
|
||||
</a>
|
||||
|
||||
前端界的好文精读,每周更新!
|
||||
|
||||
- [周刊参考池](https://github.com/dt-fe/weekly/issues/2)
|
||||
最新精读:<a href="./算法/201.精读《算法 - 二叉树》.md">201.精读《算法 - 二叉树》</a>
|
||||
|
||||
素材来源:[周刊参考池](https://github.com/ascoders/weekly/issues/2)
|
||||
|
||||
现已涵盖:
|
||||
|
||||
- 结合大厂工作经验解读的 [前沿技术](./前沿技术),[源码解读](./源码解读)。
|
||||
- 逐渐加入一些后端技术解读。
|
||||
- 一些 [商业思考](./商业思考)。
|
||||
- 已完成 [编译原理](./编译原理)、[设计模式](./设计模式) 两大基础模块。
|
||||
|
||||
### 前沿技术
|
||||
|
||||
- <a href="./前沿技术/1.精读《js 模块化发展》.md">1.精读《js 模块化发展》</a>
|
||||
- <a href="./前沿技术/2.精读《模态框的最佳实践》.md">2.精读《模态框的最佳实践》</a>
|
||||
- <a href="./前沿技术/3.精读《前后端渲染之争》.md">3.精读《前后端渲染之争》</a>
|
||||
- <a href="./前沿技术/4.精读《AsyncAwait 优越之处》.md">4.精读《AsyncAwait 优越之处》</a>
|
||||
- <a href="./前沿技术/5.精读《民工叔单页数据流方案》.md">5.精读《民工叔单页数据流方案》</a>
|
||||
- <a href="./前沿技术/6.精读《JavaScript 错误堆栈处理》.md">6.精读《JavaScript 错误堆栈处理》</a>
|
||||
- <a href="./前沿技术/7.精读《请停止 css-in-js 的行为》.md">7.精读《请停止 css-in-js 的行为》</a>
|
||||
- <a href="./前沿技术/8.精读《入坑 React 前没有人会告诉你的事》.md">8.精读《入坑 React 前没有人会告诉你的事》</a>
|
||||
- <a href="./前沿技术/9.精读《Immutable 结构共享》.md">9.精读《Immutable 结构共享》</a>
|
||||
- <a href="./前沿技术/10.精读《Web Components 的困境》.md">10.精读《Web Components 的困境》</a>
|
||||
- <a href="./前沿技术/11.精读《前端调试技巧》.md">11.精读《前端调试技巧》</a>
|
||||
- <a href="./前沿技术/12.精读《React 高阶组件》.md">12.精读《React 高阶组件》</a>
|
||||
- <a href="./前沿技术/13.精读《This 带来的困惑》.md">13.精读《This 带来的困惑》</a>
|
||||
- <a href="./前沿技术/14.精读《架构设计之 DCI》.md">14.精读《架构设计之 DCI》</a>
|
||||
- <a href="./前沿技术/15.精读《TC39 与 ECMAScript 提案》.md">15.精读《TC39 与 ECMAScript 提案》</a>
|
||||
- <a href="./前沿技术/16.精读《CSS Animations vs Web Animations API》.md">16.精读《CSS Animations vs Web Animations API》</a>
|
||||
- <a href="./前沿技术/17.精读《如何安全地使用 React context》.md">17.精读《如何安全地使用 React context》</a>
|
||||
- <a href="./前沿技术/18.精读《设计完美的日期选择器》.md">18.精读《设计完美的日期选择器》</a>
|
||||
- <a href="./前沿技术/19.精读《最佳前端面试题》及面试官技巧.md">19.精读《最佳前端面试题》及面试官技巧</a>
|
||||
- <a href="./前沿技术/20.精读《Nestjs》文档.md">20.精读《Nestjs》文档</a>
|
||||
- <a href="./前沿技术/21.精读《Web fonts: when you need them, when you don’t》.md">21.精读《Web fonts: when you need them, when you don’t》</a>
|
||||
- <a href="./前沿技术/22.精读《V8 引擎特性带来的的 JS 性能变化》.md">22.精读《V8 引擎特性带来的的 JS 性能变化》</a>
|
||||
- <a href="./前沿技术/23.精读《API 设计原则》.md">23.精读《API 设计原则》</a>
|
||||
- <a href="./前沿技术/24.精读《现代 JavaScript 概览》.md">24.精读《现代 JavaScript 概览》</a>
|
||||
- <a href="./前沿技术/25.精读《null >= 0?》.md">25.精读《null >= 0?》</a>
|
||||
- <a href="./前沿技术/26.精读《加密媒体扩展》.md">26.精读《加密媒体扩展》</a>
|
||||
- <a href="./前沿技术/27.精读《css-in-js 杀鸡用牛刀》.md">27.精读《css-in-js 杀鸡用牛刀》</a>
|
||||
- <a href="./前沿技术/28.精读《2017 前端性能优化备忘录》.md">28.精读《2017 前端性能优化备忘录》</a>
|
||||
- <a href="./前沿技术/29.精读《JS 中的内存管理》.md">29.精读《JS 中的内存管理》</a>
|
||||
- <a href="./前沿技术/30.精读《Javascript 事件循环与异步》.md">30.精读《Javascript 事件循环与异步》</a>
|
||||
- <a href="./前沿技术/31.精读《我不再使用高阶组件》.md">31.精读《我不再使用高阶组件》</a>
|
||||
- <a href="./前沿技术/32.精读《React Router4.0 进阶概念》.md">32.精读《React Router4.0 进阶概念》</a>
|
||||
- <a href="./前沿技术/33.精读《30 行 js 代码创建神经网络》.md">33.精读《30 行 js 代码创建神经网络》</a>
|
||||
- <a href="./前沿技术/34.精读《React 代码整洁之道》.md">34.精读《React 代码整洁之道》</a>
|
||||
- <a href="./前沿技术/35.精读《dob - 框架实现》.md">35.精读《dob - 框架实现》</a>
|
||||
- <a href="./前沿技术/36.精读《When You “Git” in Trouble- a Version Control Story》.md">36.精读《When You “Git” in Trouble- a Version Control Story》</a>
|
||||
- <a href="./前沿技术/37.精读《how we position and what we compare》.md">37.精读《how we position and what we compare》</a>
|
||||
- <a href="./前沿技术/38.精读《dob - 框架使用》.md">38.精读《dob - 框架使用》</a>
|
||||
- <a href="./前沿技术/39.精读《全链路体验浏览器挖矿》.md">39.精读《全链路体验浏览器挖矿》</a>
|
||||
- <a href="./前沿技术/40.精读《初探 Reason 与 GraphQL》.md">40.精读《初探 Reason 与 GraphQL》</a>
|
||||
- <a href="./前沿技术/41.精读《Ant Design 3.0 背后的故事》.md">41.精读《Ant Design 3.0 背后的故事》</a>
|
||||
- <a href="./前沿技术/42.精读《前端数据流哲学》.md">42.精读《前端数据流哲学》</a>
|
||||
- <a href="./前沿技术/43.精读《增强现实与可视化》.md">43.精读《增强现实与可视化》</a>
|
||||
- <a href="./前沿技术/44.精读《Rekit Studio》.md">44.精读《Rekit Studio》</a>
|
||||
- <a href="./前沿技术/45.精读《React's new Context API》.md">45.精读《React's new Context API》</a>
|
||||
- <a href="./前沿技术/46.精读《react-rxjs》.md">46.精读《react-rxjs》</a>
|
||||
- <a href="./前沿技术/47.精读《webpack4.0 升级指南》.md">47.精读《webpack4.0 升级指南》</a>
|
||||
- <a href="./前沿技术/49.精读《Compilers are the New Frameworks》.md">49.精读《Compilers are the New Frameworks》</a>
|
||||
- <a href="./前沿技术/50.精读《快速上手构建 ARKit 应用》.md">50.精读《快速上手构建 ARKit 应用》</a>
|
||||
- <a href="./前沿技术/51.精读《Elements of Web Dev》.md">51.精读《Elements of Web Dev》</a>
|
||||
- <a href="./前沿技术/52.精读《图解 ES 模块》.md">52.精读《图解 ES 模块》</a>
|
||||
- <a href="./前沿技术/53.精读《插件化思维》.md">53.精读《插件化思维》</a>
|
||||
- <a href="./前沿技术/54.精读《在浏览器运行 serverRender》.md">54.精读《在浏览器运行 serverRender》</a>
|
||||
- <a href="./前沿技术/55.精读《async await 是把双刃剑》.md">55.精读《async await 是把双刃剑》</a>
|
||||
- <a href="./前沿技术/56.精读《重新思考 Redux》.md">56.精读《重新思考 Redux》</a>
|
||||
- <a href="./前沿技术/57.精读《现代 js 框架存在的根本原因》.md">57.精读《现代 js 框架存在的根本原因》</a>
|
||||
- <a href="./前沿技术/58.精读《Typescript2.0 - 2.9》.md">58.精读《Typescript2.0 - 2.9》</a>
|
||||
- <a href="./前沿技术/59.精读《如何利用 Nodejs 监听文件夹》.md">59.精读《如何利用 Nodejs 监听文件夹》</a>
|
||||
- <a href="./前沿技术/60.精读《如何在 nodejs 使用环境变量》.md">60.精读《如何在 nodejs 使用环境变量》</a>
|
||||
- <a href="./前沿技术/61.精读《React 八种条件渲染》.md">61.精读《React 八种条件渲染》</a>
|
||||
- <a href="./前沿技术/62.精读《JS 引擎基础之 Shapes and Inline Caches》.md">62.精读《JS 引擎基础之 Shapes and Inline Caches》</a>
|
||||
- <a href="./前沿技术/63.精读《React 的多态性》.md">63.精读《React 的多态性》</a>
|
||||
- <a href="./前沿技术/68.精读《衡量用户体验》.md">68.精读《衡量用户体验》</a>
|
||||
- <a href="./前沿技术/69.精读《SQL vs Flux》.md">69.精读《SQL vs Flux》</a>
|
||||
- <a href="./前沿技术/72.精读《REST, GraphQL, Webhooks, & gRPC 如何选型》.md">72.精读《REST, GraphQL, Webhooks, & gRPC 如何选型》</a>
|
||||
- <a href="./前沿技术/74.精读《12 个评估 JS 库你需要关心的事》.md">74.精读《12 个评估 JS 库你需要关心的事》</a>
|
||||
- <a href="./前沿技术/76.精读《谈谈 Web Workers》.md">76.精读《谈谈 Web Workers》</a>
|
||||
- <a href="./前沿技术/77.精读《用 Reduce 实现 Promise 串行执行》.md">77.精读《用 Reduce 实现 Promise 串行执行》</a>
|
||||
- <a href="./前沿技术/79.精读《React Hooks》.md">79.精读《React Hooks》</a>
|
||||
- <a href="./前沿技术/80.精读《怎么用 React Hooks 造轮子》.md">80.精读《怎么用 React Hooks 造轮子》</a>
|
||||
- <a href="./前沿技术/81.精读《使用 CSS 属性选择器》.md">81.精读《使用 CSS 属性选择器》</a>
|
||||
- <a href="./前沿技术/83.精读《React16 新特性》.md">83.精读《React16 新特性》</a>
|
||||
- <a href="./前沿技术/84.精读《Typescript 3.2 新特性》.md">84.精读《Typescript 3.2 新特性》</a>
|
||||
- <a href="./前沿技术/86.精读《国际化布局 - Logical Properties》.md">86.精读《国际化布局 - Logical Properties》</a>
|
||||
- <a href="./前沿技术/87.精读《setState 做了什么》.md">87.精读《setState 做了什么》</a>
|
||||
- <a href="./前沿技术/88.精读《Caches API》.md">88.精读《Caches API》</a>
|
||||
- <a href="./前沿技术/89.精读《如何编译前端项目与组件》.md">89.精读《如何编译前端项目与组件》</a>
|
||||
- <a href="./前沿技术/91.精读《正则 ES2018》.md">91.精读《正则 ES2018》</a>
|
||||
- <a href="./前沿技术/94.精读《Serverless 给前端带来了什么》.md">94.精读《Serverless 给前端带来了什么》</a>
|
||||
- <a href="./前沿技术/95.精读《Function VS Class 组件》.md">95.精读《Function VS Class 组件》</a>
|
||||
- <a href="./前沿技术/96.精读《useEffect 完全指南》.md">96.精读《useEffect 完全指南》</a>
|
||||
- <a href="./前沿技术/97.精读《编写有弹性的组件》.md">97.精读《编写有弹性的组件》</a>
|
||||
- <a href="./前沿技术/99.精读《Scheduling in React》.md">99.精读《Scheduling in React》</a>
|
||||
- <a href="./前沿技术/100.精读《V8 引擎 Lazy Parsing》.md">100.精读《V8 引擎 Lazy Parsing》</a>
|
||||
- <a href="./前沿技术/101.精读《持续集成 vs 持续交付 vs 持续部署》.md">101.精读《持续集成 vs 持续交付 vs 持续部署》</a>
|
||||
- <a href="./前沿技术/102.精读《Monorepo 的优势》.md">102.精读《Monorepo 的优势》</a>
|
||||
- <a href="./前沿技术/104.精读《Function Component 入门》.md">104.精读《Function Component 入门》</a>
|
||||
- <a href="./前沿技术/105.精读《What's new in javascript》.md">105.精读《What's new in javascript》</a>
|
||||
- <a href="./前沿技术/107.精读《Optional chaining》.md">107.精读《Optional chaining》</a>
|
||||
- <a href="./前沿技术/109.精读《Vue3.0 Function API》.md">109.精读《Vue3.0 Function API》</a>
|
||||
- <a href="./前沿技术/111.精读《前端未来展望》.md">111.精读《前端未来展望》</a>
|
||||
- <a href="./前沿技术/112.精读《源码学习》.md">112.精读《源码学习》</a>
|
||||
- <a href="./前沿技术/113.精读《Nodejs V12》.md">113.精读《Nodejs V12》</a>
|
||||
- <a href="./前沿技术/117.精读《Tableau 探索式模型》.md">117.精读《Tableau 探索式模型》</a>
|
||||
- <a href="./前沿技术/118.精读《使用 css 变量生成颜色主题》.md">118.精读《使用 css 变量生成颜色主题》</a>
|
||||
- <a href="./前沿技术/119.精读《前端深水区》.md">119.精读《前端深水区》</a>
|
||||
- <a href="./前沿技术/120.精读《React Hooks 最佳实践》.md">120.精读《React Hooks 最佳实践》</a>
|
||||
- <a href="./前沿技术/121.精读《前端与 BI》.md">121.精读《前端与 BI》</a>
|
||||
- <a href="./前沿技术/123.精读《用 Babel 创造自定义 JS 语法》.md">123.精读《用 Babel 创造自定义 JS 语法》</a>
|
||||
- <a href="./前沿技术/124.精读《用 css grid 重新思考布局》.md">124.精读《用 css grid 重新思考布局》</a>
|
||||
- <a href="./前沿技术/125.精读《深度学习 - 函数式之美》.md">125.精读《深度学习 - 函数式之美》</a>
|
||||
- <a href="./前沿技术/126.精读《Nuxtjs》.md">126.精读《Nuxtjs》</a>
|
||||
- <a href="./前沿技术/127.精读《React Conf 2019 - Day1》.md">127.精读《React Conf 2019 - Day1》</a>
|
||||
- <a href="./前沿技术/129.精读《React Conf 2019 - Day2》.md">129.精读《React Conf 2019 - Day2》</a>
|
||||
- <a href="./前沿技术/132.精读《正交的 React 组件》.md">132.精读《正交的 React 组件》</a>
|
||||
- <a href="./前沿技术/133.精读《寻找框架设计的平衡点》.md">133.精读《寻找框架设计的平衡点》</a>
|
||||
- <a href="./前沿技术/134.精读《我在阿里数据中台大前端》.md">134.精读《我在阿里数据中台大前端》</a>
|
||||
- <a href="./前沿技术/138.精读《精通 console.log》.md">138.精读《精通 console.log》</a>
|
||||
- <a href="./前沿技术/139.精读《手写 JSON Parser》.md">139.精读《手写 JSON Parser》</a>
|
||||
- <a href="./前沿技术/140.精读《结合 React 使用原生 Drag Drop API》.md">140.精读《结合 React 使用原生 Drag Drop API》</a>
|
||||
- <a href="./前沿技术/141.精读《useRef 与 createRef 的区别》.md">141.精读《useRef 与 createRef 的区别》</a>
|
||||
- <a href="./前沿技术/142.精读《如何做好 CodeReview》.md">142.精读《如何做好 CodeReview》</a>
|
||||
- <a href="./前沿技术/143.精读《Suspense 改变开发方式》.md">143.精读《Suspense 改变开发方式》</a>
|
||||
- <a href="./前沿技术/144.精读《Webpack5 新特性 - 模块联邦》.md">144.精读《Webpack5 新特性 - 模块联邦》</a>
|
||||
- <a href="./前沿技术/145.精读《React Router v6》.md">145.精读《React Router v6》</a>
|
||||
- <a href="./前沿技术/146.精读《React Hooks 数据流》.md">146.精读《React Hooks 数据流》</a>
|
||||
- <a href="./前沿技术/147. 精读《@types react 值得注意的 TS 技巧》.md">147. 精读《@types react 值得注意的 TS 技巧》</a>
|
||||
- <a href="./前沿技术/148. 精读《React Error Boundaries》.md">148. 精读《React Error Boundaries》</a>
|
||||
- <a href="./前沿技术/149. 精读《React 性能调试》.md">149. 精读《React 性能调试》</a>
|
||||
- <a href="./前沿技术/150. 精读《Deno 1.0 你需要了解的》.md">150. 精读《Deno 1.0 你需要了解的》</a>
|
||||
- <a href="./前沿技术/152. 精读《recoil》.md">152. 精读《recoil》</a>
|
||||
- <a href="./前沿技术/153. 精读《snowpack》.md">153. 精读《snowpack》</a>
|
||||
- <a href="./前沿技术/154. 精读《用 React 做按需渲染》.md">154. 精读《用 React 做按需渲染》</a>
|
||||
- <a href="./前沿技术/157. 精读《如何比较 Object 对象》.md">157. 精读《如何比较 Object 对象》</a>
|
||||
- <a href="./前沿技术/158. 精读《Typescript 4》.md">158. 精读《Typescript 4》</a>
|
||||
- <a href="./前沿技术/159. 精读《对低代码搭建的理解》.md">159. 精读《对低代码搭建的理解》</a>
|
||||
- <a href="./前沿技术/160. 精读《函数缓存》.md">160. 精读《函数缓存》</a>
|
||||
- <a href="./前沿技术/161.精读《可视化搭建思考 - 富文本搭建》.md">161.精读《可视化搭建思考 - 富文本搭建》</a>
|
||||
- <a href="./前沿技术/162.精读《Tasks, microtasks, queues and schedules》.md">162.精读《Tasks, microtasks, queues and schedules》</a>
|
||||
- <a href="./前沿技术/163.精读《Spring 概念》.md">163.精读《Spring 概念》</a>
|
||||
- <a href="./前沿技术/164.精读《数据搭建引擎 bi-designer API-设计器》.md">164.精读《数据搭建引擎 bi-designer API-设计器》</a>
|
||||
- <a href="./前沿技术/165.精读《数据搭建引擎 bi-designer API-组件》.md">165.精读《数据搭建引擎 bi-designer API-组件》</a>
|
||||
- <a href="./前沿技术/166.精读《BI 搭建 - 筛选条件》.md">166.精读《BI 搭建 - 筛选条件》</a>
|
||||
- <a href="./前沿技术/190.精读《DOM diff 原理详解》.md">190.精读《DOM diff 原理详解》</a>
|
||||
- <a href="./前沿技术/191.精读《高性能表格》.md">191.精读《高性能表格》</a>
|
||||
- <a href="./前沿技术/192.精读《DOM diff 最长上升子序列》.md">192.精读《DOM diff 最长上升子序列》</a>
|
||||
- <a href="./前沿技术/193.精读《React Server Component》.md">193.精读《React Server Component》</a>
|
||||
- <a href="./前沿技术/194.精读《算法基础数据结构》.md">194.精读《算法基础数据结构》</a>
|
||||
- <a href="./前沿技术/195.精读《新一代前端构建工具对比》.md">195.精读《新一代前端构建工具对比》</a>
|
||||
- <a href="./前沿技术/196.精读《前端职业规划 - 2021 年》.md">196.精读《前端职业规划 - 2021 年》</a>
|
||||
- <a href="./前沿技术/197.精读《低代码逻辑编排》.md">197.精读《低代码逻辑编排》</a>
|
||||
|
||||
### 设计模式
|
||||
|
||||
- <a href="./设计模式/167.精读《设计模式 - Abstract Factory 抽象工厂》.md">167.精读《设计模式 - Abstract Factory 抽象工厂》</a>
|
||||
- <a href="./设计模式/168.精读《设计模式 - Builder 生成器》.md">168.精读《设计模式 - Builder 生成器》</a>
|
||||
- <a href="./设计模式/169.精读《设计模式 - Factory Method 工厂方法》.md">169.精读《设计模式 - Factory Method 工厂方法》</a>
|
||||
- <a href="./设计模式/170.精读《设计模式 - Prototype 原型模式》.md">170.精读《设计模式 - Prototype 原型模式》</a>
|
||||
- <a href="./设计模式/171.精读《设计模式 - Singleton 单例模式》.md">171.精读《设计模式 - Singleton 单例模式》</a>
|
||||
- <a href="./设计模式/172.精读《设计模式 - Adapter 适配器模式》.md">172.精读《设计模式 - Adapter 适配器模式》</a>
|
||||
- <a href="./设计模式/173.精读《设计模式 - Bridge 桥接模式》.md">173.精读《设计模式 - Bridge 桥接模式》</a>
|
||||
- <a href="./设计模式/174.精读《设计模式 - Composite 组合模式》.md">174.精读《设计模式 - Composite 组合模式》</a>
|
||||
- <a href="./设计模式/175.精读《设计模式 - Decorator 装饰器模式》.md">175.精读《设计模式 - Decorator 装饰器模式》</a>
|
||||
- <a href="./设计模式/176.精读《设计模式 - Facade 外观模式》.md">176.精读《设计模式 - Facade 外观模式》</a>
|
||||
- <a href="./设计模式/177.精读《设计模式 - Flyweight 享元模式》.md">177.精读《设计模式 - Flyweight 享元模式》</a>
|
||||
- <a href="./设计模式/178.精读《设计模式 - Proxy 代理模式》.md">178.精读《设计模式 - Proxy 代理模式》</a>
|
||||
- <a href="./设计模式/179.精读《设计模式 - Chain of Responsibility 职责链模式》.md">179.精读《设计模式 - Chain of Responsibility 职责链模式》</a>
|
||||
- <a href="./设计模式/180.精读《设计模式 - Command 命令模式》.md">180.精读《设计模式 - Command 命令模式》</a>
|
||||
- <a href="./设计模式/181.精读《设计模式 - Interpreter 解释器模式》.md">181.精读《设计模式 - Interpreter 解释器模式》</a>
|
||||
- <a href="./设计模式/182.精读《设计模式 - Iterator 迭代器模式》.md">182.精读《设计模式 - Iterator 迭代器模式》</a>
|
||||
- <a href="./设计模式/183.精读《设计模式 - Mediator 中介者模式》.md">183.精读《设计模式 - Mediator 中介者模式》</a>
|
||||
- <a href="./设计模式/184.精读《设计模式 - Memoto 备忘录模式》.md">184.精读《设计模式 - Memoto 备忘录模式》</a>
|
||||
- <a href="./设计模式/185.精读《设计模式 - Observer 观察者模式》.md">185.精读《设计模式 - Observer 观察者模式》</a>
|
||||
- <a href="./设计模式/186.精读《设计模式 - State 状态模式》.md">186.精读《设计模式 - State 状态模式》</a>
|
||||
- <a href="./设计模式/187.精读《设计模式 - Strategy 策略模式》.md">187.精读《设计模式 - Strategy 策略模式》</a>
|
||||
- <a href="./设计模式/188.精读《设计模式 - Template Method 模版模式》.md">188.精读《设计模式 - Template Method 模版模式》</a>
|
||||
- <a href="./设计模式/189.精读《设计模式 - Visitor 访问者模式》.md">189.精读《设计模式 - Visitor 访问者模式》</a>
|
||||
|
||||
### 编译原理
|
||||
|
||||
- <a href="./编译原理/64.精读《手写 SQL 编译器 - 词法分析》.md">64.精读《手写 SQL 编译器 - 词法分析》</a>
|
||||
- <a href="./编译原理/65.精读《手写 SQL 编译器 - 文法介绍》.md">65.精读《手写 SQL 编译器 - 文法介绍》</a>
|
||||
- <a href="./编译原理/66.精读《手写 SQL 编译器 - 语法分析》.md">66.精读《手写 SQL 编译器 - 语法分析》</a>
|
||||
- <a href="./编译原理/67.精读《手写 SQL 编译器 - 回溯》.md">67.精读《手写 SQL 编译器 - 回溯》</a>
|
||||
- <a href="./编译原理/70.精读《手写 SQL 编译器 - 语法树》.md">70.精读《手写 SQL 编译器 - 语法树》</a>
|
||||
- <a href="./编译原理/71.精读《手写 SQL 编译器 - 错误提示》.md">71.精读《手写 SQL 编译器 - 错误提示》</a>
|
||||
- <a href="./编译原理/78.精读《手写 SQL 编译器 - 性能优化之缓存》.md">78.精读《手写 SQL 编译器 - 性能优化之缓存》</a>
|
||||
- <a href="./编译原理/85.精读《手写 SQL 编译器 - 智能提示》.md">85.精读《手写 SQL 编译器 - 智能提示》</a>
|
||||
|
||||
### 源码解读
|
||||
|
||||
- <a href="./源码解读/48.精读《Immer.js》源码.md">48.精读《Immer.js》源码</a>
|
||||
- <a href="./源码解读/73.精读《sqorn 源码》.md">73.精读《sqorn 源码》</a>
|
||||
- <a href="./源码解读/75.精读《Epitath 源码 - renderProps 新用法》.md">75.精读《Epitath 源码 - renderProps 新用法》</a>
|
||||
- <a href="./源码解读/82.精读《Htm - Hyperscript 源码》.md">82.精读《Htm - Hyperscript 源码》</a>
|
||||
- <a href="./源码解读/92.精读《React PowerPlug 源码》.md">92.精读《React PowerPlug 源码》</a>
|
||||
- <a href="./源码解读/93.精读《syntax-parser 源码》.md">93.精读《syntax-parser 源码》</a>
|
||||
- <a href="./源码解读/98.精读《react-easy-state 源码》.md">98.精读《react-easy-state 源码》</a>
|
||||
- <a href="./源码解读/110.精读《Inject Instance 源码》.md">110.精读《Inject Instance 源码》</a>
|
||||
- <a href="./源码解读/122.精读《robot 源码 - 有限状态机》.md">122.精读《robot 源码 - 有限状态机》</a>
|
||||
- <a href="./源码解读/128.精读《Hooks 取数 - swr 源码》.md">128.精读《Hooks 取数 - swr 源码》</a>
|
||||
- <a href="./源码解读/130.精读《unstated 与 unstated-next 源码》.md">130.精读《unstated 与 unstated-next 源码》</a>
|
||||
- <a href="./源码解读/151. 精读《@umijs use-request》源码.md">151. 精读《@umijs use-request》源码</a>
|
||||
- <a href="./源码解读/155. 精读《use-what-changed 源码》.md">155. 精读《use-what-changed 源码》</a>
|
||||
- <a href="./源码解读/156. 精读《react-intersection-observer 源码》.md">156. 精读《react-intersection-observer 源码》</a>
|
||||
|
||||
### 商业思考
|
||||
|
||||
- <a href="./商业思考/90.精读《极客公园 2019》.md">90.精读《极客公园 2019》</a>
|
||||
- <a href="./商业思考/103.精读《为什么专家不再关心技术细节》.md">103.精读《为什么专家不再关心技术细节》</a>
|
||||
- <a href="./商业思考/106.精读《数据之上·智慧之光 - 2018》.md">106.精读《数据之上·智慧之光 - 2018》</a>
|
||||
- <a href="./商业思考/108.精读《智能商业》.md">108.精读《智能商业》</a>
|
||||
- <a href="./商业思考/114.精读《谁在世界中心》.md">114.精读《谁在世界中心》</a>
|
||||
- <a href="./商业思考/115.精读《Tableau 入门》.md">115.精读《Tableau 入门》</a>
|
||||
- <a href="./商业思考/116.精读《刷新》.md">116.精读《刷新》</a>
|
||||
- <a href="./商业思考/131.精读《从 0 到 1》.md">131.精读《从 0 到 1》</a>
|
||||
- <a href="./商业思考/135.精读《极客公园 IFX - 上》.md">135.精读《极客公园 IFX - 上》</a>
|
||||
- <a href="./商业思考/136.精读《极客公园 IFX - 下》.md">136.精读《极客公园 IFX - 下》</a>
|
||||
- <a href="./商业思考/137.精读《当我在分享的时候,我在做什么?》.md">137.精读《当我在分享的时候,我在做什么?》</a>
|
||||
|
||||
### 算法
|
||||
|
||||
- <a href="./算法/198.精读《算法 - 动态规划》.md">198.精读《算法 - 动态规划》</a>
|
||||
- <a href="./算法/199.精读《算法 - 滑动窗口》.md">199.精读《算法 - 滑动窗口》</a>
|
||||
- <a href="./算法/200.精读《算法 - 回溯》.md">200.精读《算法 - 回溯》</a>
|
||||
- <a href="./算法/201.精读《算法 - 二叉树》.md">201.精读《算法 - 二叉树》</a>
|
||||
|
||||
## 关注前端精读微信公众号
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
## Special Sponsors
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://e.coding.net/?utm_source=weekly" target="_blank">
|
||||
<img width="300" src="https://img.alicdn.com/tfs/TB107D.QbrpK1RjSZTEXXcWAVXa-1000-332.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
# 1 引言
|
||||
|
||||
<img src="assets/1/cube.jpeg" alt="logo" width="500" />
|
||||
<img src="https://img.alicdn.com/imgextra/i4/O1CN01mvDKCM1owPSsLDBmI_!!6000000005289-2-tps-475-297.png" alt="logo" width="500" />
|
||||
|
||||
> 如今,Javascript 模块化规范非常方便、自然,但这个新规范仅执行了 2 年,就在 4 年前,js 的模块化还停留在运行时支持,10 年前,通过后端模版定义、注释定义模块依赖。对经历过来的人来说,历史的模块化方式还停留在脑海中,反而新上手的同学会更快接受现代的模块化规范。
|
||||
|
||||
@@ -103,7 +103,7 @@ YUI3 的 sandbox 像极了差不多同时出现的 AMD 规范,但早期 yahoo
|
||||
|
||||
> 看到大家基本都提到了 HTTP/2,对这项技术解决前端模块化及资源打包等工程问题抱有非常大的期待。很多人也认为 HTTP/2 普及后,基本就没有 Webpack 什么事情了。
|
||||
|
||||
不过 Webpack 作者 @sokra 在他的文章 [webpack & HTTP/2](https://medium.com/webpack/webpack-http-2-7083ec3f3ce6#.zdo4juvgo) 里提到了一个新的 Webpack 插件 `AggressiveSplittingPlugin`。简单的说,这款插件就是为了充分利用 HTTP/2 的文件缓存能力,将你的业务代码自动拆分成若干个数十 KB 的小文件。后续若其中任意一个文件发生变化,可以保证其他的小 chunck 不需要重新下载。
|
||||
不过 Webpack 作者 @sokra 在他的文章 [webpack & HTTP/2](https://medium.com/webpack/webpack-http-2-7083ec3f3ce6#.zdo4juvgo) 里提到了一个新的 Webpack 插件 `AggressiveSplittingPlugin`。简单的说,这款插件就是为了充分利用 HTTP/2 的文件缓存能力,将你的业务代码自动拆分成若干个数十 KB 的小文件。后续若其中任意一个文件发生变化,可以保证其他的小 chunk 不需要重新下载。
|
||||
|
||||
可见,**即使不断的有新技术出现,也依然需要配套的工具来将前端工程问题解决方案推向极致。**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
# 1 引言
|
||||
|
||||
<img src="assets/11/coffee.jpg" width="500" alt="logo" />
|
||||
<img src="https://img.alicdn.com/imgextra/i2/O1CN01JC1TZ51Nxn24teojP_!!6000000001637-2-tps-1024-1296.png" width="500" alt="logo" />
|
||||
|
||||
梵高这幅画远景漆黑一片,近景的咖啡店色彩却反差很大,他只是望着黑夜中温暖的咖啡馆,交织着矛盾与孤独。代码不可能没有 BUG,调试与开发也始终交织在一起,我们在这两种矛盾中不断成长。
|
||||
|
||||
@@ -105,7 +105,7 @@ Css 不像 Js 一样方便分析规则是否存在冗余,Chrome 帮我们做
|
||||
|
||||
Chrome 会记录最后插入的 5 个元素,分别以 `$0` ~ `$4` 的方式在控制台直接输出。
|
||||
|
||||
<img src="assets/11/last-item.png" width="500" alt="last-items" />
|
||||
<img src="https://img.alicdn.com/imgextra/i4/O1CN01xsKHb822gkXhDDcCA_!!6000000007150-2-tps-417-516.png" width="500" alt="last-items" />
|
||||
|
||||
### Console.table
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
## 概述
|
||||
|
||||
原文对于深水区的想法,讲的很清楚,还是建议读者去读一下原文。
|
||||
对比 2010 年,整个前端生态已经翻新了好几遍,直到近几年的 Node BFF、IDE Cloud,抑或是客户端 AI,还是 Serverless 的建设,,前端想要深度参与的话,单纯依靠原来的 HTML/CSS/JS 三件套技能也远远不够了。再抛开技术,整个互联网创业生态也重构了好几遍。无论是技术层面还是意识层面,如今的前端开发已经进入深水区。
|
||||
对比 2010 年,整个前端生态已经翻新了好几遍,直到近几年的 Node BFF、IDE Cloud,抑或是客户端 AI,还是 Serverless 的建设,前端想要深度参与的话,单纯依靠原来的 HTML/CSS/JS 三件套技能也远远不够了。再抛开技术,整个互联网创业生态也重构了好几遍。无论是技术层面还是意识层面,如今的前端开发已经进入深水区。
|
||||
|
||||
- 深水区需要哪些技能
|
||||

|
||||
@@ -1,6 +1,6 @@
|
||||
# 1 引言
|
||||
|
||||
<img src="assets/13/logo.jpeg" width="500" alt="logo" />
|
||||
<img src="https://img.alicdn.com/imgextra/i2/O1CN014VGV7a1x3ILYqK9OD_!!6000000006387-2-tps-1024-732.png" width="500" alt="logo" />
|
||||
|
||||
javascript 的 this 是个头痛的话题,本期精读的文章更是引出了一个观点,避免使用 this。我们来看看是否有道理。
|
||||
|
||||
@@ -53,7 +53,7 @@ getName(person3) // Name: Sarah Doe
|
||||
getGreetingCallback(person3)('Jeff') // Hello Jeff, I'm Sarah Doe
|
||||
```
|
||||
|
||||
<img src="assets/13/1.png" width="500" alt="demo1" />
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN017Kw37u1oOyYHXGlqC_!!6000000005216-2-tps-1338-338.png" width="500" alt="demo1" />
|
||||
|
||||
这样 person 实例是个纯对象,没有将方法挂载到原型链上,简单易懂。
|
||||
|
||||
@@ -179,8 +179,8 @@ const dragProps = {
|
||||
|
||||
```jsx
|
||||
const dropProps = {
|
||||
onDropOver: ev => {
|
||||
// 做一些样式处理,提示用户此时松手会将元素防止在何处
|
||||
onDragOver: ev => {
|
||||
// 做一些样式处理,提示用户此时松手会将元素放置在何处
|
||||
},
|
||||
onDrop: ev => {
|
||||
ev.stopPropagation()
|
||||
@@ -0,0 +1,132 @@
|
||||
## 1 引言
|
||||
|
||||
先说结论:Webpack5 模块联邦让 Webpack 达到了线上 Runtime 的效果,让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 Npm 包、构建再发布了!
|
||||
|
||||
我们知道 Webpack 可以通过 DLL 或者 Externals 做代码共享时 Common Chunk,但不同应用和项目间这个任务就变得困难了,我们几乎无法在项目之间做到按需热插拔。
|
||||
|
||||
模块联邦是 Webpack5 新内置的一个重要功能,可以让跨应用间真正做到模块共享,所以这周让我们通过 [webpack-5-module-federation-a-game-changer-in-javascript-architecture](https://indepth.dev/webpack-5-module-federation-a-game-changer-in-javascript-architecture/#its-important-to-note-these-are-special-entry-points-they-are-only-a-few-kb-in-size-containing-a-special-webpack-runtime-that-can-interface-with-the-host-it-is-not-a-standard-entry-point--7/) 这篇文章了解什么是 “模块联邦” 功能。
|
||||
|
||||
## 2 概述 & 精读
|
||||
|
||||
### NPM 方式共享模块
|
||||
|
||||
想象一下正常的共享模块方式,对,就是 NPM。
|
||||
|
||||
如下图所示,正常的代码共享需要将依赖作为 Lib 安装到项目,进行 Webpack 打包构建再上线,如下图:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1MoLPy.z1gK0jSZLeXXb9kVXa-2494-1478.png">
|
||||
|
||||
对于项目 Home 与 Search,需要共享一个模块时,最常见的办法就是将其抽成通用依赖并分别安装在各自项目中。
|
||||
|
||||
虽然 Monorepo 可以一定程度解决重复安装和修改困难的问题,但依然需要走本地编译。
|
||||
|
||||
### UMD 方式共享模块
|
||||
|
||||
真正 Runtime 的方式可能是 UMD 方式共享代码模块,即将模块用 Webpack UMD 模式打包,并输出到其他项目中。这是非常普遍的模块共享方式:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1rQnSy4n1gK0jSZKPXXXvUXXa-2404-1484.png">
|
||||
|
||||
对于项目 Home 与 Search,直接利用 UMD 包复用一个模块。但这种技术方案问题也很明显,就是包体积无法达到本地编译时的优化效果,且库之间容易冲突。
|
||||
|
||||
### 微前端方式共享模块
|
||||
|
||||
微前端:micro-frontends (MFE) 也是最近比较火的模块共享管理方式,微前端就是要解决多项目并存问题,多项目并存的最大问题就是模块共享,不能有冲突。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1vqvTy1T2gK0jSZFvXXXnFXXa-2410-1520.png">
|
||||
|
||||
由于微前端还要考虑样式冲突、生命周期管理,所以本文只聚焦在资源加载方式上。微前端一般有两种打包方式:
|
||||
|
||||
1. 子应用独立打包,模块更解耦,但无法抽取公共依赖等。
|
||||
2. 整体应用一起打包,很好解决上面的问题,但打包速度实在是太慢了,不具备水平扩展能力。
|
||||
|
||||
### 模块联邦方式
|
||||
|
||||
终于提到本文的主角了,作为 Webpack5 内置核心特性之一的 Federated Module:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1qLz1yYj1gK0jSZFuXXcrHpXa-2414-1474.png">
|
||||
|
||||
从图中可以看到,这个方案是直接将一个应用的包应用于另一个应用,同时具备整体应用一起打包的公共依赖抽取能力。
|
||||
|
||||
让应用具备模块化输出能力,其实开辟了一种新的应用形态,即 “中心应用”,这个中心应用用于在线动态分发 Runtime 子模块,并不直接提供给用户使用:
|
||||
|
||||
<img width=300 src="https://img.alicdn.com/tfs/TB1ymbWy7Y2gK0jSZFgXXc5OFXa-1346-1442.png">
|
||||
|
||||
对微前端而言,这张图就是一个完美的主应用,因为所有子应用都可以利用 Runtime 方式复用主应用的 Npm 包和模块,更好的集成到主应用中。
|
||||
|
||||
模块联邦的使用方式如下:
|
||||
|
||||
```js
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
|
||||
|
||||
module.exports = {
|
||||
// other webpack configs...
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({
|
||||
name: "app_one_remote",
|
||||
remotes: {
|
||||
app_two: "app_two_remote",
|
||||
app_three: "app_three_remote"
|
||||
},
|
||||
exposes: {
|
||||
AppContainer: "./src/App"
|
||||
},
|
||||
shared: ["react", "react-dom", "react-router-dom"]
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./public/index.html",
|
||||
chunks: ["main"]
|
||||
})
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
模块联邦本身是一个普通的 Webpack 插件 `ModuleFederationPlugin`,插件有几个重要参数:
|
||||
|
||||
1. `name` 当前应用名称,需要全局唯一。
|
||||
2. `remotes` 可以将其他项目的 `name` 映射到当前项目中。
|
||||
3. `exposes` 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用。
|
||||
4. `shared` 是非常重要的参数,制定了这个参数,可以让远程加载的模块对应依赖改为使用本地项目的 React 或 ReactDOM。
|
||||
|
||||
比如设置了 `remotes: { app_two: "app_two_remote" }`,在代码中就可以直接利用以下方式直接从对方应用调用模块:
|
||||
|
||||
```js
|
||||
import { Search } from "app_two/Search";
|
||||
```
|
||||
|
||||
这个 `app_two/Search` 来自于 `app_two` 的配置:
|
||||
|
||||
```js
|
||||
// app_two 的 webpack 配置
|
||||
export default {
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({
|
||||
name: "app_two",
|
||||
library: { type: "var", name: "app_two" },
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
Search: "./src/Search"
|
||||
},
|
||||
shared: ["react", "react-dom"]
|
||||
})
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
正是因为 `Search` 在 `exposes` 被导出,我们因此可以使用 `[name]/[exposes_name]` 这个模块,这个模块对于被引用应用来说是一个本地模块。
|
||||
|
||||
## 3 总结
|
||||
|
||||
模块联邦为更大型的前端应用提供了开箱解决方案,并已经作为 Webpack5 官方模块内置,可以说是继 Externals 后最终的运行时代码复用解决方案。
|
||||
|
||||
另外 Webpack5 还内置了大量编译时缓存功能,可以看到,无论是性能还是多项目组织,Webpack5 都在尝试给出自己的最佳思路,期待 Webpack5 正式发布,前端工程化会迈向一个新的阶段。
|
||||
|
||||
> 讨论地址是:[精读《Webpack5 新特性 - 模块联邦》 · Issue #239 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/239)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,384 @@
|
||||
## 1 引言
|
||||
|
||||
[React Router v6](https://github.com/ReactTraining/react-router) alpha 版本发布了,本周通过 [A Sneak Peek at React Router v6](https://alligator.io/react/react-router-v6/) 这篇文章分析一下带来的改变。
|
||||
|
||||
## 2 概述
|
||||
|
||||
### <Switch> 更名为 <Routes>
|
||||
|
||||
一个不痛不痒的改动,使 API 命名更加规范。
|
||||
|
||||
```jsx
|
||||
// v5
|
||||
import { BrowserRouter, Switch, Route } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Home />
|
||||
</Route>
|
||||
<Route path="/profile">
|
||||
<Profile />
|
||||
</Route>
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
在 React Router v6 版本里,直接使用 `Routes` 替代 `Switch`:
|
||||
|
||||
```jsx
|
||||
// v6
|
||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="profile/*" element={<Profile />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### <Route> 升级
|
||||
|
||||
在 v5 版本里,想要给组件传参数是不太直观的,需要利用 RenderProps 的方式透传 `routeProps`:
|
||||
|
||||
```jsx
|
||||
import Profile from './Profile';
|
||||
|
||||
// v5
|
||||
<Route path=":userId" component={Profile} />
|
||||
<Route
|
||||
path=":userId"
|
||||
render={routeProps => (
|
||||
<Profile {...routeProps} animate={true} />
|
||||
)}
|
||||
/>
|
||||
|
||||
// v6
|
||||
<Route path=":userId" element={<Profile />} />
|
||||
<Route path=":userId" element={<Profile animate={true} />} />
|
||||
```
|
||||
|
||||
而在 v6 版本中,`render` 与 `component` 方案合并成了 `element` 方案,可以轻松传递 props 且不需要透传 `roteProps` 参数。
|
||||
|
||||
### 更方便的嵌套路由
|
||||
|
||||
在 v5 版本中,嵌套路由需要通过 `useRouteMatch` 拿到 `match`,并通过 `match.path` 的拼接实现子路由:
|
||||
|
||||
```jsx
|
||||
// v5
|
||||
import {
|
||||
BrowserRouter,
|
||||
Switch,
|
||||
Route,
|
||||
Link,
|
||||
useRouteMatch
|
||||
} from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="/profile" component={Profile} />
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
function Profile() {
|
||||
let match = useRouteMatch();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav>
|
||||
<Link to={`${match.url}/me`}>My Profile</Link>
|
||||
</nav>
|
||||
|
||||
<Switch>
|
||||
<Route path={`${match.path}/me`}>
|
||||
<MyProfile />
|
||||
</Route>
|
||||
<Route path={`${match.path}/:id`}>
|
||||
<OthersProfile />
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
在 v6 版本中省去了 `useRouteMatch` 这一步,支持直接用 `path` 表示相对路径:
|
||||
|
||||
```jsx
|
||||
// v6
|
||||
import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";
|
||||
|
||||
// Approach #1
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="profile/*" element={<Profile />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
function Profile() {
|
||||
return (
|
||||
<div>
|
||||
<nav>
|
||||
<Link to="me">My Profile</Link>
|
||||
</nav>
|
||||
|
||||
<Routes>
|
||||
<Route path="me" element={<MyProfile />} />
|
||||
<Route path=":id" element={<OthersProfile />} />
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Approach #2
|
||||
// You can also define all
|
||||
// <Route> in a single place
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="profile" element={<Profile />}>
|
||||
<Route path=":id" element={<MyProfile />} />
|
||||
<Route path="me" element={<OthersProfile />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
function Profile() {
|
||||
return (
|
||||
<div>
|
||||
<nav>
|
||||
<Link to="me">My Profile</Link>
|
||||
</nav>
|
||||
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
注意 `Outlet` 是渲染子路由的 Element。
|
||||
|
||||
### useNavigate 替代 useHistory
|
||||
|
||||
在 v5 版本中,主动跳转路由可以通过 `useHistory` 进行 `history.push` 等操作:
|
||||
|
||||
```jsx
|
||||
// v5
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
function MyButton() {
|
||||
let history = useHistory();
|
||||
function handleClick() {
|
||||
history.push("/home");
|
||||
}
|
||||
return <button onClick={handleClick}>Submit</button>;
|
||||
}
|
||||
```
|
||||
|
||||
而在 v6 版本中,可以通过 `useNavigate` 直接实现这个常用操作:
|
||||
|
||||
```jsx
|
||||
// v6
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
function MyButton() {
|
||||
let navigate = useNavigate();
|
||||
function handleClick() {
|
||||
navigate("/home");
|
||||
}
|
||||
return <button onClick={handleClick}>Submit</button>;
|
||||
}
|
||||
```
|
||||
|
||||
react-router 内部对 history 进行了封装,如果需要 `history.replace`,可以通过 `{ replace: true }` 参数指定:
|
||||
|
||||
```jsx
|
||||
// v5
|
||||
history.push("/home");
|
||||
history.replace("/home");
|
||||
|
||||
// v6
|
||||
navigate("/home");
|
||||
navigate("/home", { replace: true });
|
||||
```
|
||||
|
||||
### 更小的体积 8kb
|
||||
|
||||
由于代码几乎重构,v6 版本的代码压缩后体积从 20kb 缩小到 8kb。
|
||||
|
||||
## 3 精读
|
||||
|
||||
react-router v6 源码中有一段比较核心的理念,笔者拿出来与大家分享,对一些框架开发是大有裨益的。我们看 `useRoutes` 这段代码节选:
|
||||
|
||||
```jsx
|
||||
export function useRoutes(routes, basename = "", caseSensitive = false) {
|
||||
let {
|
||||
params: parentParams,
|
||||
pathname: parentPathname,
|
||||
route: parentRoute
|
||||
} = React.useContext(RouteContext);
|
||||
|
||||
if (warnAboutMissingTrailingSplatAt) {
|
||||
// ...
|
||||
}
|
||||
|
||||
basename = basename ? joinPaths([parentPathname, basename]) : parentPathname;
|
||||
|
||||
let navigate = useNavigate();
|
||||
let location = useLocation();
|
||||
let matches = React.useMemo(
|
||||
() => matchRoutes(routes, location, basename, caseSensitive),
|
||||
[routes, location, basename, caseSensitive]
|
||||
);
|
||||
|
||||
// ...
|
||||
|
||||
// Otherwise render an element.
|
||||
let element = matches.reduceRight((outlet, { params, pathname, route }) => {
|
||||
return (
|
||||
<RouteContext.Provider
|
||||
children={route.element}
|
||||
value={{
|
||||
outlet,
|
||||
params: readOnly({ ...parentParams, ...params }),
|
||||
pathname: joinPaths([basename, pathname]),
|
||||
route
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}, null);
|
||||
|
||||
return element;
|
||||
}
|
||||
```
|
||||
|
||||
可以看到,利用 `React.Context`,v6 版本在每个路由元素渲染时都包裹了一层 `RouteContext`。
|
||||
|
||||
拿更方便的路由嵌套来说:
|
||||
|
||||
> 在 v6 版本中省去了 `useRouteMatch` 这一步,支持直接用 `path` 表示相对路径。
|
||||
|
||||
这就是利用这个方案做到的,因为给每一层路由文件包裹了 Context,所以在每一层都可以拿到上一层的 `path`,因此在拼接路由时可以完全由框架内部实现,而不需要用户在调用时预先拼接好。
|
||||
|
||||
再以 `useNavigate` 举例,有人觉得 `navigate` 这个封装仅停留在形式层,但其实在功能上也有封装,比如如果传入但是一个相对路径,会根据当前路由进行切换,下面是 `useNavigate` 代码节选:
|
||||
|
||||
```jsx
|
||||
export function useNavigate() {
|
||||
let { history, pending } = React.useContext(LocationContext);
|
||||
let { pathname } = React.useContext(RouteContext);
|
||||
|
||||
let navigate = React.useCallback(
|
||||
(to, { replace, state } = {}) => {
|
||||
if (typeof to === "number") {
|
||||
history.go(to);
|
||||
} else {
|
||||
let relativeTo = resolveLocation(to, pathname);
|
||||
|
||||
let method = !!replace || pending ? "replace" : "push";
|
||||
history[method](relativeTo, state);
|
||||
}
|
||||
},
|
||||
[history, pending, pathname]
|
||||
);
|
||||
|
||||
return navigate;
|
||||
}
|
||||
```
|
||||
|
||||
可以看到,利用 `RouteContext` 拿到当前的 `pathname`,并根据 `resolveLocation` 对 `to` 与 `pathname` 进行路径拼接,而 `pathname` 就是通过 `RouteContext.Provider` 提供的。
|
||||
|
||||
### 巧用多层 Context Provider
|
||||
|
||||
很多时候我们利用 Context 停留在一个 `Provider`,多个 `useContext` 的层面上,这是 Context 最基础的用法,但相信读完 React Router v6 这篇文章,我们可以挖掘出 Context 更多的用法:多层 Context Provider。
|
||||
|
||||
**虽然说 Context Provider 存在多层会采取最近覆盖的原则,但这不仅仅是一条规避错误的功能,我们可以利用这个功能实现 React Router v6 这样的改良。**
|
||||
|
||||
为了更仔细说明这个特性,这里再举一个具体的例子:比如实现搭建渲染引擎时,每个组件都有一个 id,但这个 id 并不透出在组件的 props 上:
|
||||
|
||||
```jsx
|
||||
const Input = () => {
|
||||
// Input 组件在画布中会自动生成一个 id,但这个 id 组件无法通过 props 拿到
|
||||
};
|
||||
```
|
||||
|
||||
此时如果我们允许 Input 组件内部再创建一个子元素,又希望这个子元素的 id 是由 Input 推导出来的,我们可能需要用户这么做:
|
||||
|
||||
```jsx
|
||||
const Input = ({ id }) => {
|
||||
return <ComponentLoader id={id + "1"} />;
|
||||
};
|
||||
```
|
||||
|
||||
这样做有两个问题:
|
||||
|
||||
1. 将 id 暴露给 Input 组件,违背了之前设计的简洁性。
|
||||
2. 组件需要对 id 进行拼装,很麻烦。
|
||||
|
||||
这里遇到的问题和 React Router 遇到的一样,我们可以将代码简化成下面这样,但功能不变吗?
|
||||
|
||||
```jsx
|
||||
const Input = () => {
|
||||
return <ComponentLoader id="1" />;
|
||||
};
|
||||
```
|
||||
|
||||
答案是可以做到,我们可以利用 Context 实现这种方案。关键点就在于,渲染 Input 但组件容器需要包裹一个 Provider:
|
||||
|
||||
```jsx
|
||||
const ComponentLoader = ({ id, element }) => {
|
||||
<Context.Provider value={{ id }}>{element}</Context.Provider>;
|
||||
};
|
||||
```
|
||||
|
||||
那么对于内部的组件来说,在不同层级下调用 `useContext` 拿到的 id 是不同的,这正是我们想要的效果:
|
||||
|
||||
```jsx
|
||||
const ComponentLoader = ({id,element}) => {
|
||||
const { id: parentId } = useContext(Context)
|
||||
|
||||
<Context.Provider value={{ id: parentId + id }}>
|
||||
{element}
|
||||
</Context.Provider>
|
||||
}
|
||||
```
|
||||
|
||||
这样我们在 `Input` 内部调用的 `<ComponentLoader id="1" />` 实际上拼接的实际 id 是 `01`,而这完全抛到了外部引擎层处理,用户无需手动拼接。
|
||||
|
||||
## 4 总结
|
||||
|
||||
React Router v6 完全利用 Hooks 重构后,不仅代码量精简了很多,还变得更好用了,等发正式版的时候可以快速升级一波。
|
||||
|
||||
另外从 React Router v6 做的这些优化中,我们从源码中挖掘到了关于 Context 更巧妙的用法,希望这个方法可以帮助你运用到其他更复杂的项目设计中。
|
||||
|
||||
> 讨论地址是:[精读《React Router v6》 · Issue #241 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/241)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,354 @@
|
||||
## 1 引言
|
||||
|
||||
React Hooks 渐渐被国内前端团队所接受,但基于 Hooks 的数据流方案却还未固定,我们有 “100 种” 类似的选择,却各有利弊,让人难以取舍。
|
||||
|
||||
本周笔者就深入谈一谈对 Hooks 数据流的理解,相信读完文章后,可以从百花齐放的 Hooks 数据流方案中看到本质。
|
||||
|
||||
## 2 精读
|
||||
|
||||
基于 React Hooks 谈数据流,我们先从最不容易产生分歧的基础方案说起。
|
||||
|
||||
### 单组件数据流
|
||||
|
||||
单组件最简单的数据流一定是 `useState`:
|
||||
|
||||
```jsx
|
||||
function App() {
|
||||
const [count, setCount] = useState();
|
||||
}
|
||||
```
|
||||
|
||||
`useState` 在组件内用是毫无争议的,那么下个话题就一定是跨组件共享数据流了。
|
||||
|
||||
### 组件间共享数据流
|
||||
|
||||
跨组件最简单的方案就是 `useContext`:
|
||||
|
||||
```jsx
|
||||
const CountContext = createContext();
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState();
|
||||
return (
|
||||
<CountContext.Provider value={{ count, setCount }}>
|
||||
<Child />
|
||||
</CountContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Child() {
|
||||
const { count } = useContext(CountContext);
|
||||
}
|
||||
```
|
||||
|
||||
用法都是官方 API,显然也是毫无争议的,但问题是数据与 UI 不解耦,这个问题 [unstated-next](https://github.com/jamiebuilds/unstated-next) 已经为你想好解决方案了。
|
||||
|
||||
### 数据流与组件解耦
|
||||
|
||||
[unstated-next](https://github.com/jamiebuilds/unstated-next) 可以帮你把上面例子中,定义在 `App` 中的数据单独出来,形成一个自定义数据管理 Hook:
|
||||
|
||||
```jsx
|
||||
import { createContainer } from "unstated-next";
|
||||
|
||||
function useCounter() {
|
||||
const [count, setCount] = useState();
|
||||
return { count, setCount };
|
||||
}
|
||||
|
||||
const Counter = createContainer(useCounter);
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Counter.Provider>
|
||||
<Child />
|
||||
</Counter.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Child() {
|
||||
const { count } = Counter.useContainer();
|
||||
}
|
||||
```
|
||||
|
||||
数据与 `App` 就解耦了,这下 `Counter` 再也不和 `App` 绑定了,`Counter` 可以和其他组件绑定作用了。
|
||||
|
||||
这个时候性能问题就慢慢浮出了水面,首当其冲的就是 `useState` 无法合并更新的问题,我们自然想到利用 `useReducer` 解决。
|
||||
|
||||
### 合并更新
|
||||
|
||||
`useReducer` 可以让数据合并更新,这也是 React 官方 API,毫无争议:
|
||||
|
||||
```jsx
|
||||
import { createContainer } from "unstated-next";
|
||||
|
||||
function useCounter() {
|
||||
const [state, dispath] = useReducer(
|
||||
(state, action) => {
|
||||
switch (action.type) {
|
||||
case "setCount":
|
||||
return {
|
||||
...state,
|
||||
count: action.setCount(state.count),
|
||||
};
|
||||
case "setFoo":
|
||||
return {
|
||||
...state,
|
||||
foo: action.setFoo(state.foo),
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
},
|
||||
{ count: 0, foo: 0 }
|
||||
);
|
||||
|
||||
return { ...state, dispatch };
|
||||
}
|
||||
|
||||
const Counter = createContainer(useCounter);
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Counter.Provider>
|
||||
<Child />
|
||||
</Counter.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Child() {
|
||||
const { count } = Counter.useContainer();
|
||||
}
|
||||
```
|
||||
|
||||
这下即便要同时更新 `count` 和 `foo`,我们也能通过抽象成一个 `reducer` 的方式合并更新。
|
||||
|
||||
然而还有性能问题:
|
||||
|
||||
```jsx
|
||||
function ChildCount() {
|
||||
const { count } = Counter.useContainer();
|
||||
}
|
||||
|
||||
function ChildFoo() {
|
||||
const { foo } = Counter.useContainer();
|
||||
}
|
||||
```
|
||||
|
||||
更新 `foo` 时,`ChildCount` 和 `ChildFoo` 同时会执行,但 `ChildCount` 没用到 `foo` 呀?这个原因是 `Counter.useContainer` 提供的数据流是一个引用整体,其子节点 `foo` 引用变化后会导致整个 Hook 重新执行,继而所有引用它的组件也会重新渲染。
|
||||
|
||||
此时我们发现可以利用 Redux `useSelector` 实现按需更新。
|
||||
|
||||
### 按需更新
|
||||
|
||||
首先我们利用 Redux 对数据流做一次改造:
|
||||
|
||||
```jsx
|
||||
import { createStore } from "redux";
|
||||
import { Provider, useSelector } from "react-redux";
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case "setCount":
|
||||
return {
|
||||
...state,
|
||||
count: action.setCount(state.count),
|
||||
};
|
||||
case "setFoo":
|
||||
return {
|
||||
...state,
|
||||
foo: action.setFoo(state.foo),
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Child />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Child() {
|
||||
const { count } = useSelector(
|
||||
(state) => ({ count: state.count }),
|
||||
shallowEqual
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`useSelector` 可以让 `Child` 在 `count` 变化时才更新,而 `foo` 变化时不更新,这已经接近较为理想的性能目标了。
|
||||
|
||||
但 `useSelector` 的作用仅仅是计算结果不变化时阻止组件刷新,但并不能保证返回结果的引用不变化。
|
||||
|
||||
### 防止数据引用频繁变化
|
||||
|
||||
对于上面的场景,拿到 `count` 的引用是不变的,**但对于其他场景就不一定了**。
|
||||
|
||||
举个例子:
|
||||
|
||||
```jsx
|
||||
function Child() {
|
||||
const user = useSelector((state) => ({ user: state.user }), shallowEqual);
|
||||
|
||||
return <UserPage user={user} />;
|
||||
}
|
||||
```
|
||||
|
||||
**假设 `user` 对象在每次数据流更新引用都会发生变化**,那么 `shallowEqual` 自然是不起作用,那我们换成 `deepEqual`深对比呢?结果是引用依然会变,只是重渲染不那么频繁了:
|
||||
|
||||
```jsx
|
||||
function Child() {
|
||||
const user = useSelector(
|
||||
(state) => ({ user: state.user }),
|
||||
// 当 user 值变化时才重渲染
|
||||
deepEqual
|
||||
);
|
||||
|
||||
// 但此处拿到的 user 引用还是会变化
|
||||
|
||||
return <UserPage user={user} />;
|
||||
}
|
||||
```
|
||||
|
||||
是不是觉得在 `deepEqual` 的作用下,没有触发重渲染,`user` 的引用就不会变呢?答案是会变,因为 `user` 对象在每次数据流更新都会变,`useSelector` 在 `deepEqual` 作用下没有触发重渲染,但因为全局 reducer 隐去组件自己的重渲染依然会重新执行此函数,此时拿到的 `user` 引用会不断变化。
|
||||
|
||||
因此 `useSelector` `deepEqual` 一定要和 `useDeepMemo` 结合使用,才能保证 `user` 引用不会频繁改变:
|
||||
|
||||
```jsx
|
||||
function Child() {
|
||||
const user = useSelector(
|
||||
(state) => ({ user: state.user }),
|
||||
// 当 user 值变化时才重渲染
|
||||
deepEqual
|
||||
);
|
||||
|
||||
const userDeep = useDeepMemo(() => user, [user]);
|
||||
|
||||
return <UserPage user={userDeep} />;
|
||||
}
|
||||
```
|
||||
|
||||
当然这是比较极端的情况,只要看到 `deepEqual` 与 `useSelector` 同时作用了,就要问问自己其返回的值的引用会不会发生意外变化。
|
||||
|
||||
### 缓存查询函数
|
||||
|
||||
对于极限场景,即便控制了重渲染次数与返回结果的引用最大程度不变,还是可能存在性能问题,这最后一块性能问题就处在查询函数上。
|
||||
|
||||
上面的例子中,查询函数比较简单,但如果查询函数非常复杂就不一样了:
|
||||
|
||||
```jsx
|
||||
function Child() {
|
||||
const user = useSelector(
|
||||
(state) => ({ user: verySlowFunction(state.user) }),
|
||||
// 当 user 值变化时才重渲染
|
||||
deepEqual
|
||||
);
|
||||
|
||||
const userDeep = useDeepMemo(() => user, [user]);
|
||||
|
||||
return <UserPage user={userDeep} />;
|
||||
}
|
||||
```
|
||||
|
||||
我们假设 `verySlowFunction` 要遍历画布中 1000 个组件的 n 3 次方次,那组件的重渲染时间消耗与查询时间相比完全不值一提,我们需要考虑缓存查询函数。
|
||||
|
||||
一种方式是利用 [reselect](https://github.com/reduxjs/reselect) 根据参数引用进行缓存。
|
||||
|
||||
想象一下,如果 `state.user` 的引用不频繁变化,但 `verySlowFunction` 非常慢,理想情况是 `state.user` 引用变化后才重新执行 `verySlowFunction`,但上面的例子中,`useSelector` 并不知道还能这么优化,只能傻傻的每次渲染重复执行 `verySlowFunction`,哪怕 `state.user` 没有变。
|
||||
|
||||
此时我们要告诉引用,`state.user` 是否变化才是重新执行的关键:
|
||||
|
||||
```jsx
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const userSelector = createSelector(
|
||||
(state) => state.user,
|
||||
(user) => verySlowFunction(user)
|
||||
);
|
||||
|
||||
function Child() {
|
||||
const user = useSelector(
|
||||
(state) => userSelector(state),
|
||||
// 当 user 值变化时才重渲染
|
||||
deepEqual
|
||||
);
|
||||
|
||||
const userDeep = useDeepMemo(() => user, [user]);
|
||||
|
||||
return <UserPage user={userDeep} />;
|
||||
}
|
||||
```
|
||||
|
||||
在上面的例子中,通过 `createSelector` 创建的 `userSelector` 会一层层进行缓存,当第一个参数返回的 `state.user` 引用不变时,会直接返回上一次执行结果,直到其应用变化了才会继续往下执行。
|
||||
|
||||
> 这也说明了函数式保持幂等的重要性,如果 `verySlowFunction` 不是严格幂等的,这种缓存也无法实施。
|
||||
|
||||
看上去很美好,然而实战中你可能发现没有那么美好,因为上面的例子都建立在 **Selector 完全不依赖外部变量**。
|
||||
|
||||
### 结合外部变量的缓存查询
|
||||
|
||||
如果我们要查询的用户来自于不同地区,需要传递 `areaId` 加以识别,那么可以拆分为两个 Selector 函数:
|
||||
|
||||
```jsx
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const areaSelector = (state, props) => state.areas[props.areaId].user;
|
||||
|
||||
const userSelector = createSelector(areaSelector, (user) =>
|
||||
verySlowFunction(user)
|
||||
);
|
||||
|
||||
function Child() {
|
||||
const user = useSelector(
|
||||
(state) => userSelector(state, { areaId: 1 }),
|
||||
deepEqual
|
||||
);
|
||||
|
||||
const userDeep = useDeepMemo(() => user, [user]);
|
||||
|
||||
return <UserPage user={userDeep} />;
|
||||
}
|
||||
```
|
||||
|
||||
所以为了不在组件函数内调用 `createSelector`,我们需要尽可能将用到外部变量的地方抽象成一个通用 Selector,并作为 `createSelector` 的一个先手环节。
|
||||
|
||||
但 `userSelector` 提供给多个组件使用时缓存会失效,原因是我们只创建了一个 Selector 实例,因此这个函数还需要再包装一层高阶形态:
|
||||
|
||||
```jsx
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const userSelector = () =>
|
||||
createSelector(areaSelector, (user) => verySlowFunction(user));
|
||||
|
||||
function Child() {
|
||||
const customSelector = useMemo(userSelector, []);
|
||||
|
||||
const user = useSelector(
|
||||
(state) => customSelector(state, { areaId: 1 }),
|
||||
deepEqual
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
所以对于外部变量结合的环节,还需要 `useMemo` 与 `useSelector` 结合使用,`useMemo` 处理外部变量依赖的引用缓存,`useSelector` 处理 Store 相关引用缓存。
|
||||
|
||||
## 3 总结
|
||||
|
||||
基于 Hooks 的数据流方案不能算完美,我在写作这篇文章时就感觉到这种方案属于 “浅入深出”,简单场景还容易理解,随着场景逐步复杂,方案也变得越来越复杂。
|
||||
|
||||
但这种 Immutable 的数据流管理思路给了开发者非常自由的缓存控制能力,只要透彻理解上述概念,就可以开发出非常 “符合预期” 的数据缓存管理模型,只要精心维护,一切就变得非常有秩序。
|
||||
|
||||
> 讨论地址是:[精读《React Hooks 数据流》 · Issue #242 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/242)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,141 @@
|
||||
## 1 引言
|
||||
|
||||
从 [@types/react](https://unpkg.com/browse/@types/react@16.9.34/index.d.ts) 源码中挖掘一些 Typescript 使用技巧吧。
|
||||
|
||||
## 2 精读
|
||||
|
||||
### 泛型 extends
|
||||
|
||||
泛型可以指代可能的参数类型,但指代任意类型范围太模糊,当我们需要对参数类型加以限制,或者确定只处理某种类型参数时,就可以对泛型进行 extends 修饰。
|
||||
|
||||
问题:`React.lazy` 需要限制返回值是一个 `Promise<T>` 类型,且 `T` 必须是 React 组件类型。
|
||||
|
||||
方案:
|
||||
|
||||
```typescript
|
||||
function lazy<T extends ComponentType<any>>(
|
||||
factory: () => Promise<{ default: T }>
|
||||
): LazyExoticComponent<T>;
|
||||
```
|
||||
|
||||
`T extends ComponentType` 确保了 T 这个类型一定符合 `ComponentType` 这个 React 组件类型定义,我们再将 T 用到 `Promise<{ default: T }>` 位置即可。
|
||||
|
||||
## 泛型 extends + infer
|
||||
|
||||
如果有一种场景,需要拿到一个类型,这个类型是当某个参数符合某种结构时,这个结构内的一种子类型,就需要结合 泛型 extends + infer 了。
|
||||
|
||||
问题:`React.useReducer` 第一个参数是 Reducer,第二个参数是初始化参数,其实第二个参数的类型是第一个参数中回调函数第一个参数的类型,那我们怎么将这两个参数的关系联系到一起呢?
|
||||
|
||||
方案:
|
||||
|
||||
```typescript
|
||||
function useReducer<R extends Reducer<any, any>, I>(
|
||||
reducer: R,
|
||||
initializerArg: I & ReducerState<R>,
|
||||
initializer: (arg: I & ReducerState<R>) => ReducerState<R>
|
||||
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
|
||||
|
||||
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any>
|
||||
? S
|
||||
: never;
|
||||
```
|
||||
|
||||
`R extends Reducer<any, any>` 的意思在上面已经提过了,也就是 R 必须符合 `Reducer` 结构,也就是 `reducer` 必须符合这个结构,之后重点来了:`initializerArg` 利用 `ReducerState` 这个类型直接从 `reducer` 的类型 `R` 中将第一个回调参数挖了出来并返回。
|
||||
|
||||
`ReducerState` 定义中 `R extends Reducer<infer S, any> ? S : never` 的含义是:如果 R 符合 `Reducer<infer S, any>` 类型,则返回类型 `S`,这个 `S` 是 `Reducer<infer S>` 也就是 State 位置的类型,否则返回 `never` 类型。
|
||||
|
||||
所以 infer 表示待推断类型,是非常强大的功能,可以指定在任意位置代指其类型,并配合 extends 判断是否符合结构,可以使类型推断具备一定编程能力。
|
||||
|
||||
要用 extends 的另一个原因是,只有 extends 才能将结构描述出来,我们才能精确定义 infer 指代类型的位置。
|
||||
|
||||
### 类型重载
|
||||
|
||||
当一个类型拥有多种使用可能性时,可以采用类型重载定义复数类型,Typescript 作用时会逐个匹配并找到第一个满足条件的。
|
||||
|
||||
问题:`createElement` 第一个参数支持 FunctionComponent 与 ClassComponent,而且传入参数不同,返回值的类型也不同。
|
||||
|
||||
方案:
|
||||
|
||||
```typescript
|
||||
function createElement<P extends {}>(
|
||||
type: FunctionComponent<P>,
|
||||
props?: (Attributes & P) | null,
|
||||
...children: ReactNode[]
|
||||
): FunctionComponentElement<P>;
|
||||
function createElement<P extends {}>(
|
||||
type: ClassType<
|
||||
P,
|
||||
ClassicComponent<P, ComponentState>,
|
||||
ClassicComponentClass<P>
|
||||
>,
|
||||
props?: (ClassAttributes<ClassicComponent<P, ComponentState>> & P) | null,
|
||||
...children: ReactNode[]
|
||||
): CElement<P, ClassicComponent<P, ComponentState>>;
|
||||
```
|
||||
|
||||
将 `createElement` 写两遍及以上,并配合不同的参数类型与返回值类型即可。
|
||||
|
||||
### 自定义类型收窄
|
||||
|
||||
我们可以通过 `typeof` 或 `instanceof` 做一些类型收窄工作,但有些类型甚至自定义类型的收窄判断函数需要自定义,我们可以通过 `is` 关键字定义自定义类型收窄判断函数。
|
||||
|
||||
问题:`isValidElement` 判断对象是否是合法的 React 元素,我们希望这个函数具备类型收窄的功能。
|
||||
|
||||
方案:
|
||||
|
||||
```typescript
|
||||
function isValidElement<P>(
|
||||
object: {} | null | undefined
|
||||
): object is ReactElement<P>;
|
||||
|
||||
const element: string | ReactElement = "";
|
||||
|
||||
if (isValidElement(element)) {
|
||||
element; // 自动推导类型为 ReactElement
|
||||
} else {
|
||||
element; // 自动推导类型为 string
|
||||
}
|
||||
```
|
||||
|
||||
基于这个方案,我们可以创建一些很有用的函数,比如 `isArray`,`isMap`,`isSet` 等等,通过 `is` 关键字时其被调用时具备类型收窄的功能。
|
||||
|
||||
### 用 Interface 定义函数
|
||||
|
||||
一般定义函数类型我们用 `type`,但有些情况下定义的函数既可被调用,也有一些默认属性值需要定义,我们可以继续用 Interface 定义。
|
||||
|
||||
问题:`FunctionComponent` 既可以当作函数调用,同时又能定义 `defaultProps` `displayName` 等固定属性。
|
||||
|
||||
方案:
|
||||
|
||||
```typescript
|
||||
interface FunctionComponent<P = {}> {
|
||||
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
|
||||
propTypes?: WeakValidationMap<P>;
|
||||
contextTypes?: ValidationMap<any>;
|
||||
defaultProps?: Partial<P>;
|
||||
displayName?: string;
|
||||
}
|
||||
```
|
||||
|
||||
`(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null` 表示这种类型的变量可以作为函数执行:
|
||||
|
||||
```jsx
|
||||
const App: FunctionComponent = () => <div />;
|
||||
App.displayName = "App";
|
||||
```
|
||||
|
||||
## 3 总结
|
||||
|
||||
看完文章内容,相信你已经可以独立读懂 [@types/react](https://unpkg.com/browse/@types/react@16.9.34/index.d.ts) 这个包的所有类型定义!
|
||||
|
||||
更多基础内容可以阅读 [精读《Typescript2.0 - 2.9》](https://github.com/dt-fe/weekly/blob/7de3c77c3bdd7304c9e4b0c0f70c3ba6968ebd29/058.%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript2.0%20-%202.9%E3%80%8B.md) 与 [精读《Typescript 3.2 新特性》](https://github.com/dt-fe/weekly/blob/v2/084.%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript%203.2%20%E6%96%B0%E7%89%B9%E6%80%A7%E3%80%8B.md),由于 TS 更新频繁,后续 TS 技巧可能继续以阅读源码方式进行,希望这次选用的 React 类型源码可以让你印象深刻。
|
||||
|
||||
> 讨论地址是:[精读《@types/react 值得注意的 TS 技巧》 · Issue #245 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/245)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,133 @@
|
||||
## 1 引言
|
||||
|
||||
Error Boundaries 是 React16 提出来用来捕获渲染时错误的概念,今天我们一起读一读 [A Simple Guide to Error Boundaries in React](https://alligator.io/react/error-boundaries/) 这篇文章,了解一下这个重要机制。
|
||||
|
||||
## 2 概述
|
||||
|
||||
Error Boundaries 可以用来捕获渲染时错误,API 如下:
|
||||
|
||||
```jsx
|
||||
class MyErrorBoundary extends Component {
|
||||
state = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
// 更新 state,下次渲染可以展示错误相关的 UI
|
||||
return { error: error };
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
// 错误上报
|
||||
logErrorToMyService(error, info);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
// 渲染出错时的 UI
|
||||
return <p>Something broke</p>;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `static getDerivedStateFromError`: 在出错后有机会修改 state 触发最后一次错误 fallback 的渲染。
|
||||
- `componentDidCatch`: 用于出错时副作用代码,比如错误上报等。
|
||||
|
||||
这两种方法中任意一个被定义时,这个组件就会成为 `Error Boundary` 组件,可以阻止子组件渲染时报错。
|
||||
|
||||
最后作者还提出一个建议,建议将 Error Boundary 单独作为一个组件,而不是将错误监听方法与业务组件耦合,一方面考虑到复用,另一方面则因为错误检测只对子组件生效。
|
||||
|
||||
好吧,其实 React 官方文档比这篇文章介绍的详细的多得多,原文介绍到此结束。
|
||||
|
||||
## 3 精读
|
||||
|
||||
[React Error Boundaries 官方文档](https://reactjs.org/docs/error-boundaries.html) 里提到了四种无法 Catch 的错误场景:
|
||||
|
||||
1. 回调事件。由于回调事件执行时机不在渲染周期内,因此无法被 Error Boundary Catch 住,如有必要得自行 try/catch。
|
||||
2. 异步。比如 `setTimeout` 或 `requestAnimationFrame`,和第一条同理。
|
||||
3. 服务端渲染。
|
||||
4. Error Boundary 组件自身触发的错误。因为只能捕获其子组件的错误。
|
||||
|
||||
这也是使用 Error Boundaries 最容易有疑问的地方。除了上面的情况,笔者结合自身经验再列举几种异常边界场景。
|
||||
|
||||
### 无法捕获编译时错误
|
||||
|
||||
很明显,即便是 React 官方 API `Error Boundary` 也只能捕获运行时错误,而对编译时错误无能为力。
|
||||
|
||||
编译时错误包括不限于编译环境错误、运行前的框架错误检查提示、TS/Flow 类型错误等,这些都是 `Error Boundary` 无法捕获的,而且没有更好的办法 Catch 住,遇到编译错误就在编译时解决吧,仅关注运行时错误就好了。
|
||||
|
||||
### 可以作用于 Function Component
|
||||
|
||||
虽然函数式组件无法定义 `Error Boundary`,但 `Error Boundary` 可以捕获函数式组件的错误,因此可以曲线救国:
|
||||
|
||||
```jsx
|
||||
// ErrorBoundary 组件
|
||||
class ErrorBoundary extends React.Component {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 可以捕获所有组件异常,包括 Function Component 的子组件
|
||||
const App = () => {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Child />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 对 Hooks 也可生效
|
||||
|
||||
对于 Hooks 中异常也可以生效,比如下面的代码:
|
||||
|
||||
```jsx
|
||||
const Child = (props) => {
|
||||
React.useEffect(() => {
|
||||
console.log(1);
|
||||
props.a.b;
|
||||
console.log(2);
|
||||
}, [props.a.b]);
|
||||
|
||||
return <div />;
|
||||
};
|
||||
```
|
||||
|
||||
要注意的是,出现在 deps 中的错误会立即被 Catch,导致 `console.log(1)` 都无法打印。但如果是下面的代码,则可以打印出 `console.log(1)`,无法打印出 `console.log(2)`:
|
||||
|
||||
```jsx
|
||||
const Child = (props) => {
|
||||
React.useEffect(() => {
|
||||
console.log(1);
|
||||
props.a.b;
|
||||
console.log(2);
|
||||
}, []);
|
||||
|
||||
return <div />;
|
||||
};
|
||||
```
|
||||
|
||||
所以 React 官网的这句话并不是指 `Error Boundary` 对 Hooks 不生效,而是指 `Error Boundary` 无法以 Hooks 方式指定,对功能是没有影响的:
|
||||
|
||||
> componentDidCatch and getDerivedStateFromError: There are no Hook equivalents for these methods yet, but they will be added soon.
|
||||
|
||||
所以这里的理解要注意一下,另外 React 官方文档 [Hooks FAQ](https://reactjs.org/docs/hooks-faq.html#how-do-lifecycle-methods-correspond-to-hooks) 有很多宝藏,建议抽时间逐条阅读。
|
||||
|
||||
## 4 总结
|
||||
|
||||
`Error Boundary` 可以捕获所有子元素渲染时异常,包括 render、各生命周期函数,但也有很多使用限制,希望你可以正确使用它。
|
||||
|
||||
错误捕获也不是万能的,更多时候我们要避免并及时修复错误,通过错误捕获降低出错时对用户体验的影响,并在第一时间内监控起来并快速修复。
|
||||
|
||||
最后,你有明明正确使用了 `Error Boundary` 却依然无法 Catch 住的错误 Case 吗?
|
||||
|
||||
> 讨论地址是:[精读《React Error Boundaries》 · Issue #246 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/246)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,199 @@
|
||||
## 1 引言
|
||||
|
||||
在数据中台做 BI 工具经常面对海量数据的渲染处理,除了组件本身性能优化之外,经常要排查整体页面性能瓶颈点,尤其是维护一些性能做得并不好的旧代码时。
|
||||
|
||||
React 性能调试是面对这种问题的必修课,借助 [Profiling React.js Performance](https://addyosmani.com/blog/profiling-react-js/) 这篇文章一起学习一下这个技能吧。
|
||||
|
||||
## 2 精读
|
||||
|
||||
本文介绍了众多性能检测工具与方法。
|
||||
|
||||
### React Profiler
|
||||
|
||||
`Profiler` 这个 API 是一种运行时 Debug 的补充,可以通过其 callback 拿到组件渲染信息,用法如下:
|
||||
|
||||
```jsx
|
||||
const Movies = ({ movies, addToQueue }) => (
|
||||
<React.Profiler id="Movies" onRender={callback}>
|
||||
<div />
|
||||
</React.Profiler>
|
||||
);
|
||||
|
||||
function callback(
|
||||
id,
|
||||
phase,
|
||||
actualTime,
|
||||
baseTime,
|
||||
startTime,
|
||||
commitTime,
|
||||
interactions
|
||||
) {}
|
||||
```
|
||||
|
||||
这个 callback 会在每次渲染时执行,渲染分为初始化和更新阶段,通过 `phase` 区分,下面是参数详细说明:
|
||||
|
||||
- id: 传入的 id。
|
||||
- phase: "mount" 或 "update",表示更新状态。
|
||||
- actualDuration: 实际渲染耗时。
|
||||
- baseDuration: 没有使用 memo 时的渲染预计耗时。
|
||||
- startTime: 开始渲染的时间。
|
||||
- commitTime: React 提交更新的时间
|
||||
- interactions: 何种原因导致的渲染,比如 `setState` 或 hooks changed 之类。
|
||||
|
||||
注意尽量不要轻易使用 `Profiler` 检测性能,因为 `Profiler` 本身也会消耗性能。
|
||||
|
||||
如果不想获得这么详细的渲染耗时,或者不想提前在代码中埋点,可以利用 DevTools 的 Profiler 查看更直观更简洁的渲染耗时:
|
||||
|
||||
<img width=400 src="https://img.alicdn.com/tfs/TB1sPAuDuL2gK0jSZPhXXahvXXa-1846-1028.png">
|
||||
|
||||
其中 Ranked 可以展示按照渲染耗时排序后的结果,Interations 需要配合 Tracing API 使用,在后面会提到。
|
||||
|
||||
### Tracing API
|
||||
|
||||
利用 `scheduler/tracing` 提供的 `trace` API,我们可以记录某个动作的耗时,比如 “点击添加按钮收藏一个电影” 耗时多久:
|
||||
|
||||
```jsx
|
||||
import { render } from "react-dom";
|
||||
import { unstable_trace as trace } from "scheduler/tracing";
|
||||
|
||||
class MyComponent extends Component {
|
||||
addMovieButtonClick = (event) => {
|
||||
trace("Add To Movies Queue click", performance.now(), () => {
|
||||
this.setState({ itemAddedToQueue: true });
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
在 Interations 中可以看到动作触发的耗时:
|
||||
|
||||
<img width=400 src="https://img.alicdn.com/tfs/TB1XR.FDAY2gK0jSZFgXXc5OFXa-1846-1010.png">
|
||||
|
||||
这个动作还可以是渲染,比如可以记录 ReactDOM 渲染的耗时:
|
||||
|
||||
```jsx
|
||||
import { unstable_trace as trace } from "scheduler/tracing";
|
||||
|
||||
trace("initial render", performance.now(), () => {
|
||||
ReactDom.render(<App />, document.getElementById("app"));
|
||||
});
|
||||
```
|
||||
|
||||
<img width=300 src="https://img.alicdn.com/tfs/TB18hyHfcKfxu4jSZPfXXb3dXXa-1846-740.png">
|
||||
|
||||
甚至还可以追踪异步的耗时:
|
||||
|
||||
```jsx
|
||||
import {
|
||||
unstable_trace as trace,
|
||||
unstable_wrap as wrap,
|
||||
} from "scheduler/tracing";
|
||||
|
||||
trace("Some event", performance.now(), () => {
|
||||
setTimeout(
|
||||
wrap(() => {
|
||||
// 异步操作
|
||||
})
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
有了 `Profiler` 与 `trace` 这两件武器,我们可以监控任意元素的渲染耗时与交互耗时,几乎可以涵盖所有性能监控需要。
|
||||
|
||||
### Puppeteer
|
||||
|
||||
我们还可以利用 Puppeteer 实现自动化操作并打印报告:
|
||||
|
||||
```jsx
|
||||
const puppeteer = require("puppeteer");
|
||||
|
||||
(async () => {
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
const navigationPromise = page.waitForNavigation();
|
||||
await page.goto("https://react-movies-queue.glitch.me/");
|
||||
await page.setViewport({ width: 1276, height: 689 });
|
||||
await navigationPromise;
|
||||
|
||||
const addMovieToQueueBtn =
|
||||
"li:nth-child(3) > .card > .card__info > div > .button";
|
||||
await page.waitForSelector(addMovieToQueueBtn);
|
||||
|
||||
// Begin profiling...
|
||||
await page.tracing.start({ path: "profile.json" });
|
||||
// Click the button
|
||||
await page.click(addMovieToQueueBtn);
|
||||
// Stop profliling
|
||||
await page.tracing.stop();
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
```
|
||||
|
||||
首先利用 `puppeteer` 创建一个浏览器,新建一个页面并打开 `https://react-movies-queue.glitch.me/` 这个 URL,等待页面加载完毕后利用 DOM 选择器找到按钮,利用 `page.click` API 模拟点击这个按钮,并在前后利用 `page.tracing` 记录性能变化,并将这个文件上传到 DevTools Performance 面板,就会得到一份自动的性能检测报告:
|
||||
|
||||
<img width=400 src="https://img.alicdn.com/tfs/TB1623EDxz1gK0jSZSgXXavwpXa-2769-2289.png">
|
||||
|
||||
这张图相当重要,是浏览器综合运行开销分析的利器,最上面分为 4 个部分:
|
||||
|
||||
- FPS:每秒帧数,绿色竖线越高表示 FPS 越高,出现红线则表示出现了卡顿。
|
||||
- CPU:CPU 资源,用面积图展示消耗 CPU 资源的事件。
|
||||
- NET:网络消耗,每条横杠表示一种资源的加载。
|
||||
- HEAP:内存水位,由于短时间内看不出来是否会内存溢出,一般只用来简单看看内存消耗是否符合预期,对于内存溢出的检测需要用持续监控上报的方式。
|
||||
|
||||
下面会有一张 Network 详细图解,比如这张图:
|
||||
|
||||
<img width=400 src="https://img.alicdn.com/tfs/TB1D.wKDxD1gK0jSZFyXXciOVXa-2868-750.png">
|
||||
|
||||
细线表示等待的时间,粗线表示实际加载的情况,其中浅色部分表示服务器等待时间,即从发送下载请求到服务器响应第一个字节的时间。这部分可以看出资源并行加载阻塞情况以及资源服务器响应时间是否存在问题。
|
||||
|
||||
Timings 展示了几个重要时间节点,这里列举一部分:
|
||||
|
||||
- FP:First Paint,第一次绘制。
|
||||
- FCP:First Contentful Paint,第一次内容绘制。
|
||||
- LCP:Largest Contentful Paint,最大内容绘制。
|
||||
- DCL:Document Content Loaded,DOM 内容加载完毕。
|
||||
|
||||
再下面是 JS 计算消耗,用了一张火焰图,火焰图是性能分析的常用可视化工具。以下面这张图为例:
|
||||
|
||||
<img width=350 src="https://img.alicdn.com/tfs/TB1JecIDrr1gK0jSZFDXXb9yVXa-1404-616.png">
|
||||
|
||||
看火焰图首先看跨度最长的函数,也就是最长的那条线,这是最耗时的部分,从左到右是浏览器脚本的调用顺序,从上到下是函数嵌套的顺序。
|
||||
|
||||
我们可以看到鼠标位置的 34 这个函数虽然长,但并不是性能瓶颈,因为下面执行的 n 函数长度和它一样,表示 34 函数的性能几乎无损耗,其性能由其调用的 n 函数决定。
|
||||
|
||||
我们可以利用这种方式一步步排查到叶子结点,找到对性能影响最大的元子函数。
|
||||
|
||||
### User Timing API
|
||||
|
||||
我们还可以利用 `performance.mark` 自定义性能检测节点:
|
||||
|
||||
```jsx
|
||||
// Record the time before running a task
|
||||
performance.mark("Movies:updateStart");
|
||||
// Do some work
|
||||
|
||||
// Record the time after running a task
|
||||
performance.mark("Movies:updateEnd");
|
||||
|
||||
// Measure the difference between the start and end of the task
|
||||
performance.measure("moviesRender", "Movies:updateStart", "Movies:updateEnd");
|
||||
```
|
||||
|
||||
这些节点可以在上面介绍的 Performance 面板中展示出来用于自定义分析。
|
||||
|
||||
## 3 总结
|
||||
|
||||
利用 Performance 进行通用性能分析,利用 React Profiler 进行 React 定制性能分析,这两个结合在一起几乎可以完成任何性能检测。
|
||||
|
||||
一般来说,首先应该用 React Profiler 进行 React 层面的问题筛查,这样更直观,更容易定位问题。如果某些问题跳出了 React 框架范围,或者不再能以组件粒度进行度量,我们可以回到 Performance 面板进行通用性能分析。
|
||||
|
||||
> 讨论地址是:[精读《React 性能调试》 · Issue #247 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/247)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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))
|
||||