В настоящее время я использую React и столкнулся с проблемой, связанной с состояниями.
Рассмотрим функцию обратного вызова testComponent, которая при нажатии кнопки каждый раз отображает новый компонент.
Внутри этого компонента будут элементы select и input, и в зависимости от того, что выбрано, элемент input также должен обновиться.
Элемент select будет отображать имя объекта, а элемент input — его идентификатор.
Все selectedOptions начинаются со значения по умолчанию options[0], которое происходит в useEffect.
Это воссоздание моей проблемы, в котором вместо этого используется слово «тест», чтобы его было легче понять (требуется меньше контекста). Идея состоит в том, что каждый раз, когда элемент select изменяется, он также должен обновлять значение input.
export default function CreateTestComponent() {
const options: any[] = [
{
id: 1,
name: "Test 1"
},
{
id: 2,
name: "Test 2",
},
{
id: 3,
name: "Test 3"
}
]
const [selectedOptions, setSelectedOptions] = useState<any[]>([options[0]]);
const [testComponentIndex, setTestComponentIndex] = useState<number>(0);
const [components, setComponents] = useState<any[]>([]);
useEffect(() => {
setComponents([testComponent(0)]);
setTestComponentIndex((old) => old + 1);
for (let i = 0; i < options.length; i++) {
setSelectedOptions((old) => {
const temp = [...old];
temp[i] = options[0];
return temp
})
}
}, [])
const testComponent = (index: number) => {
return (
<div className = "flex flex-row gap-5" id = {`${index}`}>
<select
onChange = {((e) => {
const id = e.target.value;
setSelectedOptions((old) => {
const temp = [...old]
temp[index] = options.filter((option) => option.id == id)[0];
return temp;
})
})}>
{options.map((option, index: number) => {
return (
<option key = {index} value = {option.id}>
{option.name}
</option>
);
})}
</select>
<input readOnly value = {selectedOptions[index].id} />
</div>
)
}
return (
<>
<button type = "button" onClick = {() => {
setTestComponentIndex((old) => old + 1)
setComponents([...components, testComponent(testComponentIndex)]);
}} className = "bg-black text-white rounded px-3 py-1">
Add a Component
</button>
<div>
<h1>Component testing!</h1>
<div>
<ul className = "list-none">
{components.map((component: any, index: number) => {
return (
<li key = {index}>
<div className = "flex flex-row gap-5">
{component}
</div>
</li>
)
})}
</ul>
</div>
</div>
</>
)
}
Этот код будет работать в файле .tsx.
Как видите, состояние обновляется, но элемент ввода не обновляется. Я провел утомительное количество исследований, пытаясь выяснить, что происходит, и я почти уверен, что это потому, что это происходит внутри функции обратного вызова, в которой состояние не обновляется постоянно (что, в свою очередь, вызвало бы повторную отрисовку компонента).
Я пробовал сделать кучу вещей, чтобы обойти это препятствие. А именно, я попробовал useRef, но у него нет возможности повторного рендеринга, который, кажется, есть только useState.
Я прошел через кучу других вещей, но ни одна из них не обошла эту проблему, потому что никто из них не боролся с проблемой, заключающейся в том, что состояние снаружи и внутри функции обратного вызова не одно и то же.
Если нет способа обеспечить актуальное состояние внутри функции обратного вызова, какие еще альтернативы я мог бы попробовать, чтобы иметь возможность нажимать кнопку и каждый раз генерировать новый экземпляр компонента?
Извините, но что вы имеете в виду? Какие данные мне следует сохранить в состоянии, а затем сопоставить при рендеринге?
Сохраняйте любые данные, представляющие ваше «состояние», то есть все, что представляет собой пользовательский интерфейс, который вы хотите отобразить. Судя по вашему коду, состояние должно быть параметрами или массивами параметров, которые вы отображаете.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Я внес некоторые изменения, и все должно работать нормально. Изменения: -
отделенный TestComponent как дочерний компонент с реквизитами для индекса, selectedOption и функцией handleSelectChange.
handleSelectChange обновляет правильный параметр в состоянии, сопоставляя текущие параметры.
AddComponent добавляет в список новый экземпляр TestComponent, поддерживая синхронизацию состояния.
попробуйте и дайте мне знать, если это сработает для вас :)
import React, { useState, useEffect } from 'react';
interface Option {
id: number;
name: string;
}
const options: Option[] = [
{ id: 1, name: 'Test 1' },
{ id: 2, name: 'Test 2' },
{ id: 3, name: 'Test 3' },
];
const TestComponent = ({ index, selectedOption, handleSelectChange }: { index: number; selectedOption: Option; handleSelectChange: (index: number, id: number) => void }) => {
return (
<div className = "flex flex-row gap-5" id = {`${index}`}>
<select
value = {selectedOption.id}
onChange = {(e) => handleSelectChange(index, parseInt(e.target.value))}
>
{options.map((option, idx) => (
<option key = {idx} value = {option.id}>
{option.name}
</option>
))}
</select>
<input readOnly value = {selectedOption.id} />
</div>
);
};
export default function CreateTestComponent() {
const [selectedOptions, setSelectedOptions] = useState<Option[]>([options[0]]);
const [components, setComponents] = useState<JSX.Element[]>([]);
useEffect(() => {
setComponents([<TestComponent key = {0} index = {0} selectedOption = {options[0]} handleSelectChange = {handleSelectChange} />]);
}, []);
const handleSelectChange = (index: number, id: number) => {
setSelectedOptions((old) =>
old.map((opt, idx) => (idx === index ? options.find((option) => option.id === id)! : opt))
);
};
const addComponent = () => {
setSelectedOptions([...selectedOptions, options[0]]);
setComponents((old) => [
...old,
<TestComponent key = {old.length} index = {old.length} selectedOption = {options[0]} handleSelectChange = {handleSelectChange} />,
]);
};
return (
<>
<button type = "button" onClick = {addComponent} className = "bg-black text-white rounded px-3 py-1">
Add a Component
</button>
<div>
<h1>Component testing!</h1>
<div>
<ul className = "list-none">
{components.map((component, index) => (
<li key = {index}>
<div className = "flex flex-row gap-5">{component}</div>
</li>
))}
</ul>
</div>
</div>
</>
);
}
Нет, по какой-то причине это не работает... Изначально проблема заключалась в том, что когда я пытался выбрать другой вариант, он мне не позволял. я удалил value = {selectedOption.id}, и он позволил мне изменить выбранный вариант. Но он все еще не обновил входной тег. Почему?
Вы сохраняете JSX в своем состоянии React, что приводит к устаревшим замыканиям Javascript. Вам следует только сохранять данные в состоянии, а затем сопоставлять их с JSX при рендеринге. Вы также немного усложняете логику, используя несколько состояний для представления «данных», того, что добавляется и т. д. Ее можно свести к одному состоянию.
Пример рефакторинга:
selectedOptions, которое представляет собой массив объектов со свойствами id и name, которые вы визуализируете.import { useState } from "react";
import { nanoid } from "nanoid";
interface Option {
id: number;
name: string;
}
interface SelectedOption extends Option {
_id: string;
}
const options: Option[] = [
{ id: 1, name: "Test 1" },
{ id: 2, name: "Test 2" },
{ id: 3, name: "Test 3" },
];
const createOption = (): SelectedOption => ({
_id: nanoid(),
...options[0],
});
function CreateTestComponent() {
const [selectedOptions, setSelectedOptions] = useState<SelectedOption[]>([
createOption(),
]);
const renderOptions = (option: SelectedOption) => {
return (
<div className = "flex flex-row gap-5" id = {`${option._id}`}>
<select
onChange = {(e) => {
const id = Number(e.target.value);
setSelectedOptions((selectedOptions) =>
selectedOptions.map((selectedOption) =>
selectedOption._id === option._id
? { ...selectedOption, id }
: selectedOption
)
);
}}
>
{options.map((option) => (
<option key = {option.id} value = {option.id}>
{option.name}
</option>
))}
</select>
<input readOnly value = {option.id} />
</div>
);
};
return (
<>
<button
type = "button"
onClick = {() => {
setSelectedOptions((selectedOptions) =>
selectedOptions.concat(createOption())
);
}}
className = "bg-black text-white rounded px-3 py-1"
>
Add a Component
</button>
<div>
<h1>Component testing!</h1>
<div>
<ul className = "list-none">
{selectedOptions.map((option: SelectedOption) => {
return (
<li key = {option._id}>
<div className = "flex flex-row gap-5">
{renderOptions(option)}
</div>
</li>
);
})}
</ul>
</div>
</div>
</>
);
}
Ох, я вижу! Это имеет большой смысл! Вы сопоставляете массив выбранных параметров, а затем визуализируете компонент вместо того, что я делал, а именно сопоставлял компоненты (которые находились в устаревшем состоянии) для их рендеринга. Это здорово, большое спасибо за вашу помощь!
@Панда Да. Добро пожаловать, рад помочь. Здоровья и удачи!
Вы сохраняете JSX в своем состоянии React, что приводит к устаревшим замыканиям Javascript. Вам следует только сохранять данные в состоянии, а затем сопоставлять их с JSX при рендеринге.