React基础知识(机试复习用)

React 介绍与核心概念

React 介绍及特性

React 是由 Facebook(Meta)开发并开源的 JavaScript 库,用于构建用户界面(UI)。它的核心特性包括:

  • 声明式编程:开发者只需描述 UI 应该是什么样子,React 负责高效地更新和渲染。当数据变化时,React 自动更新对应的组件。
  • 组件化:UI 被拆分成独立、可复用的组件,每个组件管理自己的状态和渲染逻辑,极大地提高了代码的可维护性和复用性。
  • 一次学习,随处编写:React 可以渲染到多种平台(Web、移动端 React Native、VR、服务端等)。

与传统 MVC 的关系、MVVM 模式

MVC(Model-View-Controller):传统 MVC 中,Model 管理数据,View 负责展示,Controller 处理用户输入并更新 Model 和 View。

React 在传统 MVC 中的定位是 View 层——它专注于 UI 的渲染,不内置 Model 和 Controller。但在实际项目中,React 配合状态管理库(如 Redux)可以实现完整的 MVC 模式。

MVVM(Model-View-ViewModel):MVVM 中 ViewModel 作为 View 和 Model 之间的桥梁,View 通过数据绑定自动同步数据。React 并不是严格的 MVVM 框架,但配合 Hooks 或状态管理可以实现类似 MVVM 的数据流:组件(View)通过 Hooks(ViewModel)操作数据(Model),数据变化自动触发 UI 重新渲染。

React 的三大核心概念

  1. 组件(Component):React 应用由组件组成,组件是 UI 的基本构建块。可以是函数组件或类组件。
  2. 状态(State):组件内部管理的数据,状态变化会触发组件重新渲染。
  3. 属性(Props):父组件向子组件传递数据的只读媒介,是组件间单向数据流的基础。

虚拟 DOM 的好处

虚拟 DOM(Virtual DOM)是 React 的核心机制之一,它是真实 DOM 的 JavaScript 对象表示(轻量级副本)。

  • 性能优化:直接操作真实 DOM 代价高昂。React 先在虚拟 DOM 上进行变更,再通过 Diff 算法计算最小变更集,最后批量更新真实 DOM。
  • 跨平台能力:虚拟 DOM 是平台无关的 JavaScript 对象,可以渲染到不同平台(DOM、Native、Canvas 等)。
  • 开发体验:开发者无需手动操作 DOM,只需描述 UI 状态,React 自动处理更新。
// 虚拟 DOM 的简单示例
const virtualDOM = {
  type: 'div',
  props: { className: 'container' },
  children: [
    { type: 'h1', props: {}, children: ['Hello World'] }
  ]
};

React Diff 算法

React 的 Diff 算法用于比较新旧虚拟 DOM 树,找出最小差异并高效更新真实 DOM。其核心策略基于三个假设:

  1. Tree Diff:只对同层节点进行比较,复杂度从 O(n³) 降到 O(n)。
  2. Component Diff:相同类型的组件生成相似的树结构,不同类型的组件直接替换整棵子树。
  3. Element Diff:同一层级的同组子节点,通过唯一 key 来识别,实现插入、删除、移动的最小化。
// key 帮助 React 识别列表中的每个元素
{list.map(item => (
  <li key={item.id}>{item.name}</li>
))}

客观题练习

📝 以下关于虚拟 DOM 的描述,哪项是错误的?

  • A. 虚拟 DOM 是真实 DOM 的 JavaScript 对象表示
  • B. 虚拟 DOM 的 Diff 算法复杂度是 O(n³)
  • C. 虚拟 DOM 可以实现跨平台渲染
  • D. React 通过批量更新减少真实 DOM 操作

答案:B(Diff 算法通过同层比较策略将复杂度从 O(n³) 降到了 O(n))

📝 React 中,用于唯一标识列表元素的属性是?

  • A. id
  • B. ref
  • C. key
  • D. index

答案:C(key 帮助 React 识别哪些元素发生了变化、添加或删除)

📝 关于 React 在 MVC 中的定位,以下说法正确的是?

  • A. React 是一个完整的 MVC 框架
  • B. React 主要关注 View 层
  • C. React 是 MVVM 框架
  • D. React 替代了后端 MVC

答案:B(React 专注于 UI 渲染,定位为 View 层)

React 脚手架及使用

create-react-app(CRA)

create-react-app 是 React 官方提供的脚手架工具,用于快速创建单页 React 应用,内置了 webpack、Babel、ESLint 等配置。

