React-Select触发父组件重渲染:实现仅子组件重渲染
Let’s tackle this re-rendering issue step by step—you’re right that the deep component tree re-renders are hurting performance, and they’re directly tied to your closeMenuOnSelect={false} problem too. Here’s how to lock re-renders down to just your React-Select component:
Why You’re Seeing This
The root causes are:
- Formik’s
renderprop creates a new function every time your form state shifts, forcing the entire subtree (including parent components) to re-render. - Your
dependenciesprop is passed up the tree, and if it’s a new object/array reference on each render (common if created in a parent component), it triggers re-renders all the way up. - Conversion functions like
convertNamespaceToDefaultValueandconvertNamespaceToSelectgenerate new objects/arrays on every render, making React think props have changed even when they haven’t.
Step-by-Step Fixes
1. Extract React-Select into a Memoized Component
Wrap your Select in React.memo to prevent unnecessary re-renders when props haven’t actually changed. This stops Formik’s render prop from creating a new component instance every time:
import { memo } from 'react'; import Select from 'react-select'; const MemoizedNamespaceSelect = memo(({ defaultValue, options, onChange, id, isMulti, closeMenuOnSelect }) => ( <Select isMulti={isMulti} closeMenuOnSelect={closeMenuOnSelect} id={id} defaultValue={defaultValue} options={options} onChange={onChange} /> ));
2. Cache Conversion Results with useMemo
Use React’s useMemo to cache the output of your conversion functions. This ensures defaultValue and options passed to your memoized select don’t get new references on every render:
3. Optimize Formik Field Logic
Keep the render prop but wrap inner handlers in useCallback to stabilize their references, or pass your memoized component directly to Formik’s Field via the component prop. Here’s the updated full code:
import { useMemo, useCallback } from 'react'; import { Field, ErrorMessage } from 'formik'; // Include the MemoizedNamespaceSelect from above here const YourFormSection = ({ dependencies, keyField, index }) => { // Cache converted options to avoid new array references const selectOptions = useMemo(() => { return convertNamespaceToSelect(dependencies.namespacePermissions); }, [dependencies.namespacePermissions]); // Cache the default value converter function const getDefaultValue = useMemo(() => { return (fieldValue) => convertNamespaceToDefaultValue(dependencies.namespacePermissions, fieldValue); }, [dependencies.namespacePermissions]); return ( <div className="w-50"> <Field name={`${keyField}.${index}.permissions`} render={({ field: { value, name }, form: { setFieldValue, setFieldTouched } }) => { // Stabilize onChange handler reference const handleChange = useCallback((values) => { setFieldValue(name, convertSelectToNamespacesData(values)); setFieldTouched(name, true); }, [name, setFieldValue, setFieldTouched]); return ( <div> <label htmlFor="namespace-permissions" className="font-weight-medium"> Permissions in Namespace <Asterisk /> </label> <MemoizedNamespaceSelect id="namespace-permissions" isMulti closeMenuOnSelect={false} defaultValue={getDefaultValue(value)} options={selectOptions} onChange={handleChange} /> <ErrorMessage name={name} component={FormErrorMessage} /> </div> ); }} /> </div> ); };
4. Fix Parent Component Re-renders
If parents are still re-rendering, check if dependencies is being recreated in the parent. Wrap it in useMemo there too:
// In the parent component passing dependencies const dependencies = useMemo(() => { return { namespacePermissions: /* your source data here */ }; }, [/* dependencies for your namespacePermissions data */]);
Why This Works
React.memodoes a shallow prop comparison for your Select—only re-renders if props actually change.useMemoensures options and default values keep stable references unless the underlyingnamespacePermissionsdata changes.useCallbackkeeps your onChange handler’s reference stable, so it doesn’t trigger unnecessary re-renders in the memoized Select.
Once the re-rendering issue is fixed, your closeMenuOnSelect={false} should work as expected too—since the menu won’t get unmounted/re-mounted on every selection.
内容的提问来源于stack exchange,提问作者user10104341

