Compare commits

..
352 Commits
Author SHA1 Message Date
ascoders cf2a01dd0b update 2022-06-06 09:42:13 +08:00
ascoders c2897a2385 Merge branch 'master' of https://github.com/ascoders/weekly 2022-06-06 09:40:11 +08:00
ascoders 08ea922317 243 2022-06-06 09:40:07 +08:00
黄子毅 fa1e19c58f Merge pull request #421 from xuzhanhh/patch-1
fix: typo
2022-06-01 21:23:28 +08:00
xuzhanhh 444e4daeec fix: typo 2022-05-30 11:05:55 +08:00
ascoders a95ae5ac5a Merge branch 'master' of https://github.com/ascoders/weekly 2022-05-30 10:18:36 +08:00
ascoders 0fa01c716b 242 2022-05-30 10:18:29 +08:00
黄子毅 ef97b66383 Merge pull request #419 from reactjser/patch-1
Update 241.精读《react-snippets - Router 源码》.md
2022-05-24 10:27:40 +08:00
reactjser 0ca68bd61f Update 241.精读《react-snippets - Router 源码》.md
成语使用有误
2022-05-23 13:53:19 +08:00
ascoders 33e9a77a1b 241 2022-05-23 09:20:56 +08:00
ascoders fb492c7c6b Merge branch 'master' of https://github.com/ascoders/weekly 2022-05-18 14:16:08 +08:00
ascoders dd0f3b3d8b fix: typo 2022-05-18 14:16:05 +08:00
黄子毅 b68d83f282 Merge pull request #417 from kongmoumou/patch-2
Update 240.精读《React useEvent RFC》.md
2022-05-17 20:44:37 +08:00
kongmoumou 663eb89301 Update 240.精读《React useEvent RFC》.md 2022-05-16 14:52:42 +08:00
ascoders 01d658e744 240 2022-05-16 09:14:17 +08:00
ascoders 36349d365d fix: 修复 nestjs 描述 2022-05-12 23:38:36 +08:00
ascoders 61bbf1db05 239 2022-05-09 09:26:58 +08:00
ascoders 688337f7c4 Merge branch 'master' of https://github.com/ascoders/weekly 2022-04-18 09:54:36 +08:00
ascoders 8ab24fca04 238 2022-04-18 09:52:43 +08:00
黄子毅 e043a91f07 Merge pull request #412 from Trojan0523/patch-1
fix: typo
2022-04-16 17:23:21 +08:00
trojan0523 448544786b fix: typo 2022-04-16 16:46:57 +08:00
黄子毅 f5859dbe08 Merge pull request #411 from FrankKai/master
fix: typo in 144.精读《Webpack5 新特性 - 模块联邦》
2022-04-16 15:17:00 +08:00
frankkai c83a2cd983 fix: typo in 144.精读《Webpack5 新特性 - 模块联邦》 2022-04-15 16:28:09 +08:00
黄子毅 b3c5ad948b Merge pull request #409 from careteenL/patch-2
fix: typo in 精读《Typescript 4.5-4.6 新特性》
2022-04-11 10:58:36 +08:00
Careteen a0cd51951e fix: typo in 精读《Typescript 4.5-4.6 新特性》 2022-04-11 09:37:04 +08:00
ascoders 0198bcc55b Merge branch 'master' of https://github.com/ascoders/weekly 2022-04-11 09:17:47 +08:00
ascoders 35d659397a 237 2022-04-11 09:16:30 +08:00
黄子毅 b08b26b691 Merge pull request #407 from october-rain/shiyu-fix
fix: spelling mistake in 167
2022-04-06 09:38:44 +08:00
ascoders 4b56ab4ed6 236 2022-04-06 09:34:09 +08:00
october-rain 5bfd19a828 fix: spelling mistake in 167 2022-04-05 20:54:13 +08:00
ascoders 5e75ab4be0 235 2022-03-28 10:13:58 +08:00
ascoders 7f1ec4b1c6 fix typo 2022-03-23 14:50:19 +08:00
ascoders 2214f4faad 234 2022-03-21 09:23:27 +08:00
ascoders a625f6f24c update readme 2022-03-14 11:16:02 +08:00
ascoders a66c2fb578 Merge branch 'master' of https://github.com/ascoders/weekly 2022-03-14 11:15:11 +08:00
ascoders 1662fad4ae 233 2022-03-14 11:15:05 +08:00
黄子毅 600563222d Merge pull request #402 from careteenL/patch-1
fix: typo in 232.SQL 聚合查询.md
2022-03-08 20:34:10 +08:00
Careteen cbc75a5353 fix: typo in 232.SQL 聚合查询.md 2022-03-08 09:50:54 +08:00
ascoders 4cb5a8400a Merge branch 'master' of https://github.com/ascoders/weekly 2022-03-07 09:47:44 +08:00
ascoders b7c0ee0700 232 2022-03-07 09:47:37 +08:00
黄子毅 9455f3b7d3 Merge pull request #400 from ringozzt/master
fix: typo in 精读《请停止 css-in-js 的行为》.md
2022-03-03 15:13:56 +08:00
zzt a639f81e7b fix: md typo in 精读《最佳前端面试题》及面试官技巧.md 2022-03-02 17:13:12 +08:00
zzt 3bbea7136f fix: typo in 精读《请停止 css-in-js 的行为》.md 2022-03-02 16:57:06 +08:00
ascoders 868900fb76 fix typo 2022-03-02 09:54:46 +08:00
ascoders 9e470a441a update readme 2022-02-28 08:52:52 +08:00
ascoders b1a43d4843 update readme 2022-02-28 08:44:55 +08:00
ascoders 9cb731cb36 231 2022-02-28 08:43:47 +08:00
ascoders 93828e08b2 230 2022-02-28 08:38:01 +08:00
ascoders 0b06c714fa 230 2022-02-21 09:57:08 +08:00
ascoders cf5cc265cb 229 2022-02-14 09:05:48 +08:00
ascoders 366c6c90c3 fix typo 2022-02-07 14:22:35 +08:00
ascoders 98cc6ace48 Merge branch 'master' of https://github.com/ascoders/weekly 2022-02-07 09:28:50 +08:00
ascoders 751d78be0a 228 2022-02-07 09:28:34 +08:00
黄子毅 acfb7fd442 Merge pull request #393 from xch1029/patch-1
typo zustand 源码
2022-01-24 19:53:26 +08:00
xch1029 750e2cec32 Update 227. 精读《zustand 源码》.md 2022-01-24 11:18:15 +08:00
ascoders d01463fc9e Merge branch 'master' of https://github.com/ascoders/weekly 2022-01-24 08:50:23 +08:00
ascoders 534e5a0f49 227 2022-01-24 08:50:13 +08:00
黄子毅 3b2b2adde4 Merge pull request #391 from tianheg/main
fix typo
2022-01-17 17:08:31 +08:00
Jim Gao e905d37dab fix typo 2022-01-17 16:57:43 +08:00
ascoders 14dbde8615 Merge branch 'master' of https://github.com/ascoders/weekly 2022-01-17 09:08:26 +08:00
ascoders 62afd07694 226 2022-01-17 09:08:06 +08:00
黄子毅 933862c0cd Update 222.精读《深入了解现代浏览器四》.md 2022-01-11 22:09:34 +08:00
黄子毅 d4d87b029f Merge pull request #388 from 11os/patch-1
Update 72.精读《REST, GraphQL, Webhooks, & gRPC 如何选型》.md
2022-01-10 19:25:45 +08:00
11os ed6a7d10c2 Update 72.精读《REST, GraphQL, Webhooks, & gRPC 如何选型》.md 2022-01-10 16:13:08 +08:00
ascoders 6e36e81748 Merge branch 'master' of https://github.com/ascoders/weekly 2022-01-10 08:59:15 +08:00
ascoders f3a50ce6f7 225 2022-01-10 08:59:12 +08:00
黄子毅 c8c9f7c7d9 Update 221.精读《深入了解现代浏览器三》.md 2022-01-07 08:40:32 +08:00
黄子毅 7eeecc34d2 Merge pull request #386 from itLeizhou/patch-1
Update 1.精读《js 模块化发展》.md
2022-01-07 08:39:12 +08:00
itLeizhou c8448215c7 Update 1.精读《js 模块化发展》.md 2022-01-06 14:32:25 +08:00
ascoders 050f00b3e5 Merge branch 'master' of https://github.com/ascoders/weekly 2022-01-04 08:51:18 +08:00
ascoders b661e2329a 224 2022-01-04 08:51:11 +08:00
黄子毅 8691358638 Merge pull request #383 from chenjiahan/master
fix: typo in 精读《React 18》.md
2022-01-01 16:18:55 +08:00
ascoders 8116ed1f72 fix typo 2021-12-27 17:57:47 +08:00
ascoders 6ccc12acfe Merge branch 'master' of https://github.com/ascoders/weekly 2021-12-27 09:03:21 +08:00
ascoders 41a9d364a0 223 2021-12-27 09:03:18 +08:00
neverland 499a766252 fix: typo in 精读《React 18》.md 2021-12-23 11:01:35 +08:00
黄子毅 af0f3dd04d Merge pull request #382 from LiuL0703/patch-9
fix: typo
2021-12-22 10:48:29 +08:00
LiuL0703 3cd0bb8761 fix: typo 2021-12-22 09:56:12 +08:00
ascoders fa6d19f415 update readme 2021-12-20 10:21:47 +08:00
ascoders 7fce362c17 Merge branch 'master' of https://github.com/ascoders/weekly 2021-12-20 10:21:00 +08:00
ascoders fe93fce942 222 2021-12-20 10:19:04 +08:00
黄子毅 088df6eda6 Merge pull request #380 from ihavecoke/master
Update 221.精读《深入了解现代浏览器三》.md
2021-12-17 09:53:09 +08:00
mixbo 62ed443234 Update 221.精读《深入了解现代浏览器三》.md
doc: 修改文案中的错别字
2021-12-17 00:30:32 +08:00
ascoders 49fd947437 221 2021-12-13 09:13:53 +08:00
ascoders d4eb4ee606 update readme 2021-12-06 10:33:19 +08:00
ascoders 04c67e183d 220 2021-12-06 10:32:36 +08:00
ascoders 86ccf45f85 Merge branch 'master' of https://github.com/ascoders/weekly 2021-11-29 09:03:00 +08:00
ascoders 3f9febce36 219 2021-11-29 09:02:55 +08:00
黄子毅 140da78305 Merge pull request #373 from AmagiDDmxh/patch-1
Update 186.精读《设计模式 - State 状态模式》.md
2021-11-27 21:41:04 +08:00
AmagiDDmxh 12c85b9418 Update 186.精读《设计模式 - State 状态模式》.md
Provides a more comprehensive and work example
2021-11-27 10:12:59 +08:00
黄子毅 4cee1951da Merge pull request #372 from senfish/master
fix: typo
2021-11-22 21:38:23 +08:00
915016229 548fde2f85 fix: typo 2021-11-22 11:59:09 +08:00
ascoders 1ed6ac5c48 218 2021-11-22 09:13:34 +08:00
ascoders 4b1fa0c7c6 217 2021-11-15 09:36:07 +08:00
ascoders d0609b0e77 Merge branch 'master' of https://github.com/ascoders/weekly 2021-11-08 09:06:04 +08:00
ascoders 0cc317e9f1 216 2021-11-08 09:05:39 +08:00
黄子毅 8dad1ef1af Update 154. 精读《用 React 做按需渲染》.md 2021-11-07 21:17:32 +08:00
黄子毅 e81a26600f Update 85.精读《手写 SQL 编译器 - 智能提示》.md 2021-11-05 19:15:27 +08:00
ascoders 18a495ec39 Merge branch 'master' of https://github.com/ascoders/weekly 2021-11-01 09:07:05 +08:00
ascoders 713c88518f 215 2021-11-01 09:07:01 +08:00
黄子毅 a7fa58f620 Merge pull request #367 from XBIsland/fix#170
update 170.精读《设计模式\ -\ Prototype\ 原型模式》.md
2021-10-31 22:13:12 +08:00
黄子毅 50e042c90d Merge pull request #366 from XBIsland/fix#169
update 169.精读《设计模式 - Factory Method 工厂方法》.md
2021-10-31 22:12:58 +08:00
陈煜坚 b2c02f7892 update 170.精读《设计模式\ -\ Prototype\ 原型模式》.md 2021-10-31 16:53:16 +08:00
陈煜坚 b91e09f170 update 169.精读《设计模式 - Factory Method 工厂方法》.md 2021-10-31 15:42:46 +08:00
黄子毅 68cd8d1966 Merge pull request #364 from kongmoumou/patch-1
Update 214.精读《web streams》.md
2021-10-26 15:56:15 +08:00
kongmoumou fa28d1513c Update 214.精读《web streams》.md
根据上下文理解是否应该是「可读流」😂
2021-10-26 00:11:03 +08:00
ascoders 14f93ddd71 fix bad image 2021-10-25 21:59:17 +08:00
ascoders da81242d9b 214 2021-10-25 09:15:41 +08:00
ascoders 89c50fc032 Merge branch 'master' of https://github.com/ascoders/weekly 2021-10-18 09:19:33 +08:00
ascoders c81446eff5 213 2021-10-18 09:19:30 +08:00
黄子毅 d5f96ff5a3 Merge pull request #360 from sliuqin/patch-1
Update 212.精读《可维护性思考》.md
2021-10-14 10:36:12 +08:00
黄子毅 db3449ace1 Merge pull request #361 from kaichii/patch-2
错别字修改
2021-10-14 10:35:57 +08:00
Kai Qi 1dddebbb1f 错别字修改 2021-10-13 19:09:27 +08:00
蔡小伦 77e2da3f2d Update 212.精读《可维护性思考》.md
typo fixed
2021-10-13 15:44:04 +08:00
ascoders 019076e87f 212 2021-10-11 09:14:59 +08:00
ascoders 18e058af5b Merge branch 'master' of https://github.com/ascoders/weekly 2021-10-11 09:11:57 +08:00
ascoders fe7b930b95 212 2021-10-11 09:10:58 +08:00
黄子毅 1a9be6aaf8 Merge pull request #358 from xiangwan/patch-1
Update 2.精读《模态框的最佳实践》.md
2021-09-28 22:33:53 +08:00
向晚 3110990b90 Update 2.精读《模态框的最佳实践》.md
fixes  #357
2021-09-28 19:55:16 +08:00
黄子毅 e5e2d49ae1 Merge pull request #354 from fltenwall/master
fix:#103.精读《为什么专家不再关心技术细节》修正
2021-09-27 08:54:38 +08:00
ascoders 94381de60b update readme 2021-09-27 08:51:06 +08:00
fltenwall 70f20e6861 #103修正 2021-09-24 11:45:53 +08:00
ascoders fab11f31d2 210 2021-09-13 09:10:27 +08:00
ascoders 8af572d9f9 209 2021-09-06 08:08:29 +08:00
ascoders 0c542af0ab update readme 2021-08-30 09:53:37 +08:00
ascoders 4b3aae675d Merge branch 'master' of https://github.com/ascoders/weekly 2021-08-30 09:52:57 +08:00
ascoders 532d3bd6f0 208 2021-08-30 09:52:54 +08:00
黄子毅 cb20d65c2c Merge pull request #349 from imba-tjd/patch-1
fix 52 typo: 查分 -> 差分
2021-08-29 17:03:26 +08:00
谭九鼎 07b6017451 fix 52 typo: 查分 -> 差分 2021-08-28 21:10:30 +08:00
黄子毅 2c5f8ca4f3 Merge pull request #347 from feikerwu/patch-2
typo: fix typo in 207.精读《Typescript infer 关键字》.md
2021-08-26 10:08:32 +08:00
ascoders cd543b1fd4 fix typo 2021-08-26 10:06:13 +08:00
feikerwu 88560b6626 typo: fix typo in 207.精读《Typescript infer 关键字》.md 2021-08-23 12:01:17 +08:00
ascoders 1cf5e339c9 207 2021-08-23 10:05:52 +08:00
ascoders 4ac67508f4 206 2021-08-16 09:14:22 +08:00
ascoders d2044c4636 Merge branch 'master' of https://github.com/ascoders/weekly 2021-08-09 10:44:14 +08:00
ascoders 59629228f9 205 2021-08-09 10:44:03 +08:00
黄子毅 eeb4272d49 Merge pull request #339 from Trojan0523/patch-1
fix: typo
2021-08-06 10:29:21 +08:00
ascoders e8ae8557d2 fix link 2021-08-02 10:57:48 +08:00
ascoders f0ab10ac09 204 2021-08-02 10:51:49 +08:00
trojan0523 b7b0409cd5 fix: typo 2021-07-22 10:09:17 +08:00
ascoders 13963fbdd2 Merge branch 'master' of https://github.com/ascoders/weekly 2021-07-19 10:07:39 +08:00
ascoders 9b9d02b9de 203 2021-07-19 10:07:31 +08:00
黄子毅 ec65f684bb Merge pull request #334 from LiuL0703/patch-8
fix:typo
2021-07-12 18:34:20 +08:00
ascoders 022b1fdbbd 202 2021-07-12 13:41:29 +08:00
黄子毅 ff121c7e1b Merge pull request #335 from netcore-jroger/patch-1
Update 201.精读《算法 - 二叉树》.md
2021-07-07 18:06:51 +08:00
JRoger a04380c1c1 Update 201.精读《算法 - 二叉树》.md 2021-07-06 08:54:59 +08:00
Linear-Enter 3646b36db5 fix:typo 2021-07-05 10:59:33 +08:00
ascoders aaaa28953b update readme 2021-07-05 09:06:10 +08:00
ascoders 9609101870 update readme 2021-07-05 09:05:17 +08:00
黄子毅 afb601a303 201 2021-07-05 09:02:37 +08:00
ascoders c72477ed7d update readme 2021-06-28 10:46:52 +08:00
ascoders dc09550e0c Merge branch 'master' of https://github.com/ascoders/weekly 2021-06-28 10:46:20 +08:00
ascoders dc66e76dd1 200 2021-06-28 10:46:14 +08:00
黄子毅 cecef87d13 Merge pull request #329 from Turkyden/patch-1
Update 140.精读《结合 React 使用原生 Drag Drop API》.md
2021-06-18 11:11:39 +08:00
Dengju Deng d0ca3a348f Update 140.精读《结合 React 使用原生 Drag Drop API》.md 2021-06-17 18:01:49 +08:00
ascoders de4e309f39 199 2021-06-15 10:09:22 +08:00
ascoders ea56ac10ce Merge branch 'master' of https://github.com/ascoders/weekly 2021-06-07 09:33:53 +08:00
ascoders 90660c6d34 198 2021-06-07 09:33:47 +08:00
黄子毅 fcf04083a5 Merge pull request #322 from rmlzy/patch-3
Update 41.精读《Ant Design 3.0 背后的故事》.md
2021-05-31 16:05:52 +08:00
黄子毅 a1ea12a736 Merge pull request #323 from rmlzy/patch-4
Update 52.精读《图解 ES 模块》.md
2021-05-31 16:05:25 +08:00
黄子毅 02a795bb8a Merge pull request #324 from rmlzy/patch-5
Update 137.精读《当我在分享的时候,我在做什么?》.md
2021-05-31 16:05:12 +08:00
黄子毅 6bffed8eb5 Merge pull request #325 from rmlzy/patch-6
Update 136.精读《极客公园 IFX - 下》.md
2021-05-31 16:04:47 +08:00
黄子毅 ddd5ceb74a Merge pull request #326 from rmlzy/patch-7
Update 135.精读《极客公园 IFX - 上》.md
2021-05-31 16:04:34 +08:00
rmlzy 3c6cee2f49 Update 135.精读《极客公园 IFX - 上》.md
修正错别字
2021-05-31 16:00:59 +08:00
rmlzy fc2f0c6f68 Update 136.精读《极客公园 IFX - 下》.md
修正错别字
2021-05-31 15:50:42 +08:00
rmlzy 0dcf208315 Update 137.精读《当我在分享的时候,我在做什么?》.md
修正错别字
2021-05-31 15:47:03 +08:00
rmlzy 7730834e83 Update 52.精读《图解 ES 模块》.md
修正错别字
2021-05-31 15:40:39 +08:00
rmlzy 91875ab232 Update 41.精读《Ant Design 3.0 背后的故事》.md
修正格式错误
2021-05-31 15:32:52 +08:00
黄子毅 d81e6d7813 Merge pull request #320 from rmlzy/patch-1
Update 20.精读《Nestjs》文档.md
2021-05-31 10:02:25 +08:00
黄子毅 3e299d85e9 Merge pull request #321 from rmlzy/patch-2
Update 119.精读《前端深水区》.md
2021-05-31 10:02:04 +08:00
ascoders 455e1693f4 197 2021-05-31 10:01:23 +08:00
rmlzy 2a17e62107 Update 119.精读《前端深水区》.md
修正标点符号
2021-05-31 09:54:38 +08:00
rmlzy 0a090cf0a7 Update 20.精读《Nestjs》文档.md
Nextjs 目前是 Providers 配合 `@Injectable()` 装饰器实现 DI
2021-05-31 09:50:16 +08:00
ascoders c13f0af435 Merge branch 'master' of https://github.com/ascoders/weekly 2021-05-24 13:50:42 +08:00
ascoders 8ee7995ddc fix typo error 2021-05-24 13:50:31 +08:00
黄子毅 0e8c749e68 Merge pull request #318 from neighborhood999/fix/typo
fix(196): typo
2021-05-24 10:49:54 +08:00
Jie Peng ee0450ffb2 fix(196): typo
Signed-off-by: Jie Peng <im@jiepeng.me>
2021-05-24 10:32:12 +08:00
ascoders e0d8be6916 196 2021-05-24 10:12:55 +08:00
ascoders 7c4d37a428 fix typo 2021-05-17 13:21:06 +08:00
ascoders eeaebad903 195 2021-05-17 09:43:00 +08:00
黄子毅 c624e7e607 Merge pull request #315 from kamilic/patch-1
Update 194.精读《算法基础数据结构》.md
2021-05-12 13:53:55 +08:00
kamilic 0f724a20cf Update 194.精读《算法基础数据结构》.md
chores: fix typo
2021-05-11 21:08:13 +08:00
黄子毅 c7922c4a0e Merge pull request #314 from jihchi/patch-1
Update 194.精读《算法基础数据结构》.md -- 連結正確但文字錯誤
2021-05-10 14:23:45 +08:00
Jihchi Lee 9d98f72fd3 Update 194.精读《算法基础数据结构》.md 2021-05-10 11:25:53 +08:00
ascoders db9a256efa update readme 2021-05-10 08:59:01 +08:00
ascoders aedfdd4b91 Merge branch 'master' of https://github.com/dt-fe/weekly 2021-05-10 08:57:02 +08:00
ascoders 99d5c51d42 194 2021-05-10 08:56:52 +08:00
黄子毅 086b733288 Merge pull request #313 from jihchi/patch-1
96.精读《useEffect%20完全指南》-- 修正錯誤依賴
2021-05-08 10:46:03 +08:00
Jihchi Lee 6f9ccaef9e Update 96.精读《useEffect 完全指南》.md 2021-05-07 12:01:13 +08:00
ascoders dba92f52b5 update 2021-04-25 10:10:15 +08:00
ascoders 61512fba55 193 2021-04-25 10:06:54 +08:00
ascoders 542faafe04 fix: 修复算法错误 2021-04-22 17:18:24 +08:00
ascoders 70468eae8a update readme 2021-04-19 09:23:05 +08:00
ascoders 9e0c2027d2 update readme 2021-04-17 17:41:05 +08:00
ascoders 6ac4e965bb update readme 2021-04-17 17:39:58 +08:00
ascoders 5edddb5890 update readme 2021-04-17 17:37:09 +08:00
ascoders 706ac612ff update readme 2021-04-17 17:29:15 +08:00
ascoders 444a56e333 update readme 2021-04-17 17:27:31 +08:00
ascoders 2e966d717d update readme 2021-04-17 17:25:42 +08:00
ascoders 237d856cfe update readme 2021-04-17 17:24:13 +08:00
ascoders d12c076774 update readme 2021-04-16 22:58:32 +08:00
ascoders 3e3cc53715 remove assets 2021-04-16 22:38:46 +08:00
ascoders ea4d30385a 按目录整理 2021-04-16 22:22:48 +08:00
ascoders 1b03871be8 fix lint 2021-04-16 17:25:53 +08:00
ascoders 42e068aab7 update readme 2021-04-16 17:17:52 +08:00
ascoders 280efe4545 191 2021-04-12 09:51:09 +08:00
ascoders 9b54c138a7 fix bug 2021-04-07 14:01:02 +08:00
ascoders c253b7d60f rename 190 2021-04-06 09:47:32 +08:00
ascoders 367bba2692 189 2021-04-06 09:46:50 +08:00
ascoders e888c4e22c 189 2021-03-29 10:33:40 +08:00
ascoders 8a3425b5e3 188 2021-03-22 07:52:11 +08:00
ascoders 9081bde65e 187 2021-03-15 08:43:49 +08:00
ascoders 4f783678da 186 2021-03-08 09:36:35 +08:00
ascoders 299a60a881 185 2021-03-01 08:59:43 +08:00
ascoders 7e651219b0 184 2021-02-22 10:11:00 +08:00
ascoders e1e7bc6f57 183 2021-02-01 09:05:50 +08:00
ascoders beed8434ec 182 2021-01-25 10:32:08 +08:00
ascoders ab13166c0a Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2021-01-20 15:12:50 +08:00
ascoders 71a48cde3b fix type 2021-01-20 15:12:30 +08:00
黄子毅 c603ac0d1d Merge pull request #297 from sosamuel/patch-1
Fix 校正笔误
2021-01-19 21:10:59 +08:00
Samuel Chia 316d3d98aa Fix 校正笔误 2021-01-18 10:43:42 +08:00
ascoders 0465cf43db 181 2021-01-18 10:14:59 +08:00
ascoders 65de19ddb6 181 2021-01-18 10:08:02 +08:00
ascoders 9c0f11cc55 181 2021-01-18 10:07:20 +08:00
ascoders 754ceb3d5a fix: typo 2021-01-10 11:28:15 +08:00
ascoders dcc5247ba0 fix typo 2021-01-10 11:23:26 +08:00
ascoders 02de198a8b 180 2021-01-10 11:21:15 +08:00
ascoders b7cfc4c6e3 179 2020-12-26 10:50:01 +08:00
ascoders 46a094e711 178 2020-12-20 23:22:19 +08:00
ascoders b5cd1b2379 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-12-13 21:45:29 +08:00
ascoders cfe94e6aa7 177 2020-12-13 21:44:46 +08:00
黄子毅 3362f25775 Merge pull request #289 from wiolem/patch-1
docs: typo onDropOver
2020-12-09 17:59:49 +08:00
William 87f9704831 Update 140.精读《结合 React 使用原生 Drag Drop API》.md 2020-12-09 10:40:49 +08:00
ascoders 6c7084dfe1 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-12-06 21:50:03 +08:00
ascoders 164590159d 176 2020-12-06 21:49:38 +08:00
黄子毅 b8ab46d26b Merge pull request #287 from spiritree/patch-3
Update 168.精读《设计模式 - Builder 生成器》.md
2020-12-03 20:19:48 +08:00
深樹 183b4fc41a Update 168.精读《设计模式 - Builder 生成器》.md
maybe misspell `Persion`->`Person`
2020-12-01 15:28:53 +08:00
ascoders 2b7ab34978 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-11-29 18:04:36 +08:00
ascoders 2ca7132569 175 2020-11-29 18:04:27 +08:00
黄子毅 aac9aa34f8 Merge pull request #283 from JeromeLin/patch-3
Update 003.精读前后端渲染之争.md
2020-11-29 11:29:25 +08:00
黄子毅 ad8a645462 Merge pull request #285 from spiritree/patch-1
Update 171.精读《设计模式 - Singleton 单例模式》.md
2020-11-26 21:46:06 +08:00
深樹 2dbe4d6368 Update 171.精读《设计模式 - Singleton 单例模式》.md
class method: `instance` -> `getInstance`
2020-11-25 19:14:31 +08:00
ascoders d76253ef75 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-11-22 11:10:11 +08:00
ascoders 46b68f1933 174 2020-11-22 11:09:40 +08:00
JeromeLin 8aab27bc3f Update 003.精读前后端渲染之争.md 2020-11-20 09:51:01 +08:00
黄子毅 22f118e1b7 Merge pull request #282 from JeromeLin/patch-2
Update 001.精读 js 模块化发展.md
2020-11-19 21:21:20 +08:00
JeromeLin 922eb6dc59 Update 001.精读 js 模块化发展.md 2020-11-19 17:37:21 +08:00
黄子毅 20526273d3 Merge pull request #281 from giscafer/patch-1
fix: typo
2020-11-18 10:35:33 +08:00
Nickbing Lao f3222c646b fix: typo
错别字
2020-11-16 16:09:23 +08:00
ascoders 9ae28c5e85 set img size 2020-11-15 16:45:15 +08:00
ascoders e72318adb4 173 2020-11-15 16:37:09 +08:00
ascoders 2682dc0504 update 2020-11-08 20:16:33 +08:00
ascoders 8b4611b47a 172 2020-11-08 20:12:56 +08:00
ascoders 25938c60a6 171 2020-11-01 21:41:08 +08:00
ascoders 9920f0dbb7 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-10-24 20:50:18 +08:00
ascoders 90f7d89db7 170 2020-10-24 20:50:04 +08:00
黄子毅 6ddbdbbbb2 Merge pull request #276 from NaturelLee/patch-1
Correct Hooks implimentation
2020-10-20 15:44:25 +08:00
NaturelLee 3c8c9fd5ef Correct Hooks implimentation
Correct Hooks implimentation from Array to single linked list
2020-10-20 15:35:08 +08:00
ascoders 91a9bf82fd fix bug 2020-10-19 10:22:17 +08:00
ascoders 4603ac77d8 169 2020-10-18 11:25:55 +08:00
ascoders 998940af1b 168 2020-10-10 17:40:42 +08:00
ascoders 24ed724eab fix: 放大图片 2020-10-07 19:32:11 +08:00
ascoders 33b71628ab update title 2020-09-24 20:15:28 +08:00
ascoders 2dbf1429ba 167 2020-09-21 09:45:35 +08:00
ascoders 9867c1ee9d 166 2020-09-14 09:52:10 +08:00
ascoders e8a0539984 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-09-07 11:28:18 +08:00
ascoders 685e8ba32a 165 2020-09-07 11:27:07 +08:00
黄子毅 6c9493df7b Merge pull request #268 from spiritree/patch-1
Update 163.精读《Spring 概念》.md
2020-09-02 09:41:32 +08:00
深樹 1994438792 Update 163.精读《Spring 概念》.md
`lass` -> `class`
2020-09-01 16:04:21 +08:00
ascoders 60be3e354b fix 2020-08-31 10:25:00 +08:00
ascoders 9d0bb5c996 163 2020-08-31 10:22:21 +08:00
ascoders 304dc5fc36 163 2020-08-24 09:35:45 +08:00
ascoders 819b6d7452 162 2020-08-17 10:24:38 +08:00
ascoders b066c8a5d7 161 2020-08-10 10:11:03 +08:00
ascoders 80fb60c6fb 160 2020-07-27 09:37:27 +08:00
ascoders 6a2031899d 159 2020-07-20 09:55:34 +08:00
ascoders aedcc2fd56 158 2020-07-13 10:26:30 +08:00
ascoders 757eb403ac update 2020-07-06 09:50:38 +08:00
ascoders 87c2745395 156 2020-06-22 09:48:19 +08:00
ascoders 0d3d8ef54c fix bug 2020-06-15 22:56:52 +08:00
ascoders 53d5ee1960 155 2020-06-15 09:42:30 +08:00
ascoders 92a8dfc55c Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-06-08 09:53:32 +08:00
ascoders c59f46cc1d 154 2020-06-08 09:53:17 +08:00
黄子毅 ef3904a2f6 Merge pull request #253 from justjavac/patch-1
fix: snowpack issue 链接
2020-06-01 23:05:43 +08:00
迷渡 994c5844d9 fix: snowpack issue 链接 2020-06-01 21:02:00 +08:00
ascoders 3881e51b08 153 2020-06-01 09:49:07 +08:00
ascoders 815ae1367a fix typo 2020-05-25 18:20:56 +08:00
ascoders 84ed47dbdf Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-05-25 09:29:02 +08:00
ascoders 252980724a 152 2020-05-25 09:28:53 +08:00
黄子毅 225aab476d Merge pull request #250 from spiritree/patch-1
Update 148. 精读《React Error Boundaries》.md
2020-05-22 18:29:16 +08:00
深樹 f065539266 Update 148. 精读《React Error Boundaries》.md
FQA -> FAQ
2020-05-22 16:55:03 +08:00
ascoders f8d81fc3f5 151 2020-05-18 08:40:17 +08:00
ascoders 43e264cc07 update 2020-05-11 13:48:07 +08:00
ascoders 957737959f 150 2020-05-11 09:39:14 +08:00
ascoders 000e6511e6 149 2020-04-27 13:01:48 +08:00
ascoders a652284bbc 148 2020-04-20 09:31:08 +08:00
ascoders 1b88e5ac06 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-04-13 09:35:39 +08:00
ascoders 79ba9ec6a1 147 2020-04-13 09:35:36 +08:00
黄子毅 a4acf09d8b Merge pull request #244 from lz-lee/patch-1
Update 145.精读《React Router v6》.md
2020-04-10 14:57:48 +08:00
lz-lee 872ef2e346 Update 145.精读《React Router v6》.md 2020-04-09 10:44:37 +08:00
黄子毅 8795ae0aaa Merge pull request #243 from Kerminate/v2
fix: 修复引用传参
2020-04-07 11:22:51 +08:00
Kerminate 23c4190d7e fix: 修复引用传参 2020-04-07 10:25:59 +08:00
Kerminate 75d0109102 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-04-07 10:23:27 +08:00
Kerminate 893bb2d97a Merge branch 'master' of https://github.com/dt-fe/weekly into v2 2020-04-07 10:22:44 +08:00
ascoders 0c2c8c6c03 146 2020-04-07 09:32:13 +08:00
ascoders 97a313ca1e 145 2020-03-30 09:30:17 +08:00
黄子毅 3fd57e26b8 Merge pull request #240 from LiuL0703/patch-6
fix:typo
2020-03-24 11:08:49 +08:00
Linear-Enter aee6f6d6bb fix:typo 2020-03-23 10:03:02 +08:00
ascoders 00fd6e541a 144 2020-03-23 09:37:11 +08:00
ascoders 6cc1af4ca9 143 2020-03-23 09:36:17 +08:00
ascoders ed27df0fe2 update 2020-03-16 09:02:46 +08:00
ascoders 14ebdf19ca 142 2020-03-09 09:13:28 +08:00
ascoders e89a1b0c7e Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-03-02 09:16:52 +08:00
ascoders a1bd0ec7a5 141 2020-03-02 09:16:38 +08:00
黄子毅 82d3665606 Merge pull request #235 from changyuqing/patch-1
Update 140.精读《结合 React 使用原生 Drag Drop API》.md
2020-02-26 18:36:51 +08:00
changyuqing 67a289e750 Update 140.精读《结合 React 使用原生 Drag Drop API》.md 2020-02-26 15:16:28 +08:00
ascoders 1b0a7ed8ea finish 140 2020-02-24 09:08:57 +08:00
ascoders 21ba3e0640 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-02-17 09:22:38 +08:00
ascoders d5159b914e 139 2020-02-17 09:20:48 +08:00
黄子毅 c45a3d09d5 Merge pull request #232 from vivaxy/patch-1
Update link to 《mysql 上下文无关文法集合》
2020-02-12 12:12:36 +08:00
vivaxy c3ea6f2b17 Update link to 《mysql 上下文无关文法集合》 2020-02-12 10:02:35 +08:00
ascoders fb98d1febc fix typo 2020-02-11 13:07:25 +08:00
ascoders 036a9779eb Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-02-10 09:29:23 +08:00
ascoders beb89cf31b 138 2020-02-10 09:28:49 +08:00
黄子毅 5d65a777bd Merge pull request #231 from ihavecoke/patch-1
Update 002.精读模态框的最佳实践.md
2020-02-06 09:50:40 +08:00
coke 115d108404 Update 002.精读模态框的最佳实践.md
Model => Modal
2020-02-05 19:16:32 +08:00
黄子毅 f573e16473 Merge pull request #230 from Jack-Lo/v2
笔误修正
2020-01-22 11:15:12 +08:00
jack-Lo ac53e3a325 笔误修正 2020-01-21 20:34:57 +08:00
ascoders d3dad3b151 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2020-01-13 09:18:33 +08:00
ascoders 7843682b1e 137 2020-01-13 09:18:24 +08:00
黄子毅 11c34a9edc Merge pull request #227 from Kerminate/v2
fix: 修复用词准确性
2020-01-06 22:21:21 +08:00
ascoders 923c3990f7 update 2020-01-06 10:06:27 +08:00
Kerminate b091d40925 fix: 修复用词准确性 2020-01-06 09:52:27 +08:00
ascoders 5c76c9d567 135 2020-01-06 09:49:00 +08:00
ascoders 0ad9a98d33 135 2019-12-30 09:01:22 +08:00
ascoders b9fb7c80f2 134 2019-12-23 09:49:37 +08:00
ascoders 03519c41f6 133 2019-12-16 09:49:01 +08:00
ascoders 8ff1e9e21a Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2019-12-09 09:50:52 +08:00
ascoders 93882be318 132 2019-12-09 09:50:38 +08:00
黄子毅 d0dee4bbd7 Merge pull request #222 from LiuL0703/patch-5
fix:依赖取数
2019-12-06 09:17:41 +08:00
Linear-Enter 3cc44000eb fix:update 2019-12-06 00:29:06 +08:00
Linear-Enter 46cdabeb86 fix:依赖取数
依赖取数执行onErrorRetry的时机是config里的shouldRetryOnError为true才会触发
2019-12-06 00:11:30 +08:00
ascoders 71258f1b83 fix typo error 2019-12-02 15:57:52 +08:00
ascoders 1d95103bfb Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2019-12-02 15:51:38 +08:00
ascoders b9d9292040 131 2019-12-02 15:50:57 +08:00
黄子毅 7ce55795a2 Merge pull request #220 from LiuL0703/patch-4
fix: update
2019-11-29 17:05:56 +08:00
Linear-Enter 2a420c455e fix: update
源码中批量更新策略已从unstable_batchedUpdates换为useReducer
2019-11-29 15:51:04 +08:00
ascoders 069cf8f947 130 2019-11-25 08:58:49 +08:00
ascoders af86669e18 fix typo 2019-11-22 13:36:30 +08:00
ascoders 50c4409e29 fix typo 2019-11-22 13:35:05 +08:00
黄子毅 d8f2e0977d Merge pull request #163 from vivaxy/patch-1
Update 64.精读《手写 SQL 编译器 - 词法分析》.md
2019-06-07 22:05:26 +08:00
vivaxy f4e75f5e21 Update 64.精读《手写 SQL 编译器 - 词法分析》.md
Fix syntax error
2019-06-07 14:30:27 +08:00
304 changed files with 27713 additions and 124 deletions
+174
View File
@@ -0,0 +1,174 @@
本系列是 SQL 系列的开篇,介绍一些宏观与基础的内容。
## SQL 是什么?
SQL 是一种结构化查询语言,用于管理关系型数据库,我们 90% 接触的都是查询语法,但其实它包含完整的增删改查和事物处理功能。
## 声明式特性
SQL 属于声明式编程语言,而现代通用编程语言一般都是命令式的。但是不要盲目崇拜声明式语言,比如说它未来会代替低级的命令式语言,因为声明式本身也有它的缺点,它与命令式语言也有相通的地方。
为什么我们觉得声明式编程语言更高级?因为声明式语言抽象程度更高,比如 `select * from table1` 仅描述了要从 table1 查询数据,但查询的具体步骤的完全没提,这背后可能存在复杂的索引优化与锁机制,但我们都无需关心,这简直是编程的最高境界。
那为什么现在所有通用业务代码都是命令式呢?因为 **命令式给了我们描述具体实现的机会** ,而通用领域的编程正需要建立在严谨的实现细节上。比如校验用户权限这件事,即便 AI 编程提供了将 “登陆用户仅能访问有权限的资源” 转化为代码的能力,我们也不清楚资源具体指哪些,以及在权限转移过程中的资源所有权属于谁。
SQL 之所以能保留声明式特性,完全因为锁定了关系型数据管理这个特定领域,而恰恰对这个领域的需求是标准化且可枚举的,才使声明式成为可能。
基于命令式语言也完全可拓展出声明式能力,比如许多 ORM 提供了类似 `select({}).from({}).where({})` 之类的语法,甚至一个 `login()` 函数也是声明式编程的体现,因为调用者无需关心是如何登陆的,总之调用一下就完成了登陆,这不就是声明式的全部精髓吗?
## 语法分类
作为关系型数据库管理工具,SQL 需要定义、操纵与控制数据。
数据定义即修改数据库与表级别结构,这些是数据结构,或者是数据元信息,它不代表具体数据,但描述数据的属性。
数据操纵即修改一行行具体数据,增删改查。
数据控制即对事务、用户权限的管理与控制。
### 数据定义
DDLData Definition Language)数据定义,包括 `CREATE` `DROP` `ALTER` 方法。
### 数据操纵
DMLData Manipulation Language)数据操纵,包括 `SELECT` `INSERT` `UPDATE` `DELETE` 方法。
### 数据控制
DCLData Control Language)数据控制,包括 `COMMIT``ROLLBACK` 等。
所有 SQL 操作都围绕这三种类型,其中数据操纵几乎占了 90% 的代码量,毕竟数据查询的诉求远大于写,数据写入对应数据采集,而数据查询对应数据分析,数据分析领域能玩出的花样远比数据采集要多。
PS:有些情况下,会把最重要的 `SELECT` 提到 DQLData Query Language)分类下,这样分类就变成了四个。
## 集合运算
SQL 世界的第一公民是集合,就像 JAVA 世界第一公民是对象。我们只有以集合的视角看待 SQL,才能更好的理解它。
何为集合视角,即所有的查询、操作都是二维数据结构中进行的,而非小学算术里的单个数字间加减乘除关系。
集合的运算一般有 `UNION` 并集、`EXCEPT` 差集、`INTERSECT` 交集,这些都是以行为单位的操作,而各种 JOIN 语句则是以列为单位的集合运算,也是后面提到的连接查询。
只要站在二维数据结构中进行思考,运算无非是横向或纵向的操作。
## 数据范式
数据范式分为五层,每层要求都比上一层更严苛,因此是一个可以逐步遵循的范式。数据范式要求数据越来越解耦,减少冗余。
比如第一范式要求每列都具有原子性,即都是不可分割的最小数据单元。如果数据采集时,某一列作为字符串存储,并且以 "|" 分割表示省市区,那么它就不具有原子性。
当然实际生产过程往往不都遵循这种标准,因为表不是孤立的,在数据处理流中,可能在某个环节再把列原子化,而原始数据为了压缩体积,进行列合并处理。
希望违反范式的还不仅是底层表,现在大数据处理场景下,越来越多的业务采用大宽表结构,甚至故意进行数据冗余以提升查询效率,列存储引擎就是针对这种场景设计的,所以数据范式在大数据场景下是可以变通的,但依然值得学习。
## 聚合
当采用 GROUP BY 分组聚合数据时,如希望针对聚合值筛选,就不能用 WHERE 限定条件了,因为 WHERE 是基于行的筛选,而不是针对组合的。(GROUP BY 对数据进行分组,我们称这些组为 “组合”),所以需要使用针对组合的筛选语句 HAVING:
```sql
SELECT SUM(pv) FROM table
GROUP BY city
HAVING AVG(uv) > 100
```
这个例子中,如果 HAVING 换成 WHERE 就没有意义,因为 WHERE 加聚合条件时,需要对所有数据进行合并,不符合当前视图的详细级别。(关于视图详细级别,在我之前写的 [精读《什么是 LOD 表达式》](https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/215.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%BB%80%E4%B9%88%E6%98%AF%20LOD%20%E8%A1%A8%E8%BE%BE%E5%BC%8F%E3%80%8B.md) 有详细说明)。
聚合如此重要,是因为我们分析数据必须在高 LEVEL 视角看,明细数据是看不出趋势的。而复杂的需求往往伴随着带有聚合的筛选条件,明白 SQL 是如何支持的非常重要。
## CASE 表达式
CASE 表达式分为简单与搜索 CASE 表达式,简单表达式:
```sql
SELECT CASE pv WHEN 1 THEN 'low' ELSE 'high' END AS quality
```
上面的例子利用 CASE 简单表达式形成了一个新字段,这种模式等于生成了业务自定义临时字段,在对当前表进行数据加工时非常有用。搜索 CASE 表达式能力完全覆盖简单 CASE 表达式:
```sql
SELECT CASE WHEN pv < 100 THEN 'low' ELSE 'high' END AS quality
```
可以看到,搜索 CASE 表达式可以用 “表达式” 描述条件,可以轻松完成更复杂的任务,甚至可以在表达式里使用子查询、聚合等手段,这些都是高手写 SQL 的惯用技巧,所以 CASE 表达式非常值得深入学习。
## 复杂查询
SELECT 是 SQL 最复杂的部分,其中就包含三种复杂查询模式,分别是连接查询与子查询。
### 连接查询
指 JOIN 查询,比如 LEFT JOIN、RIGHT JOIN、INNER JOIN。
在介绍聚合时我们提到了,连接查询本质上就是对列进行拓展,而两个表之间不会无缘无故合成一个,所以必须有一个外键作为关系纽带:
```sql
SELECT A.pv, B.uv
FROM table1 as t1 LEFT JOIN table2 AS P t2
ON t1.productId = t2.productId
```
连接查询不仅拓展了列,还会随之拓展行,而拓展方式与连接的查询的类型有关。除了连接查询别的表,还可以连接查询自己,比如:
```sql
SELECT t1.pv AS pv1, P2.pv AS pv2
FROM tt t1, tt t2
```
这种子连接查询结果就是自己对自己的笛卡尔积,可通过 WHERE 筛选去重,后面会有文章专门介绍。
### 子查询与视图
子查询就是 SELECT 里套 SELECT,一般来说 SELECT 会从内到外执行,只有在关联子查询模式下,才会从外到内执行。
而如果把子查询保存下来,就是一个视图,这个视图并不是实体表,所以很灵活,且数据会随着原始表数据而变化:
```sql
CREATE VIEW countryGDP (country, gdp)
AS
SELECT country, SUM(gdp)
FROM tt
GROUP BY country
```
之后 `countryGDP` 这个视图就可以作为临时表来用了。
这种模式其实有点违背 SQL 声明式的特点,因为定义视图类似于定义变量,如果继续写下去,势必会形成一定命令式思维逻辑,但这是无法避免的。
## 事务
当 SQL 执行一连串操作时,难免遇到不执行完就会出现脏数据的问题,所以事务可以保证操作的原子性。一般来说每个 DML 操作都是一个内置事务,而 SQL 提供的 START TRANSACTION 就是让我们可以自定义事务范围,使一连串业务操作都可以包装在一起,成为一个原子性操作。
对 SQL 来说,原子性操作是非常安全的,即失败了不会留下任何痕迹,成功了会全部成功,不会存在中间态。
## OLAP
OLAPOnLine Analytical Processing)即实时数据分析,是 BI 工具背后计算引擎实现的基础。
现在越来越多的 SQL 数据库支持了窗口函数实现,用于实现业务上的 runningSum 或 runningAvg 等功能,这些都是数据分析中很常见的。
以 runningSum 为例,比如双十一实时表的数据是以分钟为单位的实时 GMV,而我们要做一张累计到当前时间的 GMV 汇总折线图,Y 轴就需要支持 `running_sum(GMV)` 这样的表达式,而这背后可能就是通过窗口函数实现的。
当然也不是所有业务函数都由 SQL 直接提供,业务层仍需实现大量内存函数,在 JAVA 层计算,这其中一部分是需要下推到 SQL 执行的,只有内存函数与下推函数结合在一起,才能形成我们在 BI 工具看到的复杂计算字段效果。
## 总结
SQL 是一种声明式语言,一个看似简单的查询语句,在引擎层往往对应着复杂的实现,这就是 SQL 为何如此重要却又如此普及的原因。
虽然 SQL 容易上手,但要系统的理解它,还得从结构化数据与集合的概念开始进行思想转变。
不要小看 CASE 语法,它不仅与容易与编程语言的 CASE 语法产生混淆,本身结合表达式进行条件分支判断,是许多数据分析师在日常工作中最长用的套路。
现在使用简单 SQL 创建应用的场景越来越少了,但 BI 场景下,基于 SQL 的增强表达式场景越来越多了,本系列我就是以理解 BI 场景下查询表达式为目标创建的,希望能够学以致用。
> 讨论地址是:[精读《SQL 入门》· Issue #398 · ascoders/weekly](https://github.com/ascoders/weekly/issues/398)
**如果你想参与讨论,请 [点击这里](https://github.com/ascoders/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)
+199
View File
@@ -0,0 +1,199 @@
SQL 为什么要支持聚合查询呢?
这看上去是个幼稚的问题,但我们还是一步步思考一下。数据以行为粒度存储,最简单的 SQL 语句是 `select * from test`,拿到的是整个二维表明细,但仅做到这一点远远不够,出于以下两个目的,需要 SQL 提供聚合函数:
1. 明细数据没有统计意义,比如我想知道今天的营业额一共有多少,而不太关心某桌客人消费了多少。
2. 虽然可以先把数据查到内存中再聚合,但在数据量非常大的情况下很容易把内存撑爆,可能一张表一天的数据量就有 10TB,而 10TB 数据就算能读到内存里,聚合计算可能也会慢到难以接受。
另外聚合本身也有一定逻辑复杂度,而 SQL 提供了聚合函数与分组聚合能力,可以方便快速的统计出有业务价值的聚合数据,这奠定了 SQL 语言的分析价值,因此大部分分析软件直接采用 SQL 作为直接面向用户的表达式。
## 聚合函数
常见的聚合函数有:
- COUNT:计数。
- SUM:求和。
- AVG:求平均值。
- MAX:求最大值。
- MIN:求最小值。
### COUNT
COUNT 用来计算有多少条数据,比如我们看 id 这一列有多少条:
```sql
SELECT COUNT(id) FROM test
```
但我们发现其实查任何一列的 COUNT 都是一样的,那传入 id 有什么意义呢?没必要特殊找一个具体列指代呀,所以也可以写成:
```sql
SELECT COUNT(*) FROM test
```
但这两者存在微妙差异。SQL 存在一种很特殊的值类型 `NULL`,如果 COUNT 指定了具体列,则统计时会跳过此列值为 `NULL` 的行,而 `COUNT(*)` 由于未指定具体列,所以就算包含了 `NULL`,甚至某一行所有列都为 `NULL`,也都会包含进来。所以 `COUNT(*)` 查出的结果一定大于等于 `COUNT(c1)`
当然任何聚合函数都可以跟随查询条件 WHERE,比如:
```sql
SELECT COUNT(*) FROM test
WHERE is_gray = 1
```
### SUM
SUM 求和所有项,因此必须作用于数值字段,而不能用于字符串。
```sql
SELECT SUM(cost) FROM test
```
SUM 遇到 NULL 值时当 0 处理,因为这等价于忽略。
### AVG
AVG 求所有项均值,因此必须作用于数值字段,而不能用于字符串。
```sql
SELECT AVG(cost) FROM test
```
AVG 遇到 NULL 值时采用了最彻底的忽略方式,即 NULL 完全不参与分子与分母的计算,就像这一行数据不存在一样。
### MAX、MIN
MAX、MIN 分别求最大与最小值,与上面不同的是,也可以作用于字符串上,因此可以根据字母判断大小,从大到小依次对应 `a-z`,但即便能算,也没有实际意义且不好理解,因此不建议对字符串求极值。
```sql
SELECT MAX(cost) FROM test
```
### 多个聚合字段
虽然都是聚合函数,但 MAX、MIN 严格意义上不算是聚合函数,因为它们只是寻找了满足条件的行。可以看看下面两段查询结果的对比:
```sql
SELECT MAX(cost), id FROM test -- id: 100
SELECT SUM(cost), id FROM test -- id: 1
```
第一条查询可以找到最大值那一行的 id,而第二条查询的 id 是无意义的,因为不知道归属在哪一行,所以只返回了第一条数据的 id。
当然,如果同时计算 MAX、MIN,那么此时 id 也只返回第一条数据的值,因为这个查询结果对应了复数行:
```sql
SELECT MAX(cost), MIN(cost), id FROM test -- id: 1
```
基于这些特性,最好不要混用聚合与非聚合,也就是一条查询一旦有一个字段是聚合的,那么所有字段都要聚合。
现在很多 BI 引擎的自定义字段都有这条限制,因为混用聚合与非聚合在自定义内存计算时处理起来边界情况很多,虽然 SQL 能支持,但业务自定义的函数可能不支持。
## 分组聚合
分组聚合就是 GROUP BY,其实可以把它当作一种高级的条件语句。
举个例子,查询每个国家的 GDP 总量:
```sql
SELECT SUM(GDP) FROM amazing_table
GROUP BY country
```
返回的结果就会按照国家进行分组,这时,聚合函数就变成了在组内聚合。
其实如果我们只想看中、美的 GDP,用非分组也可以查,只是要分成两条 SQL:
```sql
SELECT SUM(GDP) FROM amazing_table
WHERE country = '中国'
SELECT SUM(GDP) FROM amazing_table
WHERE country = '美国'
```
所以 GROUP BY 也可理解为,将某个字段的所有可枚举的情况都查了出来,并整合成一张表,每一行代表了一种枚举情况,不需要分解为一个个 WHERE 查询了。
### 多字段分组聚合
GROUP BY 可以对多个维度使用,含义等价于表格查询时行/列拖入多个维度。
上面是 BI 查询工具视角,如果没有上下文,可以看下面这个递进描述:
- 按照多个字段进行分组聚合。
- 多字段组合起来成为唯一 Key,即 `GROUP BY a,b` 表示 a,b 合在一起描述一个组。
- `GROUP BY a,b,c` 查询结果第一列可能看到许多重复的 a 行,第二列看到重复 b 行,但在同一个 a 值内不会重复,c 在 b 行中同理。
下面是一个例子:
```sql
SELECT SUM(GDP) FROM amazing_table
GROUP BY province, city, area
```
查询结果为:
```text
浙江 杭州 余杭区
浙江 杭州 西湖区
浙江 宁波 海曙区
浙江 宁波 江北区
北京 .........
```
### GROUP BY + WHERE
WHERE 是根据行进行条件筛选的。因此 GROUP BY + WHERE 并不是在组内做筛选,而是对整体做筛选。
但由于按行筛选,其实组内或非组内结果都完全一样,所以我们几乎无法感知这种差异:
```sql
SELECT SUM(GDP) FROM amazing_table
GROUP BY province, city, area
WHERE industry = 'internet'
```
然而,忽略这个差异会导致我们在聚合筛选时碰壁。
比如要筛选出平均分大于 60 学生的成绩总和,如果不使用子查询,是无法在普通查询中在 WHERE 加聚合函数实现的,比如下面就是一个语法错误的例子:
```sql
SELECT SUM(score) FROM amazing_table
WHERE AVG(score) > 60
```
不要幻想上面的 SQL 可以执行成功,不要在 WHERE 里使用聚合函数。
### GROUP BY + HAVING
HAVING 是根据组进行条件筛选的。因此可以在 HAVING 使用聚合函数:
```sql
SELECT SUM(score) FROM amazing_table
GROUP BY class_name
HAVING AVG(score) > 60
```
上面的例子中可以正常查询,表示按照班级分组看总分,且仅筛选出平均分大于 60 的班级。
所以为什么 HAVING 可以使用聚合条件呢?因为 HAVING 筛选的是组,所以可以对组聚合后过滤掉不满足条件的组,这样是有意义的。而 WHERE 是针对行粒度的,聚合后全表就只有一条数据,无论过滤与否都没有意义。
但要注意的是,GROUP BY 生成派生表是无法利用索引筛选的,所以 WHERE 可以利用给字段建立索引优化性能,而 HAVING 针对索引字段不起作用。
## 总结
聚合函数 + 分组可以实现大部分简单 SQL 需求,在写 SQL 表达式时,需要思考这样的表达式是如何计算的,比如 `MAX(c1), c2` 是合理的,而 `SUM(c1), c2` 这个 `c2` 就是无意义的。
最后记住 WHERE 是 GROUP BY 之前执行的,HAVING 针对组进行筛选。
> 讨论地址是:[精读《SQL 聚合查询》· Issue #401 · ascoders/weekly](https://github.com/ascoders/weekly/issues/401)
**如果你想参与讨论,请 [点击这里](https://github.com/ascoders/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)
+176
View File
@@ -0,0 +1,176 @@
SQL 复杂查询指的就是子查询。
为什么子查询叫做复杂查询呢?因为子查询相当于查询嵌套查询,因为嵌套导致复杂度几乎可以被无限放大(无限嵌套),因此叫复杂查询。下面是一个最简单的子查询例子:
```sql
SELECT pv FROM (
SELECT pv FROM test
)
```
上面的例子等价于 `SELECT pv FROM test`,但因为把表的位置替换成了一个新查询,所以摇身一变成为了复杂查询!所以复杂查询不一定真的复杂,甚至可能写出和普通查询等价的复杂查询,要避免这种无意义的行为。
我们也要借此机会了解为什么子查询可以这么做。
### 理解查询的本质
当我们查一张表时,数据库认为我们在查什么?
这点很重要,因为下面两个语句都是合法的:
```sql
SELECT pv FROM test
SELECT pv FROM (
SELECT pv FROM test
)
```
为什么数据库可以把子查询当作表呢?为了统一理解这些概念,我们有必要对查询内容进行抽象理解:**任意查询位置都是一条或多条记录**。
比如 `test` 这张表,显然是多条记录(当然只有一行就是一条记录),而 `SELECT pv FROM test` 也是多条记录,然而因为 `FROM` 后面可以查询任意条数的记录,所以这两种语法都支持。
不仅是 `FROM` 可以跟单条或多条记录,甚至 `SELECT``GROUP BY``WHERE``HAVING` 后都可以跟多条记录,这个后面再说。
说到这,也就很好理解子查询的变种了,比如我们可以在子查询内使用 `WHERE``GROUP BY` 等等,因为无论如何,只要查询结果是多条记录就行了:
```sql
SELECT sum(people) as allPeople, sum(gdp), city FROM (
SELECT people, gdp, city FROM test
GROUP BY city
HAVING sum(gdp) > 10000
)
```
这个例子就有点业务含义了。子查询是从内而外执行的,因此我们先看内部的逻辑:按照城市分组,筛选出总 GDP 超过一万的所有地区的人口数量明细。外层查询再把人口数加总,这样就能对比每个 GDP 超过一万的地区,总人口和总 GDP 分别是多少,方便对这些重点城市做对比。
不过这个例子看起来还是不太自然,因为我们没必要写成复杂查询,其实简单查询也是等价的:
```sql
SELECT sum(people) as allPeople, sum(gdp), city FROM test
GROUP BY city
HAVING sum(gdp) > 10000
```
那为什么要多此一举呢?因为复杂查询的真正用法并不在这里。
### 视图
正因为子查询的存在,我们才可能以类似抽取变量的方式,抽取子查询,这个抽取出来的抽象就是视图:
```sql
CREATE VIEW my_table(people, gdp, city)
AS
SELECT sum(people) as allPeople, sum(gdp), city FROM test
GROUP BY city
HAVING sum(gdp) > 10000
SELECT sum(people) as allPeople, sum(gdp), city FROM my_table
```
这样的好处是,这个视图可以被多条 SQL 语句复用,不仅可维护性变好了,执行时也仅需查询一次。
要注意的是,SELECT 可以使用任何视图,但 INSERT、DELETE、UPDATE 用于视图时,需要视图满足一下条件:
1. 未使用 DISTINCT 去重。
2. FROM 单表。
3. 未使用 GROUP BY 和 HAVING。
因为上面几种模式都会导致视图成为聚合后的数据,不方便做除了查以外的操作。
另外一个知识点就是物化视图,即使用 MATERIALIZED 描述视图:
```sql
CREATE MATERIALIZED VIEW my_table(people, gdp, city)
AS ...
```
这种视图会落盘,为什么要支持这个特性呢?因为普通视图作为临时表,无法利用索引等优化手段,查询性能较低,所以物化视图是较为常见的性能优化手段。
说到性能优化手段,还有一些比较常见的理念,即把读的复杂度分摊到写的时候,比如提前聚合新表落盘或者对 CASE 语句固化为字段等,这里先不展开。
### 标量子查询
上面说了,WHERE 也可以跟子查询,比如:
```sql
SELECT city FROM test
WHERE gdp > (
SELECT avg(gdp) from test
)
```
这样可以查询出 gdp 大于平均值的城市。
那为什么不能直接这么写呢?
```sql
SELECT city FROM test
WHERE gdp > avg(gdp) -- 报错,WHERE 无法使用聚合函数
```
看上去很美好,但其实第一篇我们就介绍了,WHERE 不能跟聚合查询,因为这样会把整个父查询都聚合起来。那为什么子查询可以?因为子查询聚合的是子查询啊,父查询并没有被聚合,所以这才符合我们的意图。
所以上面例子不合适的地方在于,直接在当前查询使用 `avg(gdp)` 会导致聚合,而我们并不想聚合当前查询,但又要通过聚合拿到平均 GDP,所以就要使用子查询了!
回过头来看,为什么这一节叫标量子查询?标量即单一值,因为 `avg(gdp)` 聚合出来的只有一个值,所以 WHERE 可以把它当做一个单一数值使用。反之,如果子查询没有使用聚合函数,或 GROUP BY 分组,那么就不能使用 `WHERE >` 这种语法,但可以使用 `WHERE IN`,这涉及到单条与多条记录的思考,我们接着看下一节。
### 单条和多条记录
介绍标量子查询时说到了,`WHERE >` 的值必须时单一值。但其实 WHERE 也可以跟返回多条记录的子查询结果,只要使用合理的条件语句,比如 IN:
```sql
SELECT area FROM test
WHERE gdp IN (
SELECT max(gdp) from test
GROUP BY city
)
```
上面的例子,子查询按照城市分组,并找到每一组 GDP 最大的那条记录,所以如果数据粒度是区域,那么我们就查到了每个城市 GDP 最大的那些记录,然后父查询通过 WHERE IN 找到 gdp 符合的复数结果,所以最后就把每个城市最大 gdp 的区域列了出来。
但实际上 `WHERE >` 语句跟复数查询结果也不会报错,但没有任何意义,所以我们要理解查询结果是单条还是多条,在 WHERE 判断时选择合适的条件。WHERE 适合跟复数查询结果的语法有:`WHERE IN``WHERE SOME``WHERE ANY`
### 关联子查询
所谓关联子查询,即父子查询间存在关联,既然如此,子查询肯定不能单独优先执行,毕竟和父查询存在关联嘛,所以关联子查询是先执行外层查询,再执行内层查询的。要注意的是,对每一行父查询,子查询都会执行一次,因此性能不高(当然 SQL 会对相同参数的子查询结果做缓存)。
那这个关联是什么呢?关联的是每一行父查询时,对子查询执行的条件。这么说可能有点绕,举个例子:
```sql
SELECT * FROM test where gdp > (
select avg(gdp) from test
group by city
)
```
对这个例子来说,想要查找 gdp 大于按城市分组的平均 gdp,比如北京地区按北京比较,上海地区按上海比较。但很可惜这样做是不行的,因为父子查询没有关联,SQL 并不知道要按照相同城市比较,因此只要加一个 WHERE 条件,就变成关联子查询了:
```sql
SELECT * FROM test as t1 where gdp > (
select avg(gdp) from test as t2 where t1.city = t2.city
group by city
)
```
就是在每次判断 `WHERE gdp >` 条件时,重新计算子查询结果,将平均值限定在相同的城市,这样就符合需求了。
## 总结
学会灵活运用父子查询,就掌握了复杂查询了。
SQL 第一公民是集合,所以所谓父子查询就是父子集合的灵活组合,这些集合可以出现在几乎任何位置,根据集合的数量、是否聚合、关联条件,就派生出了标量查询、关联子查询。
更深入的了解就需要大量实战案例了,但万变不离其宗,掌握了复杂查询后,就可以理解大部分 SQL 案例了。
> 讨论地址是:[精读《SQL 复杂查询》· Issue #403 · ascoders/weekly](https://github.com/ascoders/weekly/issues/403)
**如果你想参与讨论,请 [点击这里](https://github.com/ascoders/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)
+154
View File
@@ -0,0 +1,154 @@
CASE 表达式分为简单表达式与搜索表达式,其中搜索表达式可以覆盖简单表达式的全部能力,我也建议只写搜索表达式,而不要写简单表达式。
简单表达式:
```sql
SELECT CASE city
WHEN '北京' THEN 1
WHEN '天津' THEN 2
ELSE 0
END AS abc
FROM test
```
搜索表达式:
```sql
SELECT CASE
WHEN city = '北京' THEN 1
WHEN city = '天津' THEN 2
ELSE 0
END AS abc
FROM test
```
明显可以看出,简单表达式只是搜索表达式 `a = b` 的特例,因为无法书写任何符号,只要条件换成 `a > b` 就无法胜任了,而搜索表达式不但可以轻松胜任,甚至可以写聚合函数。
## CASE 表达式里的聚合函数
为什么 CASE 表达式里可以写聚合函数?
因为本身表达式就支持聚合函数,比如下面的语法,我们不会觉得奇怪:
```sql
SELECT sum(pv), avg(uv) from test
```
本身 SQL 就支持多种不同的聚合方式同时计算,所以将其用在 CASE 表达式里,也是顺其自然的:
```sql
SELECT CASE
WHEN count(city) = 100 THEN 1
WHEN sum(dau) > 200 THEN 2
ELSE 0
END AS abc
FROM test
```
只要 SQL 表达式中存在聚合函数,那么整个表达式都聚合了,此时访问非聚合变量没有任何意义。所以上面的例子,即便在 CASE 表达式中使用了聚合,其实也不过是聚合了一次后,按照条件进行判断罢了。
这个特性可以解决很多实际问题,比如将一些复杂聚合判断条件的结果用 SQL 结构输出,那么很可能是下面这种写法:
```sql
SELECT CASE
WHEN () THEN xxx
... N
ELSE NULL
END AS abc
FROM test
```
这也可以认为是一种行转列的过程,即 **把行聚合后的结果通过一条条 CASE 表达式形成一个个新的列**
## 聚合与非聚合不能混用
我们希望利用 CASE 表达式找出那些 pv 大于平均值的行,以下这种想当然的写法是错误的:
```sql
SELECT CASE
WHEN pv > avg(pv) THEN 'yes'
ELSE 'no'
END AS abc
FROM test
```
原因是,只要 SQL 中存在聚合表达式,那么整条 SQL 就都是聚合的,所以返回的结果只有一条,而我们期望查询结果不聚合,只是判断条件用到了聚合结果,那么就要使用子查询。
为什么子查询可以解决问题?因为子查询的聚合发生在子查询,而不影响当前父查询,理解了这一点,就知道为什么下面的写法才是正确的了:
```sql
SELECT CASE
WHEN pv > ( SELECT avg(pv) from test ) THEN 'yes'
ELSE 'no'
END AS abc
FROM test
```
这个例子也说明了 CASE 表达式里可以使用子查询,因为子查询是先计算的,所以查询结果在哪儿都能用,CASE 表达式也不例外。
## WHERE 中的 CASE
WHERE 后面也可以跟 CASE 表达式的,用来做一些需要特殊枚举处理的筛选。
比如下面的例子:
```sql
SELECT * FROM demo WHERE
CASE
WHEN city = '北京' THEN true
ELSE ID > 5
END
```
本来我们要查询 ID 大于 5 的数据,但我想对北京这个城市特别对待,那么就可以在判断条件中再进行 CASE 分支判断。
这个场景在 BI 工具里等价于,创建一个 CASE 表达式字段,可以拖入筛选条件生效。
## GROUP BY 中的 CASE
想不到吧,GROUP BY 里都可以写 CASE 表达式:
```sql
SELECT isPower, sum(gdp) FROM test GROUP BY CASE
WHEN isPower = 1 THEN city, area
ELSE city
END
```
上面例子表示,计算 GDP 时,对于非常发达的城市,按照每个区粒度查看聚合结果,也就是看的粒度更细一些,而对于欠发达地区,本身 gdp 也不高,直接按照城市粒度看聚合结果。
这样,就按照不同的条件对数据进行了分组聚合。由于返回行结果是混在一起的,像这个例子,可以根据 isPower 字段是否为 1 判断,是否按照城市、区域进行了聚合,如果没有其他更显著的标识,可能导致无法区分不同行的聚合粒度,因此谨慎使用。
## ORDER BY 中的 CASE
同样,ORDER BY 使用 CASE 表达式,会将排序结果按照 CASE 分类进行分组,每组按照自己的规则排序,比如:
```sql
SELECT * FROM test ORDER BY CASE
WHEN isPower = 1 THEN gdp
ELSE people
END
```
上面的例子,对发达地区采用 gdp 排序,否则采用人口数量排序。
## 总结
CASE 表达式总结一下有如下特点:
1. 支持简单与搜索两种写法,推荐搜索写法。
2. 支持聚合与子查询,需要注意不同情况的特点。
3. 可以写在 SQL 查询的几乎任何地方,只要是可以写字段的地方,基本上就可以替换为 CASE 表达式。
4. 除了 SELECT 外,CASE 表达式还广泛应用在 INSERT 与 UPDATE,其中 UPDATE 的妙用是不用将 SQL 拆分为多条,所以不用担心数据变更后对判断条件的二次影响。
> 讨论地址是:[精读《SQL CASE 表达式》· Issue #404 · ascoders/weekly](https://github.com/ascoders/weekly/issues/404)
**如果你想参与讨论,请 [点击这里](https://github.com/ascoders/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)
+124
View File
@@ -0,0 +1,124 @@
窗口函数形如:
```sql
OVER (PARTITION BY ORDER BY )
```
有两个能力:
1. 当表达式为 `rank()` `dense_rank()` `row_number()` 时,拥有分组排序能力。
2. 当表达式为 `sum()` 等聚合函数时,拥有累计聚合能力。
无论何种能力,**窗口函数都不会影响数据行数,而是将计算平摊在每一行**。
这两种能力需要区分理解。
## 底表
<img width=500 src="https://s1.ax1x.com/2022/03/26/qdUL60.png">
以上是示例底表,共有 8 条数据,城市1、城市2 两个城市,下面各有地区1~4,每条数据都有该数据的人口数。
## 分组排序
如果按照人口排序,`ORDER BY people` 就行了,但如果我们想在城市内排序怎么办?
此时就要用到窗口函数的分组排序能力:
<img width=500 src="https://s1.ax1x.com/2022/03/26/qddfIg.png">
```sql
SELECT *, rank() over (PARTITION BY city ORDER BY people) FROM test
```
该 SQL 表示在 city 组内按照 people 进行排序。
其实 PARTITION BY 也是可选的,如果我们忽略它:
```sql
SELECT *, rank() over (ORDER BY people) FROM test
```
也是生效的,但该语句与普通 ORDER BY 等价,因此利用窗口函数进行分组排序时,一般都会使用 PARTITION BY。
### 各分组排序函数的差异
我们将 `rank()` `dense_rank()` `row_number()` 的结果都打印出来:
```sql
SELECT *,
rank() over (PARTITION BY city ORDER BY people),
dense_rank() over (PARTITION BY city ORDER BY people),
row_number() over (PARTITION BY city ORDER BY people)
FROM test
```
<img width=500 src="https://s1.ax1x.com/2022/03/26/qd0uh4.png">
其实从结果就可以猜到,这三个函数在处理排序遇到相同值时,对排名统计逻辑有如下差异:
1. `rank()`: 值相同时排名相同,但占用排名数字。
2. `dense_rank()`: 值相同时排名相同,但不占用排名数字,整体排名更加紧凑。
3. `row_number()`: 无论值是否相同,都强制按照行号展示排名。
上面的例子可以优化一下,因为所有窗口逻辑都是相同的,我们可以利用 WINDOW AS 提取为一个变量:
```sql
SELECT *,
rank() over wd, dense_rank() over wd, row_number() over wd
FROM test
WINDOW wd as (PARTITION BY city ORDER BY people)
```
## 累计聚合
我们之前说过,凡事使用了聚合函数,都会让查询变成聚合模式。如果不用 GROUP BY,聚合后返回行数会压缩为一行,即使用了 GROUP BY,返回的行数一般也会大大减少,因为分组聚合了。
然而使用窗口函数的聚合却不会导致返回行数减少,那么这种聚合是怎么计算的呢?我们不如直接看下面的例子:
```sql
SELECT *,
sum(people) over (PARTITION BY city ORDER BY people)
FROM test
```
<img width=500 src="https://s1.ax1x.com/2022/03/26/qdsDJS.png">
可以看到,在每个 city 分组内,按照 people 排序后进行了 **累加**(相同的值会合并在一起),这就是 BI 工具一般说的 RUNNGIN_SUM 的实现思路,当然一般我们排序规则使用绝对不会重复的日期,所以不会遇到第一个红框中合并计算的问题。
累计函数还有 `avg()` `min()` 等等,这些都一样可以作用于窗口函数,其逻辑可以按照下图理解:
<img width=400 src="https://s1.ax1x.com/2022/03/26/qd6or9.png">
你可能有疑问,直接 `sum(上一行结果,下一行)` 不是更方便吗?为了验证猜想,我们试试 `avg()` 的结果:
<img width=400 src="https://s1.ax1x.com/2022/03/26/qdciIP.png">
可见,如果直接利用上一行结果的缓存,那么 avg 结果必然是不准确的,所以窗口累计聚合是每行重新计算的。当然也不排除对于 sum、max、min 做额外性能优化的可能性,但 avg 只能每行重头计算。
### 与 GROUP BY 组合使用
窗口函数是可以与 GROUP BY 组合使用的,遵循的规则是,窗口范围对后面的查询结果生效,所以其实并不关心是否进行了 GROUP BY。我们看下面的例子:
<img width=500 src="https://s1.ax1x.com/2022/03/26/qdgMOH.png">
按照地区分组后进行累加聚合,是对 GROUP BY 后的数据行粒度进行的,而不是之前的明细行。
## 总结
窗口函数在计算组内排序或累计 GVM 等场景非常有用,我们只要牢记两个知识点就行了:
1. 分组排序要结合 PARTITION BY 才有意义。
2. 累计聚合作用于查询结果行粒度,支持所有聚合函数。
> 讨论地址是:[精读《SQL 窗口函数》· Issue #405 · ascoders/weekly](https://github.com/ascoders/weekly/issues/405)
**如果你想参与讨论,请 [点击这里](https://github.com/ascoders/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)
+151
View File
@@ -0,0 +1,151 @@
SQL grouping 解决 OLAP 场景总计与小计问题,其语法分为几类,但要解决的是同一个问题:
ROLLUP 与 CUBE 是封装了规则的 GROUPING SETS,而 GROUPING SETS 则是最原始的规则。
为了方便理解,让我们从一个问题入手,层层递进吧。
## 底表
<img width=500 src="https://s1.ax1x.com/2022/03/26/qdUL60.png">
以上是示例底表,共有 8 条数据,城市1、城市2 两个城市,下面各有地区1~4,每条数据都有该数据的人口数。
现在想计算人口总计,以及各城市人口小计。在没有掌握 grouping 语法前,我们只能通过两个 select 语句 union 后得到:
```sql
SELECT city, sum(people) FROM test GROUP BY city
union
SELECT '合计' as city, sum(people) FROM test
```
<img width=500 src="https://s1.ax1x.com/2022/04/04/qbKPRs.png">
但两条 select 语句聚合了两次,性能是一个不小的开销,因此 SQL 提供了 GROUPING SETS 语法解决这个问题。
## GROUPING SETS
GROUP BY GROUPING SETS 可以指定任意聚合项,比如我们要同时计算总计与分组合计,就要按照空内容进行 GROUP BY 进行一次 sum,再按照 city 进行 GROUP BY 再进行一次 sum,换成 GROUPING SETS 描述就是:
```sql
SELECT
city, area,
sum(people)
FROM test
GROUP BY GROUPING SETS((), (city, area))
```
其中 `GROUPING SETS((), (city, area))` 表示分别按照 `()``(city, area)` 聚合计算总计。返回结果是:
<img width=500 src="https://s1.ax1x.com/2022/04/04/qbRnWF.png">
可以看到,值为 NULL 的行就是我们要的总计,其值是没有任何 GROUP BY 限制算出来的。
类似的,我们还可以写 `GROUPING SETS((), (city), (city, area), (area))` 等任意数量、任意组合的 GROUP BY 条件。
通过这种规则计算的数据我们称为 “超级分组记录”。我们发现 “超级分组记录” 产生的 NULL 值很容易和真正的 NULL 值弄混,所以 SQL 提供了 GROUPING 函数解决这个问题。
## 函数 GROUPING
对于超级分组记录产生的 NULL,是可以被 `GROUPING()` 函数识别为 1 的:
```sql
SELECT
GROUPING(city),
GROUPING(area),
sum(people)
FROM test
GROUP BY GROUPING SETS((), (city, area))
```
具体效果见下图:
<img width=500 src="https://s1.ax1x.com/2022/04/04/qbRLpF.png">
可以看到,但凡是超级分组计算出来的字段都会识别为 1,我们利用之前学习的 [SQL CASE 表达式](https://github.com/ascoders/weekly/blob/master/SQL/234.SQL%20CASE%20%E8%A1%A8%E8%BE%BE%E5%BC%8F.md) 将其转换为总计、小计字样,就可以得出一张数据分析表了:
```sql
SELECT
CASE WHEN GROUPING(city) = 1 THEN '总计' ELSE city END,
CASE WHEN GROUPING(area) = 1 THEN '小计' ELSE area END,
sum(people)
FROM test
GROUP BY GROUPING SETS((), (city, area))
```
<img width=500 src="https://s1.ax1x.com/2022/04/04/qbRz01.png">
然后前端表格展示时,将第一行 “总计”、“小计” 单元格合并为 “总计”,就完成了总计这个 BI 可视化分析功能。
## ROLLUP
ROLLUP 是卷起的意思,是一种特定规则的 GROUPING SETS,以下两种写法是等价的:
```sql
SELECT sum(people) FROM test
GROUP BY ROLLUP(city)
-- 等价于
SELECT sum(people) FROM test
GROUP BY GROUPING SETS((), (city))
```
再看一组等价描述:
```sql
SELECT sum(people) FROM test
GROUP BY ROLLUP(city, area)
-- 等价于
SELECT sum(people) FROM test
GROUP BY GROUPING SETS((), (city), (city, area))
```
发现规律了吗?ROLLUP 会按顺序把 GROUP BY 内容 “一个个卷起来”。用 GROUPING 函数判断超级分组记录对 ROLLUP 同样适用。
## CUBE
CUBE 又有所不同,它对内容进行了所有可能性展开(所以叫 CUBE)。
类比上面的例子,我们再写两组等价的展开:
```sql
SELECT sum(people) FROM test
GROUP BY CUBE(city)
-- 等价于
SELECT sum(people) FROM test
GROUP BY GROUPING SETS((), (city))
```
上面的例子因为只有一项还看不出来,下面两项分组就能看出来了:
```sql
SELECT sum(people) FROM test
GROUP BY CUBE(city, area)
-- 等价于
SELECT sum(people) FROM test
GROUP BY GROUPING SETS((), (city), (area), (city, area))
```
所谓 CUBE,是一种多维形状的描述,二维时有 2^1 种展开,三维时有 2^2 种展开,四维、五维依此类推。可以想象,如果用 CUBE 描述了很多组合,复杂度会爆炸。
## 总结
学习了 GROUPING 语法,以后前端同学的你不会再纠结这个问题了吧:
> 产品开启了总计、小计,我们是额外取一次数还是放到一起获取啊?
这个问题的标准答案和原理都在这篇文章里了。PS:对于不支持 GROUPING 语法数据库,要想办法屏蔽,就像前端 polyfill 一样,是一种降级方案。至于如何屏蔽,参考文章开头提到的两个 SELECT + UNION。
> 讨论地址是:[精读《SQL grouping》· Issue #406 · ascoders/weekly](https://github.com/ascoders/weekly/issues/406)
**如果你想参与讨论,请 [点击这里](https://github.com/ascoders/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,364 @@
TS 强类型非常好用,但在实际运用中,免不了遇到一些难以描述,反复看官方文档也解决不了的问题,至今为止也没有任何一篇文档,或者一套教材可以解决所有犄角旮旯的类型问题。为什么会这样呢?因为 TS 并不是简单的注释器,而是一门图灵完备的语言,所以很多问题的解决方法藏在基础能力里,但你学会了基础能力又不一定能想到这么用。
解决该问题的最好办法就是多练,通过实际案例不断刺激你的大脑,让你养成 TS 思维习惯。所以话不多说,我们今天从 [type-challenges](https://github.com/type-challenges/type-challenges) 的 Easy 难度题目开始吧。
## 精读
### [Pick](https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md)
手动实现内置 `Pick<T, K>` 函数,返回一个新的类型,从对象 T 中抽取类型 K:
```ts
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
```
结合例子更容易看明白,也就是 `K` 是一个字符串,我们需要返回一个新类型,仅保留 `K` 定义的 Key。
第一个难点在如何限制 `K` 的取值,比如传入 `T` 中不存在的值就要报错。这个考察的是硬知识,只要你知道 `A extends keyof B` 这个语法就能联想到。
第二个难点在于如何生成一个仅包含 `K` 定义 Key 的类型,你首先要知道有 `{ [A in keyof B]: B[A] }` 这个硬知识,这样可以重新组合一个对象:
```ts
// 代码 1
type Foo<T> = {
[P in keyof T]: T[P]
}
```
只懂这个语法不一定能想出思路,原因是你要打破对 TS 的刻板理解,`[K in keyof T]` 不是一个固定模板,其中 `keyof T` 只是一个指代变量,它可以被换掉,如果你换掉成另一个范围的变量,那么这个对象的 Key 值范围就变了,这正好契合本题的 `K`
```ts
// 代码 2(本题答案)
type MyPick<T, K in keyof T> = {
[P in K]: T[P]
}
```
这个题目别看知道答案后简单,回顾下还是有收获的。对比上面两个代码例子,你会发现,只不过是把代码 1 的 `keyof T` 从对象描述中提到了泛型定义里而已,所以功能上没有任何变化,但因为泛型可以由用户传入,所以代码 1 的 `P in keyof T` 因为没有泛型支撑,这里推导出来的就是 `T` 的所有 Keys,而代码 2 虽然把代码挪到了泛型,但因为用的是 `extends` 描述,所以表示 `P` 的类型被约束到了 `T` 的 Keys,至于具体是什么,得看用户代码怎么传。
所以其实放到泛型里的 `K` 是没有默认值的,而写到对象里作为推导值就有了默认值。泛型里给默认值的方式如下:
```ts
// 代码 3
type MyPick<T, K extends keyof T = keyof T> = {
[P in K]: T[P]
}
```
也就是说,这样 `MyPick<Todo>` 就也可以正确工作并原封不动返回 `Todo` 类型,也就是说,代码 3 在不传第二个参数时,与代码 1 的功能完全一样。仔细琢磨一下共同点与区别,为什么代码 3 可以做到和代码 1 功能一样,又有更强的拓展性,你对 TS 泛型的实战理解就上了一个台阶。
### [Readonly](https://github.com/type-challenges/type-challenges/blob/main/questions/00007-easy-readonly/README.md)
手动实现内置 `Readonly<T>` 函数,将对象所有属性设置为只读:
```ts
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
```
这道题反而比第一题简单,只要我们用 `{ [A in keyof B]: B[A] }` 重新声明对象,并在每个 Key 前面加上 `readonly` 修饰即可:
```ts
// 本题答案
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
```
根据这个特性我们可以做很多延伸改造,比如将对象所有 Key 都设定为可选:
```ts
type Optional<T> = {
[K in keyof T]?: T[K]
}
```
`{ [A in keyof B]: B[A] }` 给了我们描述每一个 Key 属性细节的机会,限制我们发挥的只有想象力。
### [First Of Array](https://github.com/type-challenges/type-challenges/blob/main/questions/00014-easy-first/README.md)
实现类型 `First<T>`,取到数组第一项的类型:
```ts
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
```
这题比较简单,很容易想到的答案:
```ts
// 本题答案
type First<T extends any[]> = T[0]
```
但在写这个答案时,有 10% 脑细胞提醒我没有判断边界情况,果然看了下答案,有空数组的情况要考虑,空数组时返回类型 `never` 而不是 `undefined` 会更好,下面几种写法都是答案:
```ts
type First<T extends any[]> = T extends [] ? never : T[0]
type First<T extends any[]> = T['length'] extends 0 ? never : T[0]
type First<T> = T extends [infer P, ...infer Rest] ? P : never
```
第一种写法通过 `extends []` 判断 `T` 是否为空数组,是的话返回 `never`
第二种写法通过长度为 0 判断空数组,此时需要理解两点:1. 可以通过 `T['length']` 让 TS 访问到值长度(类型的),2. `extends 0` 表示是否匹配 0,即 `extends` 除了匹配类型,还能直接匹配值。
第三种写法是最省心的,但也使用了 `infer` 关键字,即使你充分知道 `infer` 怎么用([精读《Typescript infer 关键字》](https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/207.%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript%20infer%20%E5%85%B3%E9%94%AE%E5%AD%97%E3%80%8B.md)),也很难想到它。用 `infer` 的理由是:该场景存在边界情况,最便于理解的写法是 “如果 T 形如 `<P, ...>`” 那我就返回类型 `P`,否则返回 `never`”,这句话用 TS 描述就是:`T extends [infer P, ...infer Rest] ? P : never`
### [Length of Tuple](https://github.com/type-challenges/type-challenges/blob/main/questions/00018-easy-tuple-length/README.md)
实现类型 `Length<T>` 获取元组长度:
```ts
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5
```
经过上一题的学习,很容易想到这个答案:
```ts
type Length<T extends any[]> = T['length']
```
对 TS 来说,元组和数组都是数组,但元组对 TS 来说可以观测其长度,`T['length']` 对元组来说返回的是具体值,而对数组来说返回的是 `number`
### [Exclude](https://github.com/type-challenges/type-challenges/blob/main/questions/00043-easy-exclude/README.md)
实现类型 `Exclude<T, U>`,返回 `T` 中不存在于 `U` 的部分。该功能主要用在联合类型场景,所以我们直接用 `extends` 判断就行了:
```ts
// 本题答案
type Exclude<T, U> = T extends U ? never : T
```
实际运行效果:
```ts
type C = Exclude<'a' | 'b', 'a' | 'c'> // 'b'
```
看上去有点不那么好理解,这是因为 TS 对联合类型的执行是分配率的,即:
```ts
Exclude<'a' | 'b', 'a' | 'c'>
// 等价于
Exclude<'a', 'a' | 'c'> | Exclude<'b', 'a' | 'c'>
```
### [Awaited](https://github.com/type-challenges/type-challenges/blob/main/questions/00189-easy-awaited/README.md)
实现类型 `Awaited`,比如从 `Promise<ExampleType>` 拿到 `ExampleType`
首先 TS 永远不会执行代码,所以脑子里不要有 “await 得等一下才知道结果” 的念头。该题关键就是从 `Promise<T>` 中抽取类型 `T`,很适合用 `infer` 做:
```ts
type MyAwaited<T> = T extends Promise<infer U> ? U : never
```
然而这个答案还不够标准,标准答案考虑了嵌套 `Promise` 的场景:
```ts
// 该题答案
type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer P>
? P extends Promise<unknown> ? MyAwaited<P> : P
: never
```
如果 `Promise<P>` 取到的 `P` 还形如 `Promise<unknown>`,就递归调用自己 `MyAwaited<P>`。这里提到了递归,也就是 TS 类型处理可以是递归的,所以才有了后面版本做尾递归优化。
### [If](https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.md)
实现类型 `If<Condition, True, False>`,当 `C``true` 时返回 `T`,否则返回 `F`
```ts
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
```
之前有提过,`extends` 还可以用来判定值,所以果断用 `extends true` 判断是否命中了 `true` 即可:
```ts
// 本题答案
type If<C, T, F> = C extends true ? T : F
```
### [Concat](https://github.com/type-challenges/type-challenges/blob/main/questions/00533-easy-concat/README.md)
用类型系统实现 `Concat<P, Q>`,将两个数组类型连起来:
```ts
type Result = Concat<[1], [2]> // expected to be [1, 2]
```
由于 TS 支持数组解构语法,所以可以大胆的尝试这么写:
```ts
type Concat<P extends any[], Q extends any[]> = [...P, ...Q]
```
考虑到 `Concat` 函数应该也能接收非数组类型,所以做一个判断,为了方便书写,把 `extends` 从泛型定义位置挪到 TS 类型推断的运行时:
```ts
// 本题答案
type Concat<P, Q> = [
...P extends any[] ? P : [P],
...Q extends any[] ? Q : [Q],
]
```
解决这题需要信念,相信 TS 可以像 JS 一样写逻辑。这些能力都是版本升级时渐进式提供的,所以需要不断阅读最新 TS 特性,快速将其理解为固化知识,其实还是有一定难度的。
### [Includes](https://github.com/type-challenges/type-challenges/blob/main/questions/00898-easy-includes/README.md)
用类型系统实现 `Includes<T, K>` 函数:
```ts
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
```
由于之前的经验,很容易做下面的联想:
```ts
// 如果题目要求是这样
type isPillarMen = Includes<'Kars' | 'Esidisi' | 'Wamuu' | 'Santana', 'Dio'>
// 那我就能用 extends 轻松解决了
type Includes<T, K> = K extends T ? true : false
```
可惜第一个输入是数组类型,`extends` 可不支持判定 “数组包含” 逻辑,此时要了解一个新知识点,即 TS 判断中的 `[number]` 下标。不仅这道题,以后很多困难题都需要它作为基础知识。
`[number]` 下标表示任意一项,而 `extends T[number]` 就可以实现数组包含的判定,因此下面的解法是有效的:
```ts
type Includes<T extends any[], K> = K extends T[number] ? true : false
```
但翻答案后发现这并不是标准答案,还真找到一个反例:
```ts
type Includes<T extends any[], K> = K extends T[number] ? true : false
type isPillarMen = Includes<[boolean], false> // true
```
原因很简单,`true``false` 都继承自 `boolean`,所以 `extends` 判断的界限太宽了,题目要求的是精确值匹配,故上面的答案理论上是错的。
标准答案是每次判断数组第一项,并递归(讲真觉得这不是 easy 题),分别有两个难点。
第一如何写 Equal 函数?比较流行的方案是这个:
```ts
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false
```
关于如何写 Equal 函数还引发了一次 [小讨论](https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650),上面的代码构造了两个函数,这两个函数内的 `T` 属于 deferred(延迟)判断的类型,该类型判断依赖于内部 `isTypeIdenticalTo` 函数完成判断。
有了 `Equal` 后就简单了,我们用解构 + `infer` + 递归的方式做就可以了:
```ts
// 本题答案
type Includes<T extends any[], K> =
T extends [infer F, ...infer Rest] ?
Equal<F, K> extends true ?
true
: Includes<Rest, K>
: false
```
每次取数组第一个值判断 `Equal`,如果不匹配则拿剩余项递归判断。这个函数组合了不少 TS 知识,比如:
- 递归
- 解构
- `infer`
- `extends true`
可以发现,就为了解决 `true extends boolean``true` 的问题,我们绕了一大圈使用了更复杂的方式来实现,这在 TS 体操中也算是常态,解决问题需要耐心。
### [Push](https://github.com/type-challenges/type-challenges/blob/main/questions/03057-easy-push/README.md)
实现 `Push<T, K>` 函数:
```ts
type Result = Push<[1, 2], '3'> // [1, 2, '3']
```
这道题真的很简单,用解构就行了:
```ts
// 本题答案
type Push<T extends any[], K> = [...T, K]
```
可见,想要轻松解决一个 TS 简单问题,首先你需要能解决一些困难问题 😁。
### [Unshift](https://github.com/type-challenges/type-challenges/blob/main/questions/03060-easy-unshift/README.md)
实现 `Unshift<T, K>` 函数:
```ts
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
```
`Push` 基础上改下顺序就行了:
```ts
// 本题答案
type Unshift<T extends any[], K> = [K, ...T]
```
### [Parameters](https://github.com/type-challenges/type-challenges/blob/main/questions/03312-easy-parameters/README.md)
实现内置函数 `Parameters`
`Parameters` 可以拿到函数的参数类型,直接用 `infer` 实现即可,也比较简单:
```ts
type Parameters<T> = T extends (...args: infer P) => any ? P : []
```
`infer` 可以很方便从任何具体的位置取值,属于典型难懂易用的语法。
## 总结
学会 TS 基础语法后,活用才是关键。
> 讨论地址是:[精读《type challenges - easy》· Issue #422 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/422)
**如果你想参与讨论,请 [点击这里](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)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 818 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 414 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

+36
View File
@@ -0,0 +1,36 @@
/**
* 发布辅助脚本
* @author 黄子毅
*/
const fs = require("fs");
const dirs = [
"前沿技术",
"TS 类型体操",
"设计模式",
"编译原理",
"源码解读",
"商业思考",
"算法",
"SQL"
];
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}/${encodeURIComponent(dirName)}">${dirName.replace(
".md",
""
)}</a>`
);
});
console.log("");
});
+2158
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -17,6 +17,7 @@
},
"homepage": "https://github.com/dt-fe/weekly#readme",
"dependencies": {
"esm": "^3.2.25",
"husky": "^3.0.4",
"lint-md-cli": "^0.1.1"
},
@@ -25,4 +26,4 @@
"pre-commit": "npx lint-md ./"
}
}
}
}
+279 -17
View File
@@ -1,27 +1,289 @@
# 前端精读
<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="./TS 类型体操/243.%E7%B2%BE%E8%AF%BB%E3%80%8Atype%20challenges%20-%20easy%E3%80%8B.md">243.精读《type challenges - easy》</a>
素材来源:[周刊参考池](https://github.com/ascoders/weekly/issues/2)
现已涵盖:
- 结合大厂工作经验解读的 [前沿技术](./前沿技术)[源码解读](./源码解读)。
- 逐渐加入一些后端技术解读。
- 一些 [商业思考](./商业思考)。
- 已完成 [编译原理](./编译原理)、[设计模式](./设计模式) 两大基础模块。
### 前沿技术
- <a href="./前沿技术/1.%E7%B2%BE%E8%AF%BB%E3%80%8Ajs%20%E6%A8%A1%E5%9D%97%E5%8C%96%E5%8F%91%E5%B1%95%E3%80%8B.md">1.精读《js 模块化发展》</a>
- <a href="./前沿技术/2.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%A8%A1%E6%80%81%E6%A1%86%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E3%80%8B.md">2.精读《模态框的最佳实践》</a>
- <a href="./前沿技术/3.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%89%8D%E5%90%8E%E7%AB%AF%E6%B8%B2%E6%9F%93%E4%B9%8B%E4%BA%89%E3%80%8B.md">3.精读《前后端渲染之争》</a>
- <a href="./前沿技术/4.%E7%B2%BE%E8%AF%BB%E3%80%8AAsyncAwait%20%E4%BC%98%E8%B6%8A%E4%B9%8B%E5%A4%84%E3%80%8B.md">4.精读《AsyncAwait 优越之处》</a>
- <a href="./前沿技术/5.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%B0%91%E5%B7%A5%E5%8F%94%E5%8D%95%E9%A1%B5%E6%95%B0%E6%8D%AE%E6%B5%81%E6%96%B9%E6%A1%88%E3%80%8B.md">5.精读《民工叔单页数据流方案》</a>
- <a href="./前沿技术/6.%E7%B2%BE%E8%AF%BB%E3%80%8AJavaScript%20%E9%94%99%E8%AF%AF%E5%A0%86%E6%A0%88%E5%A4%84%E7%90%86%E3%80%8B.md">6.精读《JavaScript 错误堆栈处理》</a>
- <a href="./前沿技术/7.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AF%B7%E5%81%9C%E6%AD%A2%20css-in-js%20%E7%9A%84%E8%A1%8C%E4%B8%BA%E3%80%8B.md">7.精读《请停止 css-in-js 的行为》</a>
- <a href="./前沿技术/8.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%85%A5%E5%9D%91%20React%20%E5%89%8D%E6%B2%A1%E6%9C%89%E4%BA%BA%E4%BC%9A%E5%91%8A%E8%AF%89%E4%BD%A0%E7%9A%84%E4%BA%8B%E3%80%8B.md">8.精读《入坑 React 前没有人会告诉你的事》</a>
- <a href="./前沿技术/9.%E7%B2%BE%E8%AF%BB%E3%80%8AImmutable%20%E7%BB%93%E6%9E%84%E5%85%B1%E4%BA%AB%E3%80%8B.md">9.精读《Immutable 结构共享》</a>
- <a href="./前沿技术/10.%E7%B2%BE%E8%AF%BB%E3%80%8AWeb%20Components%20%E7%9A%84%E5%9B%B0%E5%A2%83%E3%80%8B.md">10.精读《Web Components 的困境》</a>
- <a href="./前沿技术/11.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%89%8D%E7%AB%AF%E8%B0%83%E8%AF%95%E6%8A%80%E5%B7%A7%E3%80%8B.md">11.精读《前端调试技巧》</a>
- <a href="./前沿技术/12.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20%E9%AB%98%E9%98%B6%E7%BB%84%E4%BB%B6%E3%80%8B.md">12.精读《React 高阶组件》</a>
- <a href="./前沿技术/13.%E7%B2%BE%E8%AF%BB%E3%80%8AThis%20%E5%B8%A6%E6%9D%A5%E7%9A%84%E5%9B%B0%E6%83%91%E3%80%8B.md">13.精读《This 带来的困惑》</a>
- <a href="./前沿技术/14.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E4%B9%8B%20DCI%E3%80%8B.md">14.精读《架构设计之 DCI》</a>
- <a href="./前沿技术/15.%E7%B2%BE%E8%AF%BB%E3%80%8ATC39%20%E4%B8%8E%20ECMAScript%20%E6%8F%90%E6%A1%88%E3%80%8B.md">15.精读《TC39 与 ECMAScript 提案》</a>
- <a href="./前沿技术/16.%E7%B2%BE%E8%AF%BB%E3%80%8ACSS%20Animations%20vs%20Web%20Animations%20API%E3%80%8B.md">16.精读《CSS Animations vs Web Animations API》</a>
- <a href="./前沿技术/17.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A6%82%E4%BD%95%E5%AE%89%E5%85%A8%E5%9C%B0%E4%BD%BF%E7%94%A8%20React%20context%E3%80%8B.md">17.精读《如何安全地使用 React context》</a>
- <a href="./前沿技术/18.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E5%AE%8C%E7%BE%8E%E7%9A%84%E6%97%A5%E6%9C%9F%E9%80%89%E6%8B%A9%E5%99%A8%E3%80%8B.md">18.精读《设计完美的日期选择器》</a>
- <a href="./前沿技术/19.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%9C%80%E4%BD%B3%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%A2%98%E3%80%8B%E5%8F%8A%E9%9D%A2%E8%AF%95%E5%AE%98%E6%8A%80%E5%B7%A7.md">19.精读《最佳前端面试题》及面试官技巧</a>
- <a href="./前沿技术/20.%E7%B2%BE%E8%AF%BB%E3%80%8ANestjs%E3%80%8B%E6%96%87%E6%A1%A3.md">20.精读《Nestjs》文档</a>
- <a href="./前沿技术/21.%E7%B2%BE%E8%AF%BB%E3%80%8AWeb%20fonts%3A%20when%20you%20need%20them%2C%20when%20you%20don%E2%80%99t%E3%80%8B.md">21.精读《Web fonts: when you need them, when you dont》</a>
- <a href="./前沿技术/22.%E7%B2%BE%E8%AF%BB%E3%80%8AV8%20%E5%BC%95%E6%93%8E%E7%89%B9%E6%80%A7%E5%B8%A6%E6%9D%A5%E7%9A%84%E7%9A%84%20JS%20%E6%80%A7%E8%83%BD%E5%8F%98%E5%8C%96%E3%80%8B.md">22.精读《V8 引擎特性带来的的 JS 性能变化》</a>
- <a href="./前沿技术/23.%E7%B2%BE%E8%AF%BB%E3%80%8AAPI%20%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99%E3%80%8B.md">23.精读《API 设计原则》</a>
- <a href="./前沿技术/24.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%8E%B0%E4%BB%A3%20JavaScript%20%E6%A6%82%E8%A7%88%E3%80%8B.md">24.精读《现代 JavaScript 概览》</a>
- <a href="./前沿技术/25.%E7%B2%BE%E8%AF%BB%E3%80%8Anull%20%3E%3D%200%3F%E3%80%8B.md">25.精读《null >= 0?》</a>
- <a href="./前沿技术/26.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%8A%A0%E5%AF%86%E5%AA%92%E4%BD%93%E6%89%A9%E5%B1%95%E3%80%8B.md">26.精读《加密媒体扩展》</a>
- <a href="./前沿技术/27.%E7%B2%BE%E8%AF%BB%E3%80%8Acss-in-js%20%E6%9D%80%E9%B8%A1%E7%94%A8%E7%89%9B%E5%88%80%E3%80%8B.md">27.精读《css-in-js 杀鸡用牛刀》</a>
- <a href="./前沿技术/28.%E7%B2%BE%E8%AF%BB%E3%80%8A2017%20%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%A4%87%E5%BF%98%E5%BD%95%E3%80%8B.md">28.精读《2017 前端性能优化备忘录》</a>
- <a href="./前沿技术/29.%E7%B2%BE%E8%AF%BB%E3%80%8AJS%20%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E3%80%8B.md">29.精读《JS 中的内存管理》</a>
- <a href="./前沿技术/30.%E7%B2%BE%E8%AF%BB%E3%80%8AJavascript%20%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF%E4%B8%8E%E5%BC%82%E6%AD%A5%E3%80%8B.md">30.精读《Javascript 事件循环与异步》</a>
- <a href="./前沿技术/31.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%88%91%E4%B8%8D%E5%86%8D%E4%BD%BF%E7%94%A8%E9%AB%98%E9%98%B6%E7%BB%84%E4%BB%B6%E3%80%8B.md">31.精读《我不再使用高阶组件》</a>
- <a href="./前沿技术/32.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Router4.0%20%E8%BF%9B%E9%98%B6%E6%A6%82%E5%BF%B5%E3%80%8B.md">32.精读《React Router4.0 进阶概念》</a>
- <a href="./前沿技术/33.%E7%B2%BE%E8%AF%BB%E3%80%8A30%20%E8%A1%8C%20js%20%E4%BB%A3%E7%A0%81%E5%88%9B%E5%BB%BA%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E3%80%8B.md">33.精读《30 行 js 代码创建神经网络》</a>
- <a href="./前沿技术/34.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20%E4%BB%A3%E7%A0%81%E6%95%B4%E6%B4%81%E4%B9%8B%E9%81%93%E3%80%8B.md">34.精读《React 代码整洁之道》</a>
- <a href="./前沿技术/35.%E7%B2%BE%E8%AF%BB%E3%80%8Adob%20-%20%E6%A1%86%E6%9E%B6%E5%AE%9E%E7%8E%B0%E3%80%8B.md">35.精读《dob - 框架实现》</a>
- <a href="./前沿技术/36.%E7%B2%BE%E8%AF%BB%E3%80%8AWhen%20You%20%E2%80%9CGit%E2%80%9D%20in%20Trouble-%20a%20Version%20Control%20Story%E3%80%8B.md">36.精读《When You “Git” in Trouble- a Version Control Story》</a>
- <a href="./前沿技术/37.%E7%B2%BE%E8%AF%BB%E3%80%8Ahow%20we%20position%20and%20what%20we%20compare%E3%80%8B.md">37.精读《how we position and what we compare》</a>
- <a href="./前沿技术/38.%E7%B2%BE%E8%AF%BB%E3%80%8Adob%20-%20%E6%A1%86%E6%9E%B6%E4%BD%BF%E7%94%A8%E3%80%8B.md">38.精读《dob - 框架使用》</a>
- <a href="./前沿技术/39.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%85%A8%E9%93%BE%E8%B7%AF%E4%BD%93%E9%AA%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8C%96%E7%9F%BF%E3%80%8B.md">39.精读《全链路体验浏览器挖矿》</a>
- <a href="./前沿技术/40.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%88%9D%E6%8E%A2%20Reason%20%E4%B8%8E%20GraphQL%E3%80%8B.md">40.精读《初探 Reason 与 GraphQL》</a>
- <a href="./前沿技术/41.%E7%B2%BE%E8%AF%BB%E3%80%8AAnt%20Design%203.0%20%E8%83%8C%E5%90%8E%E7%9A%84%E6%95%85%E4%BA%8B%E3%80%8B.md">41.精读《Ant Design 3.0 背后的故事》</a>
- <a href="./前沿技术/42.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%89%8D%E7%AB%AF%E6%95%B0%E6%8D%AE%E6%B5%81%E5%93%B2%E5%AD%A6%E3%80%8B.md">42.精读《前端数据流哲学》</a>
- <a href="./前沿技术/43.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A2%9E%E5%BC%BA%E7%8E%B0%E5%AE%9E%E4%B8%8E%E5%8F%AF%E8%A7%86%E5%8C%96%E3%80%8B.md">43.精读《增强现实与可视化》</a>
- <a href="./前沿技术/44.%E7%B2%BE%E8%AF%BB%E3%80%8ARekit%20Studio%E3%80%8B.md">44.精读《Rekit Studio》</a>
- <a href="./前沿技术/45.%E7%B2%BE%E8%AF%BB%E3%80%8AReact's%20new%20Context%20API%E3%80%8B.md">45.精读《React's new Context API》</a>
- <a href="./前沿技术/46.%E7%B2%BE%E8%AF%BB%E3%80%8Areact-rxjs%E3%80%8B.md">46.精读《react-rxjs》</a>
- <a href="./前沿技术/47.%E7%B2%BE%E8%AF%BB%E3%80%8Awebpack4.0%20%E5%8D%87%E7%BA%A7%E6%8C%87%E5%8D%97%E3%80%8B.md">47.精读《webpack4.0 升级指南》</a>
- <a href="./前沿技术/49.%E7%B2%BE%E8%AF%BB%E3%80%8ACompilers%20are%20the%20New%20Frameworks%E3%80%8B.md">49.精读《Compilers are the New Frameworks》</a>
- <a href="./前沿技术/50.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B%E6%9E%84%E5%BB%BA%20ARKit%20%E5%BA%94%E7%94%A8%E3%80%8B.md">50.精读《快速上手构建 ARKit 应用》</a>
- <a href="./前沿技术/51.%E7%B2%BE%E8%AF%BB%E3%80%8AElements%20of%20Web%20Dev%E3%80%8B.md">51.精读《Elements of Web Dev》</a>
- <a href="./前沿技术/52.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%9B%BE%E8%A7%A3%20ES%20%E6%A8%A1%E5%9D%97%E3%80%8B.md">52.精读《图解 ES 模块》</a>
- <a href="./前沿技术/53.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%8F%92%E4%BB%B6%E5%8C%96%E6%80%9D%E7%BB%B4%E3%80%8B.md">53.精读《插件化思维》</a>
- <a href="./前沿技术/54.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%9C%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E8%BF%90%E8%A1%8C%20serverRender%E3%80%8B.md">54.精读《在浏览器运行 serverRender》</a>
- <a href="./前沿技术/55.%E7%B2%BE%E8%AF%BB%E3%80%8Aasync%20await%20%E6%98%AF%E6%8A%8A%E5%8F%8C%E5%88%83%E5%89%91%E3%80%8B.md">55.精读《async await 是把双刃剑》</a>
- <a href="./前沿技术/56.%E7%B2%BE%E8%AF%BB%E3%80%8A%E9%87%8D%E6%96%B0%E6%80%9D%E8%80%83%20Redux%E3%80%8B.md">56.精读《重新思考 Redux》</a>
- <a href="./前沿技术/57.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%8E%B0%E4%BB%A3%20js%20%E6%A1%86%E6%9E%B6%E5%AD%98%E5%9C%A8%E7%9A%84%E6%A0%B9%E6%9C%AC%E5%8E%9F%E5%9B%A0%E3%80%8B.md">57.精读《现代 js 框架存在的根本原因》</a>
- <a href="./前沿技术/58.%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript2.0%20-%202.9%E3%80%8B.md">58.精读《Typescript2.0 - 2.9》</a>
- <a href="./前沿技术/59.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A6%82%E4%BD%95%E5%88%A9%E7%94%A8%20Nodejs%20%E7%9B%91%E5%90%AC%E6%96%87%E4%BB%B6%E5%A4%B9%E3%80%8B.md">59.精读《如何利用 Nodejs 监听文件夹》</a>
- <a href="./前沿技术/60.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A6%82%E4%BD%95%E5%9C%A8%20nodejs%20%E4%BD%BF%E7%94%A8%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E3%80%8B.md">60.精读《如何在 nodejs 使用环境变量》</a>
- <a href="./前沿技术/61.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20%E5%85%AB%E7%A7%8D%E6%9D%A1%E4%BB%B6%E6%B8%B2%E6%9F%93%E3%80%8B.md">61.精读《React 八种条件渲染》</a>
- <a href="./前沿技术/62.%E7%B2%BE%E8%AF%BB%E3%80%8AJS%20%E5%BC%95%E6%93%8E%E5%9F%BA%E7%A1%80%E4%B9%8B%20Shapes%20and%20Inline%20Caches%E3%80%8B.md">62.精读《JS 引擎基础之 Shapes and Inline Caches》</a>
- <a href="./前沿技术/63.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20%E7%9A%84%E5%A4%9A%E6%80%81%E6%80%A7%E3%80%8B.md">63.精读《React 的多态性》</a>
- <a href="./前沿技术/68.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%A1%A1%E9%87%8F%E7%94%A8%E6%88%B7%E4%BD%93%E9%AA%8C%E3%80%8B.md">68.精读《衡量用户体验》</a>
- <a href="./前沿技术/69.%E7%B2%BE%E8%AF%BB%E3%80%8ASQL%20vs%20Flux%E3%80%8B.md">69.精读《SQL vs Flux》</a>
- <a href="./前沿技术/72.%E7%B2%BE%E8%AF%BB%E3%80%8AREST%2C%20GraphQL%2C%20Webhooks%2C%20%26%20gRPC%20%E5%A6%82%E4%BD%95%E9%80%89%E5%9E%8B%E3%80%8B.md">72.精读《REST, GraphQL, Webhooks, & gRPC 如何选型》</a>
- <a href="./前沿技术/74.%E7%B2%BE%E8%AF%BB%E3%80%8A12%20%E4%B8%AA%E8%AF%84%E4%BC%B0%20JS%20%E5%BA%93%E4%BD%A0%E9%9C%80%E8%A6%81%E5%85%B3%E5%BF%83%E7%9A%84%E4%BA%8B%E3%80%8B.md">74.精读《12 个评估 JS 库你需要关心的事》</a>
- <a href="./前沿技术/76.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%B0%88%E8%B0%88%20Web%20Workers%E3%80%8B.md">76.精读《谈谈 Web Workers》</a>
- <a href="./前沿技术/77.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%94%A8%20Reduce%20%E5%AE%9E%E7%8E%B0%20Promise%20%E4%B8%B2%E8%A1%8C%E6%89%A7%E8%A1%8C%E3%80%8B.md">77.精读《用 Reduce 实现 Promise 串行执行》</a>
- <a href="./前沿技术/79.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Hooks%E3%80%8B.md">79.精读《React Hooks》</a>
- <a href="./前沿技术/80.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%80%8E%E4%B9%88%E7%94%A8%20React%20Hooks%20%E9%80%A0%E8%BD%AE%E5%AD%90%E3%80%8B.md">80.精读《怎么用 React Hooks 造轮子》</a>
- <a href="./前沿技术/81.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%BD%BF%E7%94%A8%20CSS%20%E5%B1%9E%E6%80%A7%E9%80%89%E6%8B%A9%E5%99%A8%E3%80%8B.md">81.精读《使用 CSS 属性选择器》</a>
- <a href="./前沿技术/83.%E7%B2%BE%E8%AF%BB%E3%80%8AReact16%20%E6%96%B0%E7%89%B9%E6%80%A7%E3%80%8B.md">83.精读《React16 新特性》</a>
- <a href="./前沿技术/84.%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">84.精读《Typescript 3.2 新特性》</a>
- <a href="./前沿技术/86.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%9B%BD%E9%99%85%E5%8C%96%E5%B8%83%E5%B1%80%20-%20Logical%20Properties%E3%80%8B.md">86.精读《国际化布局 - Logical Properties》</a>
- <a href="./前沿技术/87.%E7%B2%BE%E8%AF%BB%E3%80%8AsetState%20%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88%E3%80%8B.md">87.精读《setState 做了什么》</a>
- <a href="./前沿技术/88.%E7%B2%BE%E8%AF%BB%E3%80%8ACaches%20API%E3%80%8B.md">88.精读《Caches API》</a>
- <a href="./前沿技术/89.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A6%82%E4%BD%95%E7%BC%96%E8%AF%91%E5%89%8D%E7%AB%AF%E9%A1%B9%E7%9B%AE%E4%B8%8E%E7%BB%84%E4%BB%B6%E3%80%8B.md">89.精读《如何编译前端项目与组件》</a>
- <a href="./前沿技术/91.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%AD%A3%E5%88%99%20ES2018%E3%80%8B.md">91.精读《正则 ES2018》</a>
- <a href="./前沿技术/94.%E7%B2%BE%E8%AF%BB%E3%80%8AServerless%20%E7%BB%99%E5%89%8D%E7%AB%AF%E5%B8%A6%E6%9D%A5%E4%BA%86%E4%BB%80%E4%B9%88%E3%80%8B.md">94.精读《Serverless 给前端带来了什么》</a>
- <a href="./前沿技术/95.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20VS%20Class%20%E7%BB%84%E4%BB%B6%E3%80%8B.md">95.精读《Function VS Class 组件》</a>
- <a href="./前沿技术/96.%E7%B2%BE%E8%AF%BB%E3%80%8AuseEffect%20%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97%E3%80%8B.md">96.精读《useEffect 完全指南》</a>
- <a href="./前沿技术/97.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%BC%96%E5%86%99%E6%9C%89%E5%BC%B9%E6%80%A7%E7%9A%84%E7%BB%84%E4%BB%B6%E3%80%8B.md">97.精读《编写有弹性的组件》</a>
- <a href="./前沿技术/99.%E7%B2%BE%E8%AF%BB%E3%80%8AScheduling%20in%20React%E3%80%8B.md">99.精读《Scheduling in React》</a>
- <a href="./前沿技术/100.%E7%B2%BE%E8%AF%BB%E3%80%8AV8%20%E5%BC%95%E6%93%8E%20Lazy%20Parsing%E3%80%8B.md">100.精读《V8 引擎 Lazy Parsing》</a>
- <a href="./前沿技术/101.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%20vs%20%E6%8C%81%E7%BB%AD%E4%BA%A4%E4%BB%98%20vs%20%E6%8C%81%E7%BB%AD%E9%83%A8%E7%BD%B2%E3%80%8B.md">101.精读《持续集成 vs 持续交付 vs 持续部署》</a>
- <a href="./前沿技术/102.%E7%B2%BE%E8%AF%BB%E3%80%8AMonorepo%20%E7%9A%84%E4%BC%98%E5%8A%BF%E3%80%8B.md">102.精读《Monorepo 的优势》</a>
- <a href="./前沿技术/104.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20Component%20%E5%85%A5%E9%97%A8%E3%80%8B.md">104.精读《Function Component 入门》</a>
- <a href="./前沿技术/105.%E7%B2%BE%E8%AF%BB%E3%80%8AWhat's%20new%20in%20javascript%E3%80%8B.md">105.精读《What's new in javascript》</a>
- <a href="./前沿技术/107.%E7%B2%BE%E8%AF%BB%E3%80%8AOptional%20chaining%E3%80%8B.md">107.精读《Optional chaining》</a>
- <a href="./前沿技术/109.%E7%B2%BE%E8%AF%BB%E3%80%8AVue3.0%20Function%20API%E3%80%8B.md">109.精读《Vue3.0 Function API》</a>
- <a href="./前沿技术/111.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%89%8D%E7%AB%AF%E6%9C%AA%E6%9D%A5%E5%B1%95%E6%9C%9B%E3%80%8B.md">111.精读《前端未来展望》</a>
- <a href="./前沿技术/112.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E3%80%8B.md">112.精读《源码学习》</a>
- <a href="./前沿技术/113.%E7%B2%BE%E8%AF%BB%E3%80%8ANodejs%20V12%E3%80%8B.md">113.精读《Nodejs V12》</a>
- <a href="./前沿技术/117.%E7%B2%BE%E8%AF%BB%E3%80%8ATableau%20%E6%8E%A2%E7%B4%A2%E5%BC%8F%E6%A8%A1%E5%9E%8B%E3%80%8B.md">117.精读《Tableau 探索式模型》</a>
- <a href="./前沿技术/118.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%BD%BF%E7%94%A8%20css%20%E5%8F%98%E9%87%8F%E7%94%9F%E6%88%90%E9%A2%9C%E8%89%B2%E4%B8%BB%E9%A2%98%E3%80%8B.md">118.精读《使用 css 变量生成颜色主题》</a>
- <a href="./前沿技术/119.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%89%8D%E7%AB%AF%E6%B7%B1%E6%B0%B4%E5%8C%BA%E3%80%8B.md">119.精读《前端深水区》</a>
- <a href="./前沿技术/120.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Hooks%20%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E3%80%8B.md">120.精读《React Hooks 最佳实践》</a>
- <a href="./前沿技术/121.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%89%8D%E7%AB%AF%E4%B8%8E%20BI%E3%80%8B.md">121.精读《前端与 BI》</a>
- <a href="./前沿技术/123.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%94%A8%20Babel%20%E5%88%9B%E9%80%A0%E8%87%AA%E5%AE%9A%E4%B9%89%20JS%20%E8%AF%AD%E6%B3%95%E3%80%8B.md">123.精读《用 Babel 创造自定义 JS 语法》</a>
- <a href="./前沿技术/124.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%94%A8%20css%20grid%20%E9%87%8D%E6%96%B0%E6%80%9D%E8%80%83%E5%B8%83%E5%B1%80%E3%80%8B.md">124.精读《用 css grid 重新思考布局》</a>
- <a href="./前沿技术/125.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%20-%20%E5%87%BD%E6%95%B0%E5%BC%8F%E4%B9%8B%E7%BE%8E%E3%80%8B.md">125.精读《深度学习 - 函数式之美》</a>
- <a href="./前沿技术/126.%E7%B2%BE%E8%AF%BB%E3%80%8ANuxtjs%E3%80%8B.md">126.精读《Nuxtjs》</a>
- <a href="./前沿技术/127.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Conf%202019%20-%20Day1%E3%80%8B.md">127.精读《React Conf 2019 - Day1》</a>
- <a href="./前沿技术/129.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Conf%202019%20-%20Day2%E3%80%8B.md">129.精读《React Conf 2019 - Day2》</a>
- <a href="./前沿技术/132.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%AD%A3%E4%BA%A4%E7%9A%84%20React%20%E7%BB%84%E4%BB%B6%E3%80%8B.md">132.精读《正交的 React 组件》</a>
- <a href="./前沿技术/133.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%AF%BB%E6%89%BE%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1%E7%9A%84%E5%B9%B3%E8%A1%A1%E7%82%B9%E3%80%8B.md">133.精读《寻找框架设计的平衡点》</a>
- <a href="./前沿技术/134.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%88%91%E5%9C%A8%E9%98%BF%E9%87%8C%E6%95%B0%E6%8D%AE%E4%B8%AD%E5%8F%B0%E5%A4%A7%E5%89%8D%E7%AB%AF%E3%80%8B.md">134.精读《我在阿里数据中台大前端》</a>
- <a href="./前沿技术/138.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%B2%BE%E9%80%9A%20console.log%E3%80%8B.md">138.精读《精通 console.log》</a>
- <a href="./前沿技术/139.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20JSON%20Parser%E3%80%8B.md">139.精读《手写 JSON Parser》</a>
- <a href="./前沿技术/140.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%BB%93%E5%90%88%20React%20%E4%BD%BF%E7%94%A8%E5%8E%9F%E7%94%9F%20Drag%20Drop%20API%E3%80%8B.md">140.精读《结合 React 使用原生 Drag Drop API》</a>
- <a href="./前沿技术/141.%E7%B2%BE%E8%AF%BB%E3%80%8AuseRef%20%E4%B8%8E%20createRef%20%E7%9A%84%E5%8C%BA%E5%88%AB%E3%80%8B.md">141.精读《useRef 与 createRef 的区别》</a>
- <a href="./前沿技术/142.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A6%82%E4%BD%95%E5%81%9A%E5%A5%BD%20CodeReview%E3%80%8B.md">142.精读《如何做好 CodeReview》</a>
- <a href="./前沿技术/143.%E7%B2%BE%E8%AF%BB%E3%80%8ASuspense%20%E6%94%B9%E5%8F%98%E5%BC%80%E5%8F%91%E6%96%B9%E5%BC%8F%E3%80%8B.md">143.精读《Suspense 改变开发方式》</a>
- <a href="./前沿技术/144.%E7%B2%BE%E8%AF%BB%E3%80%8AWebpack5%20%E6%96%B0%E7%89%B9%E6%80%A7%20-%20%E6%A8%A1%E5%9D%97%E8%81%94%E9%82%A6%E3%80%8B.md">144.精读《Webpack5 新特性 - 模块联邦》</a>
- <a href="./前沿技术/145.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Router%20v6%E3%80%8B.md">145.精读《React Router v6》</a>
- <a href="./前沿技术/146.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Hooks%20%E6%95%B0%E6%8D%AE%E6%B5%81%E3%80%8B.md">146.精读《React Hooks 数据流》</a>
- <a href="./前沿技术/147.%20%E7%B2%BE%E8%AF%BB%E3%80%8A%40types%20react%20%E5%80%BC%E5%BE%97%E6%B3%A8%E6%84%8F%E7%9A%84%20TS%20%E6%8A%80%E5%B7%A7%E3%80%8B.md">147. 精读《@types react 值得注意的 TS 技巧》</a>
- <a href="./前沿技术/148.%20%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Error%20Boundaries%E3%80%8B.md">148. 精读《React Error Boundaries》</a>
- <a href="./前沿技术/149.%20%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20%E6%80%A7%E8%83%BD%E8%B0%83%E8%AF%95%E3%80%8B.md">149. 精读《React 性能调试》</a>
- <a href="./前沿技术/150.%20%E7%B2%BE%E8%AF%BB%E3%80%8ADeno%201.0%20%E4%BD%A0%E9%9C%80%E8%A6%81%E4%BA%86%E8%A7%A3%E7%9A%84%E3%80%8B.md">150. 精读《Deno 1.0 你需要了解的》</a>
- <a href="./前沿技术/152.%20%E7%B2%BE%E8%AF%BB%E3%80%8Arecoil%E3%80%8B.md">152. 精读《recoil》</a>
- <a href="./前沿技术/153.%20%E7%B2%BE%E8%AF%BB%E3%80%8Asnowpack%E3%80%8B.md">153. 精读《snowpack》</a>
- <a href="./前沿技术/154.%20%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%94%A8%20React%20%E5%81%9A%E6%8C%89%E9%9C%80%E6%B8%B2%E6%9F%93%E3%80%8B.md">154. 精读《用 React 做按需渲染》</a>
- <a href="./前沿技术/157.%20%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A6%82%E4%BD%95%E6%AF%94%E8%BE%83%20Object%20%E5%AF%B9%E8%B1%A1%E3%80%8B.md">157. 精读《如何比较 Object 对象》</a>
- <a href="./前沿技术/158.%20%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript%204%E3%80%8B.md">158. 精读《Typescript 4》</a>
- <a href="./前沿技术/159.%20%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%AF%B9%E4%BD%8E%E4%BB%A3%E7%A0%81%E6%90%AD%E5%BB%BA%E7%9A%84%E7%90%86%E8%A7%A3%E3%80%8B.md">159. 精读《对低代码搭建的理解》</a>
- <a href="./前沿技术/160.%20%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%87%BD%E6%95%B0%E7%BC%93%E5%AD%98%E3%80%8B.md">160. 精读《函数缓存》</a>
- <a href="./前沿技术/161.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%8F%AF%E8%A7%86%E5%8C%96%E6%90%AD%E5%BB%BA%E6%80%9D%E8%80%83%20-%20%E5%AF%8C%E6%96%87%E6%9C%AC%E6%90%AD%E5%BB%BA%E3%80%8B.md">161.精读《可视化搭建思考 - 富文本搭建》</a>
- <a href="./前沿技术/162.%E7%B2%BE%E8%AF%BB%E3%80%8ATasks%2C%20microtasks%2C%20queues%20and%20schedules%E3%80%8B.md">162.精读《Tasks, microtasks, queues and schedules》</a>
- <a href="./前沿技术/163.%E7%B2%BE%E8%AF%BB%E3%80%8ASpring%20%E6%A6%82%E5%BF%B5%E3%80%8B.md">163.精读《Spring 概念》</a>
- <a href="./前沿技术/164.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%95%B0%E6%8D%AE%E6%90%AD%E5%BB%BA%E5%BC%95%E6%93%8E%20bi-designer%20API-%E8%AE%BE%E8%AE%A1%E5%99%A8%E3%80%8B.md">164.精读《数据搭建引擎 bi-designer API-设计器》</a>
- <a href="./前沿技术/165.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%95%B0%E6%8D%AE%E6%90%AD%E5%BB%BA%E5%BC%95%E6%93%8E%20bi-designer%20API-%E7%BB%84%E4%BB%B6%E3%80%8B.md">165.精读《数据搭建引擎 bi-designer API-组件》</a>
- <a href="./前沿技术/166.%E7%B2%BE%E8%AF%BB%E3%80%8ABI%20%E6%90%AD%E5%BB%BA%20-%20%E7%AD%9B%E9%80%89%E6%9D%A1%E4%BB%B6%E3%80%8B.md">166.精读《BI 搭建 - 筛选条件》</a>
- <a href="./前沿技术/190.%E7%B2%BE%E8%AF%BB%E3%80%8ADOM%20diff%20%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3%E3%80%8B.md">190.精读《DOM diff 原理详解》</a>
- <a href="./前沿技术/191.%E7%B2%BE%E8%AF%BB%E3%80%8A%E9%AB%98%E6%80%A7%E8%83%BD%E8%A1%A8%E6%A0%BC%E3%80%8B.md">191.精读《高性能表格》</a>
- <a href="./前沿技术/192.%E7%B2%BE%E8%AF%BB%E3%80%8ADOM%20diff%20%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97%E3%80%8B.md">192.精读《DOM diff 最长上升子序列》</a>
- <a href="./前沿技术/193.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Server%20Component%E3%80%8B.md">193.精读《React Server Component》</a>
- <a href="./前沿技术/194.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E3%80%8B.md">194.精读《算法基础数据结构》</a>
- <a href="./前沿技术/195.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%96%B0%E4%B8%80%E4%BB%A3%E5%89%8D%E7%AB%AF%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7%E5%AF%B9%E6%AF%94%E3%80%8B.md">195.精读《新一代前端构建工具对比》</a>
- <a href="./前沿技术/196.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%89%8D%E7%AB%AF%E8%81%8C%E4%B8%9A%E8%A7%84%E5%88%92%20-%202021%20%E5%B9%B4%E3%80%8B.md">196.精读《前端职业规划 - 2021 年》</a>
- <a href="./前沿技术/197.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%BD%8E%E4%BB%A3%E7%A0%81%E9%80%BB%E8%BE%91%E7%BC%96%E6%8E%92%E3%80%8B.md">197.精读《低代码逻辑编排》</a>
- <a href="./前沿技术/202.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%2018%E3%80%8B.md">202.精读《React 18》</a>
- <a href="./前沿技术/204.%E7%B2%BE%E8%AF%BB%E3%80%8A%E9%BB%98%E8%AE%A4%E3%80%81%E5%91%BD%E5%90%8D%E5%AF%BC%E5%87%BA%E7%9A%84%E5%8C%BA%E5%88%AB%E3%80%8B.md">204.精读《默认、命名导出的区别》</a>
- <a href="./前沿技术/205.%E7%B2%BE%E8%AF%BB%E3%80%8AJS%20with%20%E8%AF%AD%E6%B3%95%E3%80%8B.md">205.精读《JS with 语法》</a>
- <a href="./前沿技术/206.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%B8%80%E7%A7%8D%20Hooks%20%E6%95%B0%E6%8D%AE%E6%B5%81%E7%AE%A1%E7%90%86%E6%96%B9%E6%A1%88%E3%80%8B.md">206.精读《一种 Hooks 数据流管理方案》</a>
- <a href="./前沿技术/207.%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript%20infer%20%E5%85%B3%E9%94%AE%E5%AD%97%E3%80%8B.md">207.精读《Typescript infer 关键字》</a>
- <a href="./前沿技术/208.%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript%204.4%E3%80%8B.md">208.精读《Typescript 4.4》</a>
- <a href="./前沿技术/209.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%8D%95%E8%8E%B7%E6%89%80%E6%9C%89%E5%BC%82%E6%AD%A5%20error%E3%80%8B.md">209.精读《捕获所有异步 error》</a>
- <a href="./前沿技术/210.%E7%B2%BE%E8%AF%BB%E3%80%8Aclass%20static%20block%E3%80%8B.md">210.精读《class static block》</a>
- <a href="./前沿技术/211.%E7%B2%BE%E8%AF%BB%E3%80%8AMicrosoft%20Power%20Fx%E3%80%8B.md">211.精读《Microsoft Power Fx》</a>
- <a href="./前沿技术/212.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E6%80%9D%E8%80%83%E3%80%8B.md">212.精读《可维护性思考》</a>
- <a href="./前沿技术/213.%E7%B2%BE%E8%AF%BB%E3%80%8APrisma%20%E7%9A%84%E4%BD%BF%E7%94%A8%E3%80%8B.md">213.精读《Prisma 的使用》</a>
- <a href="./前沿技术/214.%E7%B2%BE%E8%AF%BB%E3%80%8Aweb%20streams%E3%80%8B.md">214.精读《web streams》</a>
- <a href="./前沿技术/215.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%BB%80%E4%B9%88%E6%98%AF%20LOD%20%E8%A1%A8%E8%BE%BE%E5%BC%8F%E3%80%8B.md">215.精读《什么是 LOD 表达式》</a>
- <a href="./前沿技术/216.%E7%B2%BE%E8%AF%BB%E3%80%8A15%20%E5%A4%A7%20LOD%20%E8%A1%A8%E8%BE%BE%E5%BC%8F%20-%20%E4%B8%8A%E3%80%8B.md">216.精读《15 大 LOD 表达式 - 上》</a>
- <a href="./前沿技术/217.%E7%B2%BE%E8%AF%BB%E3%80%8A15%20%E5%A4%A7%20LOD%20%E8%A1%A8%E8%BE%BE%E5%BC%8F%20-%20%E4%B8%8B%E3%80%8B.md">217.精读《15 大 LOD 表达式 - 下》</a>
- <a href="./前沿技术/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">218.精读《Rust 是 JS 基建的未来》</a>
- <a href="./前沿技术/219.%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%E4%B8%80%E3%80%8B.md">219.精读《深入了解现代浏览器一》</a>
- <a href="./前沿技术/220.%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%E4%BA%8C%E3%80%8B.md">220.精读《深入了解现代浏览器二》</a>
- <a href="./前沿技术/221.%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%E4%B8%89%E3%80%8B.md">221.精读《深入了解现代浏览器三》</a>
- <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>
- <a href="./前沿技术/228.%E7%B2%BE%E8%AF%BB%E3%80%8Apipe%20operator%20for%20JavaScript%E3%80%8B.md">228.精读《pipe operator for JavaScript》</a>
- <a href="./前沿技术/230.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%AF%B9%20Markdown%20%E7%9A%84%E6%80%9D%E8%80%83%E3%80%8B.md">230.精读《对 Markdown 的思考》</a>
- <a href="./前沿技术/237.%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript%204.5-4.6%20%E6%96%B0%E7%89%B9%E6%80%A7%E3%80%8B.md">237.精读《Typescript 4.5-4.6 新特性》</a>
- <a href="./前沿技术/238.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%B8%8D%E5%86%8D%E9%9C%80%E8%A6%81%20JS%20%E5%81%9A%E7%9A%84%205%20%E4%BB%B6%E4%BA%8B%E3%80%8B.md">238.精读《不再需要 JS 做的 5 件事》</a>
- <a href="./前沿技术/239.%E7%B2%BE%E8%AF%BB%E3%80%8AJS%20%E6%95%B0%E7%BB%84%E7%9A%84%E5%86%85%E9%83%A8%E5%AE%9E%E7%8E%B0%E3%80%8B.md">239.精读《JS 数组的内部实现》</a>
- <a href="./前沿技术/240.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20useEvent%20RFC%E3%80%8B.md">240.精读《React useEvent RFC》</a>
- <a href="./前沿技术/242.%E7%B2%BE%E8%AF%BB%E3%80%8Aweb%20reflow%E3%80%8B.md">242.精读《web reflow》</a>
### TS 类型体操
- <a href="./TS 类型体操/243.%E7%B2%BE%E8%AF%BB%E3%80%8Atype%20challenges%20-%20easy%E3%80%8B.md">243.精读《type challenges - easy》</a>
### 设计模式
- <a href="./设计模式/167.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Abstract%20Factory%20%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E3%80%8B.md">167.精读《设计模式 - Abstract Factory 抽象工厂》</a>
- <a href="./设计模式/168.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Builder%20%E7%94%9F%E6%88%90%E5%99%A8%E3%80%8B.md">168.精读《设计模式 - Builder 生成器》</a>
- <a href="./设计模式/169.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Factory%20Method%20%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95%E3%80%8B.md">169.精读《设计模式 - Factory Method 工厂方法》</a>
- <a href="./设计模式/170.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Prototype%20%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F%E3%80%8B.md">170.精读《设计模式 - Prototype 原型模式》</a>
- <a href="./设计模式/171.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Singleton%20%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E3%80%8B.md">171.精读《设计模式 - Singleton 单例模式》</a>
- <a href="./设计模式/172.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Adapter%20%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F%E3%80%8B.md">172.精读《设计模式 - Adapter 适配器模式》</a>
- <a href="./设计模式/173.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Bridge%20%E6%A1%A5%E6%8E%A5%E6%A8%A1%E5%BC%8F%E3%80%8B.md">173.精读《设计模式 - Bridge 桥接模式》</a>
- <a href="./设计模式/174.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Composite%20%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F%E3%80%8B.md">174.精读《设计模式 - Composite 组合模式》</a>
- <a href="./设计模式/175.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Decorator%20%E8%A3%85%E9%A5%B0%E5%99%A8%E6%A8%A1%E5%BC%8F%E3%80%8B.md">175.精读《设计模式 - Decorator 装饰器模式》</a>
- <a href="./设计模式/176.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Facade%20%E5%A4%96%E8%A7%82%E6%A8%A1%E5%BC%8F%E3%80%8B.md">176.精读《设计模式 - Facade 外观模式》</a>
- <a href="./设计模式/177.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Flyweight%20%E4%BA%AB%E5%85%83%E6%A8%A1%E5%BC%8F%E3%80%8B.md">177.精读《设计模式 - Flyweight 享元模式》</a>
- <a href="./设计模式/178.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Proxy%20%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F%E3%80%8B.md">178.精读《设计模式 - Proxy 代理模式》</a>
- <a href="./设计模式/179.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Chain%20of%20Responsibility%20%E8%81%8C%E8%B4%A3%E9%93%BE%E6%A8%A1%E5%BC%8F%E3%80%8B.md">179.精读《设计模式 - Chain of Responsibility 职责链模式》</a>
- <a href="./设计模式/180.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Command%20%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F%E3%80%8B.md">180.精读《设计模式 - Command 命令模式》</a>
- <a href="./设计模式/181.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Interpreter%20%E8%A7%A3%E9%87%8A%E5%99%A8%E6%A8%A1%E5%BC%8F%E3%80%8B.md">181.精读《设计模式 - Interpreter 解释器模式》</a>
- <a href="./设计模式/182.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Iterator%20%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F%E3%80%8B.md">182.精读《设计模式 - Iterator 迭代器模式》</a>
- <a href="./设计模式/183.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Mediator%20%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F%E3%80%8B.md">183.精读《设计模式 - Mediator 中介者模式》</a>
- <a href="./设计模式/184.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Memoto%20%E5%A4%87%E5%BF%98%E5%BD%95%E6%A8%A1%E5%BC%8F%E3%80%8B.md">184.精读《设计模式 - Memoto 备忘录模式》</a>
- <a href="./设计模式/185.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Observer%20%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%E3%80%8B.md">185.精读《设计模式 - Observer 观察者模式》</a>
- <a href="./设计模式/186.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20State%20%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F%E3%80%8B.md">186.精读《设计模式 - State 状态模式》</a>
- <a href="./设计模式/187.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Strategy%20%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F%E3%80%8B.md">187.精读《设计模式 - Strategy 策略模式》</a>
- <a href="./设计模式/188.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Template%20Method%20%E6%A8%A1%E7%89%88%E6%A8%A1%E5%BC%8F%E3%80%8B.md">188.精读《设计模式 - Template Method 模版模式》</a>
- <a href="./设计模式/189.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Visitor%20%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8F%E3%80%8B.md">189.精读《设计模式 - Visitor 访问者模式》</a>
### 编译原理
- <a href="./编译原理/64.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E8%AF%8D%E6%B3%95%E5%88%86%E6%9E%90%E3%80%8B.md">64.精读《手写 SQL 编译器 - 词法分析》</a>
- <a href="./编译原理/65.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%96%87%E6%B3%95%E4%BB%8B%E7%BB%8D%E3%80%8B.md">65.精读《手写 SQL 编译器 - 文法介绍》</a>
- <a href="./编译原理/66.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E8%AF%AD%E6%B3%95%E5%88%86%E6%9E%90%E3%80%8B.md">66.精读《手写 SQL 编译器 - 语法分析》</a>
- <a href="./编译原理/67.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E5%9B%9E%E6%BA%AF%E3%80%8B.md">67.精读《手写 SQL 编译器 - 回溯》</a>
- <a href="./编译原理/70.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E8%AF%AD%E6%B3%95%E6%A0%91%E3%80%8B.md">70.精读《手写 SQL 编译器 - 语法树》</a>
- <a href="./编译原理/71.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E9%94%99%E8%AF%AF%E6%8F%90%E7%A4%BA%E3%80%8B.md">71.精读《手写 SQL 编译器 - 错误提示》</a>
- <a href="./编译原理/78.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B9%8B%E7%BC%93%E5%AD%98%E3%80%8B.md">78.精读《手写 SQL 编译器 - 性能优化之缓存》</a>
- <a href="./编译原理/85.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%99%BA%E8%83%BD%E6%8F%90%E7%A4%BA%E3%80%8B.md">85.精读《手写 SQL 编译器 - 智能提示》</a>
### 源码解读
- <a href="./源码解读/48.%E7%B2%BE%E8%AF%BB%E3%80%8AImmer.js%E3%80%8B%E6%BA%90%E7%A0%81.md">48.精读《Immer.js》源码</a>
- <a href="./源码解读/73.%E7%B2%BE%E8%AF%BB%E3%80%8Asqorn%20%E6%BA%90%E7%A0%81%E3%80%8B.md">73.精读《sqorn 源码》</a>
- <a href="./源码解读/75.%E7%B2%BE%E8%AF%BB%E3%80%8AEpitath%20%E6%BA%90%E7%A0%81%20-%20renderProps%20%E6%96%B0%E7%94%A8%E6%B3%95%E3%80%8B.md">75.精读《Epitath 源码 - renderProps 新用法》</a>
- <a href="./源码解读/82.%E7%B2%BE%E8%AF%BB%E3%80%8AHtm%20-%20Hyperscript%20%E6%BA%90%E7%A0%81%E3%80%8B.md">82.精读《Htm - Hyperscript 源码》</a>
- <a href="./源码解读/92.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20PowerPlug%20%E6%BA%90%E7%A0%81%E3%80%8B.md">92.精读《React PowerPlug 源码》</a>
- <a href="./源码解读/93.%E7%B2%BE%E8%AF%BB%E3%80%8Asyntax-parser%20%E6%BA%90%E7%A0%81%E3%80%8B.md">93.精读《syntax-parser 源码》</a>
- <a href="./源码解读/98.%E7%B2%BE%E8%AF%BB%E3%80%8Areact-easy-state%20%E6%BA%90%E7%A0%81%E3%80%8B.md">98.精读《react-easy-state 源码》</a>
- <a href="./源码解读/110.%E7%B2%BE%E8%AF%BB%E3%80%8AInject%20Instance%20%E6%BA%90%E7%A0%81%E3%80%8B.md">110.精读《Inject Instance 源码》</a>
- <a href="./源码解读/122.%E7%B2%BE%E8%AF%BB%E3%80%8Arobot%20%E6%BA%90%E7%A0%81%20-%20%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA%E3%80%8B.md">122.精读《robot 源码 - 有限状态机》</a>
- <a href="./源码解读/128.%E7%B2%BE%E8%AF%BB%E3%80%8AHooks%20%E5%8F%96%E6%95%B0%20-%20swr%20%E6%BA%90%E7%A0%81%E3%80%8B.md">128.精读《Hooks 取数 - swr 源码》</a>
- <a href="./源码解读/130.%E7%B2%BE%E8%AF%BB%E3%80%8Aunstated%20%E4%B8%8E%20unstated-next%20%E6%BA%90%E7%A0%81%E3%80%8B.md">130.精读《unstated 与 unstated-next 源码》</a>
- <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>
- <a href="./源码解读/229.%E7%B2%BE%E8%AF%BB%E3%80%8Avue-lit%20%E6%BA%90%E7%A0%81%E3%80%8B.md">229.精读《vue-lit 源码》</a>
- <a href="./源码解读/241.%E7%B2%BE%E8%AF%BB%E3%80%8Areact-snippets%20-%20Router%20%E6%BA%90%E7%A0%81%E3%80%8B.md">241.精读《react-snippets - Router 源码》</a>
### 商业思考
- <a href="./商业思考/90.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%9E%81%E5%AE%A2%E5%85%AC%E5%9B%AD%202019%E3%80%8B.md">90.精读《极客公园 2019》</a>
- <a href="./商业思考/103.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%93%E5%AE%B6%E4%B8%8D%E5%86%8D%E5%85%B3%E5%BF%83%E6%8A%80%E6%9C%AF%E7%BB%86%E8%8A%82%E3%80%8B.md">103.精读《为什么专家不再关心技术细节》</a>
- <a href="./商业思考/106.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%95%B0%E6%8D%AE%E4%B9%8B%E4%B8%8A%C2%B7%E6%99%BA%E6%85%A7%E4%B9%8B%E5%85%89%20-%202018%E3%80%8B.md">106.精读《数据之上·智慧之光 - 2018》</a>
- <a href="./商业思考/108.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%99%BA%E8%83%BD%E5%95%86%E4%B8%9A%E3%80%8B.md">108.精读《智能商业》</a>
- <a href="./商业思考/114.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%B0%81%E5%9C%A8%E4%B8%96%E7%95%8C%E4%B8%AD%E5%BF%83%E3%80%8B.md">114.精读《谁在世界中心》</a>
- <a href="./商业思考/115.%E7%B2%BE%E8%AF%BB%E3%80%8ATableau%20%E5%85%A5%E9%97%A8%E3%80%8B.md">115.精读《Tableau 入门》</a>
- <a href="./商业思考/116.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%88%B7%E6%96%B0%E3%80%8B.md">116.精读《刷新》</a>
- <a href="./商业思考/131.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%BB%8E%200%20%E5%88%B0%201%E3%80%8B.md">131.精读《从 0 到 1》</a>
- <a href="./商业思考/135.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%9E%81%E5%AE%A2%E5%85%AC%E5%9B%AD%20IFX%20-%20%E4%B8%8A%E3%80%8B.md">135.精读《极客公园 IFX - 上》</a>
- <a href="./商业思考/136.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%9E%81%E5%AE%A2%E5%85%AC%E5%9B%AD%20IFX%20-%20%E4%B8%8B%E3%80%8B.md">136.精读《极客公园 IFX - 下》</a>
- <a href="./商业思考/137.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%BD%93%E6%88%91%E5%9C%A8%E5%88%86%E4%BA%AB%E7%9A%84%E6%97%B6%E5%80%99%EF%BC%8C%E6%88%91%E5%9C%A8%E5%81%9A%E4%BB%80%E4%B9%88%EF%BC%9F%E3%80%8B.md">137.精读《当我在分享的时候,我在做什么?》</a>
### 算法
- <a href="./算法/198.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%20-%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E3%80%8B.md">198.精读《算法 - 动态规划》</a>
- <a href="./算法/199.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%20-%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E3%80%8B.md">199.精读《算法 - 滑动窗口》</a>
- <a href="./算法/200.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%20-%20%E5%9B%9E%E6%BA%AF%E3%80%8B.md">200.精读《算法 - 回溯》</a>
- <a href="./算法/201.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%20-%20%E4%BA%8C%E5%8F%89%E6%A0%91%E3%80%8B.md">201.精读《算法 - 二叉树》</a>
- <a href="./算法/203.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%20-%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E3%80%8B.md">203.精读《算法 - 二叉搜索树》</a>
### SQL
- <a href="./SQL/231.SQL%20%E5%85%A5%E9%97%A8.md">231.SQL 入门</a>
- <a href="./SQL/232.SQL%20%E8%81%9A%E5%90%88%E6%9F%A5%E8%AF%A2.md">232.SQL 聚合查询</a>
- <a href="./SQL/233.SQL%20%E5%A4%8D%E6%9D%82%E6%9F%A5%E8%AF%A2.md">233.SQL 复杂查询</a>
- <a href="./SQL/234.SQL%20CASE%20%E8%A1%A8%E8%BE%BE%E5%BC%8F.md">234.SQL CASE 表达式</a>
- <a href="./SQL/235.SQL%20%E7%AA%97%E5%8F%A3%E5%87%BD%E6%95%B0.md">235.SQL 窗口函数</a>
- <a href="./SQL/236.SQL%20grouping.md">236.SQL grouping</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 年前,通过后端模版定义、注释定义模块依赖。对经历过来的人来说,历史的模块化方式还停留在脑海中,反而新上手的同学会更快接受现代的模块化规范。
@@ -81,7 +81,7 @@ YUI3 的 sandbox 像极了差不多同时出现的 AMD 规范,但早期 yahoo
对于 js 模块化,最近出现的 `<script type="module">` 方式,虽然还没有得到浏览器原生支持,但也是我比较看好的未来趋势,这样就连 webpack 的拆包都不需要了,直接把源代码传到服务器,配合 http2.0 完美抛开预编译的枷锁。
上述三方案都不依赖预编译,分别实现了 html、css、js 模块化,相信这就是未来。
上述三方案都不依赖预编译,分别实现了 html、css、js 模块化,相信这就是未来。
### 模块化标准推进速度仍然缓慢
@@ -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
@@ -154,7 +154,7 @@ Table 主要配置分为行、列、标记与筛选。通过这四个配置区
我们会发现,原本存在于列的 Category 被自动挪到了行,原本存在于行的 Sales 被挪到了 “标记” 区域。在正式介绍 “标记” 区域前,先理解一下为何会发生这种转变:
**表格类组件是双维度组件,折线图是单维度组件。**也就是表格的行与列都是维度,而折线图横轴作为维度后,纵轴就要作为度量。上面的例子中,折线图维度有两个字段,虽然通过分面方式渲染出来了,但当切换为支持双维度的表格后, **可以将多余的一个维度挪到表格组件另一个维度区域中**
**表格类组件是双维度组件,折线图是单维度组件。** 也就是表格的行与列都是维度,而折线图横轴作为维度后,纵轴就要作为度量。上面的例子中,折线图维度有两个字段,虽然通过分面方式渲染出来了,但当切换为支持双维度的表格后, **可以将多余的一个维度挪到表格组件另一个维度区域中**
而表格行与列都是维度的情况下,单元格的值就需要用 “标记” 中文本来表示,因此原折线图的度量字段自动转移到了 “标记” 区域。
@@ -313,7 +313,7 @@ Tableau 内置的图表分为 N 大类 - **表格、地图、柱折面饼、散
<img width=529 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566699040802-40a23a43-7a23-43d1-9d06-27a6413803e8.png#align=left&display=inline&height=314&name=image.png&originHeight=856&originWidth=1440&size=106220&status=done&width=529">
**上图也可以理解为展示出 Order Date 与 Order ID 的明细数据,按照 Order Date 分组且列合并。**下钻就是一步步接近明细数据的过程,但目的不是为了看明细表,而是看某些维度下按其他维度拆分的详细信息。
**上图也可以理解为展示出 Order Date 与 Order ID 的明细数据,按照 Order Date 分组且列合并。** 下钻就是一步步接近明细数据的过程,但目的不是为了看明细表,而是看某些维度下按其他维度拆分的详细信息。
图表下钻和表格思路是一致的:
@@ -323,9 +323,9 @@ Tableau 内置的图表分为 N 大类 - **表格、地图、柱折面饼、散
<img width=667 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566700041429-03bcb68a-dd00-4cab-a256-f37234bb2300.png#align=left&display=inline&height=423&name=image.png&originHeight=1350&originWidth=2128&size=155697&status=done&width=667">
**可以认为,当行或列上最后一个字段为度量时,就会切换为图表展示,因为图表适合展示连续状态。**如果排除上图蓝色区域,剩下的区域就是个交叉表,交叉表只是行与列同时存在维度字段的场景,仅有行或列时就变成了普通表格;而图形的下钻和表格下钻机理相同,只是把 “单元格” 的文本换成了柱子或线。
**可以认为,当行或列上最后一个字段为度量时,就会切换为图表展示,因为图表适合展示连续状态。** 如果排除上图蓝色区域,剩下的区域就是个交叉表,交叉表只是行与列同时存在维度字段的场景,仅有行或列时就变成了普通表格;而图形的下钻和表格下钻机理相同,只是把 “单元格” 的文本换成了柱子或线。
**所以对任何图表的下钻,都是对轴的下钻,**相同的是单元格属性永远不会改变,表格的单元格是文本,图形单元格是图形,一个简单折线图可以理解为对整体行与列单元格进行 “连续打通”:
**所以对任何图表的下钻,都是对轴的下钻,** 相同的是单元格属性永远不会改变,表格的单元格是文本,图形单元格是图形,一个简单折线图可以理解为对整体行与列单元格进行 “连续打通”:
<img width=406 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566700448492-a7b96c99-b600-41b0-9159-e88412f4402b.png#align=left&display=inline&height=329&name=image.png&originHeight=1342&originWidth=1654&size=105794&status=done&width=406">
@@ -10,7 +10,7 @@
## 概述
原文对于深水区的想法,讲的很清楚,还是建议读者去读一下原文。
对比 2010 年,整个前端生态已经翻新了好几遍,直到近几年的 Node BFF、IDE Cloud,抑或是客户端 AI,还是 Serverless 的建设,前端想要深度参与的话,单纯依靠原来的 HTML/CSS/JS 三件套技能也远远不够了。再抛开技术,整个互联网创业生态也重构了好几遍。无论是技术层面还是意识层面,如今的前端开发已经进入深水区。
对比 2010 年,整个前端生态已经翻新了好几遍,直到近几年的 Node BFF、IDE Cloud,抑或是客户端 AI,还是 Serverless 的建设,前端想要深度参与的话,单纯依靠原来的 HTML/CSS/JS 三件套技能也远远不够了。再抛开技术,整个互联网创业生态也重构了好几遍。无论是技术层面还是意识层面,如今的前端开发已经进入深水区。
- 深水区需要哪些技能
![image.png](https://img.alicdn.com/tfs/TB1oovQe8r0gK0jSZFnXXbRRXXa-1832-1032.png)
@@ -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 实例是个纯对象,没有将方法挂载到原型链上,简单易懂。
@@ -0,0 +1,262 @@
## 1 引言
搭配了合适的设计模式的代码,才可拥有良好的可维护性,[The Benefits of Orthogonal React Components](https://dmitripavlutin.com/orthogonal-react-components/) 这篇文章就重点介绍了正交性原理。
所谓正交,即模块之间不会相互影响。想象一个音响的音量与换台按钮间如果不是正交关系,控制音量同时可能影响换台,这样的设备很难维护:
<img width=400 src="https://img.alicdn.com/tfs/TB1dczIpQL0gK0jSZFtXXXQCXXa-1000-993.png">
前端代码也一样,UI 与数据处理逻辑分离就是一种符合正交原则的设计,这样有利于长期代码质量维护。
## 2 概述
一个拥有良好正交性的 React App 会按照如下模块分离设计:
1. UI 元素(展示型组件)。
2. 取数逻辑(fetch library, REST or GraphQL)。
3. 全局状态管理(redux)。
4. 持久化(local storage, cookies)。
文中通过两个例子说明。
### 让组件与取数逻辑正交
比如一个展示雇员列表组件 `<EmployeesPage>`:
```jsx
import React, { useState } from "react";
import axios from "axios";
import EmployeesList from "./EmployeesList";
function EmployeesPage() {
const [isFetching, setFetching] = useState(false);
const [employees, setEmployees] = useState([]);
useEffect(function fetch() {
(async function() {
setFetching(true);
const response = await axios.get("/employees");
setEmployees(response.data);
setFetching(false);
})();
}, []);
if (isFetching) {
return <div>Fetching employees....</div>;
}
return <EmployeesList employees={employees} />;
}
```
这样设计看上去没问题,但其实违背了正交原则,因为 `EmployeesPage` 既负责渲染 UI 又关心取数逻辑。正交的写法如下:
```jsx
import React, { Suspense } from "react";
import EmployeesList from "./EmployeesList";
function EmployeesPage({ resource }) {
return (
<Suspense fallback={<h1>Fetching employees....</h1>}>
<EmployeesFetch resource={resource} />
</Suspense>
);
}
function EmployeesFetch({ resource }) {
const employees = resource.employees.read();
return <EmployeesList employees={employees} />;
}
```
**`Suspense` 将 loading 状态剥离到父级组件,因此子组件只需要关心如何用数据,不需关心如何取数据(以及 loading 态)。**
### 让组件与滚动监听正交
比如一个滚动到一定距离就出现 "jump to top" 的组件 `<ScrollToTop>`,可能会这么实现:
```jsx
import React, { useState, useEffect } from "react";
const DISTANCE = 500;
function ScrollToTop() {
const [crossed, setCrossed] = useState(false);
useEffect(function() {
const handler = () => setCrossed(window.scrollY > DISTANCE);
handler();
window.addEventListener("scroll", handler);
return () => window.removeEventListener("scroll", handler);
}, []);
function onClick() {
window.scrollTo({
top: 0,
behavior: "smooth"
});
}
if (!crossed) {
return null;
}
return <button onClick={onClick}>Jump to top</button>;
}
```
可以看到,在这个组件中,按钮与滚动状态判断逻辑混合在了一起。如果我们将 “滚动到一定距离就渲染 UI” 抽象成通用组件 `IfScrollCrossed` 呢?
```jsx
import { useState, useEffect } from "react";
function useScrollDistance(distance) {
const [crossed, setCrossed] = useState(false);
useEffect(
function() {
const handler = () => setCrossed(window.scrollY > distance);
handler();
window.addEventListener("scroll", handler);
return () => window.removeEventListener("scroll", handler);
},
[distance]
);
return crossed;
}
function IfScrollCrossed({ children, distance }) {
const isBottom = useScrollDistance(distance);
return isBottom ? children : null;
}
```
有了 `IfScrollCrossed`,我们就能专注写 “点击按钮跳转到顶部” 这个 UI 组件了:
```jsx
function onClick() {
window.scrollTo({
top: 0,
behavior: "smooth"
});
}
function JumpToTop() {
return <button onClick={onClick}>Jump to top</button>;
}
```
最后将他们拼装在一起:
```jsx
import React from "react";
// ...
const DISTANCE = 500;
function MyComponent() {
// ...
return (
<IfScrollCrossed distance={DISTANCE}>
<JumpToTop />
</IfScrollCrossed>
);
}
```
这么做,我们的 `<JumpToTop>``<IfScrollCrossed>` 组件就是正交关系,而且逻辑更清晰。不仅如此,这样的抽象使 `<IfScrollCrossed>` 可以被其他场景复用:
```jsx
import React from "react";
// ...
const DISTANCE_NEWSLETTER = 300;
function OtherComponent() {
// ...
return (
<IfScrollCrossed distance={DISTANCE_NEWSLETTER}>
<SubscribeToNewsletterForm />
</IfScrollCrossed>
);
}
```
### Main 组件
上面例子中,`<MyComponent>` 就是一个 Main 组件,Main 组件封装一些脏逻辑,即它要负责不同模块的组装,而这些模块之间不需要知道彼此的存在。
一个应用会存在多个 Main 组件,它们负责拼装各种作用域下的脏逻辑。
### 正交设计的好处
- **容易维护:** 正交组件逻辑相互隔离,不用担心连带影响,因此可以放心大胆的维护单个组件。
- **易读:** 由于逻辑分离导致了抽象,因此每个模块做的事情都相对单一,很容易猜测一个组件做的事情。
- **可测试:** 由于逻辑分离,可以采取逐个击破的思路进行单测。
### 权衡
如果不采用正交设计,因为模块之间的关联导致应用最终变得难以维护。但如果将正交设计应用到极致,可能会多处许多不必要的抽象,这些抽象的复用仅此一次,造成过度设计。
## 3 精读
正交设计一定程度可以理解为合理抽象,完全不抽象与过度抽象都是不可取的,因此列举了四块需要抽象的要点:UI 元素、取数逻辑、全局状态管理、持久化。
全局状态管理注入到组件,就是一种正交的抽象模式,即组件不用关心数据从哪来,而直接使用数据,而数据管理完全交由数据流层管理。
取数逻辑往往是可能被忽略的一环,无论是像原文中直接关心到 `fetch` 方法的 UI 组件,还是利用取数工具库关心了 `loading` 状态:
```jsx
import useSWR from "swr";
function Profile() {
const { data, error } = useSWR("/api/user", fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>;
}
```
虽然将取数生命周期封装到自定义 hook `useSWR` 中,但 `error` 信息对 UI 组件来说就是一个脏数据:**这让这个 UI 组件不仅要渲染数据,还要担心取数是否会失败,或者是否在 loading 中。**
好在 Suspense 模式解决了这个问题:
```jsx
import { Suspense } from "react";
import useSWR from "swr";
function Profile() {
const { data } = useSWR("/api/user", fetcher, { suspense: true });
return <div>hello, {data.name}</div>;
}
function App() {
return (
<Suspense fallback={<div>loading...</div>}>
<Profile />
</Suspense>
);
}
```
这样 `<Profile>` 只要专注于做数据渲染,而不用担心 `useSWR('/api/user', fetcher, { suspense: true })` 这个取数过程发生了什么、是否取数失败、是否在 `loading` 中。因为取数状态由 `Suspense` 管理,而取数是否意外失败由 `ErrorBoundary` 管理。
合理的抽象使组件逻辑变得更简单,从而组件嵌套使用使不用担心额外影响。尤其在大型项目中,不要担心正交抽象会使本来就很多的模块数量再次膨胀,因为相比于维护 100 个相互影响,内部逻辑复杂的模块,维护 200 个职责清晰,相互隔离的模块也许会更轻松。
## 4 总结
从正交设计角度来看,`Hooks` 解决了状态管理与 UI 分离的问题,`Suspense` 解决了取数状态与 UI 分离的问题,`ErrorBoundary` 解决了异常与 UI 分离的问题。
在你看来,React 还有哪些逻辑需要与 UI 分离?分别使用哪些方法呢?欢迎留言。
> 讨论地址是:[精读《正交的 React 组件》 · Issue #221 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/221)
**如果你想参与讨论,请 [点击这里](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,207 @@
## 1 引言
[尤雨溪](https://github.com/yyx990803) 在 2019 JSConf 的分享 [Seeking the Balance in Framework Design](https://www.youtube.com/watch?v=ANtSWq-zI0s) 十分精彩,道出了如何进行合理的前端框架设计与框架选型。
正如所说,框架对比不能只停留在 Star 数量、Npm 下载量、Stackoverflow 问题量这些简单的数据对比,而要深入到技术细节进行比较。比较框架有多种不同维度,这次分享就从服务范围、渲染机制、状态机制这三个维度进行对比。
## 2 概述
这次分享的精彩之处在于不偏不倚的站在客观立场分析了框架各维度好的一面与坏的一面,从中我们不仅能学习到一些框架知识,还能培养思辨能力。
### 服务范围
服务范围是个比较难翻译的单词,在原 PPT 中用了 “Scope” 这个单词表示,可以理解为 “作用域、框架的承诺功能范围、服务配套齐全程度”。比如提供的是一个工具库还是整体框架,插件管理是集中式还是依赖生态。
React 是典型的小服务范围框架,核心包只实现了基本功能,而其他生态基本靠社区拓展;Angular 是典型大服务范围框架,官方对所有业务场景都做了最佳实践能力覆盖;Vue 处在中间区域,通过功能分层,既拥有小服务范围的能力,又可以搭配官方插件实现更多场景化能力。
#### 小服务范围优势
**概念少,易上手**
小的服务范围代表了小的学习成本,因为暴露的基本能力较少,概念也会比较少,对新人上手比较友好。
**生态繁荣,百花齐放**
由于很多功能没有被官方实现,社区就有机会填补这些空白,因此会冒出许多第三方库,而且一旦做得好,就有机会成为 “事实标准”,因此开发者会更加积极参与到社区开发,自己做的框架 “上升空间” 也非常大。
同时,社区的力量会导致多元化,因此整体生态完整度与创新性都会非常亮眼,而且具有持续迭代的能力。
**核心维护成本低**
官方维护的核心代码较少,因此维护成本大大降低,而且官方可以将精力放在更多核心能力增强上,比如 Suspense 等,而不是将精力消耗在生态插件上。
#### 小服务范围的劣势
**复杂场景要引入新概念**
复杂场景无法支持时,就要引入新的概念解决,这导致后续技术选型可能产生分歧,并带来持续的新概念理解成本。
**非官方的开发模式逐渐产生**
随着时间的流逝,会逐渐涌出一些新的设计模式,成为当下几乎是必不可少的方案,但却不会出现在官方文档中,造成选型时的疑惑。Redux 就是一个例子。
**生态变化快,碎片化且持续流失**
非官方的生态也意味着不稳定,而且缺乏统一的管理,碎片化的模块之间可能经常出现不兼容的问题。
而且任何模块都可能被时代无情的淘汰,就像 Flux 到 Redux 再到 Hooks,带来额外的迁移成本和认知成本。谁也不希望自己的项目架构 “变得过时”,或者随时面临被新架构取代的风险,但第三方社区几乎一定代表未来会出现一种模式取代现有模式,只是时间早晚而已。
#### 大服务范围的优势
**大部分业务场景都被内置解决**
减少不必要的技术方案调研与纷争,大服务范围的框架内置的方案就能解决几乎 100% 业务问题,团队再也不会为通用架构问题烦恼了。
**生态稳定、连贯**
稳定是指,官方维护作为背书,几乎不会存在一些生态包突然不维护、与已有版本不兼容、被植入恶意程序等等意外情况。
连贯是指,官方会统一考虑一个改动在所有生态插件造成的影响,并以一个最合理的思路做整体改造,生态包无论是接口还是兼容性都不需要担心,设计思路也会一脉相承。
#### 大服务范围的劣势
**前期上手成本高**
全家桶的概念导致上手难度偏高,因为必须理解所有内置概念后才能开始项目。
**如果内置模块无法满足业务,会觉得有些死板**
一旦发生内置功能无法满足业务的场景,就很难拓展了,因为 all in one 的思路本质上就是排斥自定义拓展的,这点从 [angular-cli](https://github.com/angular/angular-cli) 就能看出来。
之所以觉得死板,是因为这种情况没办法用优雅的方式解决,只能在现有约束的框架内通过某些 “Hack” 方式解决,自然会有种死板的感觉。
#### 中等服务范围的优势
**分层设计,允许新特性渐进加入**
Vue 通过分层设计做到了折中,即官方还是会维护生态,只不过生态不是必须的,可以按需使用。这样做的好处是兼顾了一些优势。
**低学习门槛**
与小服务范围框架一样,对于核心包来说学习成本都比较低。
**依然有最佳实践解决所有业务问题**
和大服务范围框架一样,拥有全套官方最佳实践,但不内置,不强求一定要使用,因此你可以按需使用。
#### 中等服务范围的劣势
**维护成本高**
和大服务范围框架一样,虽然生态不强求,但毕竟官方还是要持续维护的,因此维护成本高的问题依然存在。
**生态多样性不高**
虽然生态是按需的,但毕竟中等服务范围的框架官方会实现一套标准生态插件,这会极大影响社区生态的发展空间,导致 “非官方插件没人愿意做”,因此生态多样性会差一些。
### 渲染机制
渲染机制区别主要在 JSX vs Template 之间,不同的表达方式之间还是存在一些很本质的区别,然而正如一开始所说,无法一言蔽之,必须从多个角度拆解的看。
#### JSX 的优势
**纯 JS 表达 UI**
单这一点就非常重要了,满足了 All In Js 的幻想。毕竟 Html、Css 相比 Js 来说,模块化能力和灵活性都很弱,将其都收敛到 Js 不仅表达方式更统一,更重要的是都获得了与 Js 一样的模块化、灵活性、Typescript 支持等能力。
**视图即数据**
将视图看作一种数据,让针对视图的逻辑测试成为可能。
同时也将视图概念泛化了,因为数据是平台无关的,一份描述视图的 DSL 可以运行在任何平台。
#### JSX 的劣势
**开销大**
页面节点越多,Diff 开销就越大。
**动态渲染很难性能优化**
由于所有 DOM 节点都是动态生成,因此无法根据初始状态结构进行安全的优化。相比之下,Template 模式可以确定哪部分属于变量,哪部分是固定的,对固定部分的 Diff 检测都可以跳过。
**动态调度虽然改善了性能,但依赖更重的运行时**
React ConcurrentMode 是一个调度优化器,但实现的逻辑也比较复杂,加重了运行时负担。
#### Template 的优势
**原生性能**
由于 Template 对节点进行直接渲染,因此与原生性能一致。
**Runtime 更小**
由于不需要额外优化,运行时代码会小很多。
#### Template 的劣势
**被 Template 语法约束,且无法拓展**
对于 Template 不支持的,只能选择接受,因为除了框架自己,没有人能拓展 Template 的特性。当遇到一些非常动态场景,但 Template 不支持的情况,只能选择接受,并用比较 Hack 的方式绕过解决,除此之外别无他法。
**模版冗长**
JSX 可以利用循环语句或者变量赋值进行模版区块的复用,但 Template 模式每次新模版都要一行一行的打出来,这种冗长的开发体验不太友好。
**运行时解析开销或者依赖编译期逻辑**
要么通过编译器预先生成 AST,要么运行时动态将 Template 解析成 AST,无论哪种方案都有额外的开销,一种是工程依赖的开销,一种是运行时动态解析的性能开销。
#### VDom + Template 的特色
Vue 在 Template 基础上支持了虚拟 DOM,因此兼具两者特色。
性能上,在编译时就进行 AST 解析,减少了运行时解析开销。
功能上,支持模版与 JSX 两种语法。
### 状态机制
状态机制 [尤雨溪](https://github.com/yyx990803) 在 JSConf 提到要单独拆出来讲,因为内容较多,时间可能不够,本次精读也限于篇幅原因略过:
- Mutable vs Immutable。
- 依赖追踪 vs 脏检测。
- 响应式 vs 模拟响应式。
显然,状态机制方案更是仁者见仁智者见智的事情,同样得从多个维度进行独立分析,并根据实际业务场景具体选择。
最后,意识到没有一个绝对均衡的框架设计方案,因为在工程领域,没有最好只有更好。
## 3 精读
我们再延伸谈一谈为什么框架设计要寻找平衡点。
**框架设计没有银弹**
与数学公式不同,框架设计甚至整个工程技术设计都没有所谓的真理,所谓条条大路通罗马,实现同一个技术目标的众多方案之间也许就是平行关系,可以根据不同维度列出一二三的对比,但无法得出一个总的结论,孰优孰劣。
**使用场景不同**
不同使用场景决定了对框架诉求的不同。
比如开发非常定制、炫酷的可视化大屏,那么前端开发框架基本也用不上,因为关注点不会聚焦在项目路由、UI 描述、甚至是数据流,而是聚焦在性能、图形渲染等问题。解决这些领域的框架可能是 虚幻 4、Unity 等游戏引擎,但普通的前端开发框架绝不会涉足这种领域,框架一定要确定自己功能范围。
即便仅局限在 Web 领域,也需要考虑是否要支持非 Web 场景,那么将 HTML 抽象成一个通用 DSL 就可能是一种选择,但非 Web 领域毕竟不是主打业务领域,在这种业务场景周边生态维护可能就比较少,这也是需要取舍的地方。
**使用的人不同**
不同团队对框架的要求也不同。
刚起步的小团队可能更需要保姆式的框架,因为这样最节省人力成本。对于规模较大的团队,希望对框架拥有较大定制能力时,小服务范围的框架可能更受青睐。当然框架作者可以像 Vue 一样做出渐进式官方能力增强方案,以此满足不同需求的用户,但毕竟也不能将生态完全交给社区,还是要做取舍。
所以当遇到更新更酷的框架时,需要冷静思考的不只是这个框架带来的收益与花费的迁移成本哪个更高,以及团队能否接受这套框架的开发习惯,更需要思考的是这个框架自身做了哪些权衡,如果这些权衡与 React、Vue、Angular 类似,那么仅仅变化了语法或者语言的改动其实意义不大,此时需要慎重考虑。
## 4 总结
这次没有提到的状态机制对比,你能分别列举出优缺点吗?欢迎留言。
> 讨论地址是:[精读《寻找框架设计的平衡点》 · Issue #223 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/223)
**如果你想参与讨论,请 [点击这里](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,111 @@
## 1 引言
当下互联网行业里面最流行的就是 ABC:
> A: AI 人工智能 B: BIG DATA C: CLOUD
而阿里经济体中的 ABC,其中的 BIG DATA,即是我们 DT https://dt.alibaba.com/ ,我们用大数据赋能商业,创造价值。
而我们说数据中台,其实阿里提出的中台只有两个:业务中台与数据中台。业务中台的目的是让业务能够快速落地,数据中台的目的是完成数据的采集、建设、管理、使用这四个环节,让数据从生产到使用过程变得丝般顺滑,不仅不让数据资产成为累赘,还会最大限度发挥出数据潜藏的价值。
笔者所在的就是数据中台的大前端团队,既为阿里经济体提供数据服务,又着力为上云企业打造属于自己的数据中台,处在前端技术、商业模式、产品设计的最前沿,且听我慢慢道来。
## 2 精读
### 全链路数据能力
从能力上看,数据中台处理数据的方方面面,从数据产生开始就进行追踪,不仅打通了数据采集、存储、处理、查询、消费的全链路,还用以下几种方式赋能业务:研发数据管理平台并监控数据质量,研发生意参谋等数据分析产品直接服务大、中、小商家,提供统一数据服务标准化数据使用流程,将数据分析的算法能力服务化,将支撑内部的数据服务上云搭建客户自己的数据中台,研发 BI 平台完成数据决策的最后一环。
### 全链路数据技术
从技术架构上看,从底层的数据采集技术开始,逐步向上建设了数据计算与管理能力、数据服务、数据平台、数据应用与数据安全。
从使用者角度来看,现在的公司对数据的诉求可以概括为以下几点:
1. 数据从哪来,如何完全数字化:对应全链路数据采集服务。
2. 如何得到想要的数据:数据计算、建模与管理服务。
3. 如何使用数据:统一数据服务平台。
4. 如何利用数据做商业决策:BI 平台。
5. 如何保障数据安全:数据安全服务。
对阿里而言,还会额外考虑下面几点:
1. 如何让数据服务横向支撑所有业务线:数据服务平台化,数据智能化服务平台与 BI 平台。
2. 如何让数据服务普惠到每一个企业:数据服务全面上云。
3. 如何让数据服务更有价值:打通阿里经济体的数据体系,让数据相互产生化学反应。
当然,挑战性也非常大,首先是数据壁垒的挑战,要说服其他团队将数据交给你管理绝非易事。其次是价值挑战,如何证明数据中台存在的价值,并做到肉眼可见的业务增值。最后是技术挑战,对前端来说,几十款数据产品的搭建、几十万张数据报表的搭建,需要一个足够好用的数据产品搭建平台来支持;数据分析产品的下一代探索式分析也对 BI 引擎提出了新的要求;数据可视化远比普通可视化复杂,不仅要考虑大数据下的性能与可读性,还要理解商业,做出能体现数据分析价值的图表。
不论是数据搭建还是数据可视化,都是前端垂直领域的另一条好赛道,不仅有沉甸甸的业务价值,还有全新数据领域的的前端技术挑战,而且随着数据中台影响力的持续扩大,我们的前端技术也会带来业界越来越大的影响力。
### 如何建设和管理数据
想要数据用的好,首先要管的好,在大数据时代,企业必须建立一套自己的标准数仓系统对数据的采集、运维调度做全链路管理,让大数据变成好数据,让好数据可以发挥价值。
![](https://img.alicdn.com/tfs/TB1Pz.ZrAL0gK0jSZFtXXXQCXXa-1903-929.png)
> Dataphin 数仓建设平台。
数仓的建设需要从物理空间与逻辑空间,也就是底层的表开始整理,通过对数据的采集、清洗、结构化,产出一套规范的数据定义。
所谓规范的数据定义即口径、算法、命名均一致的数据规范,降低数据二义性,提升数据查找效率与准确性。之后对数据建模,建模即是对数据的进一步抽象,可能是抽象为一个 Cube 模型,这样在顶层认知上,所有数据都是不同维度的 Cube,方便统一理解。
最后通过对数据进行在线的、离线的调度计算,产出数据资产。
### 如何看数据
或导出一个 Excel 文件仔细品味,或如双十一媒体大屏般夺目,或如股票操盘手般紧盯着屏幕,或随时随地的手机浏览。在哪看,怎么看,看什么,决定着同一份数据可带来不同的效果,产生不同的价值。
稳:双十一大屏,零点起得来,24 点收得住,每个彩蛋的出现,每个数字的跳动,如丝般顺滑,这不是播放 VCR,每一帧画面都是真实的数据展现。容:即是生意参谋用户的浏览器兼容,又是多端用户的兼容,也是 BI 分析结果的数据大容量。有容乃大,方显前端功底。
**“如何看数据” 这恰是做为数据前端人的使命和责任。** 不同的人,不同的端,不同的需求,这恰是给数据前端的挑战。而让用户透过数据创造价值,也正是数据前端人的价值。
### 如何分析数据
大数据浪潮之下,必然会诞生各式各样的数据产品,产品化的方式可以降低数据应用的门槛。我们希望人人都能成为数据分析师,于是 BI (商业智能)产品应运而生,作为大数据行业中的一个重要领域,BI 产品用大数据的方式解决了企业的业务分析需求,支撑企业进行数字化转型,从经验驱动决策转变为数据驱动决策,进而给企业带来超额收益。
![](https://img.alicdn.com/tfs/TB1KJE0rpP7gK0jSZFjXXc5aXXa-2664-1060.png)
> QuickBI 数据分析工具。
**人人都是数据分析师的情况在不断增强。**
根据 Gartner 对 2020 年 BI 产品发展趋势预测:
1. 到 2020 年,为用户提供对内部和外部数据策划目录的访问权限的组织将从分析投资中获得两倍的业务价值。
2. 到 2020 年,业务部门的数据和分析专家数量的增速将是 IT 部门专家的 3 倍,这会迫使企业重新考虑其组织模式和技能。
3. 到 2021 年,自然语言处理和会话分析这两个功能,会在新用户、特别是一线工作人员中,将分析和商业智能产品的使用率从 35% 提升到 50% 以上。
**快速增涨的市场规模。**
根据中国电子信息产业发展研究院发布的《中国大数据产业发展水平评估报告》,预计 2019 年我国大数据核心产业规模突破 5700 亿元,未来 2-3 年的市场规模的增长率仍将保持 35% 左右。未来切入这部分应用环节,BI 商业智能的潜在市场规模将在数百亿的市场空间。
**大数据与前端。**
前端的职业发展除了提升自己的技能技术储备之外,选择合适行业方向和研究领域也尤为重要。如果用路和车的关系来比喻的话,把前端技能比作车的话,各个行业都是路,有的路是乡间小路,有的路是城乡公路,而大数据行业当之无愧是行业中的上高速公路,路况更好,路面更宽,如果你拥有一辆好车,为什么不来高速公路上飞驰呢?
大数据下的前端面临哪些挑战?以 BI 为例,BI 领域的四大方向:数据集、渲染引擎、数据模型与可视化都有许多可以做深的技术点,每一块都需要深入沉淀几年技术经验才能做好,需要大量优秀人才通力协作才有可能做好。你也可以阅读 [精读《前端与 BI》](https://github.com/dt-fe/weekly/blob/v2/121.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%89%8D%E7%AB%AF%E4%B8%8E%20BI%E3%80%8B.md) 了解更多 BI 相关知识。
### 我们是数据中台大前端
> “ 前端不是因为我们用 JavaScript,而是因为我们站在业务最前端,解决业务端的问题,所以我们是前端 ”。
BI 分析产品、做数据可视化、做产品搭建 .. 我们早已经跳出了“前端”的传统概念范畴。我们做大数据表格优化、 Web Excel、 SQL 编辑器、智能可视化。在数据中台,我们有着天然的复杂业务场景和海量数据优势,迫使你向自己提出更大的挑战来解决业务上的问题。如果你热爱挑战、热爱技术,请加入我们吧。
**在这里,你可以愉快的使用 React、TypesScript 写业务代码,尝试最新、最炫酷的 React Hooks 新特性,我们团队一直走在前端技术路线的最前沿,渴求技术创新。** 你也不需要担心伙伴的代码风格问题,因为我们有着严格的代码规;你不必担心每个人的代码都是一座孤岛,因为我们会对每一行代码做严格的 review;你不必担心你的成长空间,我们有定期的技术分享、团队内小竞赛,还有足够复杂的业务场景支撑;你也不必担心你会因工作日渐消瘦,下午茶和海量小零食等你来!
## 4 总结
**大数据前端人才缺口在 100 人以上,由于业务增长非常非常迅猛,春节前条件放宽、特批急召!**
如果你对我们感兴趣,请立刻把简历发送到邮箱 **ziyi.hzy@alibaba-inc.com** 吧!绝无仅有的好机会,响应速度绝对超乎你的想象!
> 讨论地址是:[精读《我在阿里数据中台大前端》 · Issue #224 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/224)
**如果你想参与讨论,请 [点击这里](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,123 @@
## 1 引言
本周精读的文章是 [Mastering JS console.log like a Pro](https://medium.com/javascript-in-plain-english/mastering-js-console-log-like-a-pro-1c634e6393f9),一起来更全面的认识 console 吧!
## 2 概述 & 精读
console 的功能主要在于控制台打印,它可以打印任何字符、对象、甚至 DOM 元素和系统信息,下面一一介绍。
### console.log( ) | info( ) | debug( ) | warn( ) | error( )
直接打印字符,区别在于展示形态的不同:
<img width=400 src="https://img.alicdn.com/tfs/TB1xZ_WveH2gK0jSZFEXXcqMpXa-1492-566.png">
新版 chrome 控制台可以将打印信息分类:
<img width=200 src="https://img.alicdn.com/tfs/TB1fZ2Vvhn1gK0jSZKPXXXvUXXa-420-446.png">
`log()``info()` 都对应 `info``warn()` 对应 `warnings``error()` 对应 `errors`,而 `debug()` 对应 `verbose`,因此建议在合适的场景使用合适的打印习惯,这样排查问题时也可以有针对性的筛选。
比如调试信息可以用 `console.debug` 仅在调试环境下输出,调试者即便开启了调试参数也不会影响正常 `info` 的查看,因为调试信息都输出在 `verbose` 中。
### 使用占位符
- %o — 对象
- %s — 字符串
- %d — 数字
如下所示,可通过占位符在一行中插入不同类型的值:
<img width=400 src="https://img.alicdn.com/tfs/TB1GtL3vlr0gK0jSZFnXXbRRXXa-1840-504.png">
### 添加 CSS 样式
- %c - 样式
<img width=400 src="https://img.alicdn.com/tfs/TB1eK23vlr0gK0jSZFnXXbRRXXa-1832-978.png">
可以总结出,**console 支持输出复杂的内容,其输出能力堪比 HTML,但输入能力太弱,仅为字符串,因此采用了占位符 + 多入参修饰的设计模式解决这个问题。**
### console.dir( )
按 JSON 模式输出。笔者在这里也补充一句:`console.log()` 会自动判断类型,如果内容是 DOM 属性,则输出 DOM 树,但 `console.dir` 会强制以 JSON 模式输出,用在 DOM 对象时可强制转换为 JSON 输出。
<img width=400 src="https://img.alicdn.com/tfs/TB1KQY1vbj1gK0jSZFuXXcrHpXa-922-302.png">
### 输出 HTML 元素
按照 HTML ELements 结构输出:
<img width=400 src="https://img.alicdn.com/tfs/TB1mZ61va61gK0jSZFlXXXDKFXa-920-255.png">
这种输出结构和 Elements 打印形式是一致的,如果要看详细属性,可以使用 `console.dir()`
### console.table
在控制台打印一个表格,属于功能增强。虽然仅文本也可以在控制台打印出漂亮的表格,但浏览器调试控制台的功能更强大,`console.table` 只是其富文本能力的一个体现。
<img width=400 src="https://img.alicdn.com/tfs/TB1WldouKbviK0jSZFNXXaApXXa-928-742.png">
### console.group( ) & console.groupEnd( )
接下来是另一个富文本能力,按分组输出:
<img width=400 src="https://img.alicdn.com/tfs/TB1UV6UvXY7gK0jSZKzXXaikpXa-919-377.png">
这种带有副作用的 API 显然是为方便阅读而设计的,然而在需要输出大量动态结构化数据的场景下,还需要进行结构转换,是比较麻烦的地方。
### console.count( )
`count()` 用来打印调用次数,一般用在循环或递归函数中。接收一个 `label` 参数以定制输出,默认直接输出 `1 2 3` 数字。
<img width=400 src="https://img.alicdn.com/tfs/TB1ELLVveL2gK0jSZPhXXahvXXa-917-500.png">
### console.assert( )
`console` 版断言工具,当且仅当第一个参数值为 `false` 时才打印第二个参数作为输出。
<img width=400 src="https://img.alicdn.com/tfs/TB1HEDUvfb2gK0jSZK9XXaEgFXa-1842-548.png">
这种输出结果为 error,所以也可被 `console.error` + 代码级别断言所取代。
### console.trace( )
打印此时的调用栈,在打印辅助调试信息时非常有用。
<img width=400 src="https://img.alicdn.com/tfs/TB1Jh_YvkL0gK0jSZFAXXcA9pXa-1840-1096.png">
### console.time( )
打印代码执行时间,性能优化和监控场景比较常见。
<img width=400 src="https://img.alicdn.com/tfs/TB1wAT2vbj1gK0jSZFuXXcrHpXa-1612-524.png">
### console.memory
打印内存使用情况。
<img width=400 src="https://img.alicdn.com/tfs/TB1tPHYvkL0gK0jSZFAXXcA9pXa-1842-440.png">
### console.clear( )
清空控制台输出。
## 3 总结
`console` 提供了如此多的输出规范,其实也是在变相制定开发规范,毕竟离开发者最近的就是调试控制台,如果你的项目打印规范与标准规范有差异,那么调试时信息看起来就会很别扭。
可以看到,大部分开源库都良好的遵循了这套规范,比如三方库绝不会输出 `log()`,而且将错误、警告与调试信息正确分开,并尽量少的用 CSS 样式、分组、`table` 等功能,因为这些功能干扰性较强,不能保证所有用户都可接受。
相对的,项目源码就比较适合使用一些醒目的自定义规范,只要这套规则能被很好的执行起来。
最后留下一个讨论点:`console` 可以作为调试、招聘信息、隐藏菜单的投放点,你还看到过哪些有意思的 `console` 使用方式呢?欢迎留言。
> 讨论地址是:[精读《精通 console.log》 · Issue #228 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/228)
**如果你想参与讨论,请 [点击这里](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,279 @@
## 1 引言
`JSON.parse` 是浏览器内置的 API,但如果面试官让你实现一个怎么办?好在有人已经帮忙做了这件事,本周我们一起精读这篇 [JSON Parser with Javascript](https://lihautan.com/json-parser-with-javascript/) 文章吧,再温习一遍大学时编译原理相关知识。
## 2 概述 & 精读
要解析 JSON 首先要理解语法概念,之前的 [精读《手写 SQL 编译器 - 语法分析》](https://github.com/dt-fe/weekly/blob/v2/066.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E8%AF%AD%E6%B3%95%E5%88%86%E6%9E%90%E3%80%8B.md) 系列也有介绍过,不过本文介绍的更形象,看下面这个语法图:
<img width=400 src="https://img.alicdn.com/tfs/TB1EbjfvQL0gK0jSZFtXXXQCXXa-1837-857.png">
这是关于 Object 类型的语法描述图,从左向右看,根据箭头指向只要能走出这个迷宫就属于正确语法。
比如第一行 `{``whitespace``}` 表示 `{ }` 属于合法的 JSON 语法。
再比如观察向下的一条最长路线:`{``whitespace``string``whitespace``:``value``}` 表示 `{ string : value }` 属于合法的 JSON 语法。
你可能会问,双引号去哪儿了?这就是语法树最核心的概念了,这张图是关于 Object 类型的 **产生式**,同理还有 string、value 的产生式,产生式中可以嵌套其他产生式,甚至形成环路,以此拥有描述纷繁多变语法的能力。
最后我们再看一个环路,即 `{``whitespace``string` ... `,``whitespace``string` ... `,` ... `}`,我们发现,只要不走回头路,这条路是可以一直 “绕圈” 下去的,因此 Object 类型拥有了任意数量子字段的能力,只是每形成一个子字段,必须经过 `,` 号分割。
### 实现 Parser
首先实现一个基本结构:
```js
function fakeParseJSON(str) {
let i = 0;
// TODO
}
```
`i` 表示访问字符的下标,当 `i` 走到字符串结尾表示遍历结束。
然后是下一步,用几个函数描述解析语法的过程:
```js
function fakeParseJSON(str) {
let i = 0;
function parseObject() {
if (str[i] === '{') {
i++;
skipWhitespace();
// if it is not '}',
// we take the path of string -> whitespace -> ':' -> value -> ...
while (str[i] !== '}') {
const key = parseString();
skipWhitespace();
eatColon();
const value = parseValue();
}
}
}
}
```
其中 `skipWhitespace` 表示匹配并跳过空格,所谓匹配意味着匹配成功,此时 `i` 下标可以继续后移,否则匹配失败。下一步则判断如果 `i` 不是结束标志 `}`,则按照 `parseString` 匹配字符串 → `skipWhitespace` 跳过空格 → `eatColon` 吃掉冒号 → `parseValue` 匹配值,这个链路循环。其中吃掉冒号表示 “匹配冒号但不会产生任何结果,所以就像吃掉了一样”,吃这个动作还可以用在其他场景,比如吃掉尾分号。
> 对于看到这儿的小伙伴,笔者要友情提示一下,原文的思路是一种定制语法解析思路,无论是 `eatColon` 还是 `parseValue` 都仅具备解析 JSON 的通用性,但不具备解析任意语法的通用性。如果你想做一个具备解析任何通用语法的解析器,读入的内容应该是语法描述,处理方式必须更加通用,如果感兴趣可以阅读 [精读《手写 SQL 编译器 - 语法分析》](https://github.com/dt-fe/weekly/blob/v2/066.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E8%AF%AD%E6%B3%95%E5%88%86%E6%9E%90%E3%80%8B.md) 系列文章了解更多。
由于 Object 第一个元素前面不允许加逗号,因此可以利用 `initial` 做一个初始化判定,在初始时机不会吃掉逗号:
```js
function fakeParseJSON(str) {
let i = 0;
function parseObject() {
if (str[i] === '{') {
i++;
skipWhitespace();
let initial = true;
// if it is not '}',
// we take the path of string -> whitespace -> ':' -> value -> ...
while (str[i] !== '}') {
if (!initial) {
eatComma();
skipWhitespace();
}
const key = parseString();
skipWhitespace();
eatColon();
const value = parseValue();
initial = false;
}
// move to the next character of '}'
i++;
}
}
}
```
那么当第一个子元素前面存在逗号时,由于没有 “吃掉逗号” 这个功能,所以读到逗号会报错,语法解析提前结束。
吃逗号和吃冒号的代码都非常简单,即判断当前字符串必须是 “要吃的那个元素”,并且在吃掉后将 `i` 下标自增 1
```js
function fakeParseJSON(str) {
// ...
function eatComma() {
if (str[i] !== ',') {
throw new Error('Expected ",".');
}
i++;
}
function eatColon() {
if (str[i] !== ':') {
throw new Error('Expected ":".');
}
i++;
}
}
```
在有了基本判定功能后,`fakeParseJSON` 需要返回 Object,因此我们只需在每个循环中对 Object 赋值,最后一并 return 即可:
```js
function fakeParseJSON(str) {
let i = 0;
function parseObject() {
if (str[i] === '{') {
i++;
skipWhitespace();
const result = {};
let initial = true;
// if it is not '}',
// we take the path of string -> whitespace -> ':' -> value -> ...
while (str[i] !== '}') {
if (!initial) {
eatComma();
skipWhitespace();
}
const key = parseString();
skipWhitespace();
eatColon();
const value = parseValue();
result[key] = value;
initial = false;
}
// move to the next character of '}'
i++;
return result;
}
}
}
```
解析 Object 的代码就完成了。
接着试着解析 Array,下面是 Array 的语法图:
<img width=400 src="https://img.alicdn.com/tfs/TB1FvYjvKH2gK0jSZFEXXcqMpXa-1837-479.png">
我们只需要吃逗号和 `parseValue` 即可:
```js
function fakeParseJSON(str) {
// ...
function parseArray() {
if (str[i] === '[') {
i++;
skipWhitespace();
const result = [];
let initial = true;
while (str[i] !== ']') {
if (!initial) {
eatComma();
}
const value = parseValue();
result.push(value);
initial = false;
}
// move to the next character of ']'
i++;
return result;
}
}
}
```
接下来到了有趣的 `value` 语法图,可以看到 `value` 是许多种基础类型的 “或” 关系组成的:
<img width=400 src="https://img.alicdn.com/tfs/TB1uGrmvND1gK0jSZFyXXciOVXa-1836-1293.png">
我们只需要继续拆解分析即可:
```js
function fakeParseJSON(str) {
// ...
function parseValue() {
skipWhitespace();
const value =
parseString() ??
parseNumber() ??
parseObject() ??
parseArray() ??
parseKeyword('true', true) ??
parseKeyword('false', false) ??
parseKeyword('null', null);
skipWhitespace();
return value;
}
}
```
其中 `parseKeyword` 函数用来解析一些保留关键字,比如将 `"true"` 解析成布尔类型 `true`
```js
function fakeParseJSON(str) {
// ...
function parseKeyword(name, value) {
if (str.slice(i, i + name.length) === name) {
i += name.length;
return value;
}
}
}
```
如上所示,只要在 name 与对应字符相等时,返回第二个传入参数即可。
### 处理异常输入
一个完整的语法解析功能需要包含错误处理,错误的情况主要分两种:
1. 非法字符。
2. 非正常结尾。
原文提到的 JSON 错误提示优化非常棒,想想你在开发中突然看到下面的提示,是不是很蒙圈:
```text
Unexpected token "a"
```
既然我们是自己写的 JSON 解析器,就可以进行更友好的异常提示,比如:
```text
// show
{ "b"a
^
JSON_ERROR_001 Unexpected token "a".
Expecting a ":" over here, eg:
{ "b": "bar" }
^
You can learn more about valid JSON string in http://goo.gl/xxxxx
```
更多 Demo 可以查看 [原文](https://lihautan.com/json-parser-with-javascript/)。
## 3 总结
这篇文章通过一个具体的例子解释如何做语法分析,对于词法解析入门非常直观,如果你想更深入理解语法解析,或者写一个通用语法解析器,可以阅读语法解析系列入门文章,笔者通过实际例子带你一步一步做一个完备的词法解析工具!
语法解析入门系列文章,建议阅读顺序:
- [精读《手写 SQL 编译器 - 词法分析》](https://github.com/dt-fe/weekly/blob/v2/064.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E8%AF%8D%E6%B3%95%E5%88%86%E6%9E%90%E3%80%8B.md)
- [精读《手写 SQL 编译器 - 文法介绍》](https://github.com/dt-fe/weekly/blob/v2/065.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%96%87%E6%B3%95%E4%BB%8B%E7%BB%8D%E3%80%8B.md)
- [精读《手写 SQL 编译器 - 语法分析》](https://github.com/dt-fe/weekly/blob/v2/066.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E8%AF%AD%E6%B3%95%E5%88%86%E6%9E%90%E3%80%8B.md)
- [精读《手写 SQL 编译器 - 回溯》](https://github.com/dt-fe/weekly/blob/v2/067.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E5%9B%9E%E6%BA%AF%E3%80%8B.md)
- [精读《手写 SQL 编译器 - 语法树》](https://github.com/dt-fe/weekly/blob/v2/070.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E8%AF%AD%E6%B3%95%E6%A0%91%E3%80%8B.md)
- [精读《手写 SQL 编译器 - 错误提示》](https://github.com/dt-fe/weekly/blob/v2/071.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E9%94%99%E8%AF%AF%E6%8F%90%E7%A4%BA%E3%80%8B.md)
- [精读《手写 SQL 编译器 - 性能优化之缓存》](https://github.com/dt-fe/weekly/blob/v2/078.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B9%8B%E7%BC%93%E5%AD%98%E3%80%8B.md)
- [精读《手写 SQL 编译器 - 智能提示》](https://github.com/dt-fe/weekly/blob/v2/085.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%99%BA%E8%83%BD%E6%8F%90%E7%A4%BA%E3%80%8B.md)
[syntax-parser](https://github.com/ascoders/syntax-parser) 这个零依赖的通用语法解析库就是根据上述文章一步一步完成的,看完了上面文章,就彻底理解了这个库的源码。
> 讨论地址是:[精读《手写 JSON Parser》 · Issue #233 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/233)
**如果你想参与讨论,请 [点击这里](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,233 @@
## 1 引言
拖拽是前端非常常见的交互操作,但显然拖拽是强 DOM 交互的,而 React 绕过了 DOM 这一层,那么基于 React 的拖拽方案就必定值得聊一聊。
结合 [How To Use The HTML Drag-And-Drop API In React](https://www.smashingmagazine.com/2020/02/html-drag-drop-api-react/) 这篇文章,让我们谈谈 React 拖拽这些事。
## 2 概述
原文说的比较简单,笔者先快速介绍其中重点部分。
首先拖拽主要的 API 有 4 个:`dragEnter` `dragLeave` `dragOver` `drop`,分别对应拖入、拖出、正在当前元素范围内拖拽、完成拖入动作。
基于这些 API,我们可以利用 React 实现一个拖入区域:
```jsx
import React from "react";
const DragAndDrop = props => {
const handleDragEnter = e => {
e.preventDefault();
e.stopPropagation();
};
const handleDragLeave = e => {
e.preventDefault();
e.stopPropagation();
};
const handleDragOver = e => {
e.preventDefault();
e.stopPropagation();
};
const handleDrop = e => {
e.preventDefault();
e.stopPropagation();
};
return (
<div
className={"drag-drop-zone"}
onDrop={e => handleDrop(e)}
onDragOver={e => handleDragOver(e)}
onDragEnter={e => handleDragEnter(e)}
onDragLeave={e => handleDragLeave(e)}
>
<p>Drag files here to upload</p>
</div>
);
};
export default DragAndDrop;
```
`preventDefault` 指的是阻止默认响应,这个响应可能是跳转页面之类的,`stopPropagation` 是阻止冒泡,这样同样监听了事件的父元素就不会收到响应,我们可以精准作用于嵌套的子元素。
接下来是拖拽状态管理,提到了 `useReducer`,顺便复习一下用法:
```jsx
...
const reducer = (state, action) => {
switch (action.type) {
case 'SET_DROP_DEPTH':
return { ...state, dropDepth: action.dropDepth }
case 'SET_IN_DROP_ZONE':
return { ...state, inDropZone: action.inDropZone };
case 'ADD_FILE_TO_LIST':
return { ...state, fileList: state.fileList.concat(action.files) };
default:
return state;
}
};
const [data, dispatch] = React.useReducer(
reducer, { dropDepth: 0, inDropZone: false, fileList: [] }
)
...
```
最后一个关键点在于拖入后的处理,利用 `dispatch` 增加拖入文件、设置拖入状态即可:
```js
const handleDrop = e => {
...
let files = [...e.dataTransfer.files];
if (files && files.length > 0) {
const existingFiles = data.fileList.map(f => f.name)
files = files.filter(f => !existingFiles.includes(f.name))
dispatch({ type: 'ADD_FILE_TO_LIST', files });
e.dataTransfer.clearData();
dispatch({ type: 'SET_DROP_DEPTH', dropDepth: 0 });
dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false });
}
};
```
`e.dataTransfer.clearData` 函数用于清除拖拽过程中产生的临时变量,这些临时变量可以通过 `e.dataTransfer.xxx =` 的方式赋值,一般用于拖拽过程中值的传递。
总结一下,利用 HTML5 的 API 将拖拽转化为状态,最终通过状态映射到 UI。
原文内容还是比较简单的,笔者在精读部分再拓展一些更体系化的内容。
## 3 精读
现阶段拖拽主要分为两种,一种是 HTML5 原生规范的拖拽,这种方式在拖拽过程中不会影响 DOM 结构。另一种是完全所见即所得的拖拽方式,拖拽过程中 DOM 位置会随之变动,好处是可以立即反馈拖拽结果,当然缺点是华而不实,一旦用在生产环境,这种拖拽过程可能导致页面结构频繁跳动,反而看不清拖拽效果。
由于本文也采用了第一种拖拽方案,因为笔者再重新整理一遍自己的封装思路。
从使用角度反推,假设我们拥有一个拖拽库,那必定要拥有两个 API:
```jsx
import { DragContainer, DropContainer } from 'dnd'
const DragItem = (
<DragContainer>
{({ dragProps }) => (
<div {...dragProps} />
)}
</DragContainer>
)
const DropItem = (
<DropContainer>
{({ dropProps }) => (
<div {...dropProps} />
)}
</DropContainer>
)
```
`DragContainer` 包裹可以被拖拽的元素,`DropContainer` 包裹可以被拖入的元素,而至于 `dragProps``dropProps` 需要透传到子元素的 dom 节点,是为了利用 DOM API 控制拖拽效果,这也是拖拽唯一对 DOM 的要求,双方元素都需要有实体 DOM 承载。
而上面例子中给出 `dragProps``dropProps` 的方式属于 RenderProps,我们可以将 `children` 当作函数执行以达到效果:
```jsx
const DragContainer = ({ children, componentId }) => {
const { dragProps } = useDnd(componentId)
return children({
dragProps
})
}
const DropContainer = ({ children, componentId }) => {
const { dropProps } = useDnd(componentId)
return children({
dropProps
})
}
```
那么这里创建了一个自定义 Hook `useDnd` 接收 `dragProps``dropProps`,这个自定义 Hook 可以这么写:
```jsx
const useDnd = ({ componentId }) => {
const dragProps = {}
const dropProps = {}
return { dragProps, dropProps }
}
```
接下来,我们就要分别实现 `drag``drop` 了。
`drag` 来说,只要实现 `onDragStart``onDragEnd` 即可:
```jsx
const dragProps = {
onDragStart: ev => {
ev.stopPropagation()
ev.dataTransfer.setData('componentId', componentId)
},
onDragEnd: ev => {
// 做一些拖拽结束的清理工作
}
}
```
`stopPropagation` 的作用在原文简介中已经介绍过了,`setData` 则是通知拖拽方,当前拖拽的组件 id 是什么,**这是由于拖拽由 `drag` 发起而由 `drop` 响应,因此必须有个数据传输过程,而 `dataTransfer` 就最适合做这件事。**
对于 `drop` 来说,只要实现 `onDragOver``onDrop` 即可:
```jsx
const dropProps = {
onDragOver: ev => {
// 做一些样式处理,提示用户此时松手会将元素放置在何处
},
onDrop: ev => {
ev.stopPropagation()
const componentId = ev.dataTransfer.getData('componentId')
// 通过 componentId 修改数据,通过 React Rerender 刷新 UI
}
}
```
重点在 `onDrop`,它是实现拖拽效果的 “真正执行处”,最终通过修改 UI 的方式更新数据。
存在一种场景,一个容器既可以被拖动,也可以被拖入,这种情况一般这个组件是个容器,但这个容器可以被拖入到其他容器中,可以自由嵌套。
实现这种场景的方式就是将 `DragContainer``DropContainer` 作用到一个组件上:
```jsx
const Box = (
<DragContainer>
{({ dragProps }) => (
<DropContainer>
{({ dropProps }) => {
<div {...dragProps} {...dropProps} />
}}
</DropContainer>
)}
</DragContainer>
)
```
之所以能嵌套,在于 HTML5 的 API 允许一个元素同时拥有 `onDragStart``onDrop` 这两种属性,而上面的语法不过是同时将这两种属性传给组件 DOM。
所以,动手实现一个拖拽库就是这么简单,只要活用 HTML5 的拖拽 API,结合 React 一些特殊语法便够了。
## 4 总结
最后留下一个思考题,许多具有拖拽功能的系统都具备 “拖拽 placeholder” 的功能,即拖拽元素的过程中,在其 “落点” 位置展示一条横线或竖线,引导出松手后元素位置落点,如图所示:
<img width=400 src="https://img.alicdn.com/tfs/TB11H04wbY1gK0jSZTEXXXDQVXa-1434-384.png">
那么这条辅助线是通过什么方式实现的呢?欢迎在评论区留言!如果你有辅助线实现方案解析的文章,欢迎分享,也可以期待笔者未来专门写一篇 “拖拽 placeholder” 实现剖析的精读。
> 讨论地址是:[精读《手写 JSON Parser》 · Issue #233 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/233)
**如果你想参与讨论,请 [点击这里](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)

Some files were not shown because too many files have changed in this diff Show More