# 创建项目
npx create-react-app my-app

# 使用 TypeScript 模板
npx create-react-app my-app --template typescript

# 进入项目并启动
cd my-app
npm start   # 启动开发服务器,http://localhost:3000

项目结构:

my-app/
├── node_modules/      # 依赖包
├── public/
   ├── index.html     # 入口 HTML 模板
   └── favicon.ico
├── src/
   ├── App.js         # 根组件
   ├── App.css
   ├── index.js       # 入口 JS 文件
   ├── index.css
   └── reportWebVitals.js
├── package.json       # 项目配置与依赖
└── README.md

客观题练习

📝 使用 create-react-app 创建 TypeScript 项目,正确的命令是?

  • A. npx create-react-app my-app –ts
  • B. npx create-react-app my-app –template typescript
  • C. npx create-react-app my-app –typescript
  • D. npx create-react-app my-app -t ts

答案:B

📝 CRA 创建的项目中,入口 JS 文件是?

  • A. src/App.js
  • B. public/index.html
  • C. src/index.js
  • D. package.json

答案:C(src/index.js 是 webpack 打包入口,通过 ReactDOM.render 渲染 App 组件到 public/index.html 的根节点)

JSX / TSX 的理解与使用

JSX(JavaScript XML)

JSX 是 JavaScript 的语法扩展,允许在 JS 代码中编写类似 HTML 的标记。它是 React.createElement() 的语法糖。

核心规则:

  1. 必须有一个根元素(或使用 <>...</> Fragment)
  2. 使用 {} 嵌入 JavaScript 表达式
  3. 使用 className 代替 classhtmlFor 代替 for
  4. 样式对象使用驼峰命名:backgroundColor 而非 background-color
  5. 自闭合标签必须以 /> 结尾
import React from 'react';

function App() {
  const name = 'React';
  const style = { color: 'blue', fontSize: '20px' };

  return (
    <div className="app">
      <h1 style={style}>Hello, {name}!</h1>
      {2 + 2}  {/* 表达式求值 */}
      <input type="text" />  {/* 自闭合 */}
    </div>
  );
}

TSX(TypeScript JSX)

TSX 是 TypeScript 中的 JSX,增加了类型检查,提高代码的健壮性和可维护性。

import React, { FC } from 'react';

interface AppProps {
  title: string;
  count?: number;  // 可选属性
}

const App: FC<AppProps> = ({ title, count = 0 }) => {
  return (
    <div>
      <h1>{title}</h1>
      <p>Count: {count}</p>
    </div>
  );
};

export default App;

客观题练习

📝 JSX 中设置 CSS 类名,正确的属性是?

  • A. class
  • B. className
  • C. cssClass
  • D. styleClass

答案:B(使用 className 替代 HTML 的 class 属性)

📝 JSX 中嵌入 JavaScript 表达式使用什么符号?

  • A. {{ }}
  • B. { }
  • C. (( ))
  • D. [[ ]]

答案:B(花括号 {} 中可以嵌入任意有效的 JavaScript 表达式)

📝 以下哪个是 JSX 中正确的注释写法?

  • A. // 注释
  • B. {/* 注释 */}
  • C.
  • D. # 注释

答案:B

函数组件

函数组件是使用 JavaScript 函数定义的 React 组件,是 React 16.8+ 引入 Hooks 后推荐的组件编写方式。

组件概念

函数组件接收 props 作为参数,返回 JSX 描述 UI。它是无副作用的纯函数——相同输入始终返回相同输出。

// 基础函数组件
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 箭头函数写法
const Welcome = ({ name }) => <h1>Hello, {name}</h1>;

事件绑定

React 中事件使用驼峰命名(如 onClick、onChange),通过 {} 绑定处理函数。event 是 React 的合成事件(SyntheticEvent)。

注意:应传递函数引用而非调用结果(onClick={handleClick} 而非 onClick={handleClick()})。

function Button() {
  const handleClick = (event) => {
    console.log('按钮被点击', event.target);
  };

  const handleChange = (e) => {
    console.log('输入值:', e.target.value);
  };

  return (
    <div>
      <button onClick={handleClick}>点击我</button>
      <input onChange={handleChange} placeholder="输入内容" />
    </div>
  );
}

组件状态(useState)

使用 useState Hook 管理函数组件内部的状态。setState 支持直接传值和函数式更新两种方式。

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(prev => prev - 1)}>-1</button>
    </div>
  );
}

生命周期(useEffect)

