在简单组件中键入变量
Type variables in a simple component
假设我有这个简单的组件
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>,
};
(试试here)
为什么我得到
The type of this module contains type variables that cannot be generalized
? (类型变量在这里没有用,但想象一下它在 initialState 中是需要的。尽量保持示例尽可能简单。)
技术原因是 ReasonReact 组件是记录类型,看起来像这样:
type fauxComponent = {
reducer: (evt, t('a)) => t('a),
render: t('a) => ReasonReact.reactElement
};
如果您尝试编译它,您将收到有关 "Unbound type parameter" 的错误。错误的区别是因为它被推断为 ReasonReact.component
类型,其中有一堆类型变量,其中一个被推断为具有多态类型。问题本质上是相同的,但没有所有间接的情况下更容易说明。
我认为你不能这样做的技术原因叫做 the value restriction。但也有实际原因。如果您明确指定 'a
为多态的,您实际上可以编译此类型:
type fauxComponent = {
reducer: 'a. (evt, t('a)) => t('a),
render: 'a. t('a) => ReasonReact.reactElement
};
这表示 'a
可以是任何东西,但这也是问题所在。因为它可以是任何东西,所以你不知道它是什么,因此除了让它通过并返回之外,你不能真正对它做任何事情。您也不知道 'a
在 reducer
和 render
中是相同的,这通常不是记录的问题,因为它们不是有状态的对象。出现问题是因为 ReasonReact "abuses" 它们就像它们一样。
那么你将如何完成你想要做的事情呢?简单,使用函子! ;) 在 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 的模块系统非常棒了:)(实际上还有很多,但这是一个好的开始)
假设我有这个简单的组件
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>,
};
(试试here)
为什么我得到
The type of this module contains type variables that cannot be generalized
? (类型变量在这里没有用,但想象一下它在 initialState 中是需要的。尽量保持示例尽可能简单。)
技术原因是 ReasonReact 组件是记录类型,看起来像这样:
type fauxComponent = {
reducer: (evt, t('a)) => t('a),
render: t('a) => ReasonReact.reactElement
};
如果您尝试编译它,您将收到有关 "Unbound type parameter" 的错误。错误的区别是因为它被推断为 ReasonReact.component
类型,其中有一堆类型变量,其中一个被推断为具有多态类型。问题本质上是相同的,但没有所有间接的情况下更容易说明。
我认为你不能这样做的技术原因叫做 the value restriction。但也有实际原因。如果您明确指定 'a
为多态的,您实际上可以编译此类型:
type fauxComponent = {
reducer: 'a. (evt, t('a)) => t('a),
render: 'a. t('a) => ReasonReact.reactElement
};
这表示 'a
可以是任何东西,但这也是问题所在。因为它可以是任何东西,所以你不知道它是什么,因此除了让它通过并返回之外,你不能真正对它做任何事情。您也不知道 'a
在 reducer
和 render
中是相同的,这通常不是记录的问题,因为它们不是有状态的对象。出现问题是因为 ReasonReact "abuses" 它们就像它们一样。
那么你将如何完成你想要做的事情呢?简单,使用函子! ;) 在 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 的模块系统非常棒了:)(实际上还有很多,但这是一个好的开始)