news 2026/5/5 14:55:26

01 React 基础与组件体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
01 React 基础与组件体系

本章覆盖从入门到可维护组件设计的完整基础。目标不是只会写 JSX,而是理解 React 如何把 JavaScript 状态映射为 UI,并能设计清晰、稳定、可演进的组件边界。

1. React 的核心模型

React 的核心思想可以概括为:

UI = f(state)

你描述某个状态下 UI 应该是什么样,React 负责根据状态变化重新计算 UI,并把差异提交到浏览器 DOM。

传统 DOM 代码常写成步骤:

button.addEventListener('click',()=>{count+=1;label.textContent=count;button.disabled=count>10;});

React 更关注结果:

function Counter() { const [count, setCount] = useState(0); return ( <button disabled={count > 10} onClick={() => setCount(count + 1)}> {count} </button> ); }

2. JSX 与 React Element

JSX 是 JavaScript 的语法扩展,会被编译为 React Element。

const element = <h1 className="title">React</h1>;

概念上类似:

constelement=React.createElement('h1',{className:'title'},'React',);

JSX 中可以放表达式:

function Greeting({ user }) { return <h1>你好,{user.name}</h1>; }

不能直接放语句:

// 错误 return <div>{if (ok) 'yes'}</div>;

正确:

return <div>{ok ? 'yes' : 'no'}</div>;

3. 组件函数

组件是接收 Props、返回 UI 描述的函数。

function KnowledgeCard({ title, summary, level }) { return ( <article> <span>{level}</span> <h3>{title}</h3> <p>{summary}</p> </article> ); }

组件必须首字母大写:

<KnowledgeCard title="组件模型" />

小写标签会被 React 当作 DOM 标签:

<div /> <button />

不要直接调用组件函数:

// 错误 return <Layout>{Article()}</Layout>;

正确:

return ( <Layout> <Article /> </Layout> );

React 需要自己调用组件,才能管理 Hook、调度、Reconciliation 和开发检查。

4. Props

Props 是父组件传给子组件的只读输入。

function CourseList({ courses, onSelect }) { return ( <section> {courses.map((course) => ( <button key={course.id} onClick={() => onSelect(course.id)}> {course.title} </button> ))} </section> ); }

Props 设计原则:

  • 用业务语义命名:onCompleteonClick更清楚。
  • 子组件不修改 Props。
  • 不随意透传大对象。
  • 回调表达“发生了什么”,不是“怎么处理”。

推荐:

<CourseCard course={course} onComplete={completeCourse} />

不推荐:

<Card data={course} onClick={handleClick} />

5. State

State 是组件的记忆。

function Toggle() { const [open, setOpen] = useState(false); return ( <button onClick={() => setOpen((value) => !value)}> {open ? '收起' : '展开'} </button> ); }

当新状态依赖旧状态时,使用函数式更新:

setCount((current) => current + 1);

不要直接修改对象或数组:

// 错误 user.name = 'Ada'; setUser(user);

正确:

setUser((user) => ({ ...user, name: 'Ada', }));

数组更新:

setItems((items) => [...items, newItem]); setItems((items) => items.filter((item) => item.id !== id)); setItems((items) => items.map((item) => item.id === id ? { ...item, done: !item.done } : item, ), );

6. 条件渲染

function StatusView({ status }) { if (status === 'loading') return <p>加载中...</p>; if (status === 'error') return <p>加载失败</p>; return <p>加载完成</p>; }

短条件:

{error && <p role="alert">{error}</p>}

二选一:

{isLoggedIn ? <Dashboard /> : <LoginPage />}

7. 列表渲染与 key

function CourseList({ courses }) { return ( <section> {courses.map((course) => ( <CourseCard key={course.id} course={course} /> ))} </section> ); }

key表示稳定身份。优先使用业务 ID,不要在会排序、插入、删除的列表中使用数组索引。

错误:

{items.map((item, index) => ( <EditableRow key={index} item={item} /> ))}

后果:

  • 输入框内容错位。
  • 展开状态跑到另一行。
  • 动画异常。
  • React 错误复用组件实例。

