React组件内存泄漏警告 异步表单校验未生效跳转问题排查
问题排查结论
异步校验失效、页面错误跳转原因
- 核心逻辑错误:
isCodeExists是基于fetch的异步函数,在isValid中调用它时,仅挂载了.then回调,但没有等待异步请求返回,就直接同步执行了函数末尾的return true。写在.then里的return false只属于Promise回调的返回值,不会成为外层isValid的返回结果,因此同步校验(正则、前缀判断)通过后,isValid会立刻返回true,handleSubmit拿到结果直接触发nextStep()跳页。 - 时序问题:等接口返回状态码2的时候,组件已经因为跳页被卸载,此时执行
setErrorMessage、setHasError不仅不会渲染错误提示,还会触发React的未挂载组件状态更新警告。 - 次要逻辑错误:接口返回2时设置的错误提示和前缀校验提示完全重复,属于复制代码未修改的笔误,不符合错误提示的对应逻辑。
内存泄漏警告原因
- 跳页后当前
PaperFormPage组件被卸载,但之前发出的fetch请求没有被中断,请求完成后仍会尝试执行状态更新逻辑,React检测到对已卸载组件的状态更新操作,就会抛出内存泄漏警告。 - 额外逻辑瑕疵:
isCodeExists函数开头就重置错误状态,和onUserInput中的重置逻辑重复,容易导致校验过程中错误提示闪烁。
修复方案
- 引入
AbortController管理异步请求,组件卸载时自动取消未完成的请求,解决内存泄漏问题 - 将
isValid、handleSubmit改为异步函数,等待接口校验结果返回后再决定是否允许跳页 - 修正错误提示的对应关系,补全请求异常的分支处理
核心修改后的代码如下:
import React, { useState, useEffect, useRef } from 'react'; import TextboxScannerElement from './TextboxScannerElement'; export default function PaperFormPage(props) { const [pageUserInput, setPageUserInput] = useState([]); const [hasError, setHasError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); // 存储请求控制器,用于取消未完成的请求 const abortControllerRef = useRef(null); // 组件卸载时取消所有未完成请求 useEffect(() => { return () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); } } }, []); useEffect(() => { if (props.pageData !== undefined) { setPageUserInput([...props.pageData]); } }, [props.pageData]); function onUserInput(element, value) { setErrorMessage(''); setHasError(false); if (typeof element === 'string') { let pageUserInputCopy = [...pageUserInput]; let index = pageUserInputCopy.findIndex((obj) => obj.key === element); let itemCopy = { ...pageUserInputCopy[index] }; itemCopy['userInput'] = value; pageUserInputCopy[index] = itemCopy; setPageUserInput(pageUserInputCopy); } } function isCodeExists(userInput) { // 取消上一次重复发起的未完成请求 if (abortControllerRef.current) { abortControllerRef.current.abort(); } abortControllerRef.current = new AbortController(); let code = encodeURIComponent(userInput); const testURL = loris.BaseURL + '/biosample/ajax/validateScannableCode.php' + '?scannable_code=' + code; return fetch( testURL, { credentials: 'include', method: 'GET', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, signal: abortControllerRef.current.signal }) .then((response) => response.json()) .then((responseData) => { return responseData == 2 ? 2 : 1; }) .catch(error => { // 主动取消的请求不需要抛出警告 if (error.name !== 'AbortError') { console.warn(error); } // 请求异常返回0,按校验不通过处理 return 0; }); } async function isValid(userInput) { if (!/^[a-zA-Z0-9]+$/.test(userInput)) { setErrorMessage('Cannot contain non-alphanumeric characters.'); setHasError(true); return false; } if (!/^.{10,}$/.test(userInput)) { setErrorMessage('Must be at least 10 characters in length.'); setHasError(true); return false; } if (!userInput.startsWith('HBCD')) { setErrorMessage('Paper Form ID must begin with "HBCD".'); setHasError(true); return false; } // 等待异步校验结果返回后再判断 const res = await isCodeExists(userInput); if (res == 2) { // 替换为接口返回2对应的实际错误提示 setErrorMessage('This scannable code already exists.'); setHasError(true); return false; } if (res == 0) { setErrorMessage('Validation request failed, please try again.'); setHasError(true); return false; } return true; } async function handleSubmit(e) { e.preventDefault(); const passValidation = await isValid(pageUserInput[0].userInput); if (passValidation) { props.updatePageData(pageUserInput); props.nextStep(); } } const form = pageUserInput.length === 0 ? null : ( <TextboxScannerElement keyStr={pageUserInput[0].key} label={pageUserInput[0].label} name={pageUserInput[0].key} onUserInput={onUserInput} value={pageUserInput[0].userInput} errorMessage={errorMessage} /> ); return ( <> <div className="instructions"> <strong>Instructions:</strong> <p>Scan the Paper Form found in the kit.</p> </div> <FormElement className="FormElement" name="form" fileUpload={false} onSubmit={handleSubmit} onUserInput={onUserInput} > {form} <ButtonElement label="Next" onUserInput={onUserInput} /> </FormElement> </> ); }
内容的提问来源于stack exchange,提问作者LearningUser
相关产品推荐
相关产品推荐

