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 表达式 - 属性使用驼峰命名(如
className、onClick) - 条件渲染使用三元运算符或
&&
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);
}, []);