8. 事件处理

React 事件使用驼峰命名:

<button onClick={handleClick}>保存</button>

传参:

<button onClick={() => deleteItem(item.id)}>删除</button>

阻止默认行为:

function submit(event) { event.preventDefault(); }

React 事件接近浏览器事件,但由 React 统一管理。

9. 表单基础

受控表单由 React State 控制:

function LessonForm({ onSubmit }) { const [form, setForm] = useState({ title: '', level: '入门', }); function updateField(field, value) { setForm((current) => ({ ...current, [field]: value, })); } function submit(event) { event.preventDefault(); onSubmit(form); } return ( <form onSubmit={submit}> <input value={form.title} onChange={(event) => updateField('title', event.target.value)} /> <select value={form.level} onChange={(event) => updateField('level', event.target.value)} > <option>入门</option> <option>进阶</option> <option>高级</option> <option>精通</option> <option>专家</option> </select> <button disabled={!form.title.trim()}>保存</button> </form> ); }

可派生的校验不要重复存 State:

const titleError = form.title.trim() ? '' : '标题不能为空'; const canSubmit = !titleError;

10. children 与组合

children让组件成为结构容器。

function Panel({ title, children }) { return ( <section className="panel"> <h2>{title}</h2> <div>{children}</div> </section> ); } function Page() { return ( <Panel title="学习概览"> <p>今天完成 3 个知识点。</p> <button>继续学习</button> </Panel> ); }

显式插槽:

function Modal({ title, children, footer }) { return ( <section role="dialog"> <header>{title}</header> <main>{children}</main> <footer>{footer}</footer> </section> ); }

11. Fragment、StrictMode、内置组件

Fragment 用于不增加 DOM 节点地组合多个元素:

return ( <> <Header /> <Main /> </> );

需要 key 时:

items.map((item) => ( <Fragment key={item.id}> <dt>{item.title}</dt> <dd>{item.summary}</dd> </Fragment> ));

StrictMode 用于开发检查:

<React.StrictMode> <App /> </React.StrictMode>

它会帮助发现不纯渲染、Effect 清理问题、废弃 API 等。开发中 Effect 看似执行两次时,通常应该修复副作用,而不是移除 StrictMode。

12. 组件职责分类

页面组件:负责页面编排。

function KnowledgePage() { return ( <PageLayout> <KnowledgeHub /> </PageLayout> ); }

容器组件:负责状态、数据、行为。

function KnowledgeHubContainer() { const [state, dispatch] = useReducer(reducer, initialState); const visibleItems = selectVisibleItems(state); return ( <KnowledgeHubView state={state} items={visibleItems} dispatch={dispatch} /> ); }

展示组件:根据 Props 渲染。

function KnowledgeCard({ item, favorite, onFavorite }) { return ( <article> <h3>{item.title}</h3> <p>{item.summary}</p> <button onClick={() => onFavorite(item.id)}> {favorite ? '取消收藏' : '收藏'} </button> </article> ); }

13. 受控与非受控组件

受控组件:

function Tabs({ value, onChange, items }) { return ( <div> {items.map((item) => ( <button key={item.value} aria-selected={value === item.value} onClick={() => onChange(item.value)} > {item.label} </button> ))} </div> ); }

非受控组件:

function Collapsible({ title, children }) { const [open, setOpen] = useState(false); return ( <section> <button onClick={() => setOpen((value) => !value)}> {title} </button> {open && children} </section> ); }

组件库基础控件更适合受控,局部业务交互可以非受控。

14. 复合组件模式

适合 Tabs、Menu、Accordion 等一组协作组件。

const TabsContext = createContext(null); function TabsRoot({ value, onChange, children }) { return ( <TabsContext.Provider value={{ value, onChange }}> {children} </TabsContext.Provider> ); } function TabsTrigger({ value, children }) { const tabs = useContext(TabsContext); return ( <button aria-selected={tabs.value === value} onClick={() => tabs.onChange(value)} > {children} </button> ); }

