Я очень новичок в реакции, и я борюсь со следующим. Я создал форму в React, и эта форма содержит раскрывающийся список. Мне нужно повторно использовать это раскрывающееся меню на нескольких страницах, поэтому я решил сделать его компонентом, который полностью отвечает за получение всех данных.
В компоненте формы я получаю все данные, и одно из этих полей - selectedServerDataId. Поле selectId содержит идентификатор значения, которое необходимо выбрать в раскрывающемся списке.
<snip includes/>
class Arrival extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
formData: {}
};
}
async componentDidMount() {
await fetch(urls.addEditUrl)
.then(response => response.json())
.then(data => {
this.setState({ formData: data });
});
}
<snip some setstate helpers/>
render() {
const { formData } = this.state;
return (
<Form >
<Selector label = "select"
onChange = {this.UpdateFormSelectData.bind(this, 'selectedServerDataId')}
value = {formData.selectedServerDataId}/>
<DateItem label = "date"
allowClear = {true}
value = {formData.date}
onChange = {this.updateFormDateData.bind(this, 'date')}/>
<snip more fields.../>
</Form>);
}
}
export default Arrival;
Выборка в родительском компоненте извлекает данные для формы редактирования, включая selectedServerDataId. Мой дочерний компонент выглядит так:
<snip usings />
class ServerDataSelector extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [],
};
}
async componentDidMount() {
await fetch(URLS.GetServerData)
.then(response => response.json())
.then(data => {
this.setState({ options: data });
});
}
render() {
const { options } = this.state;
return (
<FormItem {...formItemLayout} label = {t(this.props.label)} >
<Select setValue = {this.props.onChange} getValue = {() => this.props.value} selectedOption = {this.props.value} name = {this.props.label}>
{options.map(d => <Option key = {d.id} value = {d.id}>{d.value}</Option>)}
</Select>
//TODO add model window to manipulate list.
</FormItem>);
}
}
export default ServerDataSelector
ServerDataSelector { Selector }
В настоящее время, когда страница отображается, я сначала вижу идентификатор выбранного значения в раскрывающемся списке, а через долю секунды я вижу фактическую метку выбранного значения.
Есть ли способ гарантировать, что родительский компонент отображается только тогда, когда дочерние элементы полностью загружены?



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


