Skip to content

React

一、React 基础

1. React 是什么?

React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开发。

核心特性:

  • 组件化:将 UI 拆分成可复用的组件
  • 虚拟 DOM:高效的 DOM 更新机制
  • 单向数据流:数据从父组件流向子组件
  • 声明式编程:描述 UI 应该是什么样子

2. JSX 是什么?

JSX 是 JavaScript 的语法扩展,可以在 JavaScript 中写类似 HTML 的代码。

jsx
// JSX
const element = <h1>Hello, {name}!</h1>;

// 编译后
const element = React.createElement('h1', null, 'Hello, ', name, '!');

JSX 的特点:

  • 必须返回单个根元素(React 16+ 可以使用 Fragment)
  • 使用 {} 嵌入 JavaScript 表达式
  • 属性使用驼峰命名(如 classNameonClick
  • 条件渲染使用三元运算符或 &&
jsx
// Fragment
const element = (
  <>
    <h1>标题</h1>
    <p>内容</p>
  </>
);

// 条件渲染
{isLoggedIn ? <Welcome /> : <Login />}
{count > 0 && <Message />}

二、组件

1. 函数组件和类组件

函数组件(推荐):

jsx
function Welcome(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// 箭头函数
const Welcome = (props) => {
  return <h1>Hello, {props.name}!</h1>;
};

类组件:

jsx
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

2. 组件的生命周期

类组件生命周期:

挂载阶段:

  • constructor:组件初始化
  • componentDidMount:组件挂载后

更新阶段:

  • componentDidUpdate:组件更新后
  • shouldComponentUpdate:是否应该更新(性能优化)

卸载阶段:

  • componentWillUnmount:组件卸载前
jsx
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  componentDidMount() {
    console.log('组件已挂载');
  }
  
  componentDidUpdate(prevProps, prevState) {
    console.log('组件已更新');
  }
  
  componentWillUnmount() {
    console.log('组件即将卸载');
  }
  
  shouldComponentUpdate(nextProps, nextState) {
    return nextState.count !== this.state.count;
  }
  
  render() {
    return <div>{this.state.count}</div>;
  }
}

函数组件使用 Hooks:

jsx
import { useEffect, useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);
  
  // componentDidMount + componentDidUpdate
  useEffect(() => {
    console.log('组件已挂载或更新');
  });
  
  // componentDidMount
  useEffect(() => {
    console.log('组件已挂载');
  }, []);
  
  // componentWillUnmount
  useEffect(() => {
    return () => {
      console.log('组件即将卸载');
    };
  }, []);
  
  return <div>{count}</div>;
}

三、State 和 Props

1. State

类组件中的 State:

jsx
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }
  
  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.increment}>增加</button>
      </div>
    );
  }
}

函数组件中的 State(useState):

jsx
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  const increment = () => {
    setCount(count + 1);
    // 或者使用函数式更新
    setCount(prevCount => prevCount + 1);
  };
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>增加</button>
    </div>
  );
}

2. Props

Props 是只读的,不能在子组件中修改。

jsx
// 父组件
function Parent() {
  return <Child name="Alice" age={20} />;
}

// 子组件
function Child(props) {
  return (
    <div>
      <p>姓名: {props.name}</p>
      <p>年龄: {props.age}</p>
    </div>
  );
}

// 解构 Props
function Child({ name, age }) {
  return (
    <div>
      <p>姓名: {name}</p>
      <p>年龄: {age}</p>
    </div>
  );
}

3. Props 类型检查(PropTypes)

jsx
import PropTypes from 'prop-types';

function MyComponent({ name, age, isActive }) {
  return (
    <div>
      <p>{name} - {age}</p>
      {isActive && <p>已激活</p>}
    </div>
  );
}

MyComponent.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  isActive: PropTypes.bool
};

MyComponent.defaultProps = {
  age: 18,
  isActive: false
};

四、Hooks

1. useState

用于在函数组件中添加状态。

jsx
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Alice');
  
  return (
    <div>
      <p>{name}: {count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

2. useEffect

用于在函数组件中执行副作用(数据获取、订阅等)。

jsx
import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  // 每次渲染后执行
  useEffect(() => {
    console.log('组件渲染后执行');
  });
  
  // 仅在挂载时执行
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, []);  // 空依赖数组
  
  // 依赖变化时执行
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);  // 依赖 userId
  
  // 清理函数
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('定时器');
    }, 1000);
    
    return () => {
      clearInterval(timer);
    };
  }, []);
  
  return <div>{user?.name}</div>;
}

3. useContext

用于在函数组件中消费 Context。

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

const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div>当前主题: {theme}</div>;
}

4. useReducer

用于管理复杂的状态逻辑。

jsx
import { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>增加</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>减少</button>
    </div>
  );
}

5. useMemo

用于缓存计算结果。

jsx
import { useMemo } from 'react';

function ExpensiveComponent({ a, b }) {
  const result = useMemo(() => {
    return expensiveCalculation(a, b);
  }, [a, b]);  // 只有 a 或 b 变化时才重新计算
  
  return <div>{result}</div>;
}

