-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TypeScript: types vs. interfaces #64
Comments
Related discussion in the TypeScript ESLint repo: typescript-eslint/typescript-eslint#142 Btw: I just found out that the VSCode Lens can also show references of types: Related discussion in the VSCode repo: microsoft/vscode#76706 |
I found an interesting use case for declaration merging I haven't considered yet: const Todo = (name: string) => ({
name,
description: undefined as string | undefined,
});
interface Todo extends ReturnType<typeof Todo> {}
function renderTodo(todo: Todo) {
// ...
}
renderTodo(Todo("Do something")); Here the const createTodo = (name: string) => ({
name,
description: undefined as string | undefined,
});
type Todo = ReturnType<typeof createTodo>;
function renderTodo(todo: Todo) {
// ...
}
renderTodo(createTodo("Do something")); However, you could still use classes: class Todo {
description?: string = undefined;
constructor(
public name: string,
) {}
}
function renderTodo(todo: Todo) {
// ...
}
renderTodo(new Todo("Do something")); I don't think that this is relevant to us since creating objects with a capitalized regular function (like |
Two things I like about interfaces, that are problematic with types: Code lens visibility in VS Code Impossible types with intersecion types type X = {
foo: number,
bar: string
}
type Y = {
foo: string,
baz: boolean
}
type XY = X & Y;
const xy: XY = {
foo: 3, // Impossible to get right, because it has to be a number and a string at the same time and won’t be number | string
bar: 'test',
baz: true
} I think the result of |
Yes, I agree. Code lens is still better for interfaces. But sometimes it's even an advantage when you can inspect the type by just hovering it. Since
I agree. When using interfaces, the error message appears at the location where the actual error happened: interface X {
foo: number,
bar: string
}
interface Y {
foo: string,
baz: boolean
}
interface XY extends X, Y {} // Interface 'XY' cannot simultaneously extend types 'X' and 'Y'. Named property 'foo' of types 'X' and 'Y' are not identical. I haven't experienced these kind of errors yet, but it's a valid point against types. Here's an explanation why TypeScript can't detect impossible types: https://stackoverflow.com/a/39554027 |
You could also come up with examples where this behavior of intersection types is an advantage: type TypeA = {
name: string;
};
type TypeB = {
name: "Something";
};
type TypeAB = TypeA & TypeB;
const typeAB1: TypeAB = {
// Works
name: "Something",
};
const typeAB2: TypeAB = {
// Type '"Something else"' is not assignable to type '"Something"'
name: "Something else",
};
interface InterfaceA {
name: string;
}
interface InterfaceB {
name: "Something";
}
// Named property 'name' of types 'InterfaceA' and 'InterfaceB' are not identical.(2320)
interface InterfaceAB extends InterfaceA, InterfaceB {} |
Interfaces used to have the advantage that they can express recursive types via This is no error anymore: type B = {
a: boolean;
b: B["a"];
}; |
Btw: Here is a (closed) issue at the TypeScript repo about the CodeLens issues of types: microsoft/TypeScript#13095 |
The fact that interfaces can use the polymorphic interface I {
returnThis: () => this;
}
type T = {
returnThis: () => T;
};
class ClassI implements I {
// Error which is correct, because returnThis should return the instance
returnThis = () => new ClassI();
}
class ClassT implements T {
// No error :(
returnThis = () => new ClassT();
} This leads me to the conclusion that interfaces should be used when you're describing objects that participate in an inheritance chain. Essentially: Objects created with For regular data objects (like React I wonder whether that can be expressed as ESLint rule 😁 |
TypeScript has two ways to describe the shape of an object: a type alias and an interface. I would like to collect some arguments for the discussion around types vs interfaces.
Historically, interfaces were more powerful which is why a lot of people went with interfaces. But nowadays the differences are not that big anymore. To summarize:
Type aliases don't create new names in error messages(not correct anymore)Type aliases cannot be implemented by classes(not correct anymore, only union types are not implementable which are impossible to do with interfaces anyway)Key in AllowedKey
are impossible with interfaces (Example)this
. Type aliases need to use the type alias name (which can be impossible sometimesTypeScript 3.7 supports recursive types) (Example)this
type. Some inheritance patterns cannot be expressed via types (Example)CodeLens In VSCode has better support for interfaces(not correct anymore, see below)extends
keyword (Example)string
and"some string"
) (Example)React.FC<{}>
generic.Personally I prefer type aliases because of the following reasons:
1. It's less to write (and less to read)
Ok, that's not a big difference, but look at the next example:
2. It's a single way to handle types
One problem I see is that you can't really use interfaces in an ergonomic way when you want to use TS' utility types, like
Readonly
:type
on the other hand provides a single way to define and merge types as you like. For instance, union types are impossible with interfaces, but straightforward with type aliases.In general I think it's impossible to have a big project without using
type
, but it's possible to have a big project withoutinterface
. We used type aliases in a big project and never really missed interfaces.For me there are only three valid reasons for interfaces:
Nicer hints in VSCode
Maybe they will improve it for
type
as well. I don't see a reason why this is only implemented for interfaces. It's possible to get a CodeLens for type references though (see below).Third-party libs
It's often recommended to use interfaces for third-party libs because of declaration merging. This makes it possible to extend the type with own properties.
While this is true for libraries like Express that provide a single
Request
orResponse
object which is extended by middlewares, I don't think that it's true for third-party libraries that don't support this kind of API. Personally I even think that it's bad practice to have an API where objects can be extended by everyone. So why should I use interfaces if I don't want to support this kind of usage anyway.OO programming style
If your project/team heavily uses an object-oriented programming style interfaces make much more sense.
What do you think?
The text was updated successfully, but these errors were encountered: