【问题标题】:infinite change of constant array as prop in reactjs when parent's state is changed当父状态改变时,常量数组作为 reactjs 中的道具的无限变化
【发布时间】:2021-10-28 17:45:40
【问题描述】:

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

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 组件的数组是不变的,不会被改变(除了两个) 更重要和奇怪的是。它们都按预期运行,直到父组件的状态发生更改!这显然与 Picker 组件无关。

我需要帮助找出为什么 optGroups 在父状态发生更改时被检测为新数组,而给定数组没有被更改。 (这导致他们调用他们的 changeHandler 并开始无限循环) 首先我认为这是我的问题:

ReactJS - When I change a state, props change too

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

感谢您的帮助。

【问题讨论】:

    标签: reactjs react-props react-functional-component rerender


    【解决方案1】:

    这个问题是每当父组件每次渲染创建一个新引用时,您都会创建一个新的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}
    />;
    

    【讨论】:

    • 您好,谢谢您的回复,是的,我已经尝试使用记忆值并且它有效,使用useMemo 挂钩或将它们声明为组件可以解决问题,但是。正如我在帖子中所说,我的主要问题是,为什么新引用了这些数组?我在我的父组件中放了一个useEffect,我看到父组件在状态更改时没有被更改。 useEffect 不会在组件重新渲染时执行回调吗?此外,formState 在呈现的内容中也没有使用。所以正如我所料,父母甚至不应该重新渲染,而且它不会
    • useEffect(() => console.log('component - rerendered')) 。在您的父组件中添加此日志。并检查它是否打印得更多。但是如果您的父组件重新渲染并且不希望您的孩子重新渲染,那么您应该用React.memo 包装Picker
    • 是的先生,这正是我测试父 comp 的方式,它没有多次打印日志,这很奇怪,但我想我会坚持使用 useMemo。谢谢
    猜你喜欢
    • 1970-01-01
    • 2021-06-08
    • 2016-11-03
    • 2015-12-02
    • 1970-01-01
    • 2014-11-06
    • 1970-01-01
    • 2019-08-08
    • 2020-03-13
    相关资源
    最近更新 更多