6. useCallback

用于缓存函数。

jsx
import { useCallback, useState } from 'react';

function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('点击');
  }, []);  // 函数不会改变
  
  return <Child onClick={handleClick} />;
}

7. useRef

用于获取 DOM 引用或保存可变值。

jsx
import { useRef, useEffect } from 'react';

function MyComponent() {
  const inputRef = useRef(null);
  const countRef = useRef(0);
  
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  
  const increment = () => {
    countRef.current += 1;
    console.log(countRef.current);
  };
  
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={increment}>增加</button>
    </div>
  );
}

五、事件处理

1. 事件处理

jsx
function Button() {
  const handleClick = (e) => {
    e.preventDefault();
    console.log('点击了按钮');
  };
  
  return <button onClick={handleClick}>点击</button>;
}

2. 事件传参

jsx
function List() {
  const items = ['a', 'b', 'c'];
  
  const handleClick = (item, e) => {
    console.log(item);
  };
  
  return (
    <ul>
      {items.map(item => (
        <li key={item} onClick={(e) => handleClick(item, e)}>
          {item}
        </li>
      ))}
    </ul>
  );
}

六、条件渲染和列表渲染

1. 条件渲染

jsx
function Greeting({ isLoggedIn }) {
  // 方式1:三元运算符
  return isLoggedIn ? <Welcome /> : <Login />;
  
  // 方式2:逻辑与运算符
  return isLoggedIn && <Welcome />;
  
  // 方式3:if 语句
  if (isLoggedIn) {
    return <Welcome />;
  }
  return <Login />;
}

2. 列表渲染

jsx
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

注意事项:

  • 必须使用 key 属性
  • key 应该是稳定的、唯一的标识符
  • 不要使用数组索引作为 key(除非列表不会重新排序)

七、组件通信

1. 父子组件通信

父传子:Props

jsx
function Parent() {
  const message = "Hello";
  return <Child message={message} />;
}

function Child({ message }) {
  return <div>{message}</div>;
}

子传父:回调函数

jsx
function Parent() {
  const handleChildClick = (data) => {
    console.log(data);
  };
  return <Child onClick={handleChildClick} />;
}

function Child({ onClick }) {
  return <button onClick={() => onClick('data')}>点击</button>;
}

2. 兄弟组件通信

通过父组件中转。

3. 跨层级组件通信

Context API

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

const ThemeContext = createContext();

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div>主题: {theme}</div>;
}

Redux / Zustand 等状态管理库

八、性能优化

1. React.memo

用于缓存函数组件,避免不必要的重新渲染。

jsx
import { memo } from 'react';

const MyComponent = memo(function MyComponent({ name }) {
  return <div>{name}</div>;
});

2. useMemo

缓存计算结果。

jsx
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);

3. useCallback

缓存函数。

jsx
const handleClick = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

4. 组件懒加载

jsx
import { lazy, Suspense } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

5. 避免在 render 中创建函数

jsx
// 不推荐
<Child onClick={() => handleClick()} />

// 推荐
const handleClick = useCallback(() => {
  // ...
}, []);

<Child onClick={handleClick} />

九、常见面试题

1. React 的虚拟 DOM 是什么?

虚拟 DOM 是 React 在内存中维护的 DOM 树副本。当状态变化时,React 会创建新的虚拟 DOM 树,然后通过 Diff 算法比较新旧虚拟 DOM,找出差异,最后批量更新真实 DOM。

优势:

  • 减少直接操作 DOM 的次数
  • 跨平台(可以渲染到不同平台)
  • 提升性能

2. 为什么 React 需要 key?

key 帮助 React 识别哪些元素发生了变化,从而高效地更新虚拟 DOM。没有 key 时,React 可能错误地复用组件,导致状态不一致。

3. setState 是同步还是异步?

在 React 18 之前,setState 是异步的,但在某些情况下(如 setTimeout)可能是同步的。React 18 中,所有 setState 都是异步的,并且会进行批处理。

4. React 的 Diff 算法原理?

React 使用启发式算法进行 Diff,基于以下假设:

  • 相同类型的组件会生成相似的树结构
  • 可以使用 key 标识哪些元素发生了变化

Diff 策略:

  • Tree Diff:逐层比较
  • Component Diff:同类型组件比较,不同类型直接替换
  • Element Diff:通过 key 进行精确比较

5. useEffect 的依赖数组什么时候会出问题?

如果依赖数组不完整,可能导致闭包陷阱或遗漏依赖。

jsx
// 错误:依赖数组不完整
useEffect(() => {
  setCount(count + 1);
}, []);  // 缺少 count

// 正确:包含所有依赖
useEffect(() => {
  setCount(count + 1);
}, [count]);

// 或者使用函数式更新
useEffect(() => {
  setCount(prev => prev + 1);
}, []);

基于 VitePress 构建 | Copyright © 2026-present