Important Rules For Writing Idiomatic TypeScript

Whether you believe or not, but the fact is TypeScript is spreading like wildfire. According to a survey, it was listed as the third most- loved programming language and the fourth most wanted.
It’s so inevitable since it has replaced vanilla JavaScript as the language of choice for many packages in the JS ecosystem, with some like Yarn even, going similarly as rewriting their entire codebase in TypeScript. We feel one reason for this gigantic rise to be the fact that TypeScript fundamentally is simply JavaScript. This makes the entry bar a lot lower for existing JavaScript developers, and the way that it’s typed may also attract other devs who prefer the features typed languages provide.
This cuts the two different ways, as well, because the simplicity of choosing TypeScript has led to some cases where the language is not being used as effectively as it could be. Numerous developers still write TypeScript like they’re writing JavaScript, and this brings with it some disadvantages. We’ll be taking a look at some real world code written in TypeScript that could be improved to use the language’s strengths.
Rules-
1. Don’t neglect interfaces-
In TypeScript, an interface basically specifies the expected shape of a variable. It’s as simple as that. Let us see the simple interface to drive the exact idea.
interface FunctionProps {
foo: string;
bar: number;
}
Now if any variable is defined to implement FunctionProps, it will have to be an object with the keys foo and bar. Some other key addition will make TypeScript fail to compile. Let us see this.
const fProps: FunctionProps = {
foo: 'hello',
bar: 42,
}
Now we have an object fProps that implements the FunctionProps interface correctly. If I deviate from the shape specified in the interface by, say, writing fProps.foo = 100 or deleting fProps.bar, TypeScript will complain. fProps’ shape needs to match FunctionProps exactly or there will be hell to pay.
fProps.foo = true ❌ // foo must be a string
Since we’ve gotten that out of the way, let’s look at an example. Take this React functional component method:
const renderInputBox = (props, attribute, index) => {
return (
<div key={index} className="form-group">
{renderLabel(attribute)}
<InputBox
name={attribute.key}
value={!!isAssetPropAvailable(props, attribute) && props.inputValue}
onChange={props.handleInputChange}
placeholder={`Enter ${attribute.label}`}
/>
</div>
);
};
While this is totally fine if you were writing JavaScript, it doesn’t take advantage of interfaces. For what reason is this awful? You don’t get any IntelliSense features that you otherwise would if the method’s argument were typed.
Also, you could easily pass in a prop of an alternate expected that shape to this method and you would be none the wiser because TypeScript would not complain about it. This is simply vanilla JS, and you might as well eliminate TypeScript from the project altogether if everything was written this way. How might we improve this? Take a look at the arguments themselves, how they’re being used, and what shape is expected from them.
We should begin with props. Look at line 7 and you can see that it’s supposed to be an object with a key called inputValue. At line 8, we see another key being accessed from it called handleInputChange, which, from the context, has to an event handler for inputs. We now recognize what shape props should have, and we can create an interface for it.
interface PropsShape {
inputValue: string;
handleInputChange: (event: React.FormEvent): void;
}
Proceeding onward to attribute, we can use the same method to create an interface for it. Take a look at line 6. We’re accessing a key called key from it (clue: it’s an object). On line 9, we’re accessing another key from it called label, and with this information, we can create an interface for it.
interface AttributeShape {
key: string;
label: string;
}
We can now rewrite the method to look like this instead:
const renderInputBox = (props:PropsShape, attribute:AttributeShape, index:number) => {
return (
<div key={index} className="form-group">
{renderLabel(attribute)}
<InputBox
name={attribute.key}
value={!!isAssetPropAvailable(props, attribute) && props.inputValue}
onChange={props.handleInputChange}
placeholder={`Enter ${attribute.label}`}
/>
</div>
);
};
consider the benefits of doing this:
- You get IntelliSense wherever you use this method. And you can immediately see what its arguments should look like without having a look at it.
- You can never abuse this method because TypeScript won’t allow you to pass in arguments with wrong shapes.
- Any change to the method definition- possibly index is now a string- and TypeScript will prevent your code from compiling until you fix all the instances where the method was used.
Read more at-
No comments:
Post a Comment