函数组件没有传统的生命周期方法,而是用 useEffect Hook 处理副作用,模拟 componentDidMount、componentDidUpdate、componentWillUnmount。

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // componentDidMount + componentDidUpdate(userId 变化时)
    fetchUser(userId).then(data => setUser(data));

    // componentWillUnmount(清理函数)
    return () => {
      console.log('组件卸载或 userId 变化前清理');
    };
  }, [userId]);  // 依赖数组:仅 userId 变化时重新执行

  return <div>{user?.name}</div>;
}

客观题练习

📝 关于函数组件的描述,错误的是?

  • A. 函数组件可以使用 Hooks 管理状态
  • B. 函数组件必须使用 return 返回 JSX
  • C. 函数组件不能使用生命周期
  • D. 函数组件通过 props 接收外部数据

答案:C(useEffect Hook 可以模拟所有类组件生命周期)

📝 useEffect 的第二个参数为空数组 [] 时,effect 何时执行?

  • A. 每次渲染后都执行
  • B. 仅在组件挂载时执行一次
  • C. 仅在组件卸载时执行
  • D. 从不执行

答案:B(空依赖数组表示不依赖任何值,只在首次渲染后执行一次)

📝 函数组件中,以下哪个是正确的事件绑定方式?

  • A. onClick={handleClick()}
  • B. onClick=”handleClick()”
  • C. onClick={handleClick}
  • D. onclick={handleClick}

答案:C(传递函数引用,事件名使用驼峰 onClick)

类组件

类组件是使用 ES6 class 定义的 React 组件,继承自 React.Component。在 Hooks 出现前是管理状态的主要方式。

组件概念与结构

import React, { Component } from 'react';

class Welcome extends Component {
  // 类组件必须实现 render 方法
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

事件绑定与 this

类组件中事件处理需要关注 this 的绑定问题。两种方式:构造函数中 bind 或使用箭头函数。

class Toggle extends Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
    // 方式一:在构造函数中绑定 this
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState({ isOn: !this.state.isOn });
  }

  // 方式二:箭头函数(自动绑定 this)
  handleClickArrow = () => {
    this.setState(prev => ({ isOn: !prev.isOn }));
  };

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

State 与 setState

setState 是异步的,React 会批量处理状态更新以优化性能。依赖前一个状态时,应使用函数式更新。

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    // 函数式更新:获取最新状态
    this.setState(prev => ({ count: prev.count + 1 }));
  };

  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.increment}>+1</button>
      </div>
    );
  }
}

生命周期

类组件有三个主要生命周期阶段:

挂载阶段(Mounting)

  • constructor() —— 初始化 state 和绑定方法
  • render() —— 渲染 UI
  • componentDidMount() —— 组件挂载后,适合发起网络请求、订阅

更新阶段(Updating)

  • render() —— 重新渲染
  • componentDidUpdate(prevProps, prevState) —— 更新后,可比较前后值

卸载阶段(Unmounting)

  • componentWillUnmount() —— 组件卸载前,清理定时器、取消订阅
class LifecycleDemo extends Component {
  componentDidMount() {
    console.log('组件已挂载');
    this.timer = setInterval(() => console.log('tick'), 1000);
  }

  componentDidUpdate(prevProps) {
    if (this.props.id !== prevProps.id) {
      console.log('id 变化,重新请求数据');
    }
  }

  componentWillUnmount() {
    console.log('组件即将卸载');
    clearInterval(this.timer);
  }

  render() {
    return <div>Lifecycle Demo</div>;
  }
}

客观题练习

📝 类组件中,以下哪个生命周期方法在组件挂载后只执行一次?

  • A. render
  • B. componentDidUpdate
  • C. componentDidMount
  • D. componentWillUnmount

答案:C

📝 关于 setState 的描述,正确的是?

  • A. setState 是同步执行的
  • B. setState 会立即更新 this.state
  • C. setState 可能是异步的,React 会批量处理
  • D. setState 只能在类组件中使用

答案:C(setState 是异步的,React 批量处理以优化性能)

📝 类组件中解决 this 绑定的方式不包括?

  • A. 构造函数中使用 bind
  • B. 使用箭头函数定义方法
  • C. 在 render 中使用 bind
  • D. 使用 useEffect 绑定

答案:D(useEffect 是函数组件 Hook,类组件中不存在)

Props 与 State

Props 的使用

Props(Properties)是父组件向子组件传递数据的只读媒介,遵循单向数据流——数据从父到子。子组件不能修改 props。

// 父组件
function Parent() {
  const user = { name: 'Alice', age: 25 };
  return <Child name={user.name} age={user.age} />;
}

