面试官问我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"其实是产品经理半夜改需求时偷偷加的那些...(逃)

相关推荐
吞掉星星的鲸鱼27 分钟前
使用高德api实现天气查询
前端·javascript·css
lilye6630 分钟前
程序化广告行业(55/89):DMP与DSP对接及数据统计原理剖析
java·服务器·前端
....49230 分钟前
Vue3 + Element Plus + AntV X6 实现拖拽树组件
javascript·vue.js·elementui·antvx6
zhougl9962 小时前
html处理Base文件流
linux·前端·html
花花鱼2 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_3 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo4 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
TDengine (老段)4 小时前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
杉之5 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端5 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端