React和Vue语法差异对比我踩过的那些坑

轩辕一可 框架 阅读 1,654
赞 22 收藏
二维码
手机扫码查看
反馈

我的React组件设计,亲测靠谱

写React快三年了,早期踩的坑现在想想还是痛。特别是组件设计这块,我之前总是想着把所有逻辑塞到一个组件里,结果项目一复杂就炸得不行。现在我的做法是:单一职责,数据流向清晰。

React和Vue语法差异对比我踩过的那些坑

比如一个用户列表页面,我一般会拆分成这样:

// 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语法的一些实战经验总结,都是在真实项目中踩坑后得出的结论。有些地方可能不是最优解,但对我来说是最实用的。有更好的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论