使用:

<TabsRoot value={tab} onChange={setTab}> <TabsTrigger value="overview">总览</TabsTrigger> <TabsTrigger value="demo">Demo</TabsTrigger> </TabsRoot>

15. Headless 组件

把逻辑和样式分离。

function useDisclosure() { const [open, setOpen] = useState(false); return { open, show: () => setOpen(true), hide: () => setOpen(false), toggle: () => setOpen((value) => !value), }; }

业务组件:

function DeleteDialog() { const dialog = useDisclosure(); return ( <> <button onClick={dialog.show}>删除</button> {dialog.open && <ConfirmModal onClose={dialog.hide} />} </> ); }

16. 组件抽象时机

可以抽象的信号:

  • 重复出现三次以上。
  • 业务含义稳定。
  • 变化点可以用少量 Props 表达。
  • 抽象后调用方更容易理解。

不该抽象的信号:

  • 只是样式碰巧相似。
  • 每个使用点都有大量特殊逻辑。
  • Props 数量不断膨胀。
  • 为了复用牺牲可读性。

17. 入门到高级达标标准

入门:

  • 能写 JSX、组件、Props、State、事件、列表和表单。
  • 能避免直接修改 State。
  • 能使用稳定 key。

进阶:

  • 能设计组件职责。
  • 能区分受控和非受控。
  • 能用组合代替继承。

高级:

  • 能判断组件边界是否表达变化边界。
  • 能避免布尔 Props 爆炸。
  • 能把业务规则从展示组件中抽离。

专家:

  • 能设计组件库 API。
  • 能制定组件抽象准入标准。
  • 能让组件系统支持长期业务演进。

18. React 基础知识点扩展清单

这一节补齐入门阶段容易遗漏的细节。React Element 是描述 UI 的普通对象,Component 是产生 Element 的函数,DOM instance 是 React DOM 在浏览器中维护的真实节点。理解三者区别,才能明白为什么不要直接调用组件函数,也不要在渲染期间操作 DOM。

组件必须保持纯粹。错误示例:

let nextId = 0; function Item() { nextId += 1; return <div>{nextId}</div>; }

正确做法是把变化来源放进 Props、State 或 Context:

function Item({ id }) { return <div>{id}</div>; }

Props 默认值适合展示层兜底,但不适合掩盖业务数据缺失:

function Avatar({ size = 40, src, alt = '用户头像' }) { return <img width={size} height={size} src={src} alt={alt} />; }

children可以是字符串、数字、元素、数组、null或条件渲染结果。条件渲染要避免0被渲染:

{items.length > 0 && <List items={items} />}

组件命名要表达业务或 UI 角色:KnowledgeCardPermissionGateUserAvatar都比Box2CommonCardLeftPart更可维护。

19. 组件模式扩展

Container + View 模式适合复杂页面:

function CoursePageContainer() { const query = useCourses(); const [selectedId, setSelectedId] = useState(null); return ( <CoursePageView status={query.status} courses={query.data} selectedId={selectedId} onSelect={setSelectedId} /> ); }

受控/非受控混合模式适合组件库:

function Toggle({ checked, defaultChecked = false, onCheckedChange }) { const [innerChecked, setInnerChecked] = useState(defaultChecked); const isControlled = checked !== undefined; const value = isControlled ? checked : innerChecked; function update(next) { if (!isControlled) setInnerChecked(next); onCheckedChange?.(next); } return <button onClick={() => update(!value)}>{value ? '开' : '关'}</button>; }

专家提醒:同时支持受控和非受控会增加复杂度,只有基础组件库值得这样做。

20. 基础练习库

  • 课程卡片:标题、简介、层级、标签、收藏、禁用态、长文本截断。
  • 筛选列表:搜索、层级筛选、空状态、稳定 key。
  • 受控表单:标题必填、层级下拉、标签动态添加、提交按钮禁用。
  • 组件重构:把 300 行页面拆成容器、筛选栏、列表、卡片、空状态、弹窗。

