Введите переменные в простой компонент - PullRequest
0 голосов
/ 09 ноября 2018

Скажите, у меня есть этот простой компонент

type evt =
  | NoOp;

type t('a) = 'a;

let component = ReasonReact.reducerComponent("TestComponent");

let make = _children => {
  ...component,
  initialState: () => "hello",
  reducer: (evt, state: t('a)) =>
    switch (evt) {
    | NoOp => ReasonReact.NoUpdate
    },
  render: self => <div> {str("hello")} </div>,
};

(попробуйте здесь )

Почему я получаю

The type of this module contains type variables that cannot be generalized

? (Переменная типа здесь бесполезна, но представьте, что это было необходимо в initialState. Попытка сделать пример как можно более простым.)

1 Ответ

0 голосов
/ 09 ноября 2018

Техническая причина в том, что компонент ReasonReact имеет тип записи, который будет выглядеть примерно так:

type fauxComponent = {
  reducer: (evt, t('a)) => t('a),
  render: t('a) => ReasonReact.reactElement
};

Если вы попытаетесь скомпилировать это, вы получите сообщение об ошибке «Параметр несвязанного типа». Разница в ошибке заключается в том, что она имеет тип ReasonReact.component, который имеет набор переменных типа, одна из которых имеет полиморфный тип. Проблема, по сути, та же, но ее гораздо проще проиллюстрировать без всякой косвенности.

Эта техническая причина, по которой вы не можете этого сделать, я думаю, называется ограничение значения . Но есть и практические причины. Вы действительно можете сделать этот тип скомпилированным, если явно укажете 'a как полиморфный:

type fauxComponent = {
  reducer: 'a. (evt, t('a)) => t('a),
  render: 'a. t('a) => ReasonReact.reactElement
};

Это говорит о том, что 'a может быть чем угодно, но это тоже проблема. Поскольку это может быть что угодно, вы не можете знать, что это такое, и поэтому вы не можете ничего с ним сделать, кроме как заставить его пройти и вернуться. Вы также не знаете, что 'a одинаково в reducer и render, что обычно не является проблемой для записей, поскольку они не являются объектами с состоянием. Проблема возникает потому, что ReasonReact «злоупотребляет» ими, как если бы они были.

Так как бы вы тогда достигли того, что пытаетесь сделать? Легко, используйте функтор! ;) В Reason вы можете параметризовать модули, которые затем называются функторами, и использовать их для указания типа, который будет использоваться во всем модуле. Вот ваш пример с функторизацией:

module type Config = {
  type t;
  let initialState : t;
};

module FunctorComponent(T : Config) {
  type evt =
  | NoOp;

  type t = T.t;

  let component = ReasonReact.reducerComponent("TestComponent");

  let make = _children => {
    ...component,
    initialState: () => T.initialState,
    reducer: (evt, state: t) =>
      switch (evt) {
      | NoOp => ReasonReact.NoUpdate
      },
    render: self => <div> {ReasonReact.string("hello")} </div>,
  };
};

module MyComponent = FunctorComponent({
  type t = string;
  let initialState = "hello";
});

ReactDOMRe.renderToElementWithId(<MyComponent />, "preview");

Параметры, которые функтор принимает на самом деле, должны быть модулями, поэтому сначала мы определяем тип модуля Config, определяем его как тип параметра, а затем, когда мы создаем наш модуль MyComponent с использованием функтора, мы создаем и передаем его анонимный модуль, который реализует тип модуля Config.

Теперь вы знаете, почему многие люди думают, что модульная система OCaml и Reason такая классная :) (На самом деле есть еще много всего, но это хорошее начало)

...