ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

React企业级架构方案rea:从技术选型到性能优化的完整实践

React企业级架构方案rea:从技术选型到性能优化的完整实践

前阵子一位朋友问起我常用的前端工程方案,我随口提了句“我这套搭法,内部代号就叫 rea”。他没听懂,问我 rea 到底是个什么东西。我想了想,其实它就是我在长期做中后台项目过程中沉淀下来的一套 React 企业级应用架构方案,从技术选型、目录设计、代码规范到请求封装、权限路由、性能优化,全部包含在里面。这套方案已经在我经手的十几个模拟项目里反复落地过,踩了不少坑,也沉淀了不少经验。

这篇文章我就把 rea 这套方案的完整设计思路和实操过程拆开来讲。如果你正要搭建一个长期维护的中大型前端项目,或者正在接手一个混乱的存量 React 代码库不知道从哪下手,又或者你只是想搞明白“工程化”到底在工程什么,那么这篇内容应该能给你一个相对完整的参考。我尽量把关键的为什么也讲清楚,不搞“照抄能跑就行”那种糊涂账。

1. 整体设计与思路拆解

1.1 “rea”到底是什么:先明确要解决的一类问题

先说清楚背景。rea 是我自己约定的代号,全称可以拆成 React Enterprise Architecture,也就是一套面向企业级中后台场景的 React 应用工程化方案。为什么需要一个代号?因为在实际项目里,很少有人会说“我要做一套企业级架构”,大家关心的往往是更具体的问题:为什么这个项目跑起来要几十秒?为什么改个样式热更新还会卡?为什么一个人写的代码别人看不懂?为什么一个 bug 修完又冒出来三个?

这些问题看起来零散,背后其实是同一个根源:工程化基础设施没做好。rea 要解决的就是这一整类问题。我不追求引入最新最炫的技术,只追求在团队协作、长期维护、快速迭代这三个前提下,找到一套稳定、清晰、可复制的组合。说白了,它就是一套“让项目从第一天起就能健康生长”的骨架。

适合看这篇文章的人,我大致分三类:第一类是前端新手,你至少要知道一个正规项目的目录长什么样、有哪些配套工具;第二类是准备从零搭建项目的前端工程师,你可以直接参考我的选型和配置;第三类是正在维护老项目的同学,你可以把文章里的排查思路和优化手段拿回去对照自己的痛点。当然,如果你已经有一套非常成熟的方案,这篇文章也能给你提供一些横向对比的视角。

1.2 技术选型背后的取舍逻辑

rea 整套方案的核心技术栈是 React + TypeScript + Vite + pnpm,配套链路上有 React Router、Redux Toolkit、axios、ESLint、Prettier、Stylelint、Husky 这些。很多人看到这堆名字就会问:为什么选 Vite 不选 Webpack?为什么状态管理用 Redux Toolkit 不用 Zustand?这些问题的答案不是“因为谁火”,而是每个选择都在解决一个具体的痛点。

先说构建工具。我用 Vite 的理由非常直白:开发体验差异太大了。Webpack 在大型项目里冷启动要十秒甚至几十秒,改一行代码触发热更新可能要等两三秒,这在日常开发里是非常消耗耐心和专注力的事情。Vite 基于浏览器原生 ES Module,冷启动几乎是秒开,热更新也是毫秒级,而且配置简单。有人会担心 Vite 的生产构建能力,实际上它底层用的还是 Rollup,对于绝大多数的中后台应用来说,产物质量和构建速度都足够可靠。如果你的项目需要兼容一些老旧的浏览器环境,或者有复杂的多页应用定制需求,那用 Webpack 也完全合理,但绝大部分新项目用 Vite 是更舒服的选择。

包管理器我选 pnpm,核心原因是它解决了 node_modules 的“幽灵依赖”和重复安装问题。pnpm 使用内容寻址存储,所有依赖都硬链接到一个全局存储里,同一个版本的包只会在磁盘上存一份。这不仅让安装速度快很多,更重要的是它的依赖隔离机制能让项目结构更干净。你可能觉得这只是个细节,但我在真实项目里见过太多因为依赖提升导致的“在我电脑上是好的”的玄学问题,pnpm 的严格隔离能从根上减少这类状况。

