面试官问我React组件和state的关系,我指了指路口的红绿灯…

🚥 马路边的面试奇遇

面试官:(突然指向路口的红绿灯)你看这个红灯变绿,像不像React组件的重新渲染?

:(战术挑眉)您这红绿灯要是用React实现,isGreen这个state一变,整个组件就得重绘。不过嘛...(突然掏出手电筒)要是只换灯泡不换灯罩,可能不用整个拆了重建?

面试官:(突然打开手电筒照我眼睛)说人话!


🔋 一、React组件的能量守恒定律

1. 基本法则:触发渲染的三原色

javascript 复制代码
// 组件重渲染的三种触发方式
const 触发重渲染 = () => {
  1. setState(newValue) // 原生state变化
  2. props变更 // 父组件传值变化
  3. 父组件重渲染 // 上级组件更新
};

2. 红绿灯案例解析

jsx 复制代码
function TrafficLight() {
  const [isGreen, setIsGreen] = useState(false);

  useEffect(() => {
    const timer = setInterval(() => {
      setIsGreen(prev => !prev); // ✅ 每次切换触发重渲染
    }, 3000);
    return () => clearInterval(timer);
  }, []);

  return (
    <div className={`light ${isGreen ? 'green' : 'red'}`}>
      {/* 每次isGreen变化都会重绘整个div */}
    </div>
  );
}

🚨 二、触发重渲染的四大天王

Case 1:useState值变化

jsx 复制代码
const [count, setCount] = useState(0);

// 🚀 触发重渲染
setCount(1); 

// ❌ 不会触发(React使用Object.is比较)
setCount(0); 

Case 2:props对象引用变化

jsx 复制代码
// 父组件
<Child items={[...items]} /> // 每次都是新数组,必触发

<Child items={items} /> // 数组引用不变不触发

Case 3:Context变更

jsx 复制代码
const ThemeContext = createContext();

// 只要Provider的value变化
<ThemeContext.Provider value={newTheme}>
  <App />
</ThemeContext.Provider>

即使组件用memo包裹,只要消费了该Context的子组件都会重渲染

Case 4:祖传染色体攻击

jsx 复制代码
// 祖父组件
const Grandfather = () => {
  const [state] = useState();
  return <Father />; // 👉 只要祖父重渲染,父亲不优化的话...
};

// 父亲组件
const Father = () => <Child />; // 👉 孩子也会被迫重渲染

🕶️ 三、金钟罩铁布衫:不触发重渲染的玄学时刻

1. 对象原地变性术

jsx 复制代码
const [user, setUser] = useState({ name: '老王' });

// ❌ 不会触发
user.name = '隔壁老王'; 
setUser(user); // 引用地址没变!

// ✅ 正确做法
setUser({ ...user, name: '隔壁老王' });

2. 数组索引戏法

jsx 复制代码
const [list, setList] = useState(['A', 'B', 'C']);

// ❌ 不会触发(React认为数组没变)
list.push('D');
setList(list);

// ✅ 正确做法
setList([...list, 'D']);

3. 函数式更新隐身术

jsx 复制代码
const [count, setCount] = useState(0);

// ✅ 触发
setCount(1); 

// ❌ 不会触发(相同值)
setCount(prev => prev); 

👻 四、幽灵state现形记

实验:未使用的state会触发渲染吗?

jsx 复制代码
function GhostComponent() {
  const [usedState] = useState('显形state'); 
  const [ghostState, setGhostState] = useState('幽灵state'); 

  return (
    <div>
      <button onClick={() => setGhostState(Math.random())}>
        触发幽灵state变化
      </button>
      <p>{usedState}</p> {/* 只展示usedState */}
    </div>
  );
}

现象

点击按钮时:

  • ✅ 组件重新渲染(控制台打印执行)
  • ❌ UI纹丝不动(ghostState从未被使用)

结论
所有state变化都会触发重渲染,哪怕它是个"幽灵"!


驱魔三式:让幽灵state安息

招式1:组件分家术

jsx 复制代码
// 父组件(无state)
function Parent() {
  return <Child />; // 免疫幽灵攻击
}

// 子组件(独自承受)
const Child = React.memo(() => {
  const [ghostState, setGhostState] = useState();
  // ...处理state
});

招式2:useRef封印大法

jsx 复制代码
function StealthComponent() {
  const ghostRef = useRef();
  
  const updateGhost = () => {
    ghostRef.current = Math.random(); // ✅ 无渲染触发
  };

  return <button onClick={updateGhost}>秘密行动</button>;
}

招式3:Context选择器狙击

jsx 复制代码
const useGhostSelector = () => {
  const context = useContext(GhostContext);
  return useSelector(context, state => state.usedPart); // 精确打击
};

