普通视图

发现新文章,点击刷新页面。
昨天以前首页

子组件改状态,父组件会“炸毛”吗?

2025年9月1日 08:49

大家好,我是小杨。今天我们来聊聊React中一个非常有意思的话题:当我们在子组件中修改状态时,到底会不会影响到父组件?会不会触发父组件的生命周期?这个问题看似简单,却藏着不少React的精妙设计。

一个真实的踩坑经历

前几天我接到了一个需求:开发一个可折叠的商品分类菜单。父组件负责管理所有分类数据,子组件负责显示单个分类及其下的商品列表。

最初的代码大概是这样的:

// 父组件
function CategoryList() {
  const [categories, setCategories] = useState([]);
  
  useEffect(() => {
    // 获取分类数据
    fetchCategories().then(data => {
      setCategories(data);
    });
  }, []);
  
  return (
    <div>
      {categories.map(category => (
        <CategoryItem 
          key={category.id} 
          category={category}
        />
      ))}
    </div>
  );
}

// 子组件
function CategoryItem({ category }) {
  const [isExpanded, setIsExpanded] = useState(false);
  const [products, setProducts] = useState([]);
  
  const toggleExpand = () => {
    setIsExpanded(!isExpanded);
    if (!isExpanded && products.length === 0) {
      // 展开时加载商品数据
      fetchProducts(category.id).then(data => {
        setProducts(data);
      });
    }
  };
  
  return (
    <div className="category-item">
      <div className="header" onClick={toggleExpand}>
        <h3>{category.name}</h3>
        <span>{isExpanded ? '▼' : '►'}</span>
      </div>
      {isExpanded && (
        <div className="products">
          {products.map(product => (
            <div key={product.id}>{product.name}</div>
          ))}
        </div>
      )}
    </div>
  );
}

写完之后我心想:这逻辑清晰明了,肯定没问题!但测试时却发现了一个有趣的现象...

核心结论:各管各的,互不干扰

答案是:子组件中修改自己的状态,不会直接影响父组件,也不会触发父组件的生命周期

这就像是:你在自己的房间里收拾东西(修改状态),不会影响到客厅里的父母(父组件),他们该看电视还是看电视。

但是,事情没那么简单...

虽然子组件的状态变化不会直接影响父组件,但通过以下几种方式,子组件确实可以"间接"影响父组件:

  1. 通过回调函数传递信息
// 父组件
function Parent() {
  const [parentData, setParentData] = useState('initial');
  
  const handleChildUpdate = (newData) => {
    setParentData(newData); // 父组件状态更新
  };
  
  return <Child onUpdate={handleChildUpdate} />;
}

// 子组件
function Child({ onUpdate }) {
  const [childState, setChildState] = useState('');
  
  const handleClick = () => {
    const newData = 'updated by child';
    setChildState(newData);
    onUpdate(newData); // 通知父组件
  };
  
  return <button onClick={handleClick}>更新父组件</button>;
}
  1. 通过Context共享状态
const AppContext = createContext();

function App() {
  const [globalState, setGlobalState] = useState({});
  
  return (
    <AppContext.Provider value={{ globalState, setGlobalState }}>
      <ChildComponent />
    </AppContext.Provider>
  );
}

function ChildComponent() {
  const { globalState, setGlobalState } = useContext(AppContext);
  
  const updateGlobalState = () => {
    setGlobalState({ ...globalState, updated: true });
    // 这会影响到所有使用这个Context的组件
  };
}
  1. 状态提升(Lifting State Up)
// 状态提升到父组件
function Parent() {
  const [sharedState, setSharedState] = useState('');
  
  return (
    <div>
      <ChildA value={sharedState} onChange={setSharedState} />
      <ChildB value={sharedState} />
    </div>
  );
}

生命周期的影响范围

  • ✅ 子组件状态变化:只触发子组件自身的重渲染和useEffect
  • ✅ 父组件状态变化:触发父组件重渲染,也可能触发子组件的重渲染(如果props变化)
  • ❌ 子组件状态变化:不会触发父组件的任何生命周期方法

实际开发中的建议

  1. 状态位置要合理
