#reactjs #typescript #mobx
#reactjs #typescript #mobx
Вопрос:
Примеры интеграции компонентов React с хранилищами MobX, которые я видел, кажутся тесно связанными. Я хотел бы сделать это более повторно используемым способом и был бы признателен за помощь в понимании «правильного» способа сделать это.
Я написал следующий код (React MobX Typescript), чтобы проиллюстрировать, что я хочу сделать, и проблему, с которой я столкнулся.
В хранилище имеется несколько наблюдаемых временных меток.
/***
* Initialize simple store
*/
class MyStore {
@observable value: number;
@action setValue(val: number) { this.value = val; }
@observable startTimestamp: number;
@action setStartTimestamp(val: number) { this.startTimestamp = val; }
@observable endTimestamp: number;
@action setEndTimestamp(val: number) { this.endTimestamp = val; }
}
Допустим, я хочу создать повторно используемый компонент ввода даты, позволяющий пользователю вводить дату либо для startTimestamp, endTimestamp, либо для какого-либо другого свойства хранилища. В более общем плане, я хочу создать компонент, который я могу использовать для изменения любого произвольного свойства любого хранилища.
Мое лучшее понимание интеграции React / MobX заключается в том, что компоненты получают хранилище MobX, считывают наблюдаемые свойства хранилища и могут выполнять действия по изменению этих свойств. Однако, похоже, предполагается, что компоненты подключены к именам свойств хранилища, что делает их не полностью повторно используемыми.
Я экспериментировал со следующим подходом «прокси-хранилища», чтобы предоставить компоненту нужное мне свойство в качестве «значения»:
class MyStoreTimestampProxy {
constructor(private store: MyStore, private propertyName: 'startTimestamp' | 'endTimestamp') {
}
@observable get value() {
return this.store[this.propertyName];
}
@action setValue(val: number) {
this.store[this.propertyName] = val;
}
};
const myStoreStartTimestamp = new MyStoreTimestampProxy(myStore, 'startTimestamp');
const myStoreEndTimestamp = new MyStoreTimestampProxy(myStore, 'endTimestamp');
Однако я чувствую, что каким-то образом я делаю что-то не так, как React / MobX, и хочу понять наилучшую практику здесь. Спасибо!
Ниже приведен полный код:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';
/***
* Initialize simple store
*/
class MyStore {
@observable value: number;
@action setValue(val: number) { this.value = val; }
@observable startTimestamp: number;
@action setStartTimestamp(val: number) { this.startTimestamp = val; }
@observable endTimestamp: number;
@action setEndTimestamp(val: number) { this.endTimestamp = val; }
}
const myStore = new MyStore();
myStore.setValue(new Date().getTime());
/**
* My time input component. Takes in a label for display, and a store for reading/writing the property.
*/
interface IDateInputProps {
label: string;
store: {
value: number;
setValue(val: number): void;
}
}
interface IDateInputState {
value: string;
}
@observer class DateInput extends React.Component<IDateInputProps, IDateInputState> {
constructor(props: IDateInputProps) {
super(props);
this.state = { value: new Date(props.store.value).toDateString() };
}
render() {
return (
<div>
<label>{this.props.label}
<input
value={this.state.value}
onChange={this.onChange.bind(this)} />
</label>
</div>
);
}
onChange(event) {
const date = new Date(event.target.value);
this.setState({ value: event.target.value });
this.props.store.setValue(date.getTime());
}
}
/**
* Test view
*
*/
class TestView extends React.Component {
render() {
return (
<div>
<DateInput label="Editing the value property of store: " store={myStore}></DateInput>
{/* How to create components for startTimestamp and endTimestamp */}
</div>
);
}
};
ReactDOM.render(<TestView />, document.getElementById('root'));
Ответ №1:
Основная проблема связана с тем, что в вашем store
компоненте есть DateInput
зависимости от состояния, которые затрудняют его повторное использование. Что вам нужно, так это разбить эти ссылки, и вместо того, чтобы обращаться к ссылкам хранилища непосредственно из повторно используемого компонента, вы должны предоставить их в реквизитах от родительского.
Поехали.
Во-первых, если я правильно понял, я думаю, что ваша проблема может быть решена проще, если вы измените свое хранилище следующим образом :
class MyStore {
@observable state = {
dateInputsValues : [
{ value: '01/01/1970', label: 'value'},
{ value: '01/01/1970', label: 'startTimestamp' },
{ value: '01/01/1970', label: 'endTimestamp' }
]
}
}
Теперь вы сможете зацикливаться dateInputsValues
в вашем DateInput
коде и избегать повторения.
Тогда, вместо передачи всего хранилища, почему бы вам просто не передать props с нужными вам наблюдаемыми (т. Е. label amp; value)?
@observer
class TestView extends React.Component {
render() {
return (
<div>
{myStore.state.dateInputsValues.map(date =>
<DateInput
label={`Editing the ${date.label} property of store: `}
value={date.value}
/>
}
</div>
);
}
};
Разорвать старые ссылки на хранилище в DateInput
(что, как вы сказали, делает компонент «тесно связанным» с хранилищем и делает его вряд ли повторно используемым) . Замените их реквизитами, которые мы добавили.
Удалите DateInput
внутренние state
. В реальном коде на данный момент вам не нужно внутреннее состояние компонента. Вы можете напрямую использовать хранилище состояний для такого сценария.
Наконец, добавьте action
метод, который изменяет value
реквизит, поскольку вы, похоже, находитесь в строгом режиме MobX (в противном случае вы могли бы установить значение вне action
)
@observer
class DateInput extends React.Component<IDateInputProps, IDateInputState> {
render() {
return (
<div>
<label>{this.props.label}
<input
value={this.props.value}
onChange={this.onChange} />
</label>
</div>
);
}
onChange = event => {
const date = new Date(event.target.value);
this.setDateInputValue(date.getTime());
}
@action
setDateInputValue = val => this.props.value = val
}
Комментарии:
1. Спасибо, это очень полезно. Кажется, есть несколько противоречивых рекомендаций с рекомендациями по размещению действий MobX. Сначала я прочитал это: iconof.com/best-practices-for-mobx-with-react/… Это говорит о том, что компоненты не должны иметь действий. Но это означает, что вы должны передать хранилище, которое реализует эти действия, и это приводит к большому количеству шаблонов.
2. Мои выводы таковы: 1) Когда компонент предназначен для изменения свойств, нам нужно передать объект, содержащий эти свойства. 2) Название свойства должно быть известно заранее. 3) Компоненты могут напрямую изменять свойства передаваемого объекта.