评审问题:

  • 组件的状态是否应该提升?
  • Props 是业务语义还是实现语义?
  • key 在排序和删除后是否仍稳定?
  • children 是否合适,还是应该显式 props?
  • 是否存在直接修改对象或数组的更新?

22. 组件知识点索引

  1. JSX 只能返回一个根结构,可用 Fragment。
  2. JSX 中表达式用{},语句需要提前计算。
  3. 组件必须大写,DOM 标签小写。
  4. Props 是只读输入。
  5. State 是渲染快照,不是可变变量。
  6. setState 调度下一次渲染,不是同步赋值。
  7. 对象和数组 State 必须不可变更新。
  8. key 表示稳定身份,不是消除 warning 的装饰。
  9. 事件回调表达用户意图。
  10. 条件渲染要避免0、空字符串等误渲染。
  11. children 是组合机制。
  12. 组件函数不能直接调用。
  13. 组件渲染应保持纯粹。
  14. StrictMode 用于暴露潜在问题。
  15. 组件拆分应围绕职责和变化方向。
  16. 展示组件不应知道数据来源。
  17. 容器组件不应充满视觉细节。
  18. 页面组件负责编排,不负责所有业务规则。
  19. 受控组件适合外部协调。
  20. 非受控组件适合局部交互。
  21. 复合组件适合稳定结构的复杂 UI。
  22. Headless Hook 适合抽离交互逻辑。
  23. Render props 能表达动态渲染,但会增加嵌套。
  24. 高阶组件历史上常见,现在更多用 Hook。
  25. Error Boundary 仍需 class 或框架封装。
  26. Portal 改变 DOM 位置,不改变 React 事件冒泡树。
  27. ref 是逃生口,不是状态管理工具。
  28. 表单字段错误通常可派生。
  29. 组件库 API 要少暴露内部实现。
  30. 组件抽象要有废弃策略。

23. 组件反模式大全

23.1 布尔开关爆炸

<Card isAdmin isCompact isNew isDanger hasFooter />

问题是状态组合不可控。更好拆组件或使用variant

23.2 万能组件

<CommonRenderer type="course" mode="edit" source="remote" />

万能组件通常没人敢改。业务组件应保持明确边界。

23.3 过早组件库化

第一次重复就抽象,容易产生大量半通用组件。更稳妥的策略是先允许局部重复,再在第三次出现稳定模式时抽象。

23.4 UI 和领域规则混写

disabled={user.role !== 'admin' || order.status !== 'draft' || locked}

更好:

disabled={!canEditOrder(user, order, locked)}

23.5 props drilling 过深

如果中间 5 层组件只负责转发 Props,应考虑组合、Context 或重新设计组件边界。

24. 基础阶段面试题

  • React Element 和 Component 有什么区别?
  • 为什么组件不能直接当函数调用?
  • 为什么 State 更新要不可变?
  • key 为什么必须稳定?
  • children 和 render props 的区别是什么?
  • 什么时候抽组件,什么时候保留重复?
  • 受控组件和非受控组件如何选择?
  • StrictMode 下 Effect 执行两次意味着什么?

面试题完整答案总集:React 基础与组件

React Element 和 Component 有什么区别?

React Element 是描述 UI 的普通 JavaScript 对象,例如<Button />编译后的结果;Component 是产生 Element 的函数或类,例如function Button() { return <button /> }。Element 是“结果描述”,Component 是“生成描述的逻辑”。React 调用 Component 得到 Element,再交给 renderer 更新 DOM 或其他目标平台。

为什么组件不能直接当函数调用?

直接调用组件函数会绕过 React 的渲染流程,React 无法正确关联 Hook 调用顺序、维护组件身份、调度更新、执行 StrictMode 检查和记录 DevTools 信息。正确方式是使用<Component />,让 React 接管组件调用。

为什么 State 更新要不可变?

React 依赖引用变化来判断对象或数组是否更新。直接修改旧对象会让新旧引用相同,可能导致组件不更新、memo 判断错误、旧状态快照被污染。不可变更新能保留历史快照,也更利于调试、撤销、测试和性能优化。

