General code guide - czcorpus/kontext GitHub Wiki

General code and architecture guide [work in progress...]

Client-side

Used technologies

  • core UI functionality and plug-ins views for general use should be implemented in React
    • use TSX
    • use functional components if possible; state should present in components representing some high level UI blocks (e.g. a form, result data list etc.).
    • generate state by fetching data from stores' getters
      • sometimes this is not necessary (e.g. simple flags like "hintVisible?")
  • component logic should be based either on SimpleUIModel (legacy one) or StatelessModel<T> (current one).
    • please note that while inspired by Flux architectural pattern, both models above violate (in case of SimpleUIModel quite significantly) recommended practices of the pattern:
      • async operations are run within models (StatelessModel<T> provides an improved handling of side-effects)
      • there are no action creators, actions are typically dispatched directly from React components
      • models try to encapsulate whole business logic to avoid Action creator vs. Reducer dilemma known from Redux
      • StatelessModel<T> tries to provide best from both (functional vs. object) worlds
    • use Immutable.js for storing collections

Code

Make dependencies between objects explicit and as concrete as possible

class Foo {

  two:Two;
  
  constructor(one:One) {
    // unless you need "one" for other purposes here, 
    // this is not recommended:
    this.two = one.getTwo(); 
  }
}

class Foo {

  two:Two;

  constructor() {
    // This is not recommended as it
    // hides the dependency.
    this.two = new Two(); 
  }
}
class Foo {
  
  two:Two;
 
  // this is OK
  constructor(two:Two) {
    this.two = two;
  }
}

This should also apply for using page configuration (pages.document.PageModel.getConf()). It is always better to pass miscellaneous configuration values directly rather then passing whole layoutModel (unless you really need its API).


class AHandler {
  constructor(canonicalId:string) {
    // do stuff
  }
}

class MyPage {

  private layoutModel:Kontext.PageModel;

  private fooHandler:AHandler;

  constructor(layoutModel:Kontext.PageModel) {
    this.layoutModel = layoutModel;
    // we fetch a value from page config:  
    const cid = this.layoutModel.getConf<Kontext.FullCorpusIdent>('corpusIdent');
    // then we pass the value directly
    this.fooHandler = new AHandler(cid.canonicalId);
  }
}