// 如果多个组件需要同一状态,提升到共同的父组件
function ProductPage() {
  const [selectedCategory, setSelectedCategory] = useState(null);
  
  return (
    <div>
      <CategoryFilter 
        selectedCategory={selectedCategory}
        onSelect={setSelectedCategory}
      />
      <ProductList category={selectedCategory} />
    </div>
  );
}
  1. 使用useCallback避免不必要的重渲染
function Parent() {
  const [count, setCount] = useState(0);
  
  const handleChildEvent = useCallback((data) => {
    // 处理子组件事件
  }, []); // 依赖项数组为空,函数不会重新创建
  
  return <Child onEvent={handleChildEvent} />;
}
  1. 合理使用React.memo
const ChildComponent = React.memo(function ChildComponent({ data }) {
  // 只有当props变化时才会重渲染
  return <div>{data}</div>;
});

总结一下

  • 🎯 子组件状态变化不影响父组件
  • 🔄 状态变化只影响当前组件及其子组件
  • 📤 通过回调、Context等方式可以实现父子通信
  • 🚀 合理设计状态结构是React开发的关键

希望这篇文章能帮你理清React中状态管理的思路。如果你在开发中也遇到过类似的问题,欢迎在评论区分享你的经验!

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

为什么在render里调setState,代码会和你“翻脸”?

2025年9月1日 08:42

我明明只是修改个状态,怎么页面开始疯狂抽搐甚至直接崩了?

作为前端开发者,相信很多人都曾在React的render函数中尝试过setState,结果却遇到了意想不到的问题。今天小杨就来和大家聊聊这背后的原因,以及如何避免这个常见的陷阱。

一个亲身经历的bug案例

前几天我在开发一个商品列表组件时,遇到了一个奇怪的问题。页面在加载时会不断刷新,最后甚至直接白屏了。经过一番排查,我发现问题出在了下面这段代码:

function ProductList({ products }) {
  const [filteredProducts, setFilteredProducts] = useState(products);
  const [sortOrder, setSortOrder] = useState('asc');
  
  // 这里犯了低级错误!
  if (products.length !== filteredProducts.length) {
    setFilteredProducts(products);
  }
  
  return (
    <div>
      {/* 渲染产品列表 */}
    </div>
  );
}

看起来我只是在props变化时更新state,但这样做却导致了组件的无限重新渲染!

为什么render中不能调用setState?

简单来说,在render过程中调用setState就像是在盖房子时不断修改蓝图——工程永远无法完工,反而可能把工地搞得一团糟。

React的渲染过程分为几个阶段:

  1. Render阶段:计算虚拟DOM的变化
  2. Commit阶段:将变化应用到真实DOM
  3. 清理阶段:执行副作用和生命周期方法

当我们在render函数中调用setState时,React会:

  • 标记需要更新状态
  • 重新执行render函数
  • 发现又调用了setState
  • 再次标记需要更新状态
  • ...形成无限循环

正确的解决方案

对于上面的问题,我最终使用了useEffect来避免在render中直接调用setState:

function ProductList({ products }) {
  const [filteredProducts, setFilteredProducts] = useState(products);
  const [sortOrder, setSortOrder] = useState('asc');
  
  // 使用useEffect在适当的时候更新状态
  useEffect(() => {
    setFilteredProducts(products);
  }, [products]);
  
  const sortedProducts = useMemo(() => {
    return [...filteredProducts].sort((a, b) => {
      return sortOrder === 'asc' ? a.price - b.price : b.price - a.price;
    });
  }, [filteredProducts, sortOrder]);
  
  return (
    <div>
      {/* 渲染产品列表 */}
    </div>
  );
}

其他常见陷阱和解决方案

  1. 事件处理中的setState
    这是setState最安全的调用场所
function Counter() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(prevCount => prevCount + 1); // 正确:在事件处理中调用
  };
  
  return <button onClick={handleClick}>Count: {count}</button>;
}
  1. useEffect中的setState
    需要注意依赖项数组,避免无限循环
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(userData => {
      setUser(userData); // 正确:在useEffect中调用
    });
  }, [userId]); // 确保依赖项正确
}

总结一下

  • 🚫 避免在render函数中直接调用setState
  • ✅ 在事件处理程序或useEffect中调用setState
  • 🔄 注意setState可能引起的重新渲染,合理使用useMemo和useCallback优化性能

⭐  写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

❌
❌