[英]How to narrow the Option type inferred from fp-ts R.lookup
我正在尝试使用fp-ts
查找键的值。 密钥可能不存在。
type Data = {
a?: number;
b?: string;
c?: { e?: string; w: number };
};
const e = R.lookup('b')({ a: 23, b: 'asdfasdf', c: { e: 'asdf', w: 23}} as Data)
我认为由 Typescript 推断的e
类型是Option<string>
但它是:
O.Option<string | number | {
e?: string | undefined;
w: number;
}>
这看起来像Data
中所有可能的类型。 这是预期的行为吗? 以及如何将我的类型缩小到仅b
的“潜在”类型,以便我可以从Option<string>
继续管道。
我尝试了下面的方法,它将e
标记为Option<string>
但随后将整个 object {a: 23...
标记为“不可分配给'Record<string, string>'类型的参数”
const getB = R.lookup("b")
const e = getB<string>({ a: 234, b: "asdfasdf", c: { e: "asdf", w: 23 } } as Data);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Argument of type 'Data' is not assignable to parameter of type 'Record<string, string>'.
Property 'a' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
我认为R.lookup
的类型与您的期望不一致。 问题是R.lookup
没有跟踪您期望'b'
成为keyof Data
的事实。 它只知道b
是一个string
,并且它返回的函数具有签名:
<A>(record: Record<string, A>) => Option<A>
因此,它将数据视为Record
,而 A 的类型变为Data[keyof Data]
,这会导致您看到的类型。
我认为这主要源于想要以一种并非真正打算使用的方式使用Record
。 一个Record
对你提交给它的任何键都有一个统一的类型。 为了获得统一的类型, fp-ts
代码必须推断在每个键处可能存在数据中所有值的联合类型。
相反,如果您知道自己有一个Data
对象,则可以编写如下代码:
import * as O from 'fp-ts/Option';
const data: Data = { a: 234, b: "asdfasdf", c: { e: "asdf", w: 23 } };
const maybeB: Option<string> = O.fromNullable(data.b);
相反,如果您实际上并不知道您将拥有Data
,因为您的对象实际上是unknown
,而不是将其转换为Data
您可以首先使用库(例如io-ts
,因为您已经在使用fp-ts
)验证数据的形状。 例如
import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option';
import { flow } from 'fp-ts/function';
import * as t from 'io-ts';
const dataSchema = t.partial({
a: t.number,
b: t.string,
c: t.type({
e: t.union([t.string, t.undefined]),
w: t.number,
}),
});
// This will be equivalent to your data definition but can be inferred
// from the schema definition
type Data = t.TypeOf<typeof dataSchema>;
// The explicit type here is unnecessary but just to illustrate
const getB: (input: unknown) => O.Option<string> = flow(
dataSchema.decode, // -> Either<t.Errors, Data>
O.fromEither, // -> Option<Data>
O.chain((d) => O.fromNullable(d.b)) // Option<string>
);
console.log(getB(data)); // { _tag: 'Some', value: 'asdfasdf' }
根据其他评论,可能理想的方法是将io-ts
用于monocle-ts
。 但是看到您似乎对fp-ts
如何自己处理它感兴趣 - 仅针对单个字段 - 我为您提供这个。
要点是,一旦你有了可选的,你只需要使用 typescript 类型缩小。 这可以通过使用fromPredicate
fp-ts
Refinement
。 而对于很多基本类型(比如string
) fp-ts
提供了一些开箱即用的——比如下面的S.isString
。
import { pipe } from "fp-ts/function";
import * as O from "fp-ts/Option";
import * as R from "fp-ts/Record";
import * as S from "fp-ts/string";
type Data = {
a?: number;
b?: string;
c?: { e?: string; w: number };
};
const testData: Data = { a: 23, b: "asdfasdf", c: { e: "asdf", w: 23 } };
const b = pipe(
testData,
R.lookup("b"),
O.chain(
O.fromPredicate(S.isString)
)
);
但是,尽管这可以让您使用单个字段,但任何其他内容都需要越来越多的样板文件,所以您不妨使用io-ts
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.