Как создать повторно используемые компоненты React / MobX?

#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) Компоненты могут напрямую изменять свойства передаваемого объекта.