// 子组件
function Child({ name, age }) {
  return <p>{name} is {age} years old</p>;
}

// 带默认值的 Props
Child.defaultProps = {
  age: 18
};

Props 类型校验(PropTypes)

import PropTypes from 'prop-types';

function User({ name, age, hobbies }) {
  return /* ... */;
}

User.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  hobbies: PropTypes.arrayOf(PropTypes.string)
};

State 的使用与 setState 异步性

State 是组件内部管理的可变数据。关键点:setState 是异步的,依赖当前值时必须用函数式更新。

// 类组件
class MyComponent extends Component {
  state = { count: 0 };
  increment = () => this.setState(prev => ({ count: prev.count + 1 }));
}

// 函数组件
function MyComponent() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(prev => prev + 1);
}

// 错误用法(可能得到过期值)
this.setState({ count: this.state.count + 1 });
// 正确用法(函数形式获取最新 state)
this.setState(prev => ({ count: prev.count + 1 }));
setCount(prev => prev + 1);

客观题练习

📝 关于 Props 的说法,哪项是正确的?

  • A. 子组件可以直接修改父组件传入的 props
  • B. Props 只能在类组件中使用
  • C. Props 是只读的,用于父组件向子组件传递数据
  • D. Props 和 State 完全一样

答案:C(Props 是只读的,遵循单向数据流)

📝 连续调用两次 setState,以下写法正确的是?

  • A. this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1});
  • B. this.setState(prev => ({count: prev.count + 1})); this.setState(prev => ({count: prev.count + 1}));
  • C. this.state.count += 2;
  • D. setState 不能连续调用

答案:B(函数式更新能获取最新 state,两次都会基于最新值 +1)

📝 PropTypes 的作用是?

  • A. 提高组件渲染性能
  • B. 对组件接收的 props 进行类型检查
  • C. 自动生成文档
  • D. 替换 TypeScript

答案:B(PropTypes 在开发环境下对 props 进行运行时类型检查并输出警告)

React Refs 与事件处理

Refs

Refs 提供了一种直接访问 DOM 节点或 React 元素实例的方式。使用场景:管理焦点、文本选择、触发动画、集成第三方 DOM 库。尽量避免过度使用 Refs。

import { useRef, useEffect } from 'react';

function TextInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current?.focus();  // 自动聚焦
  }, []);

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={() => inputRef.current?.focus()}>
        聚焦输入框
      </button>
    </div>
  );
}

类组件中使用 createRef:

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }

  render() {
    return <input ref={this.inputRef} />;
  }
}

React 事件处理

React 使用合成事件(SyntheticEvent),是对浏览器原生事件的跨浏览器包装。事件委托到根节点,通过事件冒泡处理。

function Form() {
  const handleSubmit = (e) => {
    e.preventDefault();  // 阻止默认提交
    console.log('表单提交');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={(e) => console.log(e.target.value)} />
      <button type="submit">提交</button>
    </form>
  );
}

客观题练习

📝 React 中使用 Refs 的常见场景不包括?

  • A. 管理焦点
  • B. 触发动画
  • C. 替代 state 管理数据
  • D. 集成第三方 DOM 库

答案:C(Refs 不应替代 state/props 来管理数据)

📝 React 的合成事件(SyntheticEvent)描述正确的是?

  • A. 合成事件与原生事件完全无关
  • B. 合成事件直接绑定在每个 DOM 元素上
  • C. 合成事件是原生事件的跨浏览器包装,通过事件委托实现
  • D. 合成事件不能调用 preventDefault

答案:C(合成事件提供与原生事件一致的 API,通过事件委托优化性能)

React 组件通信

父子组件通信

父 → 子(Props):父组件通过 Props 向子组件传递数据。子 → 父(回调函数):父组件将回调函数作为 Props 传给子组件,子组件调用回调传回数据。

// 父组件
function Parent() {
  const [message, setMessage] = useState('');

  const handleChildData = (data) => {
    setMessage(data);
  };

  return (
    <div>
      <Child onSend={handleChildData} />
      <p>来自子组件: {message}</p>
    </div>
  );
}

// 子组件
function Child({ onSend }) {
  return (
    <button onClick={() => onSend('Hello from Child')}>
      发送消息给父组件
    </button>
  );
}

跨组件通信(Context)

Context 提供了一种在组件树中无需逐层传递 Props就能共享数据的方式。适用于全局数据(主题、语言、用户认证等)。

