本文已参与「新人创作礼」活动,一起开启掘金创作之路。
因某些原因一直未能详细阅读React官网,近期仔细阅读之后发现......
性能优化
在依赖列表中省略函数是否安全?
function Example({ someProp }) {
function doSomething() {
console.log(someProp);
}
useEffect(() => {
doSomething();
}, []); // 🔴 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)}
经常习惯性的把函数写在useEffect外部,然后在useEffect内部调用,今天才发现这样写虽然不会出什么问题但是会影响性能,正确写法如下
function Example({ someProp }) {
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]); // ✅ 安全(我们的 effect 仅用到了 `someProp`)}
错误的数据请求方式
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
async function fetchProduct() {
const response = await fetch('http://myapi/product/' + productId); // 使用了 productId prop const json = await response.json();
setProduct(json);
}
useEffect(() => {
fetchProduct();
}, []); // 🔴 这样是无效的,因为 `fetchProduct` 使用了 `productId` // ...
}
正确的数据请求方式
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
useEffect(() => {
// 把这个函数移动到 effect 内部后,我们可以清楚地看到它用到的值。
async function fetchProduct() {
const response = await fetch('http://myapi/product/' + productId);
const json = await response.json();
setProduct(json);
}
fetchProduct();
}, [productId]); // ✅ 有效,因为我们的 effect 只用到了 productId // ...
}
优化过后的数据请求
这同时也允许你通过 effect 内部的局部变量来处理无序的响应:
useEffect(() => {
let ignore = false;
async function fetchProduct() {
const response = await fetch('http://myapi/product/' + productId);
const json = await response.json();
if (!ignore) setProduct(json);
}
fetchProduct();
return () => { ignore = true };
}, [productId]);
看看 这个小 demo 和 这篇文章 来了解更多关于如何用 Hook 进行数据获取。
如果我的 effect 的依赖频繁变化,我该怎么办?
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 这个 effect 依赖于 `count` state
}, 1000);
return () => clearInterval(id);
}, []); // 🔴 Bug: `count` 没有被指定为依赖
return <h1>{count}</h1>; // count增加一次之后不再增加
}
指定 [count] 作为依赖列表就能修复这个 Bug,但会导致每次改变发生时定时器都被重置。事实上,每个 setInterval 在被清除前(类似于 setTimeout)都会调用一次。但这并不是我们想要的。要解决这个问题,我们可以使用 setState 的函数式更新形式。它允许我们指定 state 该 如何 改变而不用引用 当前 state:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量
}, 1000);
return () => clearInterval(id);
}, []); // ✅ 我们的 effect 不适用组件作用域中的任何变量
return <h1>{count}</h1>;
}
也可以使用Ref去保存state,也可以获取到最新值
function Example(props) {
// 把最新的 props 保存在一个 ref 中
const latestProps = useRef(props);
useEffect(() => { latestProps.current = props; });
useEffect(() => {
function tick() {
// 在任何时候读取最新的 props
console.log(latestProps.current);
}
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []); // 这个 effect 从不会重新执行
}
我该如何实现 shouldComponentUpdate?
你可以用 React.memo 包裹一个组件来对它的 props 进行浅比较:
function Child({seconds}){
console.log('I am rendering');
return (
<div>I am update every {seconds} seconds</div>
)
};
export default React.memo(Child)
只有当seconds发生变化时才会更新
请参考 小dome
如何记忆计算结果?
useMemo 也允许你跳过一次子节点的昂贵的重新渲染:
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
只有当a发生变化时child1||child2才会重新加载,未发生变化时采用上一次的计算结果进行渲染
如何惰性创建昂贵的对象?
第一个常见的使用场景是当创建初始 state 很昂贵时:
错误的创建state的方式
function Table(props) {
// ⚠️ createRows() 每次渲染都会被调用,多次调用会影响性能
const [rows, setRows] = useState(createRows(props.count));
// ...
}
正确的创建state的方式
为避免重新创建被忽略的初始 state,我们可以传一个 函数 给 useState,在函数中去调用
function Table(props) {
// ✅ createRows() 只会被调用一次
const [rows, setRows] = useState(() => createRows(props.count));
// ...
}
避免重新创建 useRef() 的初始值:
错误的初始化ref的方式
function Image(props) {
// ⚠️ IntersectionObserver 在每次渲染都会被创建
const ref = useRef(new IntersectionObserver(onIntersect));
// ...
}
正确的创建ref的方式
useRef 不会 像 useState 那样接受一个特殊的函数重载。相反,你可以编写你自己的函数来创建并将其设为惰性的:
function Image(props) {
const ref = useRef(null);
// ✅ IntersectionObserver 只会被惰性创建一次
function getObserver() {
if (ref.current === null) {
ref.current = new IntersectionObserver(onIntersect);
}
return ref.current;
}
// 当你需要时,调用 getObserver()
// ...
}