Formerly titled "Turning ANY into UNKNOWN" As a result of the change, 80-90% of ANY did not stay UNKNOWN, so the title was changed.
In TypeScript, there are two types of cases where "I used 'any' because it was too much trouble to write the type properly even though I wrote the code myself" and "I used 'any' because it was too much trouble to check the type of the value that comes from a third-party library. In the other case, I use "any" because I don't want to have to check the type of the value coming from a third party library.
For example, in the latter example, I was unsure of the type of the document object taken from Firestore, so I used any. ts
(doc: any) => { ... }
If we change this to UNKNOWN... ts
(doc: unknown) => { ... }
I don't know if UNKNOWN has EXISTS growing on it, he points out. I need to put a proper mold on it. ts
if (doc.exists) { // ERROR: Object is of type 'unknown'. TS2571
I'll try to put an impossible type on it to get the necessary information. ts
(doc: number) => { ... }
Then a type compatibility error will show what type is expected.
Argument of type '(doc: number) => void' is not assignable to parameter of type '(value: DocumentSnapshot
) => void | PromiseLike '.
I found the name DocumentSnapshot<DocumentData>, so I searched for it and found a reference
It's a long story, so I decided to give it an alias. ts
type Document = firebase.firestore.DocumentSnapshot<firebase.firestore.DocumentData>;
...
(doc: Document) => { ... }
After this, I get another error. ts
JSON.parse(data.json) // ERROR: Object is possibly 'undefined'. TS2532
It should not be undefined because I check its existence with doc.exists and then get it with data = doc.data() as Firestore explains, but TypeScript does not know that.
So we decide to throw an exception. ts
if (data === undefined) {
throw new TypeError("doc.data is undefined");
}
By doing this, the possibility of undefined in the flow after this point is eliminated, and TypeScript understands it properly.
By the way, I used to think "I shouldn't throw an exception if I can continue processing if I ignore it." But after I started using Sentry, my thinking changed to "If I throw an exception as soon as I detect a situation that shouldn't happen, I will be notified and can easily find the bug. I guess the fact that the exception on the user's browser reaches the developer also influences the way I think about programming.
It was hard to change all the "any" to UNKNOWN at once. We should have done them one at a time. It makes it difficult to isolate complex problems when they arise.
In the end, we have to write the type properly because we can't use it as UNKNOWN, and UNKNOWN generally disappears. I thought the transition process was to change from any to unknown so that type checking would run, and then change the type to the appropriate type while watching the error content.
UNKNOWN was left with only one location.
onClick: () => unknownI'll try to find the source code for a project I started writing years ago in ANY and process it. I wrote it while learning TypeScript, so there is quite a bit of ANY.
Day 1: 4 pomodoros, from 124 any to 56 any.
Day 2: Almost all gone
return (x as any).item ! == undefined; should be return "item" in x;<Route path="/:id" component={MyComponent} /><Route path="/:id" component={1} />, Type 'number' is not assignable to type 'FunctionComponent<any> | ComponentClass<any, any> | FunctionComponent<... ComponentClass<RouteComponentProps<any, StaticContext, PoorMansUnknown>, any> | FunctionComponent<... > | undefined', so it's not helpful.const MyComponent: React.FC<RouteComponentProps<{ id: string }>>.{x: 1} is OK, {x: 1, y: undefined} is not.{ x: number; y: number | null }, and if there is no y, do { x: 1, y: null };.obj.foo = convertType(obj.foo).new_obj = {...obj, foo: convertType(obj.foo)}ts
export const createFoo = (): FOO => {
const ret: any = {
version: 2,
};
ret.items = [];
return ret;
};
- Good
ts
export const createFoo = (): FOO => {
const version = 2;
const items: ItemID[] = [];
return { version, items };
};
- If you make any like the former, it's not checked if the object created by this function is really of type FOO.
- `e: paper.ToolEvent` really has `e.event`, but it is supposed to be absent on the type
- I used to write `const event = (e as any).event;`.
ts
// const event = e.event; // Property 'event' does not exist on type 'ToolEvent'
const event = (e as any).event; // event: any
- If you want to eliminate ANY, you can do it like this.
ts
// @ts-ignore
const event: MouseEvent = e.event;
- With a combination of these things, I rewrote the function that converts the state in the browser into an object that can be saved in Firestore as follows
- It's no good that position is a paper.Point object, so you can change it to `[number, number]` and so on.
- Extracting only the value to be stored or
- before
ts
export const convertStateItemToFirestore = (x: StateItem) => {
const ret: any;
ret.type = x.type;
ret.id = x.id;
ret.position = [x.position.x, x.position.y]
if (isPieceStateItem(x)) {
ret.text = x.text;
...
} else if (isPathStateItem(x)) {
ret.opacity = x.opacity ?? 1.0;
...
}
return ret;
}
- after
ts
export const convertStateItemToFirestore = (x: StateItem) => {
const { type, id } = x;
const position: [number, number] = [x.position.x, x.position.y];
const common = { type, id, position };
if (isPieceStateItem(x)) {
const { text, compact, scale } = x;
return {...common, text, compact, scale};
} else if (isPathStateItem(x)) {
const { opacity, dashArray, created } = x;
return {
...common, created,
opacity: opacity ?? 1.0,
dashArray: dashArray ?? [],
};
} else if (...) {
...
} else {
throw new TypeError(`unknown type: ${type}`);
}
}
- A function that takes a function and wraps it in a process and returns it, which is tricky
ts
export const onOverlayCanvas = (f: (...args: any[]) => unknown) => {
return (...args: unknown[]) => {
paper.projects[1].activate();
const ret = f(...args);
paper.projects[0].activate();
return ret;
};
};
- In order to do something about it, we needed generics.
ts
import { ToolEvent } from "paper";
export const onOverlayCanvas = <T extends [ToolEvent] | []>(
f: (...args: T) => unknown,
) => {
return (...args: T) => {
paper.projects[1].activate();
const ret = f(...args);
paper.projects[0].activate();
return ret;
};
};
isReadOnly: bool, but there was only an entry when it was true.Added 2021-02-18 Garbage cleanup for ANY Eraser Festival. - What you REQUIRE will be ANY - This caused an unacknowledged ANY in the code that uses Paper.js. - Fixed and paper.Item is now type-checked. - Spreading an instance of a class does not duplicate the method. - So I solved the problem with casts. It's not good to have casts all over the place, so we put them in one place. ts
export const attachStateItem = (
paperItem: paper.Item,
stateItem: StateItem,
): PaperItem => {
const ret = paperItem as PaperItem;
ret.item = stateItem;
return ret;
};
This page is auto-translated from /nishio/2日掛けてanyを撲滅した using DeepL. If you looks something interesting but the auto-translated English is not good enough to understand it, feel free to let me know at @nishio_en. I'm very happy to spread my thought to non-Japanese readers.