🧙 性能优化法典(新增条款)

闹鬼场景 驱魔方案 效果
未使用的UI state游荡 组件拆分 隔离在子组件内
纯逻辑state(如计时器ID) useRef镇压 完全隐形
全局state的幽灵扩散 Context选择器 精准狙杀
高频无用state波动 移出React生态 彻底驱散

🔍 五、组件渲染的量子纠缠实验

实验1:Memo的薛定谔防护

jsx 复制代码
const ExpensiveComponent = memo(({ data }) => {
  // 只有data变化时才重渲染
});

// 父组件
const Parent = () => {
  const [state] = useState();
  return <ExpensiveComponent data={state} />; 
  // 👆父组件重渲染时,子组件不会跟着渲染
};

实验2:useMemo的时间结界

jsx 复制代码
const heavyData = useMemo(() => {
  return computeHeavyData(); // 依赖项不变时缓存结果
}, [deps]);

return <Chart data={heavyData} />; 

实验3:useCallback的克隆人军团

jsx 复制代码
const onClick = useCallback(() => {
  // 依赖项不变时保持函数引用
}, [deps]);

return <Button onClick={onClick} />;

💣 六、高频作死案例现场

作死案例1:在渲染中创建新对象

jsx 复制代码
// 每次渲染都创建新style对象
<div style={{ color: 'red' }}> 
  <ChildComponent /> // 即使Child是memo也会重渲染
</div>

解法:将style提升到组件外或使用useMemo

作死案例2:匿名函数轰炸机

jsx 复制代码
// 每次渲染都生成新函数
<Button onClick={() => handleClick()} />

// 正确做法
const handleClick = useCallback(() => {...}, []);
<Button onClick={handleClick} />

作死案例3:无脑Context

jsx 复制代码
// 把整个state对象放入Context
<AppContext.Provider value={{ state, setState }}>
  {/* 任何state变化都会触发所有消费者重渲染 */}
</AppContext.Provider>

// 正确做法:拆分Context
<UserContext.Provider value={user}>
<CartContext.Provider value={cart}>

🚀 七、性能优化九阳神功

第一式:组件记忆术

jsx 复制代码
// 用memo包裹组件
const UserCard = memo(({ user }) => {
  return <div>{user.name}</div>;
});

第二式:道具稳定符

jsx 复制代码
// 用useMemo稳定props
const userData = useMemo(() => transformData(rawData), [rawData]);
return <Profile data={userData} />;

第三式:时间切片大法

jsx 复制代码
// 用startTransition标记非紧急更新
const [tab, setTab] = useState('home');

function switchTab(nextTab) {
  startTransition(() => {
    setTab(nextTab); // 低优先级更新
  });
}

🎙️ 面试官の终极大招

面试官:(突然掏出三个iPhone)如果这三个手机同时运行React应用,分别出现:

  1. 疯狂重渲染
  2. 状态不同步
  3. 性能雪崩 要怎么快速定位问题?

:(战术擦汗)可能需要:

  1. 用React DevTools的Profiler抓重渲染元凶
  2. 检查state是否被意外篡改
  3. 上memo、useMemo、虚拟列表三连

不过...(突然抢过手机)您这三个都是模型机啊!


后记:后来发现,真正的"幽灵state"其实是产品经理半夜改需求时偷偷加的那些...(逃)

相关推荐
喝拿铁写前端8 分钟前
从圣经Babel到现代编译器:没开玩笑,普通程序员也能写出自己的编译器!
前端·架构·前端框架
HED14 分钟前
VUE项目发版后用户访问的仍然是旧页面?原因和解决方案都在这啦!
前端·vue.js
拉不动的猪35 分钟前
前端自做埋点,我们应该要注意的几个问题
前端·javascript·面试
王景程1 小时前
如何测试短信接口
java·服务器·前端
安冬的码畜日常1 小时前
【AI 加持下的 Python 编程实战 2_10】DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(中)
开发语言·前端·人工智能·ai·扫雷游戏·ai辅助编程·辅助编程
烛阴1 小时前
Node.js中必备的中间件大全:提升性能、安全与开发效率的秘密武器
javascript·后端·express
小杨升级打怪中1 小时前
前端面经-JS篇(三)--事件、性能优化、防抖与节流
前端·javascript·xss
清风细雨_林木木1 小时前
Vue开发网站会有“#”原因是前端路由使用了 Hash 模式
前端·vue.js·哈希算法
鸿蒙布道师2 小时前
OpenAI为何觊觎Chrome?AI时代浏览器争夺战背后的深层逻辑
前端·人工智能·chrome·深度学习·opencv·自然语言处理·chatgpt
袈裟和尚2 小时前
如何在安卓平板上下载安装Google Chrome【轻松安装】
前端·chrome·电脑