reactjs - Yeniden kullanılabilir React/MobX bileşenleri nasıl yapılır?


Gördüğüm React bileşenlerini MobX mağazalarıyla entegre etme örnekleri oldukça sıkı bir şekilde bağlı görünüyor. Bunu daha yeniden kullanılabilir bir şekilde yapmak isterim ve bunu yapmanın 'doğru' yolunu anlamama yardımcı olmanızı takdir ederim.

Yapmak istediğimi ve karşılaştığım sorunu göstermek için aşağıdaki kodu (React + MobX + Typescript) yazdım.

Mağazada birden fazla gözlemlenebilir zaman damgası var.

/***
 * 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; }
}

Diyelim ki kullanıcıya startTimestamp, endTimestamp veya başka bir mağaza özelliği için tarih girmesine izin veren yeniden kullanılabilir bir tarih girişi bileşeni yapmak istiyorum. Daha genel olarak, herhangi bir mağazadaki herhangi bir rastgele özelliği değiştirmek için kullanabileceğim bir bileşen oluşturmak istiyorum.

React/MobX entegrasyonunu en iyi anladığım şekliyle, bileşenler bir MobX mağazası alır, mağazanın gözlemlenebilir özelliklerini okur ve bu özellikleri değiştirmek için eylemleri gerçekleştirebilir. Ancak, bu durum bileşenlerin mağaza özelliklerinin isimlerine bağlı olarak bağlandığını varsayıyor gibi görünüyor, bu da onların tamamen yeniden kullanılabilir olmamasına yol açıyor.

İstediğim özelliği bileşene "değer" olarak açığa çıkarmak için aşağıdaki "vekil depo" yaklaşımıyla denemeler yaptım:

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');

Ancak bir şekilde işleri React/MobX tarzında yapmadığımı hissediyorum ve burada en iyi uygulamayı anlamak istiyorum. Teşekkürler!

Tam kod aşağıdadır:

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'));

Cevap #1

Ana sorun, DateInput bileşeninizde store'un durum bağımlılıklarının bulunmasıdır; bu da bileşenin yeniden kullanılmasını zorlaştırır. Yapmanız gereken, bu referansları kırmak ve yeniden kullanılabilir bileşenden store referanslarına doğrudan erişmek yerine, onları üst bileşenden props olarak vermektir.

Hadi gidelim.

Öncelikle, doğru anladıysam, bence sorununuz, mağazanızı şöyle değiştirirseniz daha kolay olabilir:

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

Artık DateInput içindeki dateInputsValues üzerinde döngü kurabilecek ve kod tekrarından kaçınabileceksiniz.

O zaman, tüm mağazayı geçirmek yerine, neden sadece ihtiyacınız olan gözlemlenebilirlerle (ör. etiket ve değer) prop geçmiyorsunuz?

@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 içindeki mağazaya ait eski referansları kırın (sizin de dediğiniz gibi, bu bileşeni mağazaya 'sıkı bir şekilde bağlı' hale getiriyor ve yeniden kullanılmasını zorlaştırıyor). Bunların yerine eklediğimiz props'ları kullanın.

DateInput iç state'ini kaldırın. Mevcut kodda, şimdilik bir bileşen içi duruma ihtiyacınız yok. Bu tür bir senaryo için doğrudan durum deposunu kullanabilirsiniz.

Son olarak, bir action metodu ekleyin; value prop'unu değiştirir, çünkü görünüşe göre MobX sıkı moddasınız (aksi takdirde, değeri bir action dışında da ayarlayabilirdiniz)

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

İlgili makaleler