key 为什么必须稳定?

key 用来表示列表项身份。稳定 key 能让 React 正确复用组件实例和内部状态。使用数组索引作为 key 时,插入、删除或排序会让身份错位,导致输入框内容、展开状态或动画状态跑到错误行。

children 和 render props 的区别是什么?

children 适合静态组合和结构插槽,例如<Panel><Content /></Panel>。render props 是把函数传给组件,让组件用内部状态调用函数生成 UI。render props 更灵活,但嵌套和理解成本更高;现代 React 中很多 render props 场景可用自定义 Hook 替代。

什么时候抽组件,什么时候保留重复?

当 UI 有稳定业务含义、重复出现多次、变化点清晰、抽出后调用方更容易理解时,应该抽组件。若只是样式相似、每处逻辑差异很大、Props 不断膨胀,就应保留局部重复。好的抽象减少复杂度,坏的抽象只是隐藏复杂度。

受控组件和非受控组件如何选择?

受控组件由外部状态控制,适合表单校验、字段联动、外部重置和组件库控件。非受控组件自己管理状态,适合局部展开、简单开关、无需外部协调的交互。基础组件库常支持受控,业务局部组件优先选择简单方案。

StrictMode 下 Effect 执行两次意味着什么?

开发环境下 StrictMode 会额外执行某些渲染和 Effect 流程,用来暴露副作用不纯或清理不完整的问题。这不是生产行为,也不是 React bug。正确做法是让 Effect 可重复同步并正确清理,例如取消订阅、清除定时器、取消请求或忽略过期结果。

组件的状态是否应该提升?

看状态的使用范围。只有当前组件使用就留在当前组件;兄弟组件共享就提升到最近公共父组件;跨页面或跨业务共享再考虑 Context、状态库或服务端缓存。状态放得越高,影响范围越大,不应为了方便过度提升。

Props 是业务语义还是实现语义?

业务语义描述组件能力和用户意图,如onCompletevariant="danger"。实现语义暴露内部细节,如isReduseNewFooter。组件 API 应优先使用业务语义,这样内部实现变化时调用方不需要跟着修改。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/5 14:49:27

暗黑破坏神2 d2dx宽屏补丁:让经典游戏在现代PC上完美重生

暗黑破坏神2 d2dx宽屏补丁&#xff1a;让经典游戏在现代PC上完美重生 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 还在为…

作者头像 李华
网站建设 2026/5/5 14:49:26

前端小白也能看懂!30天带你入门AI开发,收藏这系列文章!

文章针对前端工程师转行AI开发的焦虑&#xff0c;提供了清晰的转型路径。核心内容包括&#xff1a;AI前端开发的市场机会与薪资优势&#xff0c;三个阶段的学习路径&#xff08;使用AI工具提效、独立开发AI应用、全栈AI开发&#xff09;&#xff0c;以及一个为期30天的入门计划…

作者头像 李华
网站建设 2026/5/5 14:48:27

3步终极解决方案:Visual C++ Redistributable AIO 完全指南

3步终极解决方案&#xff1a;Visual C Redistributable AIO 完全指南 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist VisualCppRedist AIO 是一个革命性的开源工…

作者头像 李华
网站建设 2026/5/5 14:47:28

2025届毕业生推荐的六大降AI率方案实际效果

Ai论文网站排名&#xff08;开题报告、文献综述、降aigc率、降重综合对比&#xff09; TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 维普针对人工智能生成内容推出的专项识别工具是维普AIGC检测服务&#xff0c;其目的在于协助…

作者头像 李华
网站建设 2026/5/5 14:46:51

VideoDownloadHelper技术指南:浏览器视频下载插件的深度解析

VideoDownloadHelper技术指南&#xff1a;浏览器视频下载插件的深度解析 【免费下载链接】VideoDownloadHelper Chrome Extension to Help Download Video for Some Video Sites. 项目地址: https://gitcode.com/gh_mirrors/vi/VideoDownloadHelper 在当今数字内容时代&…

作者头像 李华