React&Next 学习笔记 part1

前情提要

自从去年换了份工作, 已经写 Angular 一年多了. 虽然 Angular 也很好用, 但束缚真的太多了, 配合 Service/Rxjs 怎么写怎么感觉在写 JavaScript 的前缀(, 再不写写别的东西, 都要不会写主流前端了.

同时, 正好前段时间用 Hexo 写的 blog 因为太长时间没维护挂掉了. 趁这个机会, 学点新东西, 从 Angular 的大坑里跳出来, 因此选的技术栈都是和工作上用的不一样的东西.

前几天在 v2ex 看到一个讨论, 发觉 react 有好多新东西. 以前虽然也学过一会 react, 但一直都很不喜欢用, 就是因为 16 以前那套 class component, jsx 以及 css in js 这些东西都长的很丑, 实在是跟优雅没什么关系.

但是现在有 tailwindcss 和 函数式组件了, radix/shadcn 这种 headless 组件库也是真的好用, 我在工作上也受够了羸弱的诸如 v-for/ng-for 这样的 html 模版了. 之前的缺点一转攻势, 不是消失了, 就是变成优点了.

从未来前端发展趋势来看, 完全独立的组件化我觉得是一个很有趣的方向. 最开始的时候, 前端 html/css/js 是三分离的, 我还记得我刚学那会, 如果在 html 写 inline style 或者 script 都是很被人鄙视的(不是. 之后, 随着像 react(js+html) vue(all in one) 这种框架的兴起, 前端才有了组件这个叫法.

一些关于 Angular 的不吐不快

回到 Angular 的视角, 写 Angular 的时候, ivy 的 aot 虽然对性能很有帮助, 但无论干什么都要注册一大堆东西, 拿最近的一个项目已经简化了非常多的来举例子. 当你要创建一个组件的时候, 你需要做什么:

  1. 写一个 @Component 注解, 里面要定义组件的自定义 html 元素, 以及 html 和 css 文件路径. 当然你也可以写注解里, 那你就只能忍受一种比 jsx 还要丑的写法 — 将 html 和 css 都写在字符串里.

  2. 写一个 class 并 implements 一些声明周期接口, 然后还需要在 constructor 注入 service, 写一堆生命周期函数. 这一块相对还好, 类似于 vue2 的写法, 由于我写了很久 vue2, 对这套还是挺接受的.

  3. 将这个 class 传递到 @NgModule 注解的 declarations 和 exports 里去, 值得注意的是, 这一步不能 import * as, 因此每当你注册一个新组件, 你都需要配置这些东西.

  4. 当你在 @NgModule 中注册一个组件出错或者编译错误后, 所有在 @NgModule 注册的组件都会报错(只要他们有相互依赖), 紧接着就会收获满屏幕的报错, 同时不知道是哪里错了, 因为所有的组件都红了, 仅当知道是因为组件注册错误时, 才能快速定位错误.

  5. 如果使用类似 antd 这样的 ui 框架, 由于他们提供了 drawer/modal 这样的弹窗能力, 自然会使用这些工具. 但这些工具都需要再分别注册一个组件, 然后又会重复 1-4 的冗余工作.

当然, 单纯写着很麻烦的话, 可以写脚本, 用快速工具. 因此麻烦不麻烦不重要, 问题是太重了. 无论做是要注册组件也好, 注册服务也罢, 哪怕是写个 store, 都需要注册和配置一大堆的东西, 然后才开始写业务逻辑, 这才是最大的问题.

什么才是真正的模块化

前端开发从设计上来说, 不可能一开始就把所有的组件都拆出来(基础ui按钮除外). 很多功能性的组件, 都是在写的过程中, 才发现可以将一部分可复用的逻辑拆分出来, 构成一个组件, 从而实现模块化.

理解了这一点, 我们回到之前说的组件, 现在的组件在 css 上还是很耦合的, 特别是由于 scss 这种 postcss 工具, 导致 scss 写的越来越难拆.

而 html 和 js 又是另一种情况下, js 和 html 通过现代的 MVVM 方式基本解耦了. 但当我们想把一块代码变成组件时, 又需要将 js 内部的逻辑拆分, 将只属于组件的逻辑挑出来, 再进行拆分.

使用 react jsx, 能天然的将逻辑和页面组织关联, 通常情况下 jsx 某一层级的代码不会被外层代码调用, 拆分组件时, 只需要将选择的部分直接转走, 将外部参数作为 props 即可.

使用 tailwind 这样写在标签的 css, 能将 scss 的复杂嵌套关系直接对应到界面上, 拆分组件时, 同样也只需要将代码转走, 无需任何操作.

一句话总结: 在拆组件时, 不再需要根据 html 找 css, 不用根据 html 找 js, 拆分 js, 可以将原来复杂的组件注册流程全部放弃. 只需要做一件事 – 选择需要的代码, 拆分, 传递 props, 组件化就完成了.

技术选型

总结上面说的许多内容, 大体上的技术选型就是围绕 react 全家桶的

  • React 18

  • Next.js 13

  • Tailwind + radix/shadcn ui

  • jotai

  • swr

其中的有些内容还没提及, 将在后续的学习笔记中一点一点讲.

额外内容: 踩坑记录 01

App router

其实学 next.js 的时机有点巧, 正好 next 13 大改了一堆东西, 之前的文档都过时了, 学新的这套历史包袱少多了. 但问题是教程也很少, 正好也啃啃官方文档, 就当学英语了.

app router 一个核心的改变就是把之前的 getStaticPaths/getStaticProps 等几个 ssr/ssg api 改了(见 file-conventions, 顺便提一嘴, 这个文档也是我第一次看到文档有写项目结构和文件结构的, 每个文件里要写什么能写什么, 例子都给的很全, 写的很不错), getStaticPaths 换成用 generateStaticParams 了, 原来的一些参数也都改成 Route Segment Config 了. 然后 getStaticProps 就直接去掉了, 总体来说影响不大, 现在自己随便写一个方法调用就好了, 写起来其实比之前还舒服一些.

部署

最开始我还以为 build 完和别的框架差不多, 会暴露出一个类似 index.html 之类的东西, 结果 build 完, .next 里面一堆乱七八糟的东西, 查了查才看到有个 output 选项, 用这个就可以 build 出来一堆 html 文件, 然后随便用啥就可以部署了.

1
2
3
4
const nextConfig = {
output: "export",
images: { unoptimized: true },
};

目前版本代码没有 ssr, 纯静态, 这个设置自然就开着了. 当然对于我这种 ssg 的项目来说, 随便找个什么地方部署都行, 那不如直接用 vercel 的工具部署, 每次推送一下更新就自动编译了, CI 都不用自己搞了.

Eslint 和 自动化

顺其自然的用 eslint 和 perttier 这些很久了, 也都没怎么研究过 src 外面的配置文件一大堆都是干嘛的.

这次花了很多时间自己配了一套 eslint 的配置, 其实配起来也是满简单的, 也就是给 eslint 配置引入一下 extends 和 plugin, 然后根据手动调整一下规则就好了, 弄完之后 lint 的感觉特别爽(

另外一块就是弄了 prettier 的配置, 前段时间一直有强迫症, 都用重排序 import, 但是默认配置其实就只是字母排序一下, 排完其实变得更乱了, 各种不同的依赖都乱拍, 看着强迫症犯了. 这次看到 shadcn 给的模版用了一个 prettier-plugin-sort-imports 库, 研究了一下这个怎么用, 终于可以根据自己想要的格式来排序依赖了, 感觉特别特别爽(

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
importOrder: [
"^(react/(.*)$)|^(react$)",
"^(next/(.*)$)|^(next$)",
"<THIRD_PARTY_MODULES>",
"",
"^types$",
"^@/types/(.*)$",
"^@/config/(.*)$",
"^@/lib/(.*)$",
"^@/utils/(.*)$",
"^@/hooks/(.*)$",
"^@/components/ui/(.*)$",
"^@/components/(.*)$",
"^@/styles/(.*)$",
"^@/app/(.*)$",
"",
"^[./]",
]

最后还弄了点自动化, husky 配合 lintstaged, 可以自动 eslint 检查以及文章自动更新时间等等功能, 静态博客也可以写的很方便(

window is not defined

在 next 13 之后, app router 下的 tsx 和以前的是完全不一样的, 首先它会在服务端预渲染服务端组件的类 json 文件, 然后再客户端显示并 hydration.

这里有一个误区, 当使用 “use client” 时, 很容易以为在整个函数式组件执行过程中都有 window, dom 等东西, 但实际上需要等到 useLayoutEffect 时, 你才能获取到这个值, 否则哪怕你对 window 属性用了可选链, 他仍然会报错(这里还是有点疑惑的, 之后会继续研究)

具体渲染过程将在后续文章中分析, 这里先根据目前浅薄的 next.js 经验总结一下 服务端组件 和 客户端组件 的使用场景, 未来理解更深入一些再更新.

在 RSC 渲染中, 会混用 服务端组件 和 客户端组件, 通常的设计是:

  • 将数据获取的部分写成服务端组件, 这部分代码甚至可以使用诸如 fs path 这样的 node.js 库
    • 然后将服务端组件的获取到的数据传给客户端组件
  • 将固定的, 没有复杂 js 操作的部分写成服务端组件, 例如 button, link 这样的轻组件
  • 将复杂的, 对页面改动极大的组件设置写成客户端组件
  • 将需要在线数据的, 或需要 useEffect 的地方写成客户端组件
  • 将本地储存, 编译时就确定的数据写成 SSG, SSG 不影响使用服务端组件和客户端组件

One More Thing

最后补充一段 react 官方两年前视频, 有时候不得不感叹, 架构设计的超前, 与我的落后:-X.

React&Next 学习笔记 part1

https://kuusei.moe/post/20230827083025

作者

kuusei

发布于

2023-08-27

更新于

2023-09-02

许可协议

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×