当 parent 的状态改变时,常量数组作为 reactjs 中的道具的无限变化

infinite change of constant array as prop in reactjs when parent's state is changed

所以我有这个自定义组件:

export interface PickerProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'onChange' | 'name' | 'onInput'> {
    onChange?: OnChangeEventHandler<HTMLSelectElement>,
    onChangeValue?: OnChangeValueHandler,
    onInput?: OnInputEventHandler<HTMLSelectElement>,
    onInputValueChange?: OnInputChangeHandler,
    name: string,
    label: string,
    emptyOpt?: boolean,
    optGroups: {
        optTitle?: string,
        options: (Omit<OptionHTMLAttributes<HTMLOptionElement>, 'value'> & {
            value: string
        })[]
    }[]
}

export const Picker = ({
    disabled = false,
    autoComplete = "autoComplete",
    onChange = () => { },
    onChangeValue = () => { },
    onInput = () => { },
    onInputValueChange = () => { },
    autoFocus = false,
    size,
    name,
    form = '',
    optGroups = [],
    label,
    emptyOpt = true,
}: PickerProps) => {
    const [pickerValue, setPickerValue] = useState<string>();
    const pickerRef = useRef<HTMLSelectElement>(null);
    const changeHandler = (e: ChangeEvent<HTMLSelectElement>) => {
        const targetValue = e.target.value;
        onChange(e, name);
        onChangeValue(targetValue, name);
        // setPickerValue(targetValue);
    }

    const onInputHandler = (e: FormEvent<HTMLSelectElement>) => {
        onInput(e, name);
        //@ts-ignore
        onInputValueChange(e.target.value, name);
        //@ts-ignore
        setPickerValue(e.target.value, name);
    }

    useEffect(() => {
        const selectedValuesArr = [...optGroups].map(optGroup => optGroup.options
            .filter(opt => opt.selected ?? false)
            .map(sOPT => sOPT.value))
            .reduce((pv, cv) => [...pv, ...cv], []);
        const defValue = selectedValuesArr[0] ?? '';
        setPickerValue(defValue);
        setTimeout(() => pickerRef.current?.dispatchEvent(new Event('input', { bubbles: true })), 2);
    }, [optGroups, name]);

    return <>
        <div className="picker-box" tabIndex={0}>
            <span>
                {label}
            </span>
            <select
                ref={pickerRef}
                disabled={disabled}
                autoFocus={autoFocus}
                size={size}
                name={name}
                form={form}
                autoComplete={autoComplete}
                onChange={changeHandler}
                multiple={false}
                value={pickerValue}
                onInput={onInputHandler}
            >
                {emptyOpt && (
                    <option value="">انتخاب کنید</option>
                )}
                {optGroups.map((optGroup, key) => (
                    <optgroup key={key} label={optGroup.optTitle ?? 'انتخاب کنید'}>
                        {optGroup.options.map((opt, index) =>

                            <option
                                className={pickerValue === opt.value ? ' selected' : ''}
                                key={index}
                                disabled={opt.disabled}
                                value={opt.value}
                            >
                                {opt.label ?? ''}
                            </option>
                        )}
                    </optgroup>
                ))}
            </select>
        </div>
    </>;
};

这是我如何使用的示例

                <Picker
                    emptyOpt={false}
                    label="تخفیف نقدی"
                    name="cashFlag"
                    optGroups={[
                        {
                            options: [
                                { value: 'true', label: 'دارد', selected: true },
                                { value: 'false', label: 'ندارد' },
                            ]
                        }
                    ]}
                    onInputValueChange={formDataHandler}
                />

这是我的表单处理程序:

    const formDataHandler = useCallback((value: string, name: string) => {
            setFormState(fs => {
                const temp = { ...fs };
                temp[name] = value;
                return { ...temp };
            });
    }, []);

提供给 optGroups 属性的数组用于呈现选择器组件。 问题是,每当作为表单数据更改处理程序的 formDataHandler 函数被执行时。选择器组件检测到 optGroups 属性已更改。而不是。 给 Picker 组件的数组是常量并且不会改变(两个除外) 更重要和奇怪的是。它们都按预期运行,直到 parent 组件的状态发生改变!这显然与 Picker 组件无关。

我需要帮助找出为什么 parent 状态一改变 optGroups 就被检测为新数组,而给定的数组没有被改变。 (这导致他们调用他们的 changeHandler 并开始无限循环) 一开始我以为这是我的问题:

但是,经过多年的努力,我确信在 Picker 组件内部,没有更改作为 optGroups 给出的数组的位置。

感谢您的帮助。

这个问题是您正在创建一个新的 optGroups 数组,每当父组件呈现时每次都会创建一个新的引用。

<Picker
  emptyOpt={false}
  label="تخفیف نقدی"
  name="cashFlag"
  // you are creating a brand new array here each time on re-render. 
  // The reference of the array here changes whenever this component re-renders.
  optGroups={[
    {
      options: [
        { value: "true", label: "دارد", selected: true },
        { value: "false", label: "ندارد" },
      ],
    },
  ]}
  onInputValueChange={formDataHandler}
/>;

在父组件外或在单独的文件中声明常量optGroupsOptions并使用它

const optGroupsOptions = [
  {
    options: [
      { value: "true", label: "دارد", selected: true },
      { value: "false", label: "ندارد" },
    ],
  },
];

// Use the optGroupsOptions 

<Picker
emptyOpt={false}
label="تخفیف نقدی"
name="cashFlag"
optGroups={optGroupsOptions}
onInputValueChange={formDataHandler}
/>

另一种方法是使用 useMemo 挂钩确保引用在引用之间不会发生变化。

const memoizedOptGroups = useMemo(
  () => [
    {
      options: [
        { value: "true", label: "دارد", selected: true },
        { value: "false", label: "ندارد" },
      ],
    },
  ],
  []
);

现在使用这个memoizedOptGroups

<Picker
  emptyOpt={false}
  label="تخفیف نقدی"
  name="cashFlag"
  optGroups={memoizedOptGroups}
  onInputValueChange={formDataHandler}
/>;