React和Vue语法差异对比我踩过的那些坑
我的React组件设计,亲测靠谱
写React快三年了,早期踩的坑现在想想还是痛。特别是组件设计这块,我之前总是想着把所有逻辑塞到一个组件里,结果项目一复杂就炸得不行。现在我的做法是:单一职责,数据流向清晰。
比如一个用户列表页面,我一般会拆分成这样:
// UserList.jsx - 容器组件
import React, { useState, useEffect } from 'react';
import UserTable from './UserTable';
import SearchForm from './SearchForm';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [filters, setFilters] = useState({});
const fetchUsers = async (params) => {
setLoading(true);
try {
const response = await fetch(/api/users?${new URLSearchParams(params)});
const data = await response.json();
setUsers(data);
} catch (error) {
console.error('获取用户失败:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchUsers(filters);
}, [filters]);
return (
<div>
<SearchForm onFilterChange={setFilters} />
<UserTable users={users} loading={loading} />
</div>
);
}
这样拆分的好处是,UserTable只负责展示,UserList管理状态,SearchForm负责交互。后期维护的时候各司其职,改哪个模块心里都有数。
Vue响应式陷阱,很多人都踩过
说Vue的响应式,我必须提一下那个经典的坑——对象属性动态添加。很多人不知道Vue2中直接给对象添加新属性是不会触发更新的,我第一次遇到这个问题调试了一个下午。
// 错误写法
export default {
data() {
return {
userInfo: {}
};
},
methods: {
addUserSkill() {
// 这样添加的新属性不会响应式更新
this.userInfo.skill = 'JavaScript';
this.$forceUpdate(); // 别用这个,很low
}
}
};
正确的做法要么提前声明空值,要么用this.$set(Vue2)或者直接替换整个对象(Vue3):
// Vue2 正确写法
methods: {
addUserSkill() {
this.$set(this.userInfo, 'skill', 'JavaScript');
}
}
// Vue3 写法,直接赋值就行
setup() {
const userInfo = reactive({});
const addUserSkill = () => {
userInfo.skill = 'JavaScript'; // Vue3中这样就可以
};
}
这其实是Vue的响应式系统限制,底层用的是Object.defineProperty监听属性变化,新增属性监听不到。Vue3用了Proxy就解决了这个问题,但老项目还是要小心。
状态管理别搞复杂了,简单点更稳
很多人一上来就要用Redux、Vuex,其实大部分项目根本用不到那么复杂的状态管理。我现在处理全局状态,React的话就用Context + useReducer就够了,Vue直接Pinia配全局store。
// React Context + useReducer
import React, { createContext, useContext, useReducer } from 'react';
const AppContext = createContext();
const initialState = {
user: null,
theme: 'light',
notifications: []
};
function appReducer(state, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'TOGGLE_THEME':
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
default:
return state;
}
}
export function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
// 使用的地方
export function useAppContext() {
return useContext(AppContext);
}
这种写法比Redux轻量多了,项目规模不大完全够用。当然如果真的很复杂的状态管理需求,那该上Redux还得上,但别为了用而用。
这几种错误写法,别再踩坑了
React中最常见的错误就是useEffect依赖数组乱写。我见过很多人这样写:
// 错误写法 - 依赖数组为空,但里面用到了props
useEffect(() => {
console.log(props.userId); // 这里的userId永远是初始值
}, []); // 空依赖数组,永远不会重新执行
// 更错误的写法 - 忘记加依赖
useEffect(() => {
fetchData(userId); // userId变化时没有重新执行
}, []); // 应该是[userId]
还有人喜欢在组件内部定义函数然后放到依赖里,结果无限循环:
function MyComponent({ userId }) {
// 每次渲染都会创建新的函数引用
const handleData = () => {
console.log(userId);
};
useEffect(() => {
handleData();
}, [handleData]); // 因为handleData每次都是新函数,导致无限循环
return <div>...</div>;
}
正确的做法是用useCallback缓存函数引用:
const handleData = useCallback(() => {
console.log(userId);
}, [userId]); // 依赖userId
useEffect(() => {
handleData();
}, [handleData]);
Vue这边最常见的错误就是watch监听数组/对象不deep:
watch(() => state.userList, (newVal) => {
// 这样监听不了数组内部元素的变化
});
// 正确写法
watch(() => state.userList, (newVal) => {
// 逻辑
}, { deep: true });
性能优化,别过度追求
性能优化这块我也踩过不少坑,最典型的就是滥用memo和shouldComponentUpdate。有一次项目里到处都加memo,结果组件渲染反而更慢了,因为比较props的时间比重新渲染还长。
我的经验是:先找真正的性能瓶颈,再针对性优化。不要一开始就考虑优化,90%的场景默认渲染完全够用。
React这边我一般这样处理:
// 只在确实需要的时候才用memo
const ExpensiveComponent = memo(({ data, callback }) => {
return <div>{/* 复杂渲染 */}</div>;
}, (prevProps, nextProps) => {
// 自定义比较逻辑,避免浅比较的性能损耗
return prevProps.data.id === nextProps.data.id;
});
Vue这边v-memo也是一样的道理,不要盲目使用。真正的大列表渲染我才会上虚拟滚动,普通的几十条数据完全没必要。
实际项目中的坑
实际项目中最容易忽略的问题就是内存泄漏。特别是定时器和事件监听器,我在生产环境见过好几次定时器没清理导致的内存泄露问题。
// React中要记得清理
useEffect(() => {
const timer = setInterval(() => {
// 逻辑
}, 1000);
const handleClick = (e) => {
// 逻辑
};
document.addEventListener('click', handleClick);
// 清理函数很重要
return () => {
clearInterval(timer);
document.removeEventListener('click', handleClick);
};
}, []);
Vue也有同样的问题:
mounted() {
this.timer = setInterval(() => {
// 逻辑
}, 1000);
},
beforeUnmount() {
clearInterval(this.timer);
}
还有个坑就是异步操作的竞态条件。比如搜索功能,用户连续输入多个关键词,可能会出现后请求的结果先返回,显示的是旧数据。
const [abortController, setAbortController] = useState(null);
const search = async (keyword) => {
abortController?.abort(); // 取消之前的请求
const controller = new AbortController();
setAbortController(controller);
try {
const response = await fetch(/api/search?q=${keyword}, {
signal: controller.signal
});
const result = await response.json();
setResult(result);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('搜索失败:', error);
}
}
};
以上是我个人对React/Vue语法的一些实战经验总结,都是在真实项目中踩坑后得出的结论。有些地方可能不是最优解,但对我来说是最实用的。有更好的实现方式欢迎评论区交流。

暂无评论