import { createContext, useContext, useState } from 'react';

// 1. 创建 Context
const ThemeContext = createContext('light');

// 2. Provider 提供数据
function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 3. 中间组件无需传递 props
function Toolbar() {
  return <ThemedButton />;
}

// 4. useContext 消费数据
function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button
      style={{ background: theme === 'dark' ? '#333' : '#fff' }}
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
    >
      当前主题: {theme}
    </button>
  );
}

客观题练习

📝 React 中子组件向父组件传递数据的正确方式是?

  • A. 子组件直接修改父组件的 state
  • B. 通过回调函数(父组件将函数作为 props 传给子组件)
  • C. 使用全局变量
  • D. 子组件不能向父组件传递数据

答案:B

📝 以下关于 Context 的描述,错误的是?

  • A. Context 可以避免 props 逐层传递
  • B. Context 适用于主题、语言等全局数据
  • C. Context.Provider 可以嵌套使用
  • D. Context 只能传递字符串类型的数据

答案:D(Context 可以传递任意类型的值,包括对象、函数等)

📝 在函数组件中消费 Context 值,应使用哪个 Hook?

  • A. useState
  • B. useEffect
  • C. useContext
  • D. useReducer

答案:C

React Router

SPA 的理解

SPA(Single Page Application,单页应用)是一种 Web 应用架构,整个应用只有一个 HTML 页面。页面切换通过 JavaScript 动态替换内容,无需完整刷新。

  • 优点:用户体验流畅、页面切换快、前后端分离、减少服务器压力
  • 缺点:首屏加载较慢、SEO 不友好(需 SSR 解决)、需要前端路由管理

路由的两种模式

  • Hash 模式:URL 中带 #,通过 hashchange 事件监听。兼容性好,不需要服务器配置。
  • History 模式:URL 干净,通过 HTML5 History API(pushState、popState)实现。需要服务器配置 fallback 到 index.html。

react-router-dom 的使用

import { BrowserRouter, Routes, Route, Link,
  useParams, useNavigate } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
        <Link to="/user/123">用户123</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/user/:id" element={<UserPage />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

// 获取路由参数
function UserPage() {
  const { id } = useParams();
  return <p>用户 ID: {id}</p>;
}

// 编程式导航
function Home() {
  const navigate = useNavigate();
  return (
    <div>
      <button onClick={() => navigate('/about')}>
        跳转到关于页
      </button>
    </div>
  );
}

客观题练习

📝 SPA 的优点不包括以下哪项?

  • A. 用户体验流畅
  • B. 首屏加载快
  • C. 前后端分离
  • D. 页面切换快

答案:B(SPA 首屏加载通常较慢,需要加载整个应用框架)

📝 React Router 中获取动态路由参数 /user/:id 中的 id,应使用?

  • A. useNavigate
  • B. useLocation
  • C. useParams
  • D. useRoute

答案:C

📝 History 模式与 Hash 模式的主要区别是?

  • A. Hash 模式性能更好
  • B. History 模式 URL 中没有 # 号,需要服务器配置
  • C. Hash 模式不支持动态路由
  • D. History 模式只能用于 React

答案:B(History 模式 URL 更干净,但刷新时会向服务器请求该路径,需配置 fallback)

React Hooks

Hooks 的理解及出现原因

Hooks 是 React 16.8 引入的特性,允许在函数组件中使用状态和其他 React 特性。

  1. 组件间复用状态逻辑困难:HOC 和 render props 会导致”嵌套地狱”。Hooks 从组件中提取逻辑,便于复用。
  2. 复杂组件难以理解:类组件中生命周期方法包含不相关的逻辑,Hooks 将逻辑按功能拆分。
  3. this 的困扰:类组件需要绑定 this。函数组件无 this 问题。

Hooks 使用规则:

  • 只能在函数组件的顶层调用,不能在循环、条件或嵌套函数中调用
  • 只能在 React 函数组件或自定义 Hook 中调用

useState — 管理状态

const [state, setState] = useState(initialValue);

// 基本使用
const [name, setName] = useState('');

// 函数式更新(依赖前一个状态时推荐)
const [count, setCount] = useState(0);
setCount(prev => prev + 1);

// 惰性初始化
const [data, setData] = useState(() => expensiveComputation());

useEffect — 处理副作用

useEffect(() => {
  // 副作用逻辑
  return () => {
    // 清理函数(可选)
  };
}, [依赖数组]);

// 数据请求(仅在挂载时执行)
useEffect(() => {
  fetch('/api/user').then(res => res.json()).then(setUser);
}, []); // 空数组

