Привет, мне не удается отделить логику от пользовательского интерфейса с помощью пользовательского класса наблюдателя в React

#javascript #reactjs #request #observer-pattern

Вопрос:

Поэтому я прочитал эту документацию о компонентах более высокого порядка и подумал, что было бы неплохо провести рефакторинг моего ужасного компонента monolit.

Мой план:

  1. Подпишитесь на наблюдаемую ценность
  2. Извлеките данные и сохраните их на карте
  3. Уведомляйте об изменениях и каким-то образом обновляйте пользовательский интерфейс (я новичок в react и знаю, что есть много способов redux, mobx и т. Д.). Но цель моего маленького проекта-учиться, и я хочу, чтобы все было как можно более родным (за исключением библиотек пользовательского интерфейса).

Любой приведенный ниже код может быть неправильным, поэтому вот мой класс наблюдателя, который практически не изменился с 1994 года

 export default class Observable { //observer
  listeners = new Set();
  constructor(value) {
    this.value = value;
  }
  get() {
    return this.value;
  }
  set(newValue) {
    if (newValue !== this.value) {
      this.notify();
    }
  }
  subscribe(listener) {
    this.listeners.add(listener);
  }
  unsubscribe(listener) {
    this.listeners.delete(listener);
  }
  notify() {
    for (const listener of this.listeners) {
      listener();
    }
  }
}
 

А вот компонент более высокого порядка, который управляет подпиской моего наблюдаемого класса

 import React from "react";

export default function withSubscription(WrappedComponent, api) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        data: api.observableMapData //observable object
      };
      this.handleChange.bind(this);
    }

    handleChange() {
      this.setState({
        data: api.observableMapData
      });
      console.log("changed data") //never actually saw it in the console
    }

    componentDidMount() {
      //observable object->getterfield->subscribe
      api.fetchData();
      api.observableMapData.dataSourceMap.subscribe(this.handleChange);  
    }

    componentWillUnmount() {
      //observable object->getterfield->unsubscribe
      api.observableMapData.dataSourceMap.unsubscribe(this.handleChange);
    }

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

 

the observed class

 import Observable from "../services/Observable";

export default class DataSourceMap {
  _dataSources = [
    ["projects", "Project"],
    ["organizations", "Organization"],
  ];
  _dataSourceMap = new Observable(new Map());
  get dataSourceMap() {
    return this._dataSourceMap;
  }
  set dataSourceMap(val) {
    this._dataSourceMap = val;
  }

  set dataSources (sources) {
    this._dataSources  = sources;
  }
  get dataSources () {
      return this._dataSources ;
  }

}
 

And the wrapper function

 const api = new Api();
const TicketFormWithSubscription = withSubscription(
    UiFormController,
    api
  );

export default TicketFormWithSubscription;
 

And I am fetching the map like that

 export default class ApiService {
  
  observableMapData = new DataSourceMap();
  //singleton
  constructor() {
    if (!ApiService._instance) {

      ApiService._instance = this;
     // this.fetchData();
    }
    return ApiService._instance;
  }

async fetchData() {
    let tmpMap = new Map();
     for (let value of this.observableMapData.dataSources) {
      let data = await this.getAllItems(value[1]);
      console.log(data);
      tmpMap.set(value[1], data)
    }
    //setter
    this.observableMapData.setDataSourceMap = tmpMap;   
    console.log(JSON.stringify(this.observableMapData   " fetched"));
  }

...
}

 

So the main problem is that component loads faster then data is retrieved. And when I open my Autocomplete there is an error because values are undefined and dont update after the data is fetched.

an error

Наконец, вот поток реквизита

  1. В UIconteroller (события handels)
 constructor(props) {
    super(props);
    
    this.state = {
      observableMap: this.props.data,
      confimOpen: false,
      ....
}
...

render() {

    const newProps = { 
        projects : this.props.data.dataSourceMap.get('projects'),
        organizations: this.props.data.dataSourceMap.get('organizations'),
    }

    return (
      <TicketFormUi
        {...newProps}
      />
    );
  }
 

И к компоненту автозаполнения MaterialUI

 <Autocomplete
            options={props.organizations.items}
            margin="dense"
            getOptionLabel={(option) => `${option.name}`}
            renderInput={(params) => (
...

 <Autocomplete
            options={props.projects.items}
            margin="dense"
            getOptionLabel={(option) => `${option.name}`}
            renderInput={(params) => (
...
 

Я чувствую, что мой подход устарел, но выполним, заранее спасибо за свежий взгляд

Ответ №1:

nvm, который я получил, будет просто использовать крючки или acync componentDidMount()