如何制作可重用的React / MobX组件?

时间:2019-04-02 16:54:23

标签: 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'));

2 个答案:

答案 0 :(得分:4)

主要问题来自您的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,并避免代码重复。

然后,为什么不只传递带有所需可观察对象(即标签和值)的道具,而不是通过整个商店?

@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 :(得分:0)

我认为我一直在努力的核心问题是如何将原始属性传递到组件中。

由于我来自AngularJS背景,因此我尝试按照以下方式复制内容:

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

在AngularJS中,startTime和endTime可以简单地是字符串或数字。但这在React + MobX中不起作用。除非您传入拥有该属性的对象,否则组件将无法修改该属性,这在处理原始属性时会出现问题(因为它们将具有任​​意名称并且不具有自己的属性)。 / p>

最简单的方法可能是遵循以下原则:

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帮助我走了这一步。