Closed victordidenko closed 1 year ago
hi @victordidenko , I understand your idea, but whether it's Immer or Mutative, they both generate the next immutable data through mutation of the draft. so the draft type should be definite. You might consider implementing the type like this:
type S = {
x: string | number;
};
const x: S = {
x: '10',
};
const y = create(x, (draft) => {
draft.x = Number(draft.x);
});
For your example, it might be possible to use type assertions to solve this type issue.
type N = {
x: number;
};
const y = create(x, (draft) => {
draft.x = Number(draft.x);
}) as N;
The type of the immutable data should be { x: string | number; }
. Deliberately changing this fact may cause some unnecessary type issues.
If I have misunderstood anything, feel free to discuss it.
Is it conceptual limitation?
I have thought I could write such helper myself, maybe this will be enough for my cases 🤔 Something like this:
function createx<X, Y>(
original: X,
mutation: (draft: Draft<X>, result: Draft<Y>) => void
): Y {
return create(
original,
(draft) => mutation(draft, draft as any as Draft<Y>)
) as any as Y
}
I understand this is a huge leap of faith, and user could end up with badly typed object, for example if some field is required in Y
, but user forgot to set it, TypeScript will say nothing. And later this could throw an exception like reading property from undefined...
Yes, the key point here is draft === resultDraft
is true? Obviously, they are strictly equal.
The general notion of the same set of immutable data is that they would have consistent types, otherwise they do not belong to an immutable data set.
I mean, if it is a conceptual limitation of the library, you can close this issue :) I'll try my way with helpers in my case.
Or if you think this could be possible, and nice to have, to opt return type somehow (not exactly using my idea, maybe somehow with generics) — this issue could be like tracker for thoughts.
I think there is some request in community for this feature, I definitely saw such questions about immer
.
Both Immer and Mutative support currying, and the currying parameters conflicts with the current parameter in your proposal.
const baseState = {};
const producer = create((draft, p) => {
console.log(p); // console log `1`
});
const state = producer(baseState, 1);
I didn't find any relevant tickets in Immer's list of issues, if you do find them, feel free to discuss it.
Overall, this proposal has two points highlighted:
Here is a (still unanswered after 9 months) question with the same issue https://stackoverflow.com/questions/73554880/produce-a-different-type-with-immer-in-typescript
Here is a (still unanswered after 9 months) question with the same issue https://stackoverflow.com/questions/73554880/produce-a-different-type-with-immer-in-typescript
I think it should be typed as follows:
type Data<T> = Immutable<{ data: T }>
const a: Data<number|string> = { data: 0 }
draft === resultDraft
, Two exactly equal parameters with different types is itself a misunderstanding.
It was just an example, that not only me who has this question.
I understand your answers :) I don't understand though, do you agree that there is a room for improvements, or you disagree?
From this scenario, I suggest it can be solved with type assertions, or the helper you mentioned before.
Personally, I don't recommend this, it may cause type consistency problems or ambiguity in the immutable data type collection.
Ok, got you! I'll close this then
Sometimes I need to change type of original object. For example, I want to add new field, or change existing field. Currently it is impossible, either with
mutative
, and withimmer
both, as far as I know (maybe I am missing something?).And without loosing type safety! I mean, I can write
create(x as any, (draft) => { ... })
, but this is not nice.I checked your tests, and you just shuts TypeScript up with
@ts-ignore
in such cases. https://github.com/unadlib/mutative/blob/3c2e66bbfb7dc117a1c29a552790192b07e7a943/test/create.test.ts#L1764-L1765I don't know how to do it though... Maybe pass
draft
object to mutation function twice? Actually it will be the same object, but you can say to TypeScript, that they are not. Something like this:Just an idea