再说状态管理。我对状态管理的态度很务实:能用 React 内置能力解决就不用外部库,但中后台项目里跨页面、跨模块的状态实在太多,比如用户信息、权限列表、字典数据,这些放着不用外部库会越写越乱。Redux Toolkit 是市面上配套最成熟、调试工具最完善的选择,Redux DevTools 看状态变化非常直观,对团队协作和排查问题帮助很大。Zustand 确实更轻量,但它在大型团队的约束力上不如 Redux Toolkit 强,所以我把它留给小型工具类项目。

1.3 目录结构设计:从职责划分开始

目录结构是 rea 方案里我最看重的一部分。很多项目乱,就是从乱建文件夹开始的。我的核心思路是“按业务领域划分,而不是按技术角色划分”。举个例子,很多老项目喜欢建一个 views 文件夹把所有页面塞进去,再建一个 components 文件夹把所有公共组件塞进去,项目一大,这俩文件夹就变成两个无底洞,找东西全靠蒙。

rea 采用的是 features 优先的目录结构,也就是把业务模块当作一等公民。比如一个后台管理系统,我会把登录、用户管理、订单管理这些业务各自建一个 feature 文件夹,每个 feature 内部再包含它自己的 components、hooks、api、store 和页面。这样做的最大好处是内聚性:改一个业务功能时,你只需要进入一个 feature 文件夹,所有相关代码都在眼皮底下,不用在几个全局文件夹之间来回跳转。

除了 feature 之外,项目顶层还会有几个固定目录:app 用来放应用初始化和全局配置,components 放真正跨业务复用的通用组件,hooks 放通用 hooks,lib 放第三方库的封装和工具函数。目录规范这件事,我在实际推行时发现,最大的阻力不是技术而是习惯。所以我会在项目里放一份简短的 README 说明目录规则,同时在 Code Review 时坚持执行两到三周,大家形成肌肉记忆之后,项目结构就能始终保持清爽。

2. 核心细节解析与实操要点

2.1 从零初始化:脚手架与依赖安装

如果你要复刻 rea 这套方案,第一步不是去找一个现成的脚手架模板,而是亲手把项目初始化一遍。这样你能清楚每一块配置的作用,以后出了问题也知道去哪里排查。

首先用 pnpm 创建一个 Vite + React + TypeScript 项目:

pnpm create vite rea-demo --template react-ts cd rea-demo pnpm install

这一步之后你会得到一个相对干净的基础工程。接下来安装核心依赖。我不建议一次性把所有包都装上,那样容易装错版本,我习惯分批安装:

# 路由 pnpm add react-router-dom # 状态管理 pnpm add @reduxjs/toolkit react-redux # 请求 pnpm add axios # 开发依赖 pnpm add -D eslint eslint-plugin-react @typescript-eslint/parser @typescript-eslint/eslint-plugin pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier pnpm add -D stylelint stylelint-config-standard pnpm add -D husky lint-staged

装完依赖之后,最重要的就是把 package.json 里的 scripts 整理好。我习惯定义这样几个命令:

{ "scripts": { "dev": "vite", "build": "tsc -b && vite build", "preview": "vite preview", "lint": "eslint . --ext ts,tsx", "lint:fix": "eslint . --ext ts,tsx --fix", "format": "prettier --write \"src/**/*.{ts,tsx,css,html}\"", "prepare": "husky" } }

有个小细节要提一下:build 命令里我加了tsc -b,这样 TypeScript 类型检查会参与构建流程。很多人省略这步,结果类型错误全部跑到 CI 阶段才暴露,甚至直接漏进生产环境。在项目早期把类型检查纳入本地构建,能帮你省掉非常多低级错误。

2.2 规范与质量:ESLint、Prettier、Stylelint、Husky

规范工具这一块,很多人装完就算完事,实际上配置不当反而会让你很痛苦。比如 ESLint 和 Prettier 在“代码风格”这件事上是重叠的,两者没配合好就会出现“ESLint 刚报错、Prettier 又给改了”的打架现场。我的做法是:用 ESLint 管代码质量,用 Prettier 管格式风格,然后通过 eslint-config-prettier 把 ESLint 里跟格式化相关的规则全部关掉。

