Consider a scenario where you have a parent component rendering a child component, and you want to conditionally pass TypeScript properties based on certain criteria. For instance, let’s say you have a parent component passing data to a child component, and you want to enforce certain properties based on conditions.
Table of contents
Open Table of contents
Conditional Types and Type Guards
Consider a scenario where you have a parent component rendering a child component, and you want to conditionally pass TypeScript properties based on certain criteria. For instance, let’s say you have a parent component passing data to a child component, and you want to enforce certain properties based on conditions.
You might’ve seen code like this.
type CarProps = {
brand: string;
engine: "diesel" | "electric";
fuelEfficiency?: number;
batteryLife?: number;
};
And this Optional Properties
might become a problem.
Let’s adapt the TypeScript example to use a CarProps scenario where the child component receives properties based on whether the car is powered by diesel or electricity.
// Define conditional types for male and female properties
type DieselProps = {
engine: "diesel";
fuelEfficiency: number;
};
type ElectricProps = {
engine: "electric";
batteryLife: number;
};
// Create a conditional type for the child component
type CarProps = {
brand: string;
} & (DieselProps | ElectricProps);
We have EngineProps representing the conditional properties based on the car’s engine type, either ‘diesel’ or ‘electric’. The CarProps type combines the mandatory ‘brand’ property with the conditional EngineProps. The CarComponent then uses these types in its props definition.
// Parent component rendering the child
const ParentComponent = () => {
// Example usage for a male
const electricProps: CarProps = {
brand: "Tesla"
engine: "electric"
batteryLife: 300
};
// Example usage for a female
const dieselProps: CarProps = {
brand: "VW"
engine: "diesel"
fuelEfficiency: 6
};
return (
<>
<ChildComponent {...electricProps} />
<ChildComponent {...dieselProps} />
</>
);
};
// Child component receiving conditional props
const ChildComponent = (props: CarProps) => {
// Access properties based on gender using type guards
if (props.engine === 'electric') {
console.log(props.batteryLife); // TypeScript infers type here
}
if (props.engine === 'diesel') {
console.log(props.fuelEfficiency); // TypeScript infers type here
}
// Accessing the always-present property
console.log(props.brand);
return <div>{/* Component rendering logic */}</div>;
};
In this parent component example, we provide values for ‘brand’, ‘engine’, and ‘batteryLife’. TypeScript ensures that when ‘engine’ is ‘electric’, only the ‘batteryLife’ property is accessible, and any attempt to pass ‘fuelEfficiency’ would result in a type error.
Can I just use extends
?
Sure. And your TypeScript code would look like this and the rest of the code would be the same.
type BaseProps = {
brand: string;
};
// Define types for male and female properties extending the base
type DieselProps = BaseProps & {
engine: "diesel";
fuelEfficiency: number;
};
type ElectricProps = BaseProps & {
engine: "electric";
batteryLife: number;
};
Choosing the Right Approach
Let’s compare the two approaches:
Approach 1: Conditional Types
Pros:
- Dynamic Typing: It allows for dynamic typing based on conditions, making it well-suited for scenarios where certain properties depend on runtime conditions.
- Clear Conditionals: The use of and and or operators in conditional types provides clear syntax for expressing different scenarios and conditions.
- Intellisense Support: Type guards ensure that TypeScript’s intellisense works effectively, providing developers with immediate feedback and suggestions.
Cons:
- Complexity: In more intricate scenarios, the use of conditional types might lead to complex type structures, potentially making the code harder to read and understand.
Approach 2: Type Extends
Pros:
- Modularity: The type extends approach is often more modular and allows for easier reuse of types. It’s straightforward to create types that extend or compose other types.
- Readability: In certain scenarios, especially when dealing with simpler conditional logic, using type extends can result in more readable and maintainable code.
Cons:
- Static Typing: While the type extends approach allows for creating flexible types, it might not be as well-suited for dynamic typing based on runtime conditions.
- Limited Dynamic Features: It might not provide as much flexibility for scenarios where type conditions need to be determined dynamically at runtime.
Conclusion
The choice between these approaches depends on the specific requirements of the project and the nature of the conditions you are dealing with. If your use case involves dynamic conditions and runtime values, the conditional types and type guards approach might be more suitable. If you’re focused on creating modular and reusable types with static conditions, the type extends approach could be a better fit.
In many cases, a combination of both approaches might be used in a project, depending on the context and the complexity of the typing requirements. It’s essential to evaluate the readability, maintainability, and performance implications when deciding on the approach to take.