首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >immer.js +类型记录-如何编写类型化查询函数?

immer.js +类型记录-如何编写类型化查询函数?
EN

Stack Overflow用户
提问于 2022-05-21 07:50:17
回答 1查看 126关注 0票数 1

我一直在试用带有类型记录的Immer.js,但我经常遇到类似的问题。有什么模式可以用来避免一些丑陋的代码吗?

请考虑下列打字本代码:

代码语言:javascript
复制
import produce, {castDraft} from "immer";

interface Item {
  readonly id: number;
  readonly value?: string;
}

interface State {
  readonly items: Item[];
}

function findItemById(state: State, id: number): Item {
  return state.items.find(x => x.id === id)!;
}

const state: State = {
  items: [{ id: 1 }, { id: 2 }, { id: 3 }]
};

// This does not work.
produce(state, draft => {
  const item = findItemById(draft, 2);
  // Cannot assign to "value" because it is a read-only property.
  item.value = "Something";
});

// This works, but it's not ideal
produce(state, draft => {
  const item = castDraft(findItemById(draft, 2));
  item.value = "Something";
});

我们正在遵循Immer.js关于将我们的国家设置为只读的建议。我们有一个“查询函数”,用于从状态中提取一个块。在本例中,这是findItemById()。我们很快就会遇到一个问题,每当我们从produce()中的草案实例中获取状态块时,它就失去了可变包装器,我们无法将其分配给它。我们可以使用函数castDraft()来解决这个问题,但我发现这并不理想--我们将在生产者中大量使用这类查询函数,因此需要始终进行转换是有问题的,而且容易出错(如果您不小心从外部状态而不是从草稿中转换,您就有麻烦了!)

我们要做的是定义函数findItemById()的类型,使其在输入State可写时返回可写State,否则返回不可变项。我尝试了以下几种方法:

代码语言:javascript
复制
function findItemById<T extends Draft<State> | State>(
  state: T,
  id: number
): T extends Draft<State> ? Draft<Item> : Item {
  return state.items.find((x) => x.id === id)!;
}

但这似乎行不通--结果总是可以写下来的。

EN

回答 1

Stack Overflow用户

发布于 2022-05-21 08:13:33

这是因为produce函数中的第二个State参数是一个包装在WritableDraft中的State

让我们来看看什么是WritableDraft

代码语言:javascript
复制
export declare type WritableDraft<T> = {
    -readonly [K in keyof T]: Draft<T[K]>;
};

正如您可能已经注意到的,这种类型通过第一级键并使它们成为mutable。换句话说,它是shallow mutable类型。Ut意味着所有丢失的属性仍然是不可变的。

此外,您的函数findItemById需要State参数,而在这里使用泛型更好,因为您用draft参数(即WritableDraft<State> )调用它。

因此,最好将其声明为:

代码语言:javascript
复制
function findItemById<S extends State>(state: S, id: number) {
  return state.items.find((x) => x.id === id)!;
}

但是,它仍然不起作用。让我们看一看castDraft (源代码):

代码语言:javascript
复制
export function castDraft<T>(value: T): Draft<T> {
    return value as any
}

Draft类型:

代码语言:javascript
复制
export declare type Draft<T> = T extends PrimitiveType
  ? T
  : T extends AtomicObject
  ? T
  : T extends IfAvailable<ReadonlyMap<infer K, infer V>>
  ? Map<Draft<K>, Draft<V>>
  : T extends IfAvailable<ReadonlySet<infer V>>
  ? Set<Draft<V>>
  : T extends WeakReferences
  ? T
  : T extends object
  ? WritableDraft<T>
  : T

castDraft是一个shell函数,它只是在外壳下调用type assertionDraft类型是一种递归类型,它贯穿于所有嵌套类型,这就是castDraft工作的原因。

也许值得在items上而不是state上操作

代码语言:javascript
复制
interface Item {
  readonly id: number;
  readonly value?: string;
}

interface State {
  readonly items: Item[];
}

function findItemById<Items extends Item[]>(
  items: Items,
  id: number
): Draft<Item> {
  return items.find((x) => x.id === id)!;
}

const state: State = {
  items: [{ id: 1 }, { id: 2 }, { id: 3 }],
};

// This does not work.
produce(state.items, (draft) => {
  const item = findItemById(draft, 2);
  item.value = "Something"; // works
});
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72327676

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档