ESLint 配置我推荐用 Flat Config 格式,因为新版 ESLint 已经默认走这条路。基础配置大致是这样:

// eslint.config.js import js from '@eslint/js' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' import tseslint from 'typescript-eslint' export default tseslint.config( { ignores: ['dist', 'node_modules'] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], files: ['**/*.{ts,tsx}'], languageOptions: { ecmaVersion: 2022, globals: { window: true, document: true } }, plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh }, rules: { ...reactHooks.configs.recommended.rules, 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }], }, }, )

这里有个我很想强调的规则:consistent-type-imports。很多 TypeScript 项目里,类型导入和值导入混在一起,长期下来既影响编译速度也影响代码可读性。开了这个规则之后,ESLint 会强制你把类型用import type导入,虽然一开始会觉得麻烦,但习惯后你会发现代码里的每一次 import 都变得更清晰。

Husky 加上 lint-staged 是保证规范落地的关键一环。只靠 IDE 提示,总有漏网之鱼。我在 .husky/pre-commit 里配置这样一段:

npx lint-staged

再配合 lint-staged 配置:

{ "lint-staged": { "*.{ts,tsx}": ["eslint --fix", "prettier --write"], "*.{css,less,scss}": ["stylelint --fix", "prettier --write"], "*.{json,html,md}": ["prettier --write"] } }

这样每次提交代码,自动做一次规范校验和格式化。你可能会问,这样会不会拖慢提交速度?实际上它只处理你本次改动的文件,通常在一两秒内完成,相比之下它拦下的那些格式问题和低级 bug,才是真正让你省下大把时间的东西。

2.3 路由与权限设计

中后台项目的路由往往不只是“页面地址映射”,它天然要承担权限控制的职能。rea 里我设计了一套基于路由配置驱动的权限模型,核心思路是“路由表即菜单配置”。

路由配置我用createBrowserRouter来做,这是 React Router 最新的数据模式,对比传统的<Routes>写法,它支持 loader、errorElement、懒加载等能力,结构也更清晰:

// src/router/index.tsx import { createBrowserRouter, Navigate } from 'react-router-dom' import AuthGuard from '@/components/AuthGuard' import { lazy } from 'react' const LoginPage = lazy(() => import('@/features/auth/pages/LoginPage')) const DashboardPage = lazy(() => import('@/features/dashboard/pages/DashboardPage')) const UserManagePage = lazy(() => import('@/features/user/pages/UserManagePage')) export const router = createBrowserRouter([ { path: '/login', element: <LoginPage /> }, { path: '/', element: <AuthGuard />, children: [ { index: true, element: <Navigate to="/dashboard" replace /> }, { path: 'dashboard', element: <DashboardPage /> }, { path: 'user', element: <UserManagePage /> }, ], }, ])

AuthGuard 是一个关键组件,它把“是否已登录”和“是否有权限”这两个判断集中到同一个地方:

// src/components/AuthGuard.tsx import { Navigate, Outlet, useLocation } from 'react-router-dom' import { useAppSelector } from '@/store' import { selectToken } from '@/features/auth/authSlice' import { useMemo } from 'react' export default function AuthGuard() { const token = useAppSelector(selectToken) const location = useLocation() const isLogin = useMemo(() => Boolean(token), [token]) if (!isLogin) { return <Navigate to="/login" state={{ from: location }} replace /> } return <Outlet /> }

路由级别只做“登录与否”的判断,到按钮级别的权限我会单独封装一个PermissionButton组件,它读取用户角色或权限码列表,不满足条件就直接不渲染。这样权限逻辑被拆成了“路由层粗粒度控制”和“组件层细粒度控制”两个层次,实现起来简单,也不会把路由配置搞成一坨臃肿的判断分支。

3. 实操过程与核心环节实现

3.1 请求层封装:拦截器与重试策略

几乎每个中后台项目都离不开 axios,但大多数人只是简单axios.get()到处调用,没有统一封装。时间一长,同样的登录过期处理、同样错误提示逻辑散落在几十个页面里,改一个公共需求就要全局搜一遍。rea 的请求层封装目标非常明确:把“所有请求的公共逻辑”收敛到一处。

先看最核心的 axios 实例:

