ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

React学习笔记:组件拆分、状态管理与Vite+TS工程化实践

React学习笔记:组件拆分、状态管理与Vite+TS工程化实践 第二篇React学习笔记我不打算再讲一遍什么是JSX。距离上一篇笔记已经过去一阵子这期间我把“能跑起来”的demo逐个拆开重写发现真正卡住我的不是语法而是不知道一段UI逻辑该放哪、数据该归谁管。如果你也处在“能跟着教程写但一关掉教程就不知道下一步”的阶段这篇笔记应该能帮你顺一顺思路。这篇笔记会从组件拆分的真实场景讲起梳理状态与数据流的边界再搭一个Vite React 19 TypeScript的工程框架最后把路由、可视化组件和踩坑经验一并收进来。适合刚完成React基础语法学习、正在尝试写自己第一个小项目的人。1. 组件拆分从“一个App写到底”到按职责分块1.1 组件并不是“把页面切几刀”我第一次正经写React项目的时候大脑里的思路还停留在“这是一个页面那是另一个页面”。于是整个首页的逻辑全部堆在App组件里列表数据也在这里、搜索框也在这里、卡片也在这里、分页也在这里最后App函数膨胀到三百多行。功能倒是能跑但改一个输入框的placeholder我都要在密密麻麻的JSX里找半天。后来我才逐渐意识到组件拆分的核心不是“视觉上切块”而是按职责和数据的边界分块。一个列表卡片被单独抽出来不只是因为它在页面里长得像一块而是因为它只需要接受一个对象、渲染一段内容并且可以独立复用。举个例子一个博客首页里有文章列表、筛选框、分页器。如果全塞在一个组件里那所有数据都是这个组件的内部状态。一旦哪天筛选框要在别的页面出现你只能复制粘贴。可如果你一开始就把筛选框拆成SearchInput把单篇文章拆成ArticleCard那么这些组件天然就可以挪到任何地方。我个人的判断标准是当一个组件里的JSX超过差不多二三十行或者一段逻辑明显是独立的展示单元就应该考虑拆出去。不需要一步到位先拆最明显的等发现数据传来传去太麻烦再合并也是可以的。1.2 函数组件就是初学的唯一选择现在市面上的教程基本都在写函数组件和Hooks原因很简单函数组件写法简洁逻辑可以通过Hook抽取复用也不用操心this指向的问题。React官方也明确推荐函数组件。类组件的写法你只在看老代码时会遇到初学阶段没必要深钻// 类组件风格看一眼即可 import { Component } from react; class Counter extends Component { state { count: 0 }; handleClick () { this.setState({ count: this.state.count 1 }); }; render() { return ( button onClick{this.handleClick} 点击了 {this.state.count} 次 /button ); } }而函数组件配合useState是这样的import { useState } from react; function Counter() { const [count, setCount] useState(0); return ( button onClick{() setCount(count 1)} 点击了 {count} 次 /button ); }两段代码一比你就知道为什么函数组件能成为主流。少了一层render包裹状态声明也直观得多而且后续用useEffect、useMemo这些能力时函数组件的表达会自然很多。1.3 列表渲染里key的作用比想象中大初学阶段你可能只是照着教程写{articles.map(article ArticleCard key{article.id} article{article} /)}却没想过这个key到底有什么用。React渲染列表时需要知道每一项在列表中的身份。当数据顺序变化、某个元素被插入或删除时React通过key来判断哪些节点可以复用、哪些需要重建。如果key不对就会发生很诡异的现象输入框内容错位、组件状态串了。初学阶段记住两个原则key要放在组件或元素的最外层不能放在组件内部的某个DOM上。key要稳定唯一最好来自数据自己的ID。不要用数组下标当key。因为下标会随顺序变化一旦删掉中间项后面所有项的下标都变了React会误判节点身份。// 不推荐用 index 当 key {list.map((item, index) Row key{index} data{item} /)} // 推荐用数据自身的 id {list.map(item Row key{item.id} data{item} /)}数组下标做key在纯展示型数据里可能看不出问题但一旦列表项有input、有内部状态问题就会很扎眼。这个坑我踩过后文第6章还会再提。2. 状态与数据流父子组件之间到底谁说了算2.1 state是用来“通知页面更新”的很多初学者困惑我直接声明一个普通变量不好吗为什么非要用useState// 这样写页面不会更新 let count 0; function handleClick() { count count 1; }原因在于React不知道这个变量变了。useState的setter函数会触发一次“重新渲染”React拿到新的state后重新执行组件函数页面上的内容才会更新。所以state不是普通的变量存储它是React更新机制的入口。import { useState } from react; function LikeButton() { const [liked, setLiked] useState(false); return ( button onClick{() setLiked(!liked)} style{{ color: liked ? #e5484d : #666 }} {liked ? 已点赞 : 点赞} /button ); }这里liked变化后按钮文字和颜色会同步变化。如果换成普通变量页面就纹丝不动。2.2 props:父组件的数据边界props是父组件向子组件传数据的通道。子组件收到的props是只读的不能直接修改父组件的数据。为什么不能直接改因为React推崇单向数据流。如果每个子组件都能随意改父级数据那数据流向就会变得混乱出了bug你很难定位是谁改的。子组件想改变数据正确的做法是让父组件传一个函数下来子组件调用这个函数由父组件执行修改。这样设计的好处是每个数据都有明确的所有者代码的可预测性会大幅提升。对比项stateprops数据归属组件自己管理父组件传给子组件是否可变通过setter修改子组件内只读修改方式setState触发重新渲染父组件修改后经新渲染传入典型用途表单输入、开关状态、接口数据配置项、回填数据、回调函数function ArticleCard({ article, onDelete }) { return ( div h3{article.title}/h3 button onClick{() onDelete(article.id)}删除/button /div ); }ArticleCard不知道onDelete内部怎么实现它只负责在点击时把article.id交回给父级。父组件想怎么删、要不要发请求是父组件自己的事。2.3 受控组件:把表单交给状态处理初学React表单时有个绕不开的概念受控组件。简单说就是表单元素的value由state控制onChange事件负责更新state。function SearchBox() { const [keyword, setKeyword] useState(); return ( input value{keyword} onChange{(e) setKeyword(e.target.value)} / ); }这个写法意味着输入框显示什么完全取决于keyword这个state。当用户在输入框敲字时onChange触发setKeyword组件重新渲染输入框拿到新的value。整个过程形成一个闭环state是唯一数据源。非受控组件用defaultValue加ref读取虽然也存在但初学阶段建议一律写成受控组件。后续做校验、联动、格式化输入时受控组件会让你有更多掌控力。2.4 状态提升:把共同数据放到最近的父组件我第一次写搜索功能时把keyword各自放在搜索框组件和文章列表组件里结果两边数据互不相通。后来才知道要用“状态提升”当多个子组件需要共享同一份数据时把数据放到它们最近的共同父组件中由父组件向下传递。function BlogPage() { const [keyword, setKeyword] useState(); const filteredArticles articles.filter((article) article.title.includes(keyword) ); return ( div SearchInput value{keyword} onChange{setKeyword} / ArticleList articles{filteredArticles} / /div ); }keyword待在BlogPage里SearchInput负责展示和回传ArticleList接收过滤后的结果。这样两边的数据自然同源不需要在多个组件间手动同步。初学阶段你可能一开始会把state放在最顶层的组件里图省事但这样会导致顶层组件数据过于臃肿。比较好的节奏是先用state等发现其他组件也需要这份数据时再提升到父级。3. 把项目推向工程化Vite React 19 TypeScript的初学组合3.1 为什么我放弃CRA选了Vite初学React时很多古早教程会让你用Create React AppCRA初始化项目。但现在React官方文档都推荐用框架或者Vite。CRA的开发服务器启动比较慢配置复杂维护也接近停滞状态。Vite基于原生ESM开发时按需编译热更新更快插件生态也更活跃。初始化一个React 19 TypeScript项目只需要一条命令npm create vitelatest my-app -- --template react-ts然后进入项目目录装依赖启动cd my-app npm install npm run dev这条命令生成的项目默认入口在src/main.tsx组件写在src/App.tsx样式文件是src/index.css。对初学者来说目录清爽没有一堆看不懂的配置文件。3.2 React 19对初学者意味着什么React 19已经不是一个遥不可及的版本很多新项目初始化时直接就是19。对初学阶段来说不需要把新特性全部搞懂但有几个点可以先眼熟一下use()一个可以直接在渲染阶段读取Promise或Context的Hook写数据请求的老套路可以简化。useOptimistic用于乐观更新提交表单时可以先展示目标结果等服务器确认后再回滚。ref可以作为props直接传给函数组件不再必须要forwardRef。useActionState处理表单异步提交的状态配合form action使用非常顺手。这些特性的共同方向是React在努力让“服务端交互”和“异步状态”写法更贴近直觉。不过初学阶段不必急着用先把props、state、事件这三个基本功吃透新特性可以在写表单、写接口请求时逐步引入。3.3 TypeScript初学级的Props类型定义在一个React TS项目里初学者最常遇到的问题就是“这个组件参数要不要写类型怎么定义”最基础的写法是定义一个接口interface Article { id: number; title: string; author: string; views: number; } function ArticleCard({ article }: { article: Article }) { return ( div h3{article.title}/h3 p{article.author} · {article.views}次浏览/p /div ); } // 父组件里直接使用 ArticleCard article{article} /{ article }: { article: Article }是在声明这个组件接收一个名为article的props类型必须是Article。当你传错字段或漏传时编辑器会立刻标红提示而不需要等到运行时报错。TS在初学阶段最大的价值不是“类型系统有多高级”而是它在写代码期间就帮你去除了一大批低级错误比如单词拼错、数据结构对不上、可选值没判空。interface User { name: string; age?: number; // 可选字段 }age?表示可以不传。访问可能不存在的字段时TS会提醒你处理undefined这比你运行时到处console.log去猜要高效得多。3.4 初学阶段的目录怎么组织错误的做法是匆匆建一个src/components然后把所有文件丢进去。文件一旦多起来找起来就费劲了。我比较推荐按“功能/页面”划分src/ ├── components/ # 通用组件 │ ├── Layout/ │ └── ArticleCard/ ├── pages/ # 页面级组件 │ ├── HomePage/ │ └── ArticlePage/ ├── hooks/ # 自定义Hook ├── types/ # 公共类型定义 ├── utils/ # 工具函数 └── main.tsx对于刚起步的小项目这个结构不一定是最优解但可以帮你建立一种习惯组件按照用途归位而不是全部堆在一起。等以后项目复杂了再根据业务域做更细的划分。4. 页面多起来了用react-router-dom处理路由与嵌套关系4.1 SPA为什么需要路由一个React项目如果没有路由页面切换就只能靠state手动控制显示哪个组件地址栏的URL也不会变化刷新后状态全丢。路由解决的核心问题是让URL与界面保持同步。Vite初始化的项目默认就是单页应用SPA浏览器拿到的是一个空HTML和一堆JS。路由切换时不刷新整个页面只更新局部组件配合React DOM diff机制体验接近原生App。react-router-dom是React生态里最常用的路由库。安装方式npm install react-router-dom4.2 最基础的配置方式React Router 7也就是v7和React 19配合得很好推荐用createBrowserRouter创建路由配置而不是老的BrowserRouter Routes模式。写在main.tsx里import { createBrowserRouter, RouterProvider } from react-router-dom; import HomePage from ./pages/HomePage; import ArticlePage from ./pages/ArticlePage; import Layout from ./components/Layout; const router createBrowserRouter([ { path: /, element: Layout /, children: [ { index: true, element: HomePage / }, { path: article/:id, element: ArticlePage / }, ], }, ]); function App() { return RouterProvider router{router} /; }这里Layout是一个壳子里面用Outlet /渲染子路由对应的组件。嵌套路由的好处是公共的侧边栏、导航栏、页脚可以只写一次子页面变化时壳子不动。// Layout.tsx import { Outlet, Link } from react-router-dom; export default function Layout() { return ( div nav Link to/首页/Link /nav main Outlet / /main /div ); }4.3 动态路由参数与404兜底当你需要打开某一篇文章时URL里要带上文章ID比如/article/123。在路由配置里跟在:后面的就是动态参数{ path: article/:id, element: ArticlePage / }在ArticlePage里通过useParams取到idimport { useParams } from react-router-dom; function ArticlePage() { const { id } useParams(); return div当前文章ID{id}/div; }这时再配合一段fetch或useEffect请求接口就能根据ID加载文章。初学阶段先明白“URL参数 - 请求数据”这个链路后面接接口时思路会清晰很多。404路由同样简单加一个通配路径{ path: *, element: NotFoundPage / }4.4 Link与useNavigate怎么选初学容易忽略导航方式的状态差异。Link用于用户点击后跳转它是声明式的相当于HTML里的a浏览器会记录历史记录。useNavigate()适合在某段逻辑执行完毕后跳转比如登录成功、提交数据完成后。// 点击触发跳转 Link to/article/123查看文章/Link // 逻辑触发跳转 const navigate useNavigate(); function handleSave() { await saveData(); navigate(/article/123); }一般推荐优先用Link因为它在语义上更清晰也方便右键新标签页打开。只有在事件回调或异步处理里才用useNavigate。5. 顺路看了一眼React Flow可视化与状态思维的碰撞热搜词里看到React Flow本来只是好奇结果读了一下官方文档就觉得它非常适合作为初学进阶的练习题材。React Flow是一个基于React的节点连线可视化库典型场景是流程图、脑图、工作流编排。初学阶段接触React Flow并不要求你立刻去做一个完整编辑器而是可以从里面复习很多React的基础概念节点数据是state连线也是state拖拽是事件回调节点更新是setState。5.1 用React Flow搭一个最小流程图项目里装一下npm install xyflow/react再引入样式文件注册节点和边import { ReactFlow, Background, Controls } from xyflow/react; import xyflow/react/dist/style.css; const initialNodes [ { id: 1, position: { x: 0, y: 0 }, data: { label: 开始 } }, { id: 2, position: { x: 150, y: 100 }, data: { label: 处理 } }, ]; const initialEdges [ { id: e1-2, source: 1, target: 2 }, ]; function FlowDemo() { const [nodes, setNodes] useState(initialNodes); const [edges, setEdges] useState(initialEdges); return ( div style{{ width: 600, height: 400 }} ReactFlow nodes{nodes} edges{edges} onNodesChange{(changes) setNodes(nodes applyNodeChanges(changes, nodes))} Background / Controls / /ReactFlow /div ); }applyNodeChanges来自xyflow/react它接收React Flow内部发出的变化事件返回新的节点数组然后我们通过setNodes更新状态。这段交互链路和React的基础思维完全一致本质上就是“事件 - state - 渲染”。如果你学到这里有点无聊了可以试着把每个节点改造成带输入框的可编辑节点点击节点获取当前节点数据编辑内容后调setNodes更新对应节点。做完这个小练习你对“数据驱动的界面”会有非常直观的感受。5.2 给节点增加交互数据节点的data字段可以放任意自定义信息比如一个表单配置、一个状态标记。拖拽节点时React Flow会自动更新节点坐标但自定义数据需要自己处理const onNodeClick (_event, node) { setSelectedId(node.id); };这类代码写起来很简单但它能帮你理解一个常见模式组件在渲染时并不直接操作DOM而是更新数据让UI根据数据自动变化。6. 初学阶段最容易踩的坑和我留下的自查清单6.1 报错信息的正确看法初学阶段我见到红色报错就慌后来才发现React的报错信息其实相当友好。它通常会明确告诉你“哪个组件出了问题”“错误类型是什么”。最常见的几个报错信息常见原因解决办法Cannot read properties of undefined (reading map)接口数据未返回就渲染列表渲染前判空。Each child in a list should have a unique key prop列表项缺少key或key重复补充或修正keyToo many re-renders在render阶段直接setState把setState移到事件回调或useEffect里TypeError: setState is not a function解构state数组时写错了检查const [a, setA] useState(...)第一个报错是初学使用接口数据时的高频问题。解决方案不复杂{articles articles.map(...)} // 或者 {articles?.map(...)}如果数据还要做类型安全处理更推荐提前给初始state一个空数组const [articles, setArticles] useStateArticle[]([]);这样在数据到达之前articles不是一个undefined而是一个空数组直接map也不会报错。6.2 在事件里读不到最新的state很多初学者遇到过这样的场景点击按钮时打印state发现打印出来的是旧值。比如function Counter() { const [count, setCount] useState(0); const handleClick () { console.log(count); // 这里读到的可能还是旧值 setCount(count 1); }; }这是因为事件触发时组件函数已经基于旧的count渲染过一次闭包里捕获的count就是这次渲染时的值。想要拿到最新值可以用函数式更新setCount((prev) prev 1);函数式更新不依赖闭包里的旧值React会确保prev是最新值。在需要连续多次更新的场景比如快速点击增加下尤其有用。6.3 初学阶段别碰的清单很多人一学完React基础就急着去学Redux、Zustand、useMemo、memo、自定义Hook全家桶结果反而被概念淹没。我的建议是别急着上状态管理库。你还没遇到“跨页面组件共享复杂状态”的实际痛点Redux的概念只会让你更焦虑。别背Hooks执行顺序的面试题。先把每天写的useEffect依赖数组理清楚。别试图一步到位做性能优化。memo、useCallback是你感觉“卡”的时候再去研究的东西。别被“Agent”“智能体”这类热词带走。React相关面试里经常看到类似概念但初学阶段的核心任务是把组件、状态、渲染机制、路由这些地基打牢。地基不稳热词聊得再多一写代码就露馅。6.4 给学习者的一句真心话第二篇笔记写到这里其实不算结束了因为学习React的本质是“写足够多的组件、处理足够多的状态变化”。我在这个阶段最大的体会是不要怕代码写得不优雅先让代码跑起来再去想怎么重构。很多设计模式你学的时候不懂为什么要那样写等你被代码维护性搞烦了自然就理解了。接下来第三篇笔记我大概率会写Hooks进阶、自定义Hook封装以及用React TypeScript做一个小型项目的完整过程。如果你也在学React可以按这个节奏走先写demo再拆组件再补TS再上路由。等你把这些串起来之后React在你眼里就不再是一门“玄学”了。
返回列表