#reactjs #typescript #components
#reactjs #typescript #Компоненты
Вопрос:
В настоящее время я создаю компонент с несколькими флажками, например:
Checkbox.tsx
import './Checkbox.scss';
import React, {ChangeEvent, Component,} from 'react';
/*
* The properties of Checkbox
*/
interface ICheckboxProps<TYPE> {
id: string;
name: string;
value: TYPE;
checked?: boolean;
//if a cross should be used instead of a hook
cross?: boolean;
disabled?: boolean;
//should show a label on the right side of the checkbox
showLabel?: boolean;
//get the string for the value of the checkbox
labelFunc?: (value: TYPE) => string;
// an onChange function which gets called with the state.checked argument
onChange?: (checkbox: ICheckboxState<TYPE>) => void;
}
interface ICheckboxState<TYPE> {
// checked state of the checkbox
checked: boolean;
value: TYPE;
}
class CheckboxComponent<TYPE> extends Component<ICheckboxProps<TYPE>, ICheckboxState<TYPE>> {
constructor(props: ICheckboxProps<TYPE>) {
super(props);
console.log(props);
// set the initial state
this.state = {
checked: props.checked == null ? false : props.checked,
value: props.value,
};
console.log(props);
}
/*
* Render the component as ReactElement
*/
public render(): JSX.Element {
console.log('Checkbox render: ');
console.log(this.state);
return (
<div className={'checkbox'}>
<input
id={this.props.id}
type={'checkbox'}
name={this.props.name}
className={this.props.cross === true ? 'checkbox-cross' : 'checkbox-hook'}
onChange={this.onInputElementChangeEvent}
checked={this.state.checked}
disabled={this.props.disabled}
/>
{this.props.showLabel === true ? (
<label
className="checkbox-label"
htmlFor={this.props.id}>
{typeof this.props.labelFunc === 'function' ?
this.props.labelFunc(this.state.value) : String(this.state.value)}
</label>
) : null}
</div>
);
}
private onInputElementChangeEvent = (e: ChangeEvent<HTMLInputElement>): void => {
this.onChange(e.target.checked);
}
private onChange(checked: boolean): void {
// set the new state when the "onChange" event of the checkbox happens
this.setState({
checked: checked,
value: this.state.value,
}, () => {
// if there is an onChange function supscribed to the event handler than execute it with the current "checked" as
if (typeof this.props.onChange === 'function') {
this.props.onChange(this.state);
}
});
}
public isChecked(): boolean {
return this.state.checked;
}
//return only the value if it's checked
public getValue(): TYPE {
return this.state.value;
}
}
export const Checkbox = (CheckboxComponent);
и
MultiCheckbox.tsx
import './MultiCheckbox.scss';
import React, {Component,} from 'react';
import {Checkbox} from "../Checkbox";
/*
* The properties of Checkbox
*/
interface IMultiCheckboxProps<TYPE> {
id: string;
values: TYPE[];
idFunc: (value: TYPE) => any;
//if a cross should be used instead of a hook
cross?: boolean;
initialChecked?: boolean;
disabled?: boolean;
//get the string for the value of the checkbox
labelFunc: (value: TYPE) => string;
// an onChange function which gets called with the state.checked argument
onChange?: (selected: TYPE[]) => void;
//all checkbox
allButton?: boolean;
//empty checkbox value
emptyButton?: boolean;
//label for empty checkbox
emptyLabel?: string;
}
interface IMultiCheckboxState<TYPE> {
values: SelectedValue<TYPE>[];
all: boolean;
empty: boolean;
}
interface SelectedValue<TYPE> {
id: any;
value: TYPE;
selected: boolean;
}
class MultiCheckboxComponent<TYPE> extends Component<IMultiCheckboxProps<TYPE>, IMultiCheckboxState<TYPE>> {
constructor(props: IMultiCheckboxProps<TYPE>) {
super(props);
// set the initial state
this.state = {
values: props.values.map(value => {
return {
id: props.idFunc(value),
value: value,
selected: props.initialChecked == null ? false : this.props.initialChecked
};
}),
all: props.initialChecked == null ? false : this.props.initialChecked,
empty: false
};
}
/*
* Render the component as ReactElement
*/
public render(): JSX.Element {
console.log('render')
console.log(this.state);
const id = 'multicheckbox-' this.props.id;
const subId = id '-checkbox-';
var checkboxes = this.state.values.map(value =>
<Checkbox
key={subId value.id}
id={subId value.id}
name={this.props.labelFunc(value.value)}
checked={value.selected}
showLabel={true}
value={value.value}
labelFunc={this.props.labelFunc}
cross={this.props.cross}
disabled={this.props.disabled}
onChange={(state) => this.onCheckboxChanged(state.checked, state.value)}
/>
);
if (this.props.allButton) {
checkboxes = checkboxes.concat(
<Checkbox
key={subId 'all'}
id={subId 'all'}
name={'Alle'}
value={'Alle'}
showLabel={true}
labelFunc={(value) => value}
cross={this.props.cross}
disabled={this.props.disabled}
checked={this.state.all}
onChange={(state) =>
this.setAllChecked(state.checked)
}
/>
);
}
if (this.props.emptyButton) {
}
console.log(checkboxes);
return (
<div
id={id}
key={id}
>{checkboxes}</div>
);
}
private onCheckboxChanged(checked: boolean, value: TYPE): void {
alert(value.toString() ' is checked: ' checked);
//TODO set boolean true/false on this.state.values -> checked!
}
private setAllChecked(checked: boolean): void {
console.log(checked);
console.log(this.state);
this.setState({
values: this.state.values.map(val => {
return {
id: val.id,
value: val.value,
selected: checked
};
}),
all: checked,
empty: this.state.empty
}, this.onSelectedChanged);
}
private onSelectedChanged(): void {
if (this.props.onChange) {
this.props.onChange(this.state.values.map(value => {
return value.value
}));
}
}
}
export const MultiCheckbox = (MultiCheckboxComponent);
И моя главная проблема заключается в том, что всякий раз, когда я нажимаю на «All-Checkbox», другие записи не обновляются…
Состояние get изменяется в «MultiCheckboxComponent», но «конструктор» не вызывается в «Checkbox», поэтому его состояние не обновляется и не отображается правильно. Я новичок в React и хочу создать компонент без «redux-store», который можно использовать в разных формах (локальное хранилище) и заполняет его значения / состояния до более конкретного компонента, который сохраняет его в redux.
Нравится:
FooComponent (list of Foo) -> MultiCheckboxComponent -> multiple Checkboxes
FeeComponent (list of Fee) -> MultiCheckboxComponent -> multiple Checkboxes
LuuComponent (stuff) -> single Checkbox
Но всякий раз, когда я вызываю «setState ()» в MultiCheckboxComponent, происходит «рендеринг», и рендеринг также происходит в CheckboxComponent, но «реквизиты» не используются («Конструктор» не вызывается). Как я могу установить состояние для «дочернего элемента» из «родительского»?
Ответ №1:
Я полагаю, что у вас возникли проблемы, потому что вы думаете о том, как подобные вещи должны быть смоделированы в react немного неправильно.
Конструктор вызывается только один раз — при создании компонента. Я не думаю, что состояние внутри ваших компонентов checkbox необходимо — в конце концов, они, похоже, в значительной степени дублируют реквизиты.
Когда вы пишете код React — думайте о своих реквизитах как об изменяющихся значениях, которые будут вызывать обновления в ваших дочерних компонентах. Используйте их, чтобы повлиять на внешний вид компонента.
Вот некоторые изменения, которые я бы внес:
- Избавьтесь от состояния флажка — вам, вероятно, это не нужно
- Поскольку вам не нужно состояние, вы можете написать чистый функциональный компонент (без состояния). Это упрощает анализ вашего кода, поскольку в нем меньше движущихся частей
- Уничтожьте свой реквизит, чтобы не повторять this.props.X
- Используйте короткое замыкание, чтобы уменьшить детализацию (возврат ложного значения в средство визуализации игнорирует его — подробнее об этом здесь https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical—operator )
Это оставляет вас со следующим:
interface ICheckboxProps<TYPE> {
id: string;
name: string;
value: TYPE;
checked?: boolean;
//if a cross should be used instead of a hook
cross?: boolean;
disabled?: boolean;
//should show a label on the right side of the checkbox
showLabel?: boolean;
//get the string for the value of the checkbox
labelFunc?: (value: TYPE) => string;
// an onChange function which gets called with the state.checked argument
onChange?: (checkbox: ICheckboxState<TYPE>) => void;
}
function CheckboxComponent<TYPE>(props: ICheckboxProps<TYPE>) {
console.log('Checkbox render: ');
// Destructure props - save writing out this.props.foo each time
const { name, id, cross, onChange, checked, disabled, showLabel } = this.props;
return (
<div className="checkbox">
<input
id={id}
type="checkbox"
name={name}
className={cross ? 'checkbox-cross' : 'checkbox-hook'}
onChange={(e) => onChange amp;amp; onChange(e.target.checked)}
checked={checked}
disabled={disabled}
/>
{showLabel amp;amp; (
<label className="checkbox-label" htmlFor={id}>
{labelFunc ? labelFunc(value) : value}?
</label>
)}
</div>
);
}
export const Checkbox = (CheckboxComponent);
Дайте мне знать, если вам понадобится дополнительная помощь в этом!
Комментарии:
1. Но, делая это так, я больше не могу использовать флажок в качестве неконтролируемого компонента.
2. Я не уверен, что вы имеете в виду — в настоящее время это не неконтролируемый компонент. Чтобы компонент был неконтролируемым, нужно позволить DOM быть единственным источником истины, из которого вы затем извлекаете значения в тот момент, когда вам это нужно (т.Е. После завершения работы с формой). На данный момент ваше состояние (в некоторой степени) контролируется родительским элементом — по крайней мере, оно хранится в родительском элементе, но ссылка на обновление значения в дочернем элементе разорвана.
3. Подробнее о неконтролируемых компонентах смотрите здесь . Вы используете ссылку для извлечения значения из DOM reactjs.org/docs/uncontrolled-components.html