Основная проблема здесь заключается в том, что у вас есть два компонента, которые имеют строгие иерархические отношения рендеринга (родительский -> дочерний), но равную ответственность за выборку данных.
Это приводит к ситуации, когда вы должны реагировать на получение данных между несколькими компонентами в определенном порядке, что является нетривиальной проблемой параллелизма. Одним из решений было бы использовать контейнер общего состояния (с использованием Redux или Контекстный API) и чтобы оба компонента сохраняли свои результаты в этом сегменте общего состояния, а затем, когда в контейнере есть все данные, вы их представляете (до тех пор, пока вы можете показывать счетчик Например).
Более простым решением было бы просто переместить выборку в один из компонентов, полностью исключить эту разделенную ответственность за выборку данных и позволить компоненту, извлекающему данные, также контролировать рендеринг.
Один из способов - получить все данные в родительском элементе, а дочерний элемент будет просто чистым компонентом, который отображается при получении данных:
class Arrival extends React.Component {
// ...
async componentDidMount() {
const [formData, options] = await Promise.all([
fetch(urls.addEditUrl).then(response => response.json()),
fetch(URLS.GetServerData).then(response => response.json())
]);
this.setState({ formData, options });
}
render() {
const { formData, options } = this.state;
return (
<Form>
{/* ... */}
{formData &&
options && (
<Selector
label = "select"
options = {this.state.options} {/* pass the options here */}
onChange = {this.UpdateFormSelectData.bind(
this,
"selectedServerDataId"
)}
value = {formData.selectedServerDataId}
/>
)}
</Form>
);
}
}
Selector теперь является только презентационным компонентом, без необходимости указывать:
const ServerDataSelector = props => (
<FormItem {...formItemLayout} label = {t(this.props.label)}>
<Select
setValue = {this.props.onChange}
getValue = {() => this.props.value}
selectedOption = {this.props.value}
name = {this.props.label}
>
{this.props.options.map(d => ( {/* use props.options */}
<Option key = {d.id} value = {d.id}>
{d.value}
</Option>
))}
</Select>
</FormItem>
);
При желании вы можете показать счетчик или индикатор загрузки:
...
{
formData && options ? (
<Selector
label = "select"
onChange = {this.UpdateFormSelectData.bind(this, "selectedServerDataId")}
options = {this.state.options}
value = {formData.selectedServerDataId}
/>
) : (
<Spinner />
);
}
В качестве альтернативы вы можете попросить ребенка получить все данные, а родитель просто отобразит ребенка.
class ServerDataSelector extends React.Component {
// ...
async componentDidMount() {
const [formData, options] = await Promise.all([
fetch(urls.addEditUrl).then(response => response.json()),
fetch(URLS.GetServerData).then(response => response.json())
]);
this.setState({
value: formData.selectedServerDataId,
options
});
}
render() {
return (
<FormItem {...formItemLayout} label = {t(this.props.label)}>
<Select
setValue = {this.props.onChange}
getValue = {() => this.state.value} {/* use the value from the state */}
selectedOption = {this.state.value} {/* use the value from the state */}
name = {this.props.label}
>
{this.state.options.map(d => (
<Option key = {d.id} value = {d.id}>
{d.value}
</Option>
))}
</Select>
</FormItem>
);
}
}
Вы также можете добавить в дочерний элемент счетчик, чтобы предотвратить рывки страниц и улучшить UX:
render() {
if (!this.state.value || !this.state.options) {
return <Spinner />;
}
return (
<FormItem {...formItemLayout} label = {t(this.props.label)}>
<Select
setValue = {this.props.onChange}
getValue = {() => this.state.value}
selectedOption = {this.state.value}
name = {this.props.label}
>
{this.state.options.map(d => (
<Option key = {d.id} value = {d.id}>
{d.value}
</Option>
))}
</Select>
</FormItem>
);
}
Рад помочь товарищу. Компоненты в React должны быть многоразовыми, ваша идея верна. Наличие чего-либо с одной ответственностью - это твердый принцип разработки программного обеспечения, независимо от технологической части (серверная часть, интерфейсная часть и т. д.). Я полностью согласен с вами по всем этим пунктам. Единственная проблема в вашем подходе - это то, где вы нарисовали границу компонента. Мое предложение здесь основано на небольшом образце кода, который вы дали, но лучший совет, который я получил, - это взять ваше представление о повторном использовании и единоличной ответственности и немного покачать дизайн вашего компонента :)
Это действительно небольшой фрагмент кода. Компонент данных будет представлять собой раскрывающийся список, рядом с которым будет находиться кнопка добавления, которая откроет окно модели для добавления нового элемента в раскрывающийся список. В настоящее время работаю над идеей, которая, кажется, работает, но еще не реализована;) Я также поделюсь этой частью кода.
После многих попыток и ошибок я нашел способ исправить это.
В моем родительском состоянии я добавил следующий объект:
this.state = {
formData: null,
loadedComponents: {
parentComponentIsLoaded: false,
childComponent1IsLoaded: false,
childComponent2IsLoaded: false,
}
};
Затем я добавил следующие 2 метода в свой родительский
componentLoaded = (field, data) => {
const { loadedComponents } = this.state;
var fieldName = field + "IsLoaded";
this.setState({ loadedComponents: { ...loadedComponents, [fieldName]: true } });
}
allComponentsLoaded() {
const { loadedComponents } = this.state;
console.info(loadedComponents);
for (var o in loadedComponents) {
if (!loadedComponents[o]) return false;
}
return true;
}
Изменил метод рендеринга на это:
return (
formData ?
<Form className = {this.allComponentsLoaded() ? '' : 'hidden'} >
<ChildComponet1 {someprops} isLoaded = {this.componentLoaded}/>
<ChildComponent2 {someprops} isLoaded = {this.componentLoaded}/>
<snip />
</Form> : <div />);
И добавил следующую строку в методе childs componentDidMount после выборки данных.
this.props.isLoaded('childComponet1', true)
Возможно, это очень плохой дизайн реакции, и если вы действительно используете ответ MEM035, но он работает
Спасибо за развернутый ответ. Ваш ответ наводит меня на мысль, что я стараюсь реагировать, а не реагировать. Как бэкэнд-программист я всегда стараюсь создавать компоненты, которые можно использовать повторно и за которые я несу ответственность. Я посмотрю на предоставленные библиотеки, но в противном случае я пойду с родительской загрузкой всего маршрута. Хотя я надеюсь, что найду способ справиться с этим, потому что это могло бы сэкономить мне много кода внешнего интерфейса в приложении, которое я делаю.