Code Bootcamp 发布的视频《Every React Concept Explained in 12 Minutes》的启发,下面用中文对 React 的 22 个核心概念进行梳理。每个部分都配有简短示例和关键细节,所引用的事实和规则均来自官方文档,以便读者能够快速掌握核心思想并在开发中遵循最佳实践。

1. 组件(Components)

页面所有的可见部分,如按钮(buttons),输入(inputs),整页

  1. 是一个JS函数
  2. 返回“标记”,即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(属性)来传递数据,使组件具有可复用性

  1. 创造属性并给它传值
  2. 用属性中的值
<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 命名,例如 onClickonChange。传入的事件处理函数不应带括号,而是函数引用。阻止默认行为需要调用 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(如 useContextuseMemouseCallback)在需要共享数据、记忆化值或回调时使用

使用 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> 提供值,然后在后代组件中通过 useContextcontextType 获取值

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 官方提供了一套构建界面的思想流程,尤其在开发较复杂的应用时非常有用

  1. 分解 UI:根据设计稿或 JSON 数据,将页面划分为组件层级。可以参考单一职责原则,将复杂组件拆分成更小的组件
  2. 构建静态版本:先不处理交互,只根据数据模型渲染出 UI;使用组件和 props 组合界面,不使用 state
  3. 找出最小的状态集合:确定哪些数据会变化,将不可变数据视为 props,可从现有数据计算出的值不需要放入 state
  4. 确定状态的归属:找出依赖该状态的所有组件,找到它们的最近公共祖先,将状态放在那里,并通过 props 传递。可使用 useState 在祖先组件中管理状态
  5. 添加逆向数据流:子组件需要更改父组件的状态时,将更新函数作为 props 传入,在子组件的事件处理器中调用,从而更新父组件的状。

遵循这一流程可以帮助开发者在设计 React 应用时合理组织组件和状态,使数据流更清晰。

参考

https://www.youtube.com/watch?v=wIyHSOugGGw&t=122s&ab_channel=CodeBootcamp

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