useRef
1.存储变量
import { useState, useRef } from 'react';
export default function Stopwatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current);
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>
Start
</button>
<button onClick={handleStop}>
Stop
</button>
</>
);
}
注意:不要在组件渲染期间读或者写ref.current! 这违背了React纯函数的设计理念
//错误
function MyComponent() {
// 🚩 Don't write a ref during rendering
myRef.current = 123;
// 🚩 Don't read a ref during rendering
return <h1>{myOtherRef.current}</h1>;
}
//正确
function MyComponent() {
useEffect(() => {
// ✅ You can read or write refs in effects
myRef.current = 123;
});
function handleClick() {
// ✅ You can read or write refs in event handlers
doSomething(myOtherRef.current);
}
}
- 渲染是一种 计算过程 ,它不应该试图“做”其他事(比如获取DOM改变其类名)。
- React 无法保证组件函数以任何特定的顺序执行,因此你无法通过设置变量在它们之间进行通信。所有的交流都必须通过 props 进行。
2.操作Dom
当你将 ref 放在像 <input /> 这样输出浏览器元素的内置组件上时,React 会将该 ref 的 current 属性设置为相应的 DOM 节点(例如浏览器中实际的 <input /> )。
但是,如果你尝试将 ref 放在 你自己的 组件上,例如 <MyInput />,默认情况下你会得到 null。这个示例演示了这种情况。
import { useRef } from 'react';
//错误
function MyInput(props) {
return <input {...props} />;
}
export default function MyForm() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
聚焦输入框
</button>
</>
);
}
//正确
//一般来说react不允许自己创建的组件暴露自内部的DOM,但是子组件包裹上forwardRef就可以了
import { forwardRef, useRef } from 'react';
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
聚焦输入框
</button>
</>
);
}
3.避免重复创建ref里面内容
//若new VideoPlayer()耗时较长,不合适
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
//较优方案——不建议在渲染期间修改ref,但该处仅初始化执行一次,无伤大雅
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
useCallback
1.避免组件的重渲染
By default, when a component re-renders, React re-renders all of its children recursively.
useMemo caches the result of calling your function.
useCallback caches the function itself.
Caching a function with useCallback is only valuable in a few cases:
- You pass it as a prop to a component wrapped in
memo. You want to skip re-rendering if the value hasn’t changed. Memoization lets your component re-render only if dependencies changed. - The function you’re passing is later used as a dependency of some Hook. For example, another function wrapped in
useCallbackdepends on it, or you depend on this function fromuseEffect.
//ProductPage.js
function ProductPage({ productId, referrer, theme }) {
// Tell React to cache your function between re-renders...
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ...so as long as these dependencies don't change...
return (
<div className={theme}>
{/* ...ShippingForm will receive the same props and can skip re-rendering */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
//ShippingForm.js
import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});
2.从记忆回调更新状态
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos([...todos, newTodo]);
}, [todos]);
// ...
VS
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos(todos => [...todos, newTodo]);
}, []); // ✅ No need for the todos dependency
// ...
3.防止副作用被频繁触发
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ Only changes when createOptions changes
// ...
//进一步优化
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
function createOptions() { // ✅ No need for useCallback or function dependencies!
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Only changes when roomId changes
// ...
4.优化自定义hook
useContext
import {useContext,createContext } from "react";
const ThemeContext=createContext(null)
function App() {
return (
<ThemeContext.Provider value={'dark'}>
<Form/>
</ThemeContext.Provider>
);
}
//Form组件及其子组件都可以通过useContext拿到themeContext的值
//const theme=useContext(ThemeContext)