当 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}
/>;
所以我有这个自定义组件:
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}
/>;