Как сделать повторно используемые компоненты React / MobX? - PullRequest
3 голосов
/ 02 апреля 2019

Примеры интеграции компонентов 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'));

Ответы [ 2 ]

4 голосов
/ 03 апреля 2019

Основная проблема связана с тем, что в вашем DateInput компоненте есть зависимости store, что делает его практически не пригодным для повторного использования.Вам нужно разбить ссылки на тезисы, и вместо того, чтобы обращаться к ссылкам на хранилища непосредственно из повторно используемого компонента, вы должны отдавать их в подпорках от родителя.

Пойдем.


Во-первых, если я правильно понял, я думаю, что ваша проблема может быть проще, если вы измените свой магазин следующим образом:

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 иИзбегайте повторения кода.

Тогда, вместо того, чтобы проходить через весь магазин, почему бы вам просто не передать реквизиты с необходимыми наблюдаемыми (т. Е. Меткой и ценностью)?

@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
}
0 голосов
/ 03 апреля 2019

Я думаю, что основная проблема, с которой я боролся, заключается в том, как передать примитивные свойства в компонент.

Поскольку я работаю с фоном AngularJS, я пытался воспроизвести что-то вроде этого:

<input ng-model="myStore.startTime" />
<input ng-model="myStore.endTime" />

В AngularJS startTime и endTime могут быть просто строками или числами.Но это не работает в React + MobX.Компонент не может изменить свойство, если вы не передадите объект, который владеет свойством, и это проблема при работе с примитивными свойствами (поскольку они будут иметь произвольные имена и не будут иметь собственных свойств).

Самым простым подходом может быть что-то вроде этого:

class MyStore {
  @observable startTime: { value: number };
  @observable endTime: { value: number };
}

Теперь, так как startTime и endTime являются объектами, которые имеют свойство, а не примитивы, я могу передать их в компонент следующим образом:

@observer 
class DateInput extends React.Component<{timestamp: {value: number}}> {    

А затем создать экземпляр, например, так:

<DateInput timestamp={myStore.startTime}></DateInput>
<DateInput timestamp={myStore.endTime}></DateInput>

Кстати, я бы прочитал, что я должен держать действия в магазине, но это, кажется, добавляет много дополнительных шаблонов и осложнений.поэтому я рад, что вместо этого компонент должен выполнить действие.

Спасибо @ samb102 за помощь в этом.

...