// 订阅与清理
useEffect(() => {
  const subscription = api.subscribe(id);
  return () => subscription.unsubscribe();
}, [id]);

useCallback — 缓存函数引用

避免子组件不必要的重新渲染,返回缓存的函数。配合 React.memo 使用。

const memoizedCallback = useCallback(
  () => doSomething(a, b),
  [a, b],
);

// 典型场景:配合 React.memo 避免子组件重渲染
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []); // 永远不变的函数引用

  return <Child onClick={handleClick} />;
}

const Child = React.memo(({ onClick }) => {
  console.log('Child 渲染');
  return <button onClick={onClick}>Click</button>;
});

useMemo — 缓存计算结果

仅在依赖变化时重新计算,返回缓存的值。适用于昂贵的计算。

const memoizedValue = useMemo(
  () => expensiveComputation(deps),
  [deps]
);

// 示例:过滤列表
const [users, setUsers] = useState([]);
const [query, setQuery] = useState('');

const filteredUsers = useMemo(
  () => users.filter(u => u.name.includes(query)),
  [users, query]
);

useRef — 可变引用

常用于访问 DOM 和保存不触发重新渲染的可变值。

// 访问 DOM
function TextInput() {
  const inputRef = useRef(null);
  useEffect(() => inputRef.current?.focus(), []);
  return <input ref={inputRef} />;
}

// 保存可变值(不触发重渲染)
function Timer() {
  const intervalRef = useRef(null);
  const [count, setCount] = useState(0);

  const start = () => {
    intervalRef.current = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
  };

  const stop = () => clearInterval(intervalRef.current);
  return /* ... */;
}

useContext — 消费 Context

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>按钮</button>;
}

useReducer — 复杂状态管理

useState 的替代方案,适合管理复杂的、下一个状态依赖前一个状态的状态逻辑。

const [state, dispatch] = useReducer(reducer, initialState);

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: action.payload };
    default:
      throw new Error('Unknown action type');
  }
}

// 使用
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
dispatch({ type: 'increment' });
dispatch({ type: 'reset', payload: 10 });

客观题练习

📝 以下关于 Hooks 使用规则的说法,正确的是?

  • A. 可以在循环中调用 useState
  • B. Hooks 只能在函数组件顶层调用
  • C. 可以在条件语句中使用 useEffect
  • D. 普通 JavaScript 函数中可以调用 Hooks

答案:B

📝 useCallback 和 useMemo 的主要区别是?

  • A. useCallback 缓存函数,useMemo 缓存计算结果
  • B. useCallback 缓存计算结果,useMemo 缓存函数
  • C. 二者完全相同
  • D. useCallback 只能在类组件中使用

答案:A

📝 useRef 创建的 ref 对象,修改其 current 属性会触发组件重新渲染吗?

  • A. 会
  • B. 不会
  • C. 取决于值是否改变
  • D. 只在类组件中会

答案:B(修改 ref.current 不会触发组件重新渲染)

📝 useReducer 相比 useState 更适合什么场景?

  • A. 简单的基础类型状态
  • B. 多个子值、状态逻辑复杂的场景
  • C. 所有场景都一样
  • D. 只能在 Redux 中使用

答案:B

Redux 与 Recoil

Redux

Redux 是 JavaScript 状态管理容器,提供可预测的状态管理。适用于大型应用中跨组件共享的复杂状态。

三大原则:

  1. 单一数据源:整个应用的 State 存储在唯一的 Store 中。
  2. State 只读:唯一改变 State 的方式是发起 Action。
  3. 纯函数执行修改:Reducer 是纯函数,接收旧 State 和 Action,返回新 State。

核心概念:Store(存储状态)、Action(描述事件)、Reducer(更新规则)、Dispatch(触发更新)

import { createStore } from 'redux';

// 1. Action Types
const INCREMENT = 'INCREMENT';

// 2. Action Creator
const increment = () => ({ type: INCREMENT });

// 3. Reducer
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    default:
      return state;
  }
};

// 4. Store
const store = createStore(counterReducer);
store.subscribe(() => console.log(store.getState()));
store.dispatch(increment());

React-Redux(Redux Toolkit):

import { Provider, useSelector, useDispatch } from 'react-redux';
import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: state => { state.value += 1; },
    decrement: state => { state.value -= 1; },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

const store = configureStore({
  reducer: { counter: counterSlice.reducer }
});

function Counter() {
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => dispatch(counterSlice.actions.increment())}>
        +1
      </button>
    </div>
  );
}