// src/lib/request.ts import axios from 'axios' import { message } from 'antd' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000, }) request.interceptors.request.use( (config) => { const token = localStorage.getItem('access_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, (error) => Promise.reject(error), ) request.interceptors.response.use( (response) => { const res = response.data // 假设后端统一返回 { code, data, message } if (res.code !== 0) { message.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res.data }, (error) => { const status = error.response?.status if (status === 401) { // 清空本地凭证,跳转登录页 localStorage.clear() window.location.href = '/login' return Promise.reject(new Error('登录已过期')) } message.error(error.response?.data?.message || '网络异常,请稍后重试') return Promise.reject(error) }, ) export default request

这段代码里有几个细节值得说一下。第一,上拦截器里从 localStorage 取 token,这个操作在每个请求时都会执行,看起来有点重复,但它能保证你在任何时刻修改 token 后,后续请求都会自动带上新的值。第二,response 拦截器里我直接解包返回res.data,这样业务代码里request.get()拿到的就是后端真正的数据,而不是裹着一层 { code, data, message } 的对象。这种约定能减少业务代码里的解包操作。第三,401 处理是集中式的,不管哪个接口触发登录过期,行为都是一致的。

我还要补充一个很多人忽略的细节:带缓存性质的 GET 请求要考虑幂等性和缓存策略。比如字典列表这种变化频率很低的数据,我用一个简单的 Map 做了内存缓存,下次请求直接从缓存取,避免每次进页面都要重新拉一遍接口。

3.2 状态管理重构:从 Context 到 Redux Toolkit

很多 React 初学者是从 Context 开始管理状态的,我也一样。但 Context 有个天生的问题:当 Provider 的值变化时,所有消费它的组件都会重新渲染,不管它们是否真的依赖变化的那部分数据。在中后台项目里,用户状态、字典状态变化并不频繁,但配合页面级状态一旦用 Context 管理,很容易出现“改一个数,整棵树重渲染”的性能隐患。

rea 的状态管理统一落在 Redux Toolkit 上。以用户模块为例,先创建 slice:

// src/features/auth/authSlice.ts import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' import * as authApi from './api' interface AuthState { token: string | null userInfo: Record<string, any> | null loading: boolean } const initialState: AuthState = { token: localStorage.getItem('access_token'), userInfo: null, loading: false, } export const fetchUserInfo = createAsyncThunk('auth/fetchUserInfo', async () => { const data = await authApi.getUserInfo() return data }) const authSlice = createSlice({ name: 'auth', initialState, reducers: { setToken(state, action) { state.token = action.payload localStorage.setItem('access_token', action.payload) }, logout(state) { state.token = null state.userInfo = null localStorage.removeItem('access_token') }, }, extraReducers: (builder) => { builder .addCase(fetchUserInfo.pending, (state) => { state.loading = true }) .addCase(fetchUserInfo.fulfilled, (state, action) => { state.userInfo = action.payload state.loading = false }) .addCase(fetchUserInfo.rejected, (state) => { state.loading = false }) }, }) export const { setToken, logout } = authSlice.actions export default authSlice.reducer

这是我非常熟悉的一种 Redux Toolkit 写法。reducers 里放的是同步操作,比如直接更新 state、写 localStorage;AsyncThunk 放异步操作,比如请求用户信息。对比原来的 Context + useReducer 方案,它的优势在于状态更新逻辑独立成模块,组件里只需要useAppSelector和useAppDispatch就能访问和修改,而且 DevTools 可以非常清晰地看到每个 action 的变化过程。

搭配 hooks 可以让组件代码显得更干净。我比较推荐这样封装:

// src/hooks/useAuth.ts import { useAppDispatch, useAppSelector } from '@/store' import { logout as storeLogout, setToken } from '@/features/auth/authSlice' export const useAuth = () => { const dispatch = useAppDispatch() const userInfo = useAppSelector((state) => state.auth.userInfo) const token = useAppSelector((state) => state.auth.token) return { userInfo, token, loginIn: (token: string) => dispatch(setToken(token)), logout: () => dispatch(storeLogout()), } }

组件里只需要const { userInfo, logout } = useAuth(),完全不需要关心 Redux 的细节。这一层封装我觉得非常有必要,它把状态管理的技术选型和业务组件解耦,将来即使换状态方案,业务代码的改动面也小很多。

