随笔记录react面试题
useEffect
中,空数组 []
作为依赖数组的作用在 useEffect
中,空数组 []
作为依赖数组的作用是告诉 React 该副作用函数(useEffect
)只会在组件的 首次渲染 时执行一次,且 不再执行 后续的渲染。简单来说,空数组表示 “没有依赖”,所以这个副作用函数不会响应任何变化,只会在组件加载时执行。
useEffect
的基本工作原理:
useEffect
接受两个参数:
useEffect
会监视数组中的变量,一旦其中的任何一个变量发生变化,副作用函数就会重新执行。空数组 []
的作用:
当你传递一个空数组 []
作为 useEffect
的第二个参数时,React 会将其视为没有任何依赖项。这意味着副作用函数只会在 组件首次挂载时执行一次,并且 不再执行。也就是说,只有组件加载时才会执行 setTimeout
设置定时器,之后组件的任何重新渲染都不会触发 setTimeout
的重新执行。
常见用途:
使用空数组通常用于以下几种情况:
import { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false);
useEffect(() => {
// 组件挂载时设置定时器
const timer = setTimeout(() => {
setLoading(true);
setCount(prevCount => prevCount + 1);
}, 2000);
// 组件卸载时清除定时器
return () => clearTimeout(timer);
}, []); // 依赖数组为空,意味着只会在组件首次渲染时执行
return (
<div>
{loading ? <p>Loaded!</p> : <p>Loading...</p>}
<p>Count: {count}</p>
</div>
);
}
useEffect(() => {...}, [])
: 这个 useEffect
在组件首次渲染时执行,且只执行一次。
count
和 loading
状态。[]
表示 useEffect
的副作用只会在组件首次挂载时执行一次,后续的更新不会重新执行这个副作用函数。在 React 中,setState
是一个异步操作,它会触发组件的重新渲染。当你在 setTimeout
或任何异步函数中使用 setState
时,可能会遇到一些意想不到的行为,尤其是在组件已经卸载的情况下。这是因为 React 可能会在 setTimeout
完成后,仍然尝试执行 setState
,而组件可能已经被卸载或没有完全渲染。
setTimeout
中直接使用 setState
?异步行为导致的不确定性:
React 的 setState
是异步的,它不会立即更新状态,而是将更新排入队列。setTimeout
本身也是异步的,因此在 setTimeout
中直接调用 setState
可能导致不可预测的行为,尤其是在多个异步操作之间。
组件卸载后调用 setState
会产生警告:
如果组件在 setTimeout
调用的 setState
之前已经卸载,React 会给出警告:Can't perform a React state update on an unmounted component
。如果你试图在卸载后的组件上调用 setState
,会导致内存泄漏。
清除定时器:
在 setTimeout
中使用 setState
时,最好的实践是确保组件在 setState
执行时仍然挂载。你可以通过在组件卸载时清除定时器来避免这种问题。
import { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false);
useEffect(() => {
// 组件挂载时设置定时器
const timer = setTimeout(() => {
setLoading(true);
setCount(prevCount => prevCount + 1);
}, 2000);
// 组件卸载时清除定时器
return () => clearTimeout(timer);
}, []);
return (
<div>
{loading ? <p>Loaded!</p> : <p>Loading...</p>}
<p>Count: {count}</p>
</div>
);
}
useEffect
中创建定时器,并且在返回的清理函数中清除它,以防组件在定时器执行之前卸载。在异步函数中检查组件是否仍然挂载:
如果你需要在异步操作(如 setTimeout
、fetch
请求等)中执行 setState
,可以使用一个标志来检查组件是否仍然挂载。
import { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false);
useEffect(() => {
let isMounted = true; // 标志位,记录组件是否挂载
const timer = setTimeout(() => {
if (isMounted) {
setLoading(true);
setCount(prevCount => prevCount + 1);
}
}, 2000);
// 组件卸载时清除定时器并设置标志为 false
return () => {
clearTimeout(timer);
isMounted = false;
};
}, []);
return (
<div>
{loading ? <p>Loaded!</p> : <p>Loading...</p>}
<p>Count: {count}</p>
</div>
);
}
isMounted
标志位,确保只有在组件挂载时才调用 setState
。使用 useRef
来保存组件挂载状态:
使用 useRef
可以更简洁地保存组件是否挂载的状态,并且它不会触发组件重新渲染。
import { useState, useEffect, useRef } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false);
const isMounted = useRef(false); // 使用 useRef 保存挂载状态
useEffect(() => {
isMounted.current = true; // 组件挂载时设置为 true
const timer = setTimeout(() => {
if (isMounted.current) {
setLoading(true);
setCount(prevCount => prevCount + 1);
}
}, 2000);
return () => {
clearTimeout(timer);
isMounted.current = false; // 组件卸载时设置为 false
};
}, []);
return (
<div>
{loading ? <p>Loaded!</p> : <p>Loading...</p>}
<p>Count: {count}</p>
</div>
);
}
useRef
可以避免每次组件重新渲染时都检查挂载状态,因此相对更高效。setTimeout
中直接调用 setState
可能导致在组件卸载后调用 setState
,引发警告或错误。isMounted
标志位,或使用 useRef
来确保组件仍然挂载,避免内存泄漏和警告。这些做法确保了异步操作不会影响已卸载的组件,避免了 React 的警告信息。
useReducer
和 useMemo
的基本概念useReducer
:
useReducer
接受一个 reducer 函数和初始状态,并返回当前状态和一个 dispatch
函数,用于更新状态。useReducer
是基于 “action” 和 “state” 的逻辑来更新状态的,它提供了一种更加结构化的方式来管理状态,适用于大型或复杂组件。useMemo
:
useMemo
不会改变组件的状态,只会优化组件的渲染过程,特别是当计算值比较昂贵时。useReducer
和 useMemo
结合使用useReducer
和 useMemo
可以结合使用,但它们各自的职责不同:useReducer
用于状态管理,而 useMemo
用于性能优化。
使用场景:
useReducer
通常用于复杂的状态管理,比如多个相关的状态、需要特定的更新逻辑等。useMemo
用于缓存某些值,避免重复的昂贵计算,或者当状态变化时只重新计算某些特定的部分。useReducer
和 useMemo
使用假设你有一个复杂的表单组件,其中有多个状态和一些计算依赖于当前的状态。在这种情况下,useReducer
用于管理复杂的表单状态,而 useMemo
用于缓存一些昂贵的计算结果,减少不必要的渲染。
import React, { useReducer, useMemo } from 'react';
// 初始化状态
const initialState = {
firstName: '',
lastName: '',
age: 0,
};
// 定义 reducer 函数
function reducer(state, action) {
switch (action.type) {
case 'SET_FIRST_NAME':
return { ...state, firstName: action.payload };
case 'SET_LAST_NAME':
return { ...state, lastName: action.payload };
case 'SET_AGE':
return { ...state, age: action.payload };
default:
return state;
}
}
function ComplexForm() {
const [state, dispatch] = useReducer(reducer, initialState);
// 使用 useMemo 缓存计算结果,避免每次重新计算
const fullName = useMemo(() => {
console.log('Calculating full name...');
return `${state.firstName} ${state.lastName}`;
}, [state.firstName, state.lastName]);
const isAdult = useMemo(() => {
console.log('Checking adult status...');
return state.age >= 18;
}, [state.age]);
return (
<div>
<input
type="text"
value={state.firstName}
onChange={(e) => dispatch({ type: 'SET_FIRST_NAME', payload: e.target.value })}
placeholder="First Name"
/>
<input
type="text"
value={state.lastName}
onChange={(e) => dispatch({ type: 'SET_LAST_NAME', payload: e.target.value })}
placeholder="Last Name"
/>
<input
type="number"
value={state.age}
onChange={(e) => dispatch({ type: 'SET_AGE', payload: Number(e.target.value) })}
placeholder="Age"
/>
<div>
<p>Full Name: {fullName}</p>
<p>Adult Status: {isAdult ? 'Adult' : 'Not an Adult'}</p>
</div>
</div>
);
}
export default ComplexForm;
性能优化:通过 useMemo
,我们避免了每次状态更新都重新计算 fullName
和 isAdult
。例如,在用户输入名字时,如果名字没有变化,那么 fullName
就不需要重新计算,从而提高性能。
简化状态管理:useReducer
提供了一种集中式的方式来管理组件状态,使得复杂的状态更新逻辑更加清晰和易于维护。
避免重复计算:useMemo
会缓存计算结果,只有当依赖项(比如 firstName
、lastName
或 age
)发生变化时才重新计算。这意味着即使组件重新渲染,fullName
和 isAdult
也不会重复计算,除非相关状态发生变化。
useReducer
和 useMemo
?useReducer
和 useMemo
会非常有效。useMemo
可以减少不必要的重新计算,而 useReducer
提供了更强的状态管理能力。useReducer
用于管理复杂的状态,特别是需要依赖复杂逻辑或多个状态项时。useMemo
用于缓存计算结果,减少昂贵计算的频繁执行,提升性能。useReducer
管理状态和通过 useMemo
缓存计算结果,优化性能,减少不必要的重新渲染和计算。