二十五、React Hooks
大约 5 分钟约 1586 字
(一)useMemo
- 引入
useMemo()
解决sum()
执行过慢导致count
增加慢的问题 - 作用:缓存函数执行结果
useCallback()
缓存函数对象本身
- 组件也是函数,可以缓存组件渲染结果
src/index.js
1. import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
src/App.js
2. import React, { useState } from "react";
import { useMemo } from "react";
import Some from "./components/Some";
function sum(a, b) {
console.log("App---sum");
// 模拟复杂函数
const begin = Date.now();
while (1) {
if (Date.now() - begin > 3000) break;
}
return a + b;
}
const App = () => {
// 触发组件渲染
const [count, setCount] = useState(1);
// sum函数每次组件渲染都会执行
// const result = sum(123, 456);
// 模拟变量
let a = 123,
b = 456;
if (count % 10 === 0) a += count;
const result = useMemo(() => {
return sum(a, b);
}, [a, b]);
const someEle = useMemo(() => {
return <Some a={a} b={b} />;
}, [a, b]);
return (
<div>
<h1>App</h1>
<h2>result: {result}</h2>
<h2>count: {count}</h2>
<button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
<hr />
{someEle}
</div>
);
};
export default App;
src/components/Some.js
3. import React from "react";
const Some = (props) => {
// 模拟复杂组件
const begin = Date.now();
while (1) {
if (Date.now() - begin > 3000) break;
}
return (
<div>
<h2>Some</h2>
<h3>{props.a + props.b}</h3>
</div>
);
};
export default Some;
(二)useImperativeHandle
src/App.js
1. import React, { useState } from "react";
import { useEffect } from "react";
import { useRef } from "react";
import Some from "./components/Some";
const App = () => {
const [count, setCount] = useState(1);
const someRef = useRef();
useEffect(() => {
// console.log(someRef);
// someRef.current.innerText = "Some" + count;
someRef.current.changeInpValue(count);
}, [count]);
return (
<div>
<h1>App</h1>
<h2>count: {count}</h2>
<button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
<hr />
{/*
无法直接获取react组件的dom对象
=> 因为组件内部有多个标签,需要配合forwardRef使用
=> 组件属性不再由组件本身控制,应配合useImperativeHandle()使用
*/}
<Some ref={someRef} />
</div>
);
};
export default App;
src/components/Some.js
2. import React, { useRef, useImperativeHandle } from "react";
// const Some = () => {
// const inputRef = useRef();
// const clickHandler = () => {
// console.log(inputRef.current.value);
// };
// return (
// <div>
// <h2>Some</h2>
// <input type="text" ref={inputRef} />
// <button onClick={clickHandler}>提交</button>
// </div>
// );
// };
/*
forwardRef用于指定组件向外部暴露的ref(父组件访问子组件的ref值)
参数1:props
参数2:ref,父组件想访问的ref
【最好不用,组件属性脱离了组件本身的控制】
*/
// const Some = React.forwardRef((props, ref) => {
// const inputRef = useRef();
// const clickHandler = () => {
// console.log(inputRef.current.value);
// };
// return (
// <div>
// <h2 ref={ref}>Some</h2>
// <input type="text" ref={inputRef} />
// <button onClick={clickHandler}>提交</button>
// </div>
// );
// });
/*
配合useImperativeHandle()钩子保证组件属性由组件控制
用于指定ref返回的值
参数1:暴露给外部组件的ref值
参数2:回调函数,该函数返回值会成为ref的值,通常返回一个操作DOM对象的方法
*/
const Some = React.forwardRef((props, ref) => {
const inputRef = useRef();
const clickHandler = () => {
console.log(inputRef.current.value);
};
useImperativeHandle(ref, () => {
return {
changeInpValue(val) {
inputRef.current.value = val;
},
};
});
return (
<div>
<h2>Some</h2>
<input type="text" ref={inputRef} />
<button onClick={clickHandler}>提交</button>
</div>
);
});
export default Some;
(三)三个 effect
- 执行顺序:
useInsertionEffect
=>useLayoutEffect
=>useEffect
- React18 中
useLayoutEffect
和useEffect
区别微乎其微,因为useEffect
执行时机会动态调整 - 开发中通常先用
useEffect
,当它不满足需求时再考虑另外两个钩子
src/App.js
1. import React from "react";
import { useState, useEffect, useLayoutEffect, useInsertionEffect, useRef } from "react";
import Some from "./components/Some";
const App = () => {
const [count, setCount] = useState(1);
const h3Ref = useRef();
// 绘制屏幕之后执行
useEffect(() => {
console.log("useEffect", h3Ref);
});
// DOM改变之后绘制屏幕之前执行
useLayoutEffect(() => {
console.log("useLayoutEffect", h3Ref);
});
// DOM改变之前执行,用于向DOM对象插入样式
useInsertionEffect(() => {
console.log("useInsertionEffect", h3Ref);
});
return (
<div>
<h1>App</h1>
<h3 ref={h3Ref}>count: {count}</h3>
<button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
<hr />
<Some />
</div>
);
};
export default App;
src/components/Some.js
2. import React, { useRef } from "react";
const Some = () => {
const inputRef = useRef();
const clickHandler = () => {
console.log(inputRef.current.value);
};
return (
<div>
<h2>Some</h2>
<input type="text" ref={inputRef} />
<button onClick={clickHandler}>提交</button>
</div>
);
};
export default Some;
(四)useDebugValue
- 用来给自定义钩子设置标签,标签会在
React
开发工具中显示 - 用来区分调试自定义钩子,不常用
src/App.js
1. import React from "react";
import { useState } from "react";
import useMyHook from "./hooks/useMyHook";
const App = () => {
const [count, setCount] = useState(1);
useMyHook();
return (
<div>
<h1>App</h1>
<h3>count: {count}</h3>
<button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
</div>
);
};
export default App;
src/hooks/useMyHook.js
2. import { useEffect, useDebugValue } from "react";
const useMyHook = () => {
useDebugValue("哈哈哈");
useEffect(() => {
console.log("自定义钩子的代码");
});
};
export default useMyHook;
(五)useDeferredValue
useDeferredValue()
- 为传入的
state
创建一个延迟值 - 参数:
state
- 为传入的
- 设置延迟值后,每次
state
修改时都会触发两次组件渲染- 这两次渲染对其他部分没有区别,但是延迟值会变化,两次值分别是
state
的旧值和新值
- 这两次渲染对其他部分没有区别,但是延迟值会变化,两次值分别是
- 延迟值总会比原版的
state
慢一步更新
src/App.js
1. import React from "react";
import { useState, useDeferredValue } from "react";
import StudentList from "./components/StudentList";
const App = () => {
console.log("组件重新渲染了~~~");
const [count, setCount] = useState(1);
const deferredCount = useDeferredValue(count);
console.log(count, deferredCount);
/*
多个组件用到同一个state时,某一个组件的卡顿会导致所有组件不能及时渲染
此时可以给延迟组件设置为延迟值,同时使用React.memo()包裹,延迟值变化时再渲染
*/
const [filterWord, setFilterWord] = useState("");
const changeHandler = (e) => {
setFilterWord(e.target.value);
};
const deferredFilterWord = useDeferredValue(filterWord);
return (
<div>
<h1>App</h1>
<h3>count: {count}</h3>
<button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
<hr />
<input type="text" value={filterWord} onChange={changeHandler} />
<StudentList filterWord={deferredFilterWord} />
</div>
);
};
export default App;
src/components/StudentList.js
2. import React from "react";
const STU_DATA = [];
for (let i = 1; i < 101; i++) {
STU_DATA.push("学生" + i);
}
const StudentList = (props) => {
const stu = STU_DATA.filter((item) => item.indexOf(props.filterWord) !== -1);
// 模拟组件卡顿
const begin = Date.now();
while (1) {
if (Date.now() - begin > 3000) break;
}
return (
<div>
<ul>
{stu.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
};
export default React.memo(StudentList);
(六)useTransition 和 useId
startTransition
- 在回调函数中设置
setState
,会在其他setState
生效后再执行,也可以优化卡顿问题
- 在回调函数中设置
useId()
- 生成唯一
id
,使用于需要唯一id
的场景,但不适用于列表的key
- 生成唯一
src/App.js
1. import React from "react";
import { useState, useTransition, useId } from "react";
// import { startTransition } from "react";// 使用方法一
import StudentList from "./components/StudentList";
const App = () => {
console.log("组件重新渲染了~~~");
const [count, setCount] = useState(1);
// 多个组件用到同一个state时,某一个组件的卡顿会导致所有组件不能及时渲染
const [filterWord, setFilterWord] = useState("");
const [filterWord2, setFilterWord2] = useState("");
// 使用方法二
const [isPending, startTransition] = useTransition();
console.log(isPending);
const changeHandler = (e) => {
setFilterWord(e.target.value);
startTransition(() => {
setFilterWord2(e.target.value);
});
};
const id = useId();
console.log(useId(), useId());
return (
<div>
<h1>App</h1>
<h3>count: {count}</h3>
<button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
<hr />
<label htmlFor={"keyword" + id}>关键词</label>
<input id={"keyword" + id} type="text" value={filterWord} onChange={changeHandler} />
{!isPending && <StudentList filterWord={filterWord2} />}
</div>
);
};
export default App;
src/components/StudentList.js
2. import React from "react";
const STU_DATA = [];
for (let i = 1; i < 101; i++) {
STU_DATA.push("学生" + i);
}
const StudentList = (props) => {
const stu = STU_DATA.filter((item) => item.indexOf(props.filterWord) !== -1);
// 模拟组件卡顿
const begin = Date.now();
while (1) {
if (Date.now() - begin > 3000) break;
}
return (
<div>
<ul>
{stu.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
};
export default React.memo(StudentList);