3.3 性能优化:代码分割、懒加载、缓存策略

性能优化是最容易“马后炮”的部分。很多项目是等到线上卡得不行才开始优化,然后发现拆包、删包、加缓存处处受制。rea 的做法是从工程初始化就把性能地基打好,后面省心很多。

首先要做的是路由级代码分割。前文的路由配置里我用到了lazy(() => import(...)),这就是 React 的代码分割机制。它的原理非常朴素:浏览器不会一次性加载全部页面的 JavaScript,而是访问到某个路由时才去请求对应的 chunk。一个包含 50 个页面的后台项目,如果全部打进一个 bundle 里,初始加载可能就是几 MB 的 JS,再怎么优化服务器传输,前端解析执行的时间也摆在那里。代码分割之后,首屏只加载登录页和公共框架,可能只需要一两百 KB,效果立竿见影。

配合代码分割,Suspense 的 fallback 也值得设计一下。不要简单扔一个空白页面,至少给一个居中的 loading 提示。实际项目中我见过因为 fallback 太简陋导致用户反复点击刷新按钮的情况。更好的做法是做一个带骨架屏的组件,给用户一种“页面正在加载”的预期。

其次,组件级别的性能优化要区分场景。中后台里最常见的性能问题之一是大表格渲染,几百行甚至上千行的数据一次性渲染出来,即使现代浏览器也扛不住。我常用的方案有三个:第一优先做分页,这是产品层面的简化;第二做虚拟列表,只渲染可视区域的行;第三是给表格行加上稳定的 key 和 memo,防止无效重复渲染。关于虚拟列表,我推荐直接使用成熟库而不是造轮子,核心参数就是行高、可视区域高度和 overscan 数量。overscan 太小会出现快速滚动时白屏,太大又会增加渲染负担,实测下来 20 到 30 行是一个比较平衡的数值。

最后说缓存策略。前端缓存分两个层面,HTTP 层面的缓存交给静态资源服务配置,代码层面我更关心状态和数据的缓存。刘列表搜索条件这种 UI 状态,可以用 sessionStorage 做缓存,用户切页再回来时还能保留筛选条件。而字典类接口数据用模块级缓存即可。这里有个小技巧:缓存不要写得太隐蔽,最好封装成一个工具类,统一处理读、写、过期策略。我自己会写一个cache.ts,内部用 Map 加时间戳实现,代码不过百行,但能避免在各种业务代码里手写 localStorage 操作。

4. 常见问题与排查技巧实录

4.1 Vite 版本与插件不兼容

Vite 的迭代速度非常快,生态里的插件经常会滞后。我踩过的第一个坑是 Vite 5 刚发布时,某个常用插件还停留在适配 Vite 4 的版本,一启动就报ERROR: This plugin is not compatible with Vite 5。这个问题的排查思路很固定:先确认 Vite 大版本,再查插件的 peerDependencies 是否覆盖当前版本。

最省心的做法是,在开始项目前先锁定 Vite 版本,不要第一时间升级。我现在的策略是跟着主 flow 走,但要给插件一周左右的缓冲期。如果必须在旧版本 Vite 上用新插件,可以在vite.config.ts里临时关闭某些插件,或者用官方推荐的 Vite 兼容层。这类问题属于“时间换空间”的问题,核心是不要盲从新版。

4.2 TypeScript 类型报错与循环依赖

新手上 TypeScript 最头疼的就是“明明代码没问题,但类型老是报错”。我在 rea 方案里推了--tsc -b之后,这类问题变得更显眼,但也更可控。常见的几个问题是:第三方库缺少类型定义、strict模式下空值问题、以及 import 顺序引发的循环依赖。

循环依赖这类问题比较隐蔽。我在一个权限模块里遇到过一次:authSlice 引用了 router,router 又引用 AuthGuard,AuthGuard 再引用 authSlice,绕成了一圈,结果在某些场景下运行时报 “Cannot access before initialization”。排查思路是:把这个“环”拆开,把 router 的创建和数据流解耦。我最终把 router 改造成“用函数创建路由表、在组件挂载前注入依赖”的模式,模块之间的依赖变成了单向的,问题就消失了。

