React组件代码优化与请求中断问题技术求助
Hey there! Let's tackle your React component issues step by step. I'll help you streamline the code, boost performance, and fix those lingering AJAX requests when the component unmounts.
1. Fix Pending Requests on Component Unmount
The mounted flag approach is a workaround—it skips state updates but doesn't actually cancel the request. To truly stop in-flight requests, we'll use an AbortController (most modern request libraries, including custom ones like myRequestor, support this). Here's how it works:
- Initialize an AbortController in the constructor
- Pass its
signalto each request - Call
abort()incomponentWillUnmountto cancel all pending requests
2. Optimize Performance in submitHandler
Your current code loops through the response array multiple times (find, map, sort). We can cut down on unnecessary iterations and reduce re-renders by:
- Using
some()instead offind()when we only need to check for existence (faster, stops at the first match) - Chaining
map()andsort()into a single operation (cleaner, same performance but more readable) - Merging multiple
setStatecalls into one to avoid extra re-renders
3. Streamline Overall Code
We'll clean up redundant code, simplify state updates, and extract reusable logic to make the component more maintainable:
- Reduce duplicate error-handling code into a helper function
- Simplify array transformations in
fetchInitial - Merge multiple
setStatecalls in event handlers
Optimized Full Code
import React from 'react'; import PropTypes from 'prop-types'; import ApplicationLoadingOverlay from './path-to-ApplicationLoadingOverlay'; // Adjust import paths as needed import ReadinessComponentView from './path-to-ReadinessComponentView'; import Grid from './path-to-Grid'; import Heading from './path-to-Heading'; import Divider from './path-to-Divider'; import Text from './path-to-Text'; import Tenant from './path-to-Tenant'; import ModalManagerExample from './path-to-ModalManagerExample'; import InputField from './path-to-InputField'; import InputElement from './path-to-InputElement'; import Submit from './path-to-Submit'; import Notification from './path-to-Notification'; import { MyRequestorContext } from './path-to-MyRequestorContext'; const propTypes = { name: PropTypes.string, tenant: PropTypes.arrayOf(PropTypes.any), myRequestor: PropTypes.object, // eslint-disable-line react/forbid-prop-types }; let myRequestor = null; const CallerUtil = () => { myRequestor = React.useContext(MyRequestorContext); return <ApplicationLoadingOverlay isOpen backgroundStyle="clear" />; }; class ReadinessComponent extends React.Component { constructor(props) { super(props); this.state = { body: '', tenant: null, error: null, errorMsg: '', isOpen: false, showTable: false, isInvalid: false, isLoading: false, tenantId: '', items: [], tenantItems: null, }; // Initialize AbortController for request cancellation this.abortController = new AbortController(); // Bind methods this.onChangeTenantDropDown = this.onChangeTenantDropDown.bind(this); this.onSubmitHandler = this.onSubmitHandler.bind(this); this.onChangeHandler = this.onChangeHandler.bind(this); this.tenantIdHandler = this.tenantIdHandler.bind(this); this.onClose = this.onClose.bind(this); this.addNewTenant = this.addNewTenant.bind(this); this.fetchInitial = this.fetchInitial.bind(this); this.handleError = this.handleError.bind(this); } componentDidMount() { this.setState({ isLoading: true }); this.fetchInitial(); } componentWillUnmount() { // Cancel all pending requests when component unmounts this.abortController.abort(); } // Helper function to handle error states consistently handleError(errorMsg) { this.setState({ error: 'Failure', errorMsg, isLoading: false, isOpen: true, showTable: false, body: '', tenantId: '', }); } onChangeTenantDropDown(value) { // Find tenant info in one go, then update state once const tenantInfo = this.state.tenantItems.find(t => t.name === value); this.setState({ tenant: value, tenantId: tenantInfo?.id || '', }); } onSubmitHandler() { if (!this.state.body.trim()) { this.setState({ isInvalid: true }); return; } this.setState({ isLoading: true }); const params = { tenantId: this.state.tenantId, tenantShortName: this.state.tenant, contactName: this.state.body, }; const { request } = myRequestor.get({ url: '/getReadinessCheck', params, signal: this.abortController.signal, // Pass abort signal }); request .then(({ data }) => { // Skip logic if request was aborted if (this.abortController.signal.aborted) return; // Use some() instead of find() for faster existence check const hasTenantError = data.some(vrsn => vrsn.name === 'TENANT_ERROR'); if (!hasTenantError) { this.handleError('Invalid Tenant'); return; } // Chain map and sort for cleaner code const readinessResp = data .map(version => version.latest ? { ...version, name: `${version.name}_LATEST` } : version ) .sort((a, b) => { const textA = a.name.toUpperCase(); const textB = b.name.toUpperCase(); return textA.localeCompare(textB); }); // Update state once with all changes this.setState({ items: readinessResp, error: 'Success', showTable: true, isLoading: false, body: '', tenantId: '', }); }) .catch(error => { // Skip error handling if request was aborted if (this.abortController.signal.aborted) return; this.handleError('Failure'); }); } onChangeHandler(event) { this.setState({ body: event.target.value, isInvalid: false }); // Reset invalid state on input } tenantIdHandler(event) { this.setState({ tenantId: event.target.value }); } onClose() { this.setState(prevState => ({ isOpen: !prevState.isOpen })); } addNewTenant(shortName, tenantId) { this.setState(prevState => ({ tenantItems: [...prevState.tenantItems, { name: shortName, id: tenantId }], })); } fetchInitial() { this.setState({ isLoading: true }); const { request } = myRequestor.get({ url: '/tenants', signal: this.abortController.signal, // Pass abort signal }); request .then(({ data }) => { if (this.abortController.signal.aborted) return; if (data[0]?.error) { this.setState({ isLoading: false, tenantItems: [{ name: 'Default', id: 'Default' }], }); this.handleError(data[0].error); return; } // Simplify tenant list transformation const tenants = data.map(tenant => ({ name: tenant.shortName, id: tenant.key, })); this.setState({ tenantItems: tenants, isLoading: false, }); }) .catch(error => { if (this.abortController.signal.aborted) return; this.handleError('Failed to fetch tenants'); }); } render() { const { error, isOpen, body, showTable, tenantItems, isLoading } = this.state; if (!tenantItems) { return <CallerUtil />; } if (isLoading) { return <ApplicationLoadingOverlay isOpen backgroundStyle="clear" />; } if (showTable) { return ( <div> <h1>Readiness Status</h1> <ReadinessComponentView readinessInfo={this.state.items} /> </div> ); } return ( <div className="ruleSupportEvaluation"> <Grid> <Grid.Row> <Grid.Column> <Heading level={1}>Readiness Check</Heading> </Grid.Column> </Grid.Row> <Grid.Row> <Grid.Column className="ruleSupportEvaluation" large={3} medium={3}> <div> <Heading className="info" level={3}>About</Heading> <Divider /> <Text className="description" fontSize={18}>Readiness Check</Text> </div> </Grid.Column> <Grid.Column className="ruleSupportEvaluation" large={8} medium={8}> <div> <div className="readinessComponent"> <Tenant tenant={tenantItems} change={this.onChangeTenantDropDown} /> <ModalManagerExample addNewTenant={this.addNewTenant} /> </div> <InputField type="text" label="Tenant-Id" value={this.state.tenantId} placeholder="Tenant Id" onChange={this.tenantIdHandler} /> <InputElement isInvalid={this.state.isInvalid} change={this.onChangeHandler} value={body} /> <Submit click={this.onSubmitHandler} /> {error === 'Failure' && ( <Notification errorMessage={this.state.errorMsg} close={this.onClose} isOpen={isOpen} /> )} </div> </Grid.Column> </Grid.Row> </Grid> </div> ); } } ReadinessComponent.propTypes = propTypes; export default ReadinessComponent;
Key Improvements Breakdown
- Request Cancellation: Uses
AbortControllerto truly stop pending requests when the component unmounts, preventing unnecessary network traffic and state update warnings. - Performance:
- Replaced
find()withsome()for existence checks (stops iterating at the first match). - Merged multiple
setStatecalls to minimize re-renders. - Streamlined array transformations with chained methods.
- Replaced
- Maintainability:
- Extracted error handling into a reusable
handleErrorfunction to reduce code duplication. - Simplified state updates in event handlers.
- Added checks for aborted requests to avoid unnecessary logic execution.
- Cleaned up redundant variable declarations.
- Extracted error handling into a reusable
内容的提问来源于stack exchange,提问作者Gitesh

