Dart for JS/TS Developers
Dart is a strongly typed, sound null-safe language that compiles to native ARM and JavaScript. If you know TypeScript you already understand most of it — the type syntax is different but the concepts are the same. This lesson maps the key Dart features onto their TypeScript equivalents so you can start reading Flutter code immediately.
Types and sound null safety
Section titled “Types and sound null safety”TypeScript’s string | null union becomes Dart’s String?. The ? suffix on any type means “this value may be null.” Without ?, the type is guaranteed non-null and the compiler enforces it at every assignment.
// TypeScriptlet name: string = 'Ava';let maybeAge: number | null = null;let definiteAge: number = 30;
function greet(n: string): string { return `Hello, ${n}!`;}// DartString name = 'Ava';int? maybeAge = null;int definiteAge = 30;
String greet(String n) { return 'Hello, $n!';}var, final, and const
Section titled “var, final, and const”// TypeScriptlet counter = 0; // mutableconst MAX = 100; // runtime constantconst PI = 3.14159; // runtime constant
// 'as const' for compile-time literalconst COLORS = ['red', 'green'] as const;// Dartvar counter = 0; // mutable, type inferredfinal max = 100; // runtime constant (set once)const pi = 3.14159; // compile-time constant
// const list = compile-time literalconst colors = ['red', 'green'];Classes and constructors
Section titled “Classes and constructors”// TypeScriptclass User { readonly id: string; name: string;
constructor(id: string, name: string) { this.id = id; this.name = name; }
greet(): string { return `Hi, I'm ${this.name}`; }}
const u = new User('1', 'Ava');console.log(u.greet());// Dartclass User { final String id; String name;
User(this.id, this.name); // shorthand constructor
String greet() => 'Hi, I\'m $name';}
final u = User('1', 'Ava');print(u.greet());async / await and Future
Section titled “async / await and Future”// TypeScriptasync function fetchUser(id: string): Promise<User> { const res = await fetch(`/api/users/${id}`); return res.json() as Promise<User>;}
// Usageconst user = await fetchUser('42');// DartFuture<User> fetchUser(String id) async { final res = await http.get(Uri.parse('/api/users/$id')); return User.fromJson(jsonDecode(res.body));}
// Usagefinal user = await fetchUser('42');Future<T> is Dart’s Promise<T>. The async/await syntax is identical. Dart also has Stream<T> for sequences of async values — the equivalent of an async generator / RxJS Observable.
Collections, spreads, and collection-if
Section titled “Collections, spreads, and collection-if”// TypeScriptconst nums: number[] = [1, 2, 3];const map: Record<string, number> = { a: 1, b: 2 };const set: Set<string> = new Set(['x', 'y']);
// spreadconst more = [...nums, 4, 5];
// conditional itemconst isAdmin = true;const items = ['home', ...(isAdmin ? ['admin'] : [])];// DartList<int> nums = [1, 2, 3];Map<String, int> map = {'a': 1, 'b': 2};Set<String> set = {'x', 'y'};
// spreadfinal more = [...nums, 4, 5];
// collection-if (Dart-native syntax)final isAdmin = true;final items = [ 'home', if (isAdmin) 'admin',];Runnable Dart example
Section titled “Runnable Dart example”import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
// Pure Dart logic — same as you would write in Node/TS
List<String> buildGreetings(List<String> names, {bool formal = false}) {
return [
for (final name in names)
formal ? 'Good day, $name.' : 'Hey, $name!',
];
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const names = ['Ava', 'Ben', 'Cleo'];
final greetings = buildGreetings(names, formal: false);
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: const Text('Dart for JS Devs')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
for (final g in greetings)
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(g, style: const TextStyle(fontSize: 16)),
),
),
],
),
),
);
}
}