🚥 马路边的面试奇遇
面试官:(突然指向路口的红绿灯)你看这个红灯变绿,像不像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应用,分别出现:
- 疯狂重渲染
- 状态不同步
- 性能雪崩 要怎么快速定位问题?
我:(战术擦汗)可能需要:
- 用React DevTools的Profiler抓重渲染元凶
- 检查state是否被意外篡改
- 上memo、useMemo、虚拟列表三连
不过...(突然抢过手机)您这三个都是模型机啊!
后记:后来发现,真正的"幽灵state"其实是产品经理半夜改需求时偷偷加的那些...(逃)