【React】总结 22 个React核心概念
受 Code Bootcamp 发布的视频《Every React Concept Explained in 12 Minutes》的启发,下面用中文对 React 的 22 个核心概念进行梳理。每个部分都配有简短示例和关键细节,所引用的事实和规则均来自官方文档,以便读者能够快速掌握核心思想并在开发中遵循最佳实践
受 Code Bootcamp 发布的视频《Every React Concept Explained in 12 Minutes》的启发,下面用中文对 React 的 22 个核心概念进行梳理。每个部分都配有简短示例和关键细节,所引用的事实和规则均来自官方文档,以便读者能够快速掌握核心思想并在开发中遵循最佳实践。
1. 组件(Components)
页面所有的可见部分,如按钮(buttons),输入(inputs),整页
- 是一个JS函数
- 返回“标记”,即JSX
function Button() {
return <button>Click Me!</button>;
}
组件的命名应使用大写开头,它们可以组合成更复杂的界面。官方建议通过组件组合(composition)而非继承来重用代码:React 的强大之处在于组合模型,推荐使用组合而不是继承来复用组件
2. JSX
SX(JavaScript XML)是一种语法扩展,用于在 JavaScript 中编写类似 HTML 的标记。组件返回的不是普通 HTML,而是 JSX——可以嵌入 JavaScript 表达式的模板。比如:
const App = () => (
<div>
<h1>Hello React</h1>
<p>欢迎来到 React 世界!</p>
</div>
);
JSX 最终会被编译为 React.createElement() 调用,从而生成 React 元素树;这些元素是不可变对象,React 在每次更新时比较新旧元素树并仅更新变化部分
3. 花括号(Curly Braces)
在 JSX 中,单层花括号 {} 用于嵌入 JavaScript 表达式,使标记动态化。例如:
const user = 'Alice';
return <h1>Hello {user}!</h1>;
属性也可以使用花括号传入表达式或对象。行内样式需要传入一个 JavaScript 对象,所以会出现双层花括号:外层是 JSX 的表达式,内层是样式对象;样式名采用驼峰式命名
const boxStyle = { backgroundColor: 'black', color: 'pink' };
return <div style={boxStyle}>Hi</div>;
4. 片段(Fragments)
组件必须返回单个根元素,但有时不想额外嵌套 div。Fragments 允许我们将多个子元素组合,而不会在 DOM 中产生新的节点:
return (
<>
<Header />
<Content />
<Footer />
</>
);
可以使用 <></> 的短语法,也可以使用 <React.Fragment>。唯一允许传给片段的属性是 key
5. 属性(属性)
组件通过 props(属性)来传递数据,使组件具有可复用性
- 创造属性并给它传值
- 用属性中的值
<Greeting text={'Yo'}/>
function Greeting(props){ // 这里props 是个object
return <h1>{props.value}<h1/>
}
属性是只读的,组件不能直接修改它们;尝试修改 props 会导致不可预测的行为。为了共享代码,React 建议通过 props 和组合模式而不是创建继承层次
6. Children
props.children 是一个特殊属性,用于在组件标签之间插入子元素。例如:
function Card({ children }) {
return <div className="card">{children}</div>;
}
<Card>
<h2>标题</h2>
<p>内容</p>
</Card>
children 可以是单个元素或元素数组。React 提供 React.Children 工具帮助安全地遍历、计数或转换 children;例如,React.Children.toArray(children) 可以将未知的子节点转为数组
7. 键(Keys)
在列表渲染时,key 用于标识哪些元素发生了变化。React 利用 key 来比较新旧元素并高效更新 DOM。应该使用稳定且独一无二的值作为 key,不要使用数组索引(除非列表永远不会重新排序),这样可以避免更新时出现错误或性能问题
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
8. 渲染(Rendering)
React 元素是描述 UI 的最小单位。调用 root.render(element) 会将元素渲染到 DOM 中,随后 React 仅更新改变的部分。元素是不可变的,每一次渲染都会创建新的元素树,React 通过虚拟 DOM 比较新旧树来决定最小的更新
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
9. 事件处理(Event Handling)
React 的事件采用 camelCase 命名,例如 onClick、onChange。传入的事件处理函数不应带括号,而是函数引用。阻止默认行为需要调用 e.preventDefault(),而不是在 HTML 中返回 false
function Link() {
function handleClick(e) {
e.preventDefault();
console.log('链接被点击');
}
return <a href="#" onClick={handleClick}>点我</a>;
}
10 状态(State)
状态是组件的内部数据,用于描述随时间变化的值。可以在函数组件中使用 useState() 钩子添加状态:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<><p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</>
);
}
useState 返回当前状态和更新函数;更新函数会触发重新渲染。不要直接修改状态变量,而是调用更新函数。Hook 必须在组件顶层调用,不能在循环或条件内调用
11. 受控组件(Controlled Components)
表单元素如 <input>、<textarea> 和 <select> 默认维护内部状态。受控组件由 React 管理其值:将表单值存储在组件状态中,并在用户输入时调用 setState 更新,输入框的 value 属性始终来自状态,从而确保单一数据源
function NameForm() {
const [name, setName] = useState('');
function handleChange(e) {
setName(e.target.value);
}
return <input value={name} onChange={handleChange} />;
}
12. 钩子(Hooks)
Hooks 是 React 16.8 引入的函数,用于在函数组件中使用状态和其他特性。除了 useState,常见的还有:
- useEffect:同步组件与外部系统。当需要与网络、浏览器 API 或其他非 React 控制的系统交互时,在组件内部调用
useEffect(setup, dependencies?)。setup函数可以返回一个清理函数;React 在卸载组件或依赖变化前先执行清理函数,再运行新的setup - useRef:持久化某个可变值而不触发重新渲染。
useRef(initialValue)返回含有current属性的对象,可以读写该属性;更改ref.current不会导致组件重新渲染 - 其他 Hook(如
useContext、useMemo、useCallback)在需要共享数据、记忆化值或回调时使用
使用 Hook 时必须遵守钩子规则:只能在 React 函数组件顶层调用,不能在循环、条件或嵌套函数中调用
官方文档:https://react.dev/reference/react/hooks
13 纯度(Purity)
React 假设组件是纯函数:对于相同的输入(props、状态和上下文),它们必须始终返回相同的 JSX。纯组件不修改在渲染前存在的变量或对象,不产生副作用。这样可以避免难以理解的 bug:如果一个组件修改了外部变量,每次渲染都可能产生不同结果。
若组件需要修改共享数据,应通过 props 传入该数据并在父组件中更新,而不是在组件内部写入外部变量。React 的 Strict Mode 会在开发环境下执行组件两次以发现潜在的非纯代码
举个“不纯”的例子:
// 假设这是一个 React 函数组件
let counter = 0; // 全局变量
function ImpureGreeting({ name }) {
// 渲染时会修改外部变量,也用了 new Date() 这种每次渲染会变的东西 ⇒ 所以不纯
counter += 1;
const time = new Date().toLocaleTimeString();
return (
<div>
<p>Hello, {name}! You are visitor #{counter}.</p>
<p>Rendered at {time}</p>
</div>
);
}
官方文档:https://react.dev/learn/keeping-components-pure
14 严格模式(Strict Mode)
<React.StrictMode> 是一个仅在开发环境下启用的工具,用于突出潜在问题。它不会渲染任何可见 UI,但会为其子组件激活额外的检查。例如,它会执行组件的渲染函数两次,从而帮助识别不安全的生命周期方法或副作用。它还会提醒你使用已弃用的 API
const root = createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
官方文档:https://react.dev/reference/react/StrictMode
15 副作用(Effects)
通过 useEffect 可以在组件渲染完成后执行副作用,如数据获取、订阅事件或操作 DOM。这让开发者可以将“渲染”与“副作用”分离,保证渲染过程是纯粹的。调用 useEffect 时传入一个 setup 函数和可选的依赖数组:
useEffect(() => {
document.title = `计数:${count}`;
}, [count]);
当依赖 count 发生变化时,React 会先调用上一个 setup 返回的清理函数,再执行新的 setup
有关 useEffect 依赖项,可以参阅我的另外一篇博客:
https://blog.csdn.net/XPRNB/article/details/149209664?spm=1001.2014.3001.5501
或官方文档:
https://zh-hans.react.dev/reference/react/useEffect
16 引用(Refs)
当需要直接访问 DOM 节点或在渲染之间保存可变值时,使用 useRef。Refs 不参与 React 的数据流,不会触发重新渲染:
import { useRef } from 'react';
function FocusableInput() {
const inputRef = useRef(null);
function handleClick() {
// 直接聚焦 DOM 节点
inputRef.current.focus();
}
return (
<><input ref={inputRef} />
<button onClick={handleClick}>聚焦输入框</button>
</>
);
}
useRef 返回的对象具有 current 属性,可以读写该属性而不会导致重新渲染
17 上下文(Context)
Context 允许组件在不显式传递 props 的情况下分享数据。例如应用主题、当前用户等全局数据。创建 Context 并使用 <Context.Provider> 提供值,然后在后代组件中通过 useContext 或 contextType 获取值
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
const theme = React.useContext(ThemeContext);
return <button className={theme}>主题按钮</button>;
}
18 门户(Portals)
Portal 允许将子节点渲染到 DOM 树外的指定容器中,常用于模态框、弹窗等。使用 ReactDOM.createPortal(child, container) 创建 portal。即使在不同的 DOM 层级,事件冒泡和上下文仍按常规工作。示例:
function Modal({ children }) {
return ReactDOM.createPortal(
<div className="modal">{children}</div>,
document.getElementById('modal-root')
);
}
19 悬停与 Suspense
<Suspense> 组件用于在加载异步资源(如懒加载组件、数据请求)期间显示后备 UI。当子组件尚未准备好时,fallback 属性中的内容会渲染出来
import React, { Suspense, lazy } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<div>加载中…</div>}>
<LazyComponent />
</Suspense>
);
}
20 错误边界(Error Boundaries)
错误边界用于捕获其子树在渲染、生命周期方法或构造函数中的 JavaScript 错误,并用降级 UI 代替崩溃的组件树。错误边界必须是类组件,实现 static getDerivedStateFromError() 或 componentDidCatch():
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error(error, info);
}
render() {
if (this.state.hasError) {
return <h1>出现错误。</h1>;
}
return this.props.children;
}
}
错误边界不会捕获事件处理器中的错误或异步代码错误;这些错误应通过 try/catch 手动捕获。
官方文档:https://legacy.reactjs.org/docs/error-boundaries.html
21 组合 vs 继承(Composition vs Inheritance)
React 鼓励组件的组合而非继承。对于需要灵活插槽的组件,可以利用 children 或通过自定义 prop 将元素作为参数传入。例如,将两侧内容分离的 SplitPane:
function SplitPane({ left, right }) {
return (
<div className="split-pane">
<div className="left">{left}</div>
<div className="right">{right}</div>
</div>
);
}
function App() {
return (
<SplitPane left={<Contacts />} right={<Chat />} />
);
}
Facebook 在数千个组件中没有发现需要继承的用例;如果需要复用非 UI 功能,建议抽取到独立模块并通过导入使用
22. Thinking in React
React 官方提供了一套构建界面的思想流程,尤其在开发较复杂的应用时非常有用
- 分解 UI:根据设计稿或 JSON 数据,将页面划分为组件层级。可以参考单一职责原则,将复杂组件拆分成更小的组件
- 构建静态版本:先不处理交互,只根据数据模型渲染出 UI;使用组件和 props 组合界面,不使用 state
- 找出最小的状态集合:确定哪些数据会变化,将不可变数据视为 props,可从现有数据计算出的值不需要放入 state
- 确定状态的归属:找出依赖该状态的所有组件,找到它们的最近公共祖先,将状态放在那里,并通过 props 传递。可使用
useState在祖先组件中管理状态 - 添加逆向数据流:子组件需要更改父组件的状态时,将更新函数作为 props 传入,在子组件的事件处理器中调用,从而更新父组件的状。
遵循这一流程可以帮助开发者在设计 React 应用时合理组织组件和状态,使数据流更清晰。
参考
https://www.youtube.com/watch?v=wIyHSOugGGw&t=122s&ab_channel=CodeBootcamp
更多推荐



所有评论(0)