React Hook
useRef
作用
useRef返回一个包含current属性的对象,并且改变此对象的值不会导致重新渲染。
useRef通常用于以下三种场景:
保存一个值
useRef()保存一个值,并在后面的重新渲染中仍能获取当前的值。
复制 import React, {useState, useRef} from 'react';
export function App(props) {
const [val,setVal] = useState(0)
const s = useRef(1)
const a = {current: 0}
setInterval(()=>{
setVal(val+1)
s.current += 1
a.current += 1
console.log(s,a)
},3000);
return (
<div className='App'>
<div> {val} </div>
</div>
);
}
获取DOM节点
如果要获取真实的DOM节点,那么需要:
ref对象的current属性就指向了该DOM节点,如果节点被删除或不存在,则current为null。
复制 import React, {useState, useRef} from 'react';
export function App(props) {
const divRef = useRef(null)
console.log(divRef)
return (
<div className='App'>
<div ref={divRef}></div>
</div>
);
}
避免重新创建
React 会保存一次初始 ref 值,并在下一次渲染时忽略它。
复制 import React, {useState, useRef} from 'react';
function createData(){
console.log("执行了createData函数")
return 1
}
export function App(props) {
const [val, setVal] = useState(0)
// 每次重新渲染都会执行createData,造成一定的性能浪费
// const a = useRef(createData())
// 可以通过useRef这样来避免重复创建
const a = useRef(null)
if(a.current === null){
a.current = createData();
}
// 调用setVal来触发重新渲染
setInterval(()=>{
setVal(val+1);
},1000)
return (
<div className='App'>
<div></div>
</div>
);
}
useMemo
useMemo是一个react Hook,它的作用是在重新渲染时缓存计算结果,来提升性能。
避免昂贵的重新计算
某些时候我们可能会有某个计算量非常庞大的函数,这个函数执行起来非常的慢,理想情况下是将这个计算的结果缓存起来,只有当某些变量改变时才执行函数重新计算,这个时候就可以使用useMemo。这其实和vue的computed相似。
复制 function slowFunction(num) {
for (let i = 0; i < 10000; i++) {
for (let i = 0; i < 10000; i++) {}
}
return num * 10;
}
const numValue = slowFunction(num);
复制 function slowFunction(num) {
for (let i = 0; i < 10000; i++) {
for (let i = 0; i < 10000; i++) {}
}
return num * 10;
}
const numValue = useMemo(() => slowFunction(num), [num]);
第一种情况,每次组件重新渲染时都会导致slowFunction
函数重新计算,但是在第二种情况下,react会缓存slowFunction
的计算结果,只有当num
改变时,才会重新计算。
useMemo所带来的性能提升可以查看下面示例:
避免子组件重新渲染
当父组件的Props或者state改变时,会导致组件自身和其所有子孙组件都重新渲染,但是有时候我们不需要让子组件重新渲染,这个时候可以使用memo
,通过memo缓存组件,如果该组件的所有Prop都没有改变,则不会重新渲染该组件。
复制 // App.jsx
import "./styles.css";
import Child from "./Child";
import { useState } from "react";
export default function App() {
const [re, reRender] = useState(1);
console.log("父组件更新");
return (
<div className="App">
<h1>避免子组件重新渲染</h1>
<button onClick={() => reRender((n) => n + 1)}>组件重新渲染</button>
<div>
<Child text="使用memo" />
</div>
</div>
);
}
// Child.jsx
import { memo } from "react";
function Child({ text }) {
console.log("子组件渲染");
return <div>Child: {text + "的组件"}</div>;
}
export default memo(Child);
useCallback
useCallback是一个react Hook,用于在重新渲染期间缓存函数声明。
useCallback需要传递两个参数:一个需要被缓存的函数和一个依赖数组。
react会通过 Object.is
来比较上一次渲染时的依赖数组的每一项与当前的值是否相同,如果全部相同则会返回上一次的函数,否则 useCallback
会返回此次渲染的函数。
跳过组件重新渲染
之前说过react可以通过使用memo
来跳过组件重新渲染,以此来优化性能。
复制 // App.jsx
import { useState, memo } from "react";
const Child = memo(function ({ text }) {
console.log("子组件渲染");
return <div>Child: {text + "的组件"}</div>;
});
export default function App() {
const [re, reRender] = useState(1);
console.log("父组件更新");
return (
<div className="App">
<h1>避免子组件重新渲染</h1>
<button onClick={() => reRender((n) => n + 1)}>组件重新渲染</button>
<div>
<Child text="使用memo" />
</div>
</div>
);
}
只有当组件Child的props
有更改的情况下才会重新渲染,否则不会重新渲染组件。
但是有时候memo并不会按照我们想象的情况来工作,当我们传递一个函数作为prop到Child组件时,由于每次App组件重新渲染都会重新执行app组件函数,遇到函数声明或者箭头函数等都会创建一个新的函数,这时memo通过Object.is比较两个函数时会认为prop更改了,就会导致memo优化失效。
复制 import { useState, memo } from "react";
const Child = memo(function ({ fn}) {
return <div>Child</div>;
});
export default function App() {
const [num, setNum] = useState(1)
// 每当App函数重新执行时遇到函数声明都会创建一个新的函数
function fn(num){ return num * 2 }
console.log("父组件更新");
return (
<div className="App">
<h1>避免子组件重新渲染</h1>
<button onClick={() => setNum((n) => n + 1)}>组件重新渲染</button>
<div>
<Child fn={fn} />
</div>
</div>
);
}
这个时候需要利用useCallback
缓存函数,将num
作为依赖项,只有当num
改变时才会重新执行函数声明,创建一个新的函数。
复制 import { useState, memo } from "react";
const Child = memo(function ({ fn}) {
return <div>Child</div>;
});
export default function App() {
const [num, setNum] = useState(1)
// 只有当num改变时才会重新执行函数声明创建一个新的函数。
const fn = useCallback(function fn(num){ return num * 2 }, [num])
console.log("父组件更新");
return (
<div className="App">
<h1>避免子组件重新渲染</h1>
<button onClick={() => setNum((n) => n + 1)}>组件重新渲染</button>
<div>
<Child fn={fn} />
</div>
</div>
);
}
从缓存函数中更新状态
有时,我们可能需要在缓存回调函数中基于之前的值来更新状态。
复制 import { useState, useCallback } from "react";
function Child({ todo }) {
const [num, setNum] = useState(1);
const fn = useCallback(
function () {
const n = num;
setNum(n + todo);
},
[num]
);
return (
<div>
<h3>num: {num}</h3>
<button onClick={fn}>num+1</button>
</div>
);
}
这看起来和useState更新状态很像,例如:
复制 const fn = () => {
setNum((n) => n + todo);
};
这也能基于num
上一次渲染的值来更新num
状态,但是这里的todo
却并不是上一次渲染的值,如果我们需要根据上一次渲染的todo
和num
来更新num
,我们需要useCallback
。
复制 // 只有当num更新后,缓存函数中的todo才会获取最新值。
const fn = useCallback(
function () {
const n = num;
setNum(n + todo);
},
[num]
);
避免Effect频繁执行
以下面的代码为例:
复制 import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [re, reRender] = useState(0);
const [roomId, setRoomId] = useState(0);
function createOptions() {
return {
serverUrl: "https://localhost:1234",
roomId: roomId
};
}
useEffect(() => {
const options = createOptions();
// ...
console.log("useEffect执行");
}, [createOptions]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => reRender((n) => n + 1)}>重新渲染</button>
</div>
);
}
当组件重新渲染后,会声明一个新的createOptions
函数,这就导致了useEffect
会重复执行。
复制 const createOptions = useCallback(
function createOptions() {
return {
serverUrl: "https://localhost:1234",
roomId
};
},
[roomId]
);
useEffect(() => {
const options = createOptions();
// ...
console.log("useEffect执行");
}, [createOptions]);
当然,也可以直接将函数声明放到useEffect
中。
复制 useEffect(() => {
function createOptions() {
return {
serverUrl: "https://localhost:1234",
roomId
};
}
const options = createOptions();
// ...
console.log("useEffect执行");
}, [roomId]);
useReducer