为什么选择 React?
1. 简单直观的更新机制
Vue 和 React 都是优秀的前端框架,各有特色:
- Vue 在国内更受欢迎,很大程度上是因为它延续了传统的 HTML 开发方式,对从传统 Web 开发转型的开发者更友好
- React 则采用了更现代的函数式编程思想,其更新机制更加直观:函数重新执行 = 组件重新渲染
- 这种函数式的更新机制让 React 的学习曲线更加平缓,只需要理解几个核心概念:
- 组件就是函数
- 状态变化触发函数重新执行
- 函数重新执行 = 重新渲染
- 只需要掌握几个核心 Hooks 就能开始开发
2. TypeScript 的完美支持
React 与 TypeScript 的配合堪称完美:
tsx
// 完整的类型推导
interface Props {
name: string;
age: number;
onUpdate: (newAge: number) => void;
}
function UserProfile({ name, age, onUpdate }: Props) {
// 编辑器会提供完整的类型提示
const handleClick = () => {
onUpdate(age + 1);
};
return (
<div>
<h1>{name}</h1>
<p>年龄: {age}</p>
<button onClick={handleClick}>增加年龄</button>
</div>
);
}
优势:
- 组件 Props 类型检查
- Hooks 类型推导
- 事件处理函数类型安全
- 状态管理类型安全
- 减少运行时错误
- 提高开发效率,减少查文档时间
3. 开发效率对比
不同框架的学习路径:
rust
Vue 学习路径:
HTML 基础 -> 模板语法 -> 响应式原理 -> 计算属性 -> 组件通信 -> 状态管理
React 学习路径:
JavaScript 基础 -> 函数式编程 -> 核心 Hooks -> 开始开发
React 的核心优势:
-
更新机制简单直观
- 函数重新执行就是重新渲染
- 不需要理解复杂的响应式原理
- 状态变化直接触发更新
-
核心 Hooks 记忆点:
tsx
// 1. 状态管理
const [state, setState] = useState<Type>(initialValue);
// 2. 副作用处理
useEffect(() => {
// 副作用代码
return () => {
// 清理代码
};
}, [依赖项]);
// 3. 性能优化
const memoizedValue = useMemo(() => computeValue(), [依赖项]);
const memoizedCallback = useCallback(() => handleEvent(), [依赖项]);
// 4. 引用保持
const ref = useRef<Type>(initialValue);
简单介绍
React 是一个用于构建用户界面的 JavaScript 库,它的设计理念深受函数式编程的影响。React 将 UI 拆分为独立的、可复用的组件,每个组件都是一个纯函数,接收 props 并返回要渲染的内容。
函数式编程在 React 中的应用
1. 纯函数
React 组件本质上就是纯函数:
jsx
// 纯函数组件
function Welcome(props) {
return <h1>你好, {props.name}</h1>;
}
纯函数的特点:
- 相同的输入总是产生相同的输出
- 没有副作用
- 不依赖外部状态
2. 不可变性
React 强调数据的不可变性:
jsx
// 错误的方式
const obj = { count: 1 };
obj.count = 2; // 直接修改
// 正确的方式
const newObj = { ...obj, count: 2 }; // 创建新对象
React 核心机制
1. 函数式更新机制
React 的核心更新机制可以理解为:
rust
函数执行 -> 状态变化 -> 重新渲染
关键点:
- 组件本质上是一个函数
- 函数内的任何状态变化都会触发重新执行
- 每次执行都会创建新的虚拟 DOM
- 通过对比新旧虚拟 DOM 来决定实际更新
2. 状态管理与副作用
React 的核心理念是:函数内部任何变量的变化都会触发更新机制。这带来了两个主要问题:
- 性能问题:频繁更新导致性能下降
- 副作用问题:某些操作不应该在每次更新时都执行
解决方案:Hooks 机制
jsx
function Component() {
// 1. 状态管理
const [count, setCount] = useState(0);
// 2. 副作用处理
useEffect(() => {
// 这里的代码不会在每次渲染时都执行
// 只有在依赖项变化时才会执行
}, [count]);
// 3. 记忆化
const memoizedValue = useMemo(() => {
// 复杂计算的结果会被缓存
return expensiveComputation(count);
}, [count]);
}
3. 更新流程详解
scss
组件函数执行
↓
创建/更新状态 (useState)
↓
执行副作用 (useEffect)
↓
生成虚拟 DOM
↓
对比新旧虚拟 DOM
↓
更新真实 DOM
4. 为什么需要 Hooks?
- 解决副作用问题
jsx
// 没有 Hooks 时的问题
function Component() {
const data = fetchData(); // 每次渲染都会执行
return <div>{data}</div>;
}
// 使用 Hooks 后
function Component() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []); // 只在组件挂载时执行一次
return <div>{data}</div>;
}
- 状态隔离
jsx
function Component() {
// 状态被隔离在 Hooks 中
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// 这些状态的变化不会相互影响
// 每个 Hook 都有自己的更新机制
}
- 性能优化
jsx
function ExpensiveComponent() {
const [count, setCount] = useState(0);
// 使用 useMemo 避免重复计算
const expensiveValue = useMemo(() => {
return complexCalculation(count);
}, [count]);
// 使用 useCallback 避免函数重新创建
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
}
更新机制详解
1. React 的更新原理
想象你在画一幅画:
- 每次需要修改时,你不是直接擦掉重画
- 而是先画一张新的草稿,对比新旧两张画
- 只修改真正需要改变的部分
React 的更新机制就是这样:
rust
数据变化 -> 生成新的虚拟DOM -> 对比新旧差异 -> 只更新需要改变的部分
2. 触发更新的常见情况
就像画画的场景:
- 你修改了画的内容(状态变化)
- 别人给了你新的参考图(props 变化)
- 你决定重新画一遍(强制更新)
3. 避免更新陷阱
就像画画时要注意的问题:
- 不要一直重画
jsx
function BadComponent() {
const [count, setCount] = useState(0);
// 错误示范:每次渲染都改变状态
setCount(count + 1);
return <div>{count}</div>;
}
- 只在需要时更新
jsx
function GoodComponent() {
const [count, setCount] = useState(0);
// 正确示范:只在点击时更新
return (
<button onClick={() => setCount(count + 1)}>
点击次数:{count}
</button>
);
}
4. 小心无限刷新的陷阱
就像画画时陷入的循环:
- 你画了一笔,觉得不对
- 擦掉重画,还是不对
- 又擦掉重画...陷入死循环
React 中常见的无限刷新问题:
- 在渲染时直接修改状态
jsx
function BadComponent() {
const [count, setCount] = useState(0);
// 错误:每次渲染都会触发新的渲染
setCount(count + 1);
return <div>{count}</div>;
}
- useEffect 依赖项设置不当
jsx
function BadEffect() {
const [data, setData] = useState({});
// 错误:data 变化会触发 effect,effect 又会改变 data
useEffect(() => {
setData({ ...data, new: 'value' });
}, [data]); // 错误:把 data 作为依赖项
}
- 正确的处理方式
jsx
function GoodEffect() {
const [data, setData] = useState({});
// 正确:只在组件挂载时执行一次
useEffect(() => {
setData({ new: 'value' });
}, []); // 空依赖数组,只在挂载时执行
}
记住:React 的更新是"单向流动"的,就像水流一样,不能形成循环。
常用 Hooks 详解
1. useState
- 用于管理组件内部状态
- 每次更新都会创建新的状态值
- 异步更新,多个 setState 会合并
jsx
const [state, setState] = useState(initialValue);
2. useEffect
- 处理副作用(数据获取、订阅、手动修改 DOM)
- 依赖项数组控制执行时机
- 清理函数在组件卸载或重新执行前运行
jsx
useEffect(() => {
// 副作用代码
return () => {
// 清理代码
};
}, [依赖项]);
3. useCallback
- 缓存函数引用
- 避免不必要的子组件重渲染
- 依赖项变化时才会创建新函数
jsx
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
4. useMemo
- 缓存计算结果
- 避免重复计算
- 依赖项变化时重新计算
jsx
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b),
[a, b]
);
最佳实践
- 合理使用依赖项
jsx
// 好的实践
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []); // 空依赖数组,只在挂载时执行
- 避免不必要的重渲染
jsx
// 使用 React.memo 包装纯展示组件
const MemoizedComponent = React.memo(function MyComponent(props) {
return <div>{props.value}</div>;
});
- 状态提升
jsx
// 将共享状态提升到最近的共同父组件
function Parent() {
const [sharedState, setSharedState] = useState();
return (
<>
<ChildA state={sharedState} />
<ChildB state={sharedState} />
</>
);
}
总结
React 的核心在于:
- 组件化开发
- 虚拟 DOM 和 Diff 算法
- 单向数据流
- Hooks 机制
理解这些核心概念,就能更好地使用 React 进行开发,避免常见问题,写出更高效的代码。