Moment For Technology

Is your type really safe if you don't write any, 0 TS error?

Posted on Dec. 2, 2022, 2:26 p.m. by Inaaya Barman
Category: The front end Tag: typescript The front end

preface

TS can be used as an error preposition, but incorrectly using TS is not guaranteed. For example, beginners often write any or even allow some TS errors, which is beyond the scope of this discussion. What we're going to talk about is that even if you can write code that looks safe without any, without any TS error, can you really do that with TS error prefacing and avoid the component crashing or even the page going blank? The answer, of course, is no. Let's take a look at some classic examples of incorrect use of TS.

1.objArr[index].key

Interface IName {name: string; } let names: IName[] | undefined; // This will not be marked red, but the actual runtime will probably report an error, because obviously obj? [2] May be of undefined type. names? .[2].name;Copy the code

This is actually dangerous because it is unacceptable to have a blank page once an error occurs.

So how to avoid it? It's an array of objects that are evaluated in a variable after the subscript instead of being evaluated directly.

Interface IName {name: string; } let names: IName[] | undefined; const thirdName = names? . [2]; // In this case, it will be marked in red, and VSCode will help you automatically fill in the name field. thirdName.nameCopy the code

Obj.key. GrandsonKey operation causes problems when the type declaration of the key trusts that the incoming value is within the key list

const object = { a: { c: 'c' }, b: { c: 'c' }, }; Function foo(key: keyof Object) {const d = object[key].c; } // @ts-ignore // then the caller passes a 'c', so the entire scope of the code fails on the first line of the foo function, and if there is no catch, the entire page will be white. foo('c');Copy the code

But it's not enough to just point out the problem. How can this be avoided? This solution is for reference only and is not optimal.

interface IC { c: string; } const object: Recordstring, IC | undefined = { a: { c: 'c' }, b: { c: 'c' }, }; Function foo(key?) foo(key?) foo(key?) : string) { const d = key ? object[key]? .c : 'default'; } foo('c');Copy the code

3. Obj.key. During the grandsonKey operation, the key trusted the external mandatory transmission

interface Iter { key: { grandsonKey: string; }; Function foo(object: Iter) {const a = object.key.grandsonKey; } // Const object = {} as Iter; foo(object);Copy the code

Further reflection

In example 2, my solution here was to set the type wider so that our code would be marked red if it didn't deal with such a large range, which would actually solve the key problem of error.

If you don't catch the error, or if you set the errorBoundary with errorBoundary, the page will go blank. If the error is significant, this is a P0 accident (even if the upstream exception is bigger).

Even if there is errorBoundary, if a component directly shows the default copy of component error, it is not a fine error handling. The ideal error handling is to consider whether the abnormal data can be compatible, such as giving a default value to ensure the normal operation of the entire program.

And on the other hand, we use a larger range of type definition 'a' | 'b' - string | undefined indeed can let TS is not compatible with the code error is put forward, which prompted us to abnormal data into normal data, guarantee the normal operation procedure, rather than throwing an error.

But this was rejected by the boss, because it solved the problem and created another problem: the caller didn't know what to pass.

Such as the function foo in example 2 we are exposed to the business use, business party originally knew he should pass the 'a' | 'b' parameter of type, but in our distrust because parameters instead of string | undefined type, the caller know only to pass the string, even didn't seem to preach, but in fact, No pass is only compatible, can run, but will never get the expected results.

So my latest idea is that the scenario to be exposed to the outside world should have an access layer. The function of the access layer is to provide the normal interface definition, so that the caller can know the design intention of the function.

The access layer does not define the processing function, but introduces and calls the function of the compatibility layer, and the parameter directly passes through the original data.

At the compatibility layer, we adopt a strategy of complete mistrust for function parameters, such as setting every attribute to be nonmandatory, not enumerated, and setting all attributes to strings. This ensures full compatibility when we use it.

In the consumer layer we really define the handler functions and accept the same parameters as the access layer, because the exception data has been processed by the compatibility layer, so it can be used safely.

Of course, this is only a preliminary idea, has not really landed, welcome to point out their own views or give a better solution.

Search
About
mo4tech.com (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.