// 根组件
function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

Recoil

Recoil 是 Facebook 推出的轻量级状态管理库。核心概念:Atom(状态单元)和 Selector(派生状态)。

import { RecoilRoot, atom, selector,
  useRecoilState, useRecoilValue } from 'recoil';

// 1. 定义 Atom
const textState = atom({
  key: 'textState',  // 全局唯一 key
  default: '',
});

// 2. 定义 Selector(派生状态)
const charCountState = selector({
  key: 'charCountState',
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});

// 3. 使用
function TextInput() {
  const [text, setText] = useRecoilState(textState);
  return (
    <input value={text}
      onChange={(e) => setText(e.target.value)} />
  );
}

function CharCount() {
  const count = useRecoilValue(charCountState);
  return <p>字符数: {count}</p>;
}

function App() {
  return (
    <RecoilRoot>
      <TextInput />
      <CharCount />
    </RecoilRoot>
  );
}

客观题练习

📝 Redux 三大原则不包括?

  • A. 单一数据源
  • B. State 只读
  • C. 使用纯函数执行修改
  • D. 状态分散管理

答案:D(Redux 强调单一数据源,而非分散管理)

📝 Redux Toolkit 中 createSlice 的作用是?

  • A. 创建 React 组件
  • B. 同时生成 action creators 和 reducer
  • C. 创建路由配置
  • D. 创建 CSS 样式

答案:B(createSlice 简化了 Redux 的使用,自动生成 action 和 reducer)

📝 Recoil 中,Atom 和 Selector 的关系是?

  • A. Selector 是 Atom 的另一种写法
  • B. Selector 是基于 Atom 或其他 Selector 的派生状态
  • C. Atom 和 Selector 互不相关
  • D. Selector 替代 Atom

答案:B(Selector 类似 Vue 的 computed,是派生状态)

总结

知识点核心要点
React 核心概念组件、State、Props;虚拟 DOM;Diff 算法(同层比较、key)
函数组件useState 状态、useEffect 生命周期、事件驼峰绑定、无 this
类组件constructor/render/componentDidMount/componentWillUnmount;this 绑定问题
组件通信父→子 Props;子→父 回调函数;跨组件 Context(Provider + useContext)
React RouterSPA 概念;Hash/History 模式;Routes/Route/Link/useParams/useNavigate
HooksuseState/useEffect/useCallback/useMemo/useRef/useContext/useReducer
状态管理Redux:Store/Action/Reducer/Dispatch;Recoil:Atom/Selector

主观题实战:实现一个任务管理组件

题目要求

请使用 React + Hooks 实现一个任务管理(Todo List)组件,满足以下功能:

  1. 展示任务列表,每条任务包含:标题、完成状态、创建时间
  2. 可以添加新任务(输入框 + 添加按钮,回车也可添加)
  3. 可以切换任务的完成状态(勾选复选框或点击文字)
  4. 可以删除单个任务
  5. 底部显示统计:总计 / 已完成 / 未完成 数量
  6. 支持按完成状态筛选:全部 / 已完成 / 未完成
  7. 空列表时显示友好的空状态提示

参考答案

import React, { useState, useMemo, useCallback } from 'react';

// ========== 类型定义 ==========
// 在实际项目中这些可以用 TypeScript interface 定义
// interface Todo {
//   id: number;
//   title: string;
//   completed: boolean;
//   createdAt: Date;
// }

