Adding Type Annotations to DOM Functions
Now that we have defined our types for the API response, we can refactor our DOM manipulation functions to include type annotations. That helps us catch errors, and it makes the code easier to read.
Refactor clearDefinitionsSection Function
Here is the clearDefinitionsSection function:
// Helper to clear the definitions section
const clearDefinitionsSection = () => {
const definitionsSection = document.getElementById("definitions");
definitionsSection.innerHTML = "";
return definitionsSection;
};
We can refactor this function to include type annotations for the return value:
// Helper to clear the definitions section
const clearDefinitionsSection = (): HTMLElement => {
const definitionsSection = document.getElementById(
"definitions",
) as HTMLElement;
definitionsSection.innerHTML = "";
return definitionsSection;
};
We added a return type annotation for the function: HTMLElement. Notice that we used a type assertion (as HTMLElement) to cast the return value of document.getElementById("definitions") to an HTMLElement. This is necessary because the getElementById method returns an Element | null type, and we need to specify that the return value is an HTMLElement.
Type Assertion in TypeScript
A type assertion is how we tell the compiler the type of a variable when the compiler cannot infer it on its own. It is like type casting in other languages, but it does not change the type of the variable at runtime. The TypeScript compiler uses it during static analysis, and that is all it does.
Apply the same approach to the remaining DOM functions.
Refactor createDefinitionsHeading Function
// Helper to create the definitions heading
const createDefinitionsHeading = (): HTMLElement => {
const definitionsHeading: HTMLElement = document.createElement("h1");
definitionsHeading.classList.add("text-2xl", "font-semibold");
definitionsHeading.innerText = "Definitions";
return definitionsHeading;
};
Refactor createDefinitionDiv Function
// Helper to create the definition div
const createDefinitionDiv = (): HTMLElement => {
const definitionDiv = document.createElement("div");
definitionDiv.classList.add("bg-sky-50");
return definitionDiv;
};
Refactor createPartOfSpeechElement Function
// Helper to create the part of speech element
const createPartOfSpeechElement = (partOfSpeech: string): HTMLElement => {
const partOfSpeechName: HTMLElement = document.createElement("p");
partOfSpeechName.classList.add(
"px-4",
"py-2",
"font-semibold",
"text-white",
"bg-sky-600",
);
partOfSpeechName.innerText = partOfSpeech;
return partOfSpeechName;
};
Refactor createDefinitionsList Function
// Helper to create the definitions list
const createDefinitionsList = (): HTMLElement => {
const definitionsList: HTMLElement = document.createElement("ul");
definitionsList.classList.add(
"p-2",
"ml-6",
"font-light",
"list-disc",
"text-sky-700",
);
return definitionsList;
};
Refactor createDefinitionItem Function
// Helper to create the definition item
const createDefinitionItem = (definitionObj: Definition): HTMLElement => {
const definitionsItem: HTMLElement = document.createElement("li");
definitionsItem.innerText = definitionObj.definition;
return definitionsItem;
};
Notice we used our custom Definition type to annotate the definitionObj parameter. This ensures that definitionObj has the structure defined by the Definition type. If it does not match, TypeScript will raise a type error during static analysis. However, this will not produce runtime errors. If the API response structure changes, TypeScript will not catch it while the application is running.
Runtime Behavior and Erased Types
TypeScript type checking happens at compile time. Once compiled to JavaScript, all type annotations are removed. This is called type erasure. The resulting JavaScript contains no type information, so no type checking happens at runtime. Type annotations are only for the developer and the TypeScript compiler.
Refactor displayWordDefinition Function
// Display the word definitions
const displayWordDefinition = (meanings: Meaning[] | undefined): void => {
const definitionsSection: HTMLElement = clearDefinitionsSection();
const definitionsHeading: HTMLElement = createDefinitionsHeading();
definitionsSection.appendChild(definitionsHeading);
meanings?.forEach((meaning: Meaning) => {
const definitionDiv: HTMLElement = createDefinitionDiv();
definitionsSection.appendChild(definitionDiv);
const { partOfSpeech, definitions } = meaning;
const partOfSpeechName: HTMLElement =
createPartOfSpeechElement(partOfSpeech);
definitionDiv.appendChild(partOfSpeechName);
const definitionsList: HTMLElement = createDefinitionsList();
definitionDiv.appendChild(definitionsList);
const definitionListItems: HTMLElement[] =
definitions.map(createDefinitionItem);
definitionsList.append(...definitionListItems);
});
};
Notice the return type annotation: void. This specifies that the function does not return a value.
Void Return Type in TypeScript
In TypeScript, the void type indicates that a function does not return a value. In JavaScript, functions that do not explicitly return a value implicitly return undefined. TypeScript’s void makes this intent explicit. If you try to return a value from a void function, TypeScript will give a compile-time error.
Checkpoint: Commit your progress.
git add .
git commit -m "dictionary-06: Add type annotations to DOM functions"
git push