Appearance
React 核心与 Vue/React 对照
设计哲学 · 生命周期 · Hooks · 写法对照 · 合成事件
一、React vs Vue 设计哲学
- 开发模式:React:JSX 开发模式;Vue:模板语法开发模式
- 智能依赖收集:React
useEffect需手动加依赖;Vue3watchEffect和computed自动收集 - React 心智负担:闭包陷阱(过时变量)、依赖选择、重复渲染、hooks 必须顶层调用
- 根源:React 每次组件渲染,所有代码被重新调用一次
二、React 生命周期(类组件)
| 阶段 | 生命周期 | 说明 |
|---|---|---|
| 挂载 | constructor | 初始化 state,绑定方法 |
| 挂载 | static getDerivedStateFromProps | props→state 映射,无 this |
| 挂载 | render | 渲染 JSX(纯函数) |
| 挂载 | componentDidMount | DOM 已挂载,发起 AJAX 请求 |
| 更新 | shouldComponentUpdate | 控制是否重新渲染(性能优化) |
| 更新 | getSnapshotBeforeUpdate | DOM 更新前捕获信息(如滚动位置) |
| 更新 | componentDidUpdate | DOM 已更新,可操作 DOM |
| 卸载 | componentWillUnmount | 清除定时器、取消订阅 |
| 错误 | getDerivedStateFromError | 渲染阶段捕获错误,返回降级 UI |
| 错误 | componentDidCatch | 记录错误日志 |
被废弃的三个方法:componentWillMount、componentWillReceiveProps、componentWillUpdate 因 Fiber 异步渲染可能被重复调用,React 16.3 起标记 UNSAFE_。
三、Vue vs React 生命周期对比
| 功能 | Vue2 | Vue3 (setup) | React (类) | React (函数) |
|---|---|---|---|---|
| 创建前 | beforeCreate | setup() | constructor | 函数体执行 |
| 挂载后 | mounted | onMounted | componentDidMount | useEffect(fn, []) |
| 更新后 | updated | onUpdated | componentDidUpdate | useEffect(fn, [deps]) |
| 卸载 | beforeDestroy/destroyed | onBeforeUnmount/onUnmounted | componentWillUnmount | useEffect return cleanup |
| 错误捕获 | errorCaptured | onErrorCaptured | componentDidCatch | ErrorBoundary 组件 |
关键差异:
- Vue 自动优化子组件渲染(依赖收集),React 需手动
shouldComponentUpdate/PureComponent/React.memo - 两者都符合洋葱模型:父→子创建,子→父挂载
四、Composition API vs React Hook
| 特性 | Composition API | React Hook |
|---|---|---|
| 调用次数 | setup 只执行一次 | 每次渲染都重新调用 |
| 调用顺序 | 无限制,可在条件/循环中使用 | 必须顶层调用 |
| 依赖收集 | 响应式系统自动收集 | 需手动声明依赖数组 |
| GC 压力 | 较小 | 较大(每次渲染创建新闭包) |
五、Vue → React 常用写法对照
组件传值:
jsx
// Vue:<Child :msg="msg" @update="handleUpdate" />
// React
<Child msg={msg} onUpdate={handleUpdate} />
function Child({ msg, onUpdate }) {
return <button onClick={() => onUpdate('new')}>{msg}</button>
}获取 DOM:
js
// Vue:const inputRef = ref(null) // template: ref="inputRef"
// React
const inputRef = useRef(null) // <input ref={inputRef} />
inputRef.current?.focus()列表渲染:
jsx
// Vue:<div v-for="item in list" :key="item.id">{{ item.name }}</div>
// React
{list.map(item => <div key={item.id}>{item.name}</div>)}计算属性:
js
// Vue:const fullName = computed(() => firstName + ' ' + lastName)
// React
const fullName = useMemo(() => firstName + ' ' + lastName, [firstName, lastName])监听器:
js
// Vue:watch(count, (newVal) => { console.log(newVal) })
// React
useEffect(() => { console.log(count) }, [count])
// 不传第二参数 → 每次渲染执行;传 [] → 仅挂载时执行一次六、React forwardRef + useImperativeHandle
子组件暴露方法给父组件(类似 Vue 的 defineExpose + ref):
jsx
// 子组件
const Scroll = forwardRef((props, ref) => {
const bScroll = useRef(null);
useImperativeHandle(ref, () => ({
refresh() { bScroll.current?.refresh() },
scrollTo(x, y) { bScroll.current?.scrollTo(x, y) }
}));
return <div>...</div>;
});
// 父组件
const scrollRef = useRef(null);
<Scroll ref={scrollRef} />
scrollRef.current.refresh(); // 调用子组件方法七、React Hooks 项目编码规范
- 全面使用函数组件 + Hooks,不再用 class 组件
- 组件内部状态用 Hooks,业务数据统一放 Redux
- AJAX 请求和数据处理放在
actionCreator中(redux-thunk 处理异步) useEffect统一放在组件顶部,紧跟 props 解构之后mapDispatchToProps返回的函数命名xxxDispatch,避免与 action 名冲突- 返回 JSX 的函数统一放在组件最后面
八、为什么 React 需要合成事件
- 兼容性考虑,抹平不同浏览器事件对象差异
- 避免垃圾回收