【问题标题】:How to make reusable React/MobX components?如何制作可重用的 React/MobX 组件?
【发布时间】:2019-04-02 16:54:23
【问题描述】:

我见过的将 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'));

【问题讨论】:

    标签: reactjs typescript mobx


    【解决方案1】:

    主要问题在于您的DateInput 组件中有store 的状态依赖项,这使得它几乎不可重用。您需要打破这些引用,而不是直接从可重用组件访问存储引用,您必须从父级的 props 中提供它们。

    我们走吧。


    首先,如果我理解得很好,我认为如果您像这样修改您的商店,您的问题会更容易:

    class MyStore {
       @observable state = {
         dateInputsValues : [
                              { value: '01/01/1970', label: 'value'}, 
                              { value: '01/01/1970', label: 'startTimestamp' }, 
                              { value: '01/01/1970', label: 'endTimestamp' }
                            ]
       }
    }
    

    现在,您将能够在您的 DateInput 中循环 dateInputsValues 并避免代码重复。

    那么,与其传递整个商店,不如只传递带有你需要的可观察对象的道具(即标签和值)?

    @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
    }
    

    【讨论】:

    • 谢谢,这很有帮助。似乎有一些与放置 MobX 动作的最佳实践相矛盾的建议。我先读到这个:iconof.com/best-practices-for-mobx-with-react/… 这表明组件不应该有动作。但这确实意味着您必须传入实现这些操作的商店,这会导致大量样板文件。
    • 我的收获是:1)当一个组件打算修改属性时,我们需要传入一个包含这些属性的对象。 2) 物业名称应事先知晓。 3) 组件可以直接修改传入对象的属性。
    猜你喜欢
    • 2019-11-26
    • 1970-01-01
    • 2021-06-14
    • 2014-10-05
    • 1970-01-01
    • 1970-01-01
    • 2020-05-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多