React这玩意天生就是直肠子,从一个节点开始,以及后续的子组件,当触发状态改变的时候,不管子组件有没有用父组件这老东西,他都要更新一遍,以至于在diff对比这块速度是比不上Vue,vue的依赖收集机制,可以知道知道更新的数据,以及触发对应 的组件渲染,那么react在更新组件这块就依赖于咱手动来控制,控制什么呢,
- Props
坡破死,这玩意在日常开发过程中是用的最多的,编写受控组件、页面交互基本离不开他。我们直接用最简单的模板做测试
javascript
// APP.tsx
function App() {
console.log(count, "App函数执行");
useEffect(() => {
console.log(count, "APP挂载");
}, []);
useEffect(() => {
console.log(count, "APPcount变化");
}, [count]);
const [count, setCount] = useState(0);
return (
<>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
</div>
<Test></Test>
</>
);
}
javascript
import React from "react";
export const Test = ({ count, onChange }) => {
console.log(111, "Test组件执行");
useEffect(() => {
console.log(222, "Test组件挂载");
}, []);
return <div>Test</div>;
};
可以点击count 加之后,我的Test组件明明没有用到父组件的Count 变量还是被重新渲染了,这是为嘛呢
- react 的更新由于state count值发生变化,导致触发了函数重新渲染
- 那么这玩意是个函数啊,当他执行到Test组件的时候,就又会去重新执行一遍Test函数,所以人家又执行一遍啊,没毛病
可是... 雪啊 飘进双眼..
咳咳!
但是咱都是有追求的人,高质量程序搬砖师,婶能忍,叔不能忍啊,所以第一招,就是memo啊,这函数怎么念起来有点emo
memo
我们在test组件上包一层memo
javascript
export const TestMemo = React.memo(() => {
console.log(111, "Test组件执行");
useEffect(() => {
console.log(222, "Test组件挂载");
}, []);
return <div>test</div>;
});
此时count值更新后,就没有test组件渲染的事啦
那emo,做了啥这么牛逼?
在没emo前,test的props相当于也是 {} ,那么每次渲染的时候 {} === {} 是返回false 的,所以react又重新渲染了,加了之后,会进行一层浅比较,为什么是浅呢,我猜是因为效率吧,所以如果你的props里又函数之类的,用了memo 也得用usecallback ,来保证每次函数渲染的时候返回的引用是一样的
useCallback
举个例子
我setCount 包一个函数传递下去,
javascript
//app.tsx
<TestMemo onChange={() => setCount((count) => count + 1)}></TestMemo>
// test.tsx
export const TestMemo = React.memo(({ onChange }) => {
console.log(111, "Test组件执行");
useEffect(() => {
console.log(222, "Test组件挂载");
}, []);
return <div onClick={onChange}>test1</div>;
});
里面点击的时候调用onChange,来触发count更新 组件再次渲染。
看onchange每次都不一样,所以test组件还是又被无辜的渲染了,解决这个问题,就是包一层useCallback咯
javascript
const onChange = useCallback(() => setCount((count) => count + 1), []);
<TestMemo onChange={onChange}></TestMemo>
useMemo
这玩意跟useCallback,只不过他缓存的是值,可以用来缓存组件,如果一个组件有100条数据呢,渲染一次可多费劲啊
javascript
//Test,tsx
export const Test = ({ onChange }) => {
console.log(111, "Test组件执行");
useEffect(() => {
console.log(222, "Test组件挂载");
}, []);
return (
<>
{new Array(10000).fill(1).map((item, index) => {
return (
<div onClick={onChange} key={index}>
Test{index}
</div>
);
})}
</>
);
};
javascript
const TestUsemo = useMemo(() => {
return <Test onChange={()=>void} ></Test>;
}, []);
如果不包裹,那么每次Test渲染都要经历1000次的重新渲染, 这样包裹一个组件,只要deps不发生变化,组件/值就不会渲染。
页面结构解决
编写清晰的页面结构可以解决 性能优化问题,将变的抽离出来 ,让他自己变去,不影响到自组件
javascript
import { useEffect, useState, useCallback, useMemo, memo } from "react";
import reactLogo from "../assets/react.svg";
import viteLogo from "/vite.svg";
const CountComponent: React.FC<object> = (props: React.PropsWithChildren<object>) => {
const [count, setCount] = useState(0);
console.log("CountComponent渲染", count);
useEffect(() => {
console.log("count变化", count);
}, [count]);
return (
<>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">Click on the Vite and React logos to learn more</p>
{props.children}
</>
);
};
const Children = () => {
console.log("子组件渲染");
return (
<>
<div>我是子组件</div>
</>
);
};
function Test1() {
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<CountComponent>
<Children></Children>
</CountComponent>
</>
);
}
export default Test1;
这里没有用任何什么 hooks包裹,但是父组件CountComponent重新渲染后,并没有引入子组件的变化,这里的props.children,内部应该也是做了优化。
所以将变和不变的结构分离出来,比无脑使用各种hooks,性能更好。react在业务中使用,我们更应该去设计合理的props以及清晰的页面结构,不仅能提升性能,也能锻炼自己的设计能力。可能这就是俗称的 "计能" 吧