react问一问
文章目录
- 前言
- 1. 在 `useEffect` 中,空数组 `[]` 作为依赖数组的作用
- 具体解释:
- 例子:
- 代码解析:
- 总结:
- 2. 为什么不推荐在 `setTimeout` 中直接使用 `setState` ?
- 解决方法
- 总结
- 3. **`useReducer` 和 `useMemo` 的基本概念**
- 2. **`useReducer` 和 `useMemo` 结合使用**
- 4.结合 `useReducer` 和 `useMemo` 使用
- 代码示例:
- **为什么要这样结合使用?**
- **什么时候结合使用 `useReducer` 和 `useMemo`?**
- 总结
前言
随笔记录react面试题
1. 在 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
在组件首次渲染时执行,且只执行一次。- 设置一个 2 秒的定时器,2 秒后更新
count
和loading
状态。 - 在组件卸载时(如页面跳转、组件被移除),会清除定时器,避免内存泄漏。
- 设置一个 2 秒的定时器,2 秒后更新
总结:
- 空数组
[]
表示useEffect
的副作用只会在组件首次挂载时执行一次,后续的更新不会重新执行这个副作用函数。 - 这种方式通常用于需要在组件挂载时执行一次的操作,比如设置定时器、发起 API 请求、订阅事件等。
在 React 中,setState
是一个异步操作,它会触发组件的重新渲染。当你在 setTimeout
或任何异步函数中使用 setState
时,可能会遇到一些意想不到的行为,尤其是在组件已经卸载的情况下。这是因为 React 可能会在 setTimeout
完成后,仍然尝试执行 setState
,而组件可能已经被卸载或没有完全渲染。
2. 为什么不推荐在 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);// 组件卸载时清除定时器并设置标志为 falsereturn () => {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; // 组件挂载时设置为 trueconst 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 的警告信息。
3. useReducer
和 useMemo
的基本概念
-
useReducer
:- 用于管理复杂的组件状态,尤其是状态逻辑比较复杂或涉及多个子状态的场景。
- 类似于 Redux 的 reducer,
useReducer
接受一个 reducer 函数和初始状态,并返回当前状态和一个dispatch
函数,用于更新状态。 useReducer
是基于 “action” 和 “state” 的逻辑来更新状态的,它提供了一种更加结构化的方式来管理状态,适用于大型或复杂组件。
-
useMemo
:- 用于 缓存 计算结果,避免重复执行昂贵的计算。
- 它会记住传递给它的函数的结果,只有当依赖项发生变化时,才会重新计算。这样可以减少不必要的计算,提升性能。
useMemo
不会改变组件的状态,只会优化组件的渲染过程,特别是当计算值比较昂贵时。
2. useReducer
和 useMemo
结合使用
useReducer
和 useMemo
可以结合使用,但它们各自的职责不同:useReducer
用于状态管理,而 useMemo
用于性能优化。
-
使用场景:
useReducer
通常用于复杂的状态管理,比如多个相关的状态、需要特定的更新逻辑等。useMemo
用于缓存某些值,避免重复的昂贵计算,或者当状态变化时只重新计算某些特定的部分。
4.结合 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><inputtype="text"value={state.firstName}onChange={(e) => dispatch({ type: 'SET_FIRST_NAME', payload: e.target.value })}placeholder="First Name"/><inputtype="text"value={state.lastName}onChange={(e) => dispatch({ type: 'SET_LAST_NAME', payload: e.target.value })}placeholder="Last Name"/><inputtype="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
用于缓存计算结果,减少昂贵计算的频繁执行,提升性能。- 这两个 Hook 可以结合使用,通过
useReducer
管理状态和通过useMemo
缓存计算结果,优化性能,减少不必要的重新渲染和计算。