4.3 热更新慢与依赖预构建

Vite 开发时热更新慢,最常见的起因是依赖预构建没有生效。Vite 会对 node_modules 里的依赖做预构建,把 CommonJS 转成 ESM,同时合并大量依赖模块。当你在vite.config.ts里加了新的optimizeDeps.include或者改了某些配置,Vite 需要重启 dev server 才能重新预构建。

如果启动后访问页面很慢,优先按这个顺序排查:第一,检查 dev server 启动日志里有没有 “new dependencies optimized” 的提示;第二,确认没有把整个 node_modules 塞进resolve.alias的错误配置;第三,看看是不是有某个第三方库体积异常,刻意不用预构建的境地。日常开发中,尽量避免引入体积过大且没有 ESM 产物的大型库,这能在源头上规避很多热更新问题。

这类问题的共性是“依赖驱动开发”:项目的依赖越复杂,构建和开发阶段的不确定因素就越多。所以在 rea 的体系里,我对新依赖的引入保持克制。增加一个依赖前,我会默认问自己三句话:这个功能必须用库吗?有没有替代方案?这个库的维护状况和体积值不值得引入?

4.4 权限按钮灰显与路由守卫的边界

权限控制做得太细容易过度设计,做得太粗又会有安全隐患。我遇到过一个问题:前端把按钮渲染出来,后端接口也做了校验,但是前端因为状态没有同步,按钮有时候显示有时候不显示。后来发现是权限数据放在组件本地状态里,刷新页面后请求用户信息的接口还没返回时,组件已经渲染了一版“无权限”状态,等数据到位之后没有任何机制触发按钮重新渲染。

解决方式是把权限状态纳入 Redux 全局状态。登录后异步获取用户信息和权限码,存到 store 里面;组件通过useAppSelector订阅权限数组,数据到达后自然触发重新渲染。同时,权限码的获取应该和用户信息合并到一个接口里,避免“先登录、再拿权限、再渲染界面”的多阶段等待。

我把权限相关的常见问题和方案整理成一个表格,方便大家快速对照:

场景问题表现推荐处理方案
路由访问控制未登录用户可直达内部页面用 AuthGuard 包裹业务路由,统一做登录态检查
按钮级权限初始渲染出现无权限状态全局 store 管理权限码,用订阅方式触发更新
权限更新用户角色切换后界面未刷新重新调用用户信息接口,整体重置状态,不要局部赋值
接口越权前端隐藏但接口仍被调用后端必须做最终校验,前端隐藏只是体验层面

表格里体现的是一个核心思想:前端权限控制永远是体验层面的,真正的安全校验必须落在后端。前端做权限的唯一目的,是不让用户看到和操作他无权访问的内容,而不是“认为这样用户就访问不到”。

最后分享一个我实操中的体会

这套 rea 方案并非一次成型,它是在我踩过非常多坑之后逐步收敛出来的。最开始的版本也杂乱无章,目录结构改了好几次,状态管理方案换过,甚至构建工具从 Webpack 迁到 Vite 的过程也经历过阵痛。但有一点一直没有变,就是我先明确“项目要长期维护”这个前提,再倒推每一个技术选型和结构设计。

如果你正要开始搭建新项目,我的建议是不要照搬我的整套配置,而是先想清楚你的项目规模和团队情况:三五个人维护三五个页面,那没必要上这么重的规范体系;几十个人维护一个大型后台,那就值得把目录结构、代码规范和工具链认真搭一遍。architecture 这个东西讲究的是匹配,不是越复杂越好。

再补一个我个人的小习惯:项目里的 README 我会写得非常详细,不只是安装和启动命令,还包括目录结构说明、代码规范要求、常用命令手册和排坑指南。这样做的好处是半年后你自己回来看项目,或者新人加入时,能通过一个文件快速建立全貌,不用再去翻散落在各处的注释和聊天记录。

rea 以后会不会继续演化?肯定会。新技术出来了我也会去做实验性尝试,但我会保持一个原则:所有正式项目里的技术,都必须服务于“稳定可维护”这个目标。希望这篇文章里的思路、代码和踩坑经验,能帮你在搭建自己的 React 项目时少走一些弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进