// ========== TodoList 组件 ==========
function TodoList() {
  // ---------- 状态管理 ----------
  const [todos, setTodos] = useState([]);           // 任务列表
  const [inputValue, setInputValue] = useState('');  // 输入框值
  const [filter, setFilter] = useState('all');       // 筛选条件: all | completed | active

  // ---------- 添加任务 ----------
  const addTodo = useCallback(() => {
    // 去除首尾空格,空内容不添加
    const trimmed = inputValue.trim();
    if (!trimmed) return;

    // 创建新任务对象
    const newTodo = {
      id: Date.now(),                    // 使用时间戳作为唯一 ID
      title: trimmed,
      completed: false,
      createdAt: new Date().toLocaleString(),
    };

    // 函数式更新:基于前一个状态追加
    setTodos(prev => [newTodo, ...prev]); // 新任务添加到列表顶部
    setInputValue('');                   // 清空输入框
  }, [inputValue]);

  // ---------- 切换完成状态 ----------
  const toggleTodo = useCallback((id) => {
    setTodos(prev =>
      prev.map(todo =>
        todo.id === id
          ? { ...todo, completed: !todo.completed }  // 翻转完成状态
          : todo
      )
    );
  }, []);

  // ---------- 删除任务 ----------
  const deleteTodo = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);

  // ---------- 键盘事件(回车添加) ----------
  const handleKeyDown = useCallback((e) => {
    if (e.key === 'Enter') {
      addTodo();
    }
  }, [addTodo]);

  // ---------- 派生数据:筛选后的任务列表 ----------
  const filteredTodos = useMemo(() => {
    switch (filter) {
      case 'completed':
        return todos.filter(t => t.completed);
      case 'active':
        return todos.filter(t => !t.completed);
      default:
        return todos;
    }
  }, [todos, filter]);

  // ---------- 派生数据:统计数据 ----------
  const stats = useMemo(() => ({
    total: todos.length,
    completed: todos.filter(t => t.completed).length,
    active: todos.filter(t => !t.completed).length,
  }), [todos]);

  // ========== 渲染 ==========
  return (
    <div className="todo-container">
      <h2>📋 任务管理</h2>

      {/* ---- 输入区域 ---- */}
      <div className="todo-input-area">
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyDown={handleKeyDown}
          placeholder="输入任务,按回车添加..."
        />
        <button onClick={addTodo}>添加</button>
      </div>

      {/* ---- 筛选按钮 ---- */}
      <div className="todo-filters">
        {['all', 'active', 'completed'].map(type => (
          <button
            key={type}
            className={filter === type ? 'active' : ''}
            onClick={() => setFilter(type)}
          >
            {type === 'all' ? `全部(${stats.total})` :
             type === 'active' ? `未完成(${stats.active})` :
             `已完成(${stats.completed})`}
          </button>
        ))}
      </div>

      {/* ---- 任务列表 ---- */}
      {filteredTodos.length === 0 ? (
        /* 空状态 */
        <p className="empty-state">
          {todos.length === 0
            ? '✨ 还没有任务,添加一个吧!'
            : '没有匹配的任务'}
        </p>
      ) : (
        <ul className="todo-list">
          {filteredTodos.map(todo => (
            <li
              key={todo.id}
              className={`todo-item ${todo.completed ? 'completed' : ''}`}
            >
              {/* 复选框 */}
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
              />

              {/* 任务内容(点击也可切换) */}
              <span
                className="todo-title"
                onClick={() => toggleTodo(todo.id)}
              >
                {todo.title}
              </span>

              {/* 创建时间 */}
              <span className="todo-time">{todo.createdAt}</span>

              {/* 删除按钮 */}
              <button
                className="delete-btn"
                onClick={() => deleteTodo(todo.id)}
              >
                删除
              </button>
            </li>
          ))}
        </ul>
      )}

      {/* ---- 底部统计 ---- */}
      <div className="todo-footer">
        总计 {stats.total} 项 | 已完成 {stats.completed} | 未完成 {stats.active}
      </div>
    </div>
  );
}

export default TodoList;

关键注释说明

  1. useState:管理 todos 数组、inputValue 输入框值和 filter 筛选条件三个独立状态。每项关注一个关注点。
  2. useCallback:addTodo、toggleTodo、deleteTodo 三个核心操作都使用了 useCallback 缓存函数引用,避免子组件不必要的重新渲染。
  3. useMemo:filteredTodos(筛选结果)和 stats(统计数据)都是派生数据,使用 useMemo 缓存,仅在依赖变化时重新计算,避免每次渲染都重新过滤。
  4. 函数式 setState:addTodo 中使用 setTodos(prev => [...]) 确保基于最新状态追加,防止并发状态更新丢失数据。
  5. 不变性(Immutability):toggleTodo 使用 map 创建新数组而非直接修改原对象;deleteTodo 使用 filter 创建新数组。这确保 React 能正确检测状态变化。
  6. 空状态处理:列表为空时显示友好提示,区分了”还没有任务”和”当前筛选无匹配”两种情况。
  7. 键盘事件:绑定 onKeyDown 监听回车键,提升用户体验。

考察知识点总结

  • useState — 组件状态管理
  • useCallback — 事件处理函数缓存
  • useMemo — 派生数据(筛选 + 统计)
  • 函数式 setState — 确保状态更新的正确性
  • 不可变数据操作 — map / filter / 展开运算符
  • 条件渲染 — 空状态、筛选结果
  • 列表渲染 — key 的正确使用
  • 表单处理 — 受控组件 + 键盘事件
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