Dart 시작하기 – 노마드 코더 Nomad Coders
이 포스트는 노마드 코더의 강의 <Dart 시작하기> 강의를 공부하며 정리한 노트입니다.
Dart 시작하기
# 0.1 Why Dart
Dart 는 ARM32, ARM64, x86_64, 그리고 Javascript 로 컴파일될 수 있다.
JIT (Just In Time) : 각 아키텍쳐에서 실행 가능한 코드로 Dart를 컴파일한다. 긴 시간이 걸리기에, 작은 변화가 있을 때마다 JIT 컴파일을 실행한다면, 개발 경험의 품질이 저하될 것이다.
AOT (Ahead Of Time) : UI 일부 수정 같은 작은 변화를 JIT 로 대응하는 것은 비효율적이다. dart VM 을 이용하는 AOT 방식은, 코드의 결과를 바로 보여줄 것이다.
요약 : 개발 중에는 AOT, 배포 시에는 JIT
Dart의 강점은 Dart 팀과 Flutter 팀은 긴밀히 협업하고 있다는 데 있다. 즉, Flutter를 위해 Dart 를 변경 가능하다. React 팀이 Javascript 를 변경할 수 없다는 것과 대조적이다.
# 0 INTRODUCTION
# 0.2 How To Learn
# 1 VARIABLES
# 1.0 Hello World
-
dart는 main() 함수를 찾는다. main() 함수가 없다면, 컴파일러는 에러를 띄운다.
-
모든 문의 끝에는 세미콜론을 붙여야 한다.
void main() { print("Hello World"); }
# 1.1 The Var Keyword
- 변수 만들기
- var 키워드 : type 을 추론한다.
var name = "James";
- 타입 키워드 사용
String name = "James"; name = "hello"
- var 키워드 : type 을 추론한다.
-
변수를 재할당(Update)할 때는, type이 일치해야 한다.
var name = "James"; name = 1; //에러 발생
- var : 함수나 메소드 내부의 지역 변수 선언
- 타입 키워드 : class 에서 변수나 프로퍼티 선언할 때
# 1.2 Dynamic Type
- Dart 의 규칙은 strict 하기보다는 유연한 편이다
-
dynamic 타입은 용어 그대로, 동적 타입을 제공한다.
dynamic name; name = "James"; name = 12;
# 1.3 Nullable Varialbes
-
Null Safety : 개발자가 null 값을 참조할 수 없도록 하는 것
bool isEmpty(String string) => string.length == 0; void main() { isEmpth(null); }
- Null Safety 가 적용되지 않은 컴파일러는 정상적인 컴파일을 진행하고, 해당 프로그램이 실행될 때, 런타임 에러를 발생시킬 것이다.
-
Null Safety 가 적용된 컴파일러는, 컴파일 과정에서 에러를 발생시켜 런타임 에러를 방지한다.
-
만약 값이 null 이거나 특정 타입의 값이어야 할 때는, ? 기호를 사용한다.
void main() { String? name = "James"; name = null; }
-
만약 해당 타입의 메서드를 사용하고 싶다면, ?. 연산자를 사용하면 된다.
void main() { String? name = "James"; name?.isNotEmpty; }
# 1.4 Final Variables
-
자바스크립트의 const 키워드처럼, 변수의 값은 수정될 수 없다.
void main() { final name = "James"; // 타입을 명시하지 않아도 알아서 추론한다. name = "Han"; // 컴파일 에러 final String codename = "Bond"; codename = "pizza"; // 컴파일 에러 }
# 1.5 Late Variables
-
late 는 초기 값 없이 변수를 선언할 수 있게 한다.
void main() { late final String name; // 블라블라 name = "James"; }
- 만약 값이 할당되지 않은 상태에서 late 변수를 참조하려 한다면, Dart는 에러를 발생시킨다.
- Data Fecting 할 때 사용한다!
# 1.6 Constant Variables
- 수정될 수 없다 + 선언과 동시에 할당되어야 한다. (Compile-time Constant : 컴파일 시에 변수에 할당된 값을 알 수 있어야 한다.)
void main() { const name = "tom"; // 컴파일 시점에 바뀌지 않는 값 final username=fetchAPI(); // 컴파일 시점에 바뀌는 값 }
# 2 DATA TYPES
# 2.0 Basic Data Types
- Dart 는 거의 완전한 객체지향 언어이다. 즉, 모든 것이 class 이다.
-
int, double 은 num 을 상속한다.
- String, bool, int, double, num…
# 2.1 Lists
-
List 를 만들기 위해서는 두 가지 방법을 쓸 수 있다.
void main() { var numbers = [1, 2, 3, 4]; List<int> numbers = [1, 2, 3, 4]; }
- 추가적인 기능으로 collection if 와 collection for 을 지원한다.
- colletion if 는 존재할 수도, 안 할 수도 있는 요소를 가져올 수 있다.
void main(){ var giveMeSix = true; int case1 = [ 1, 2, 3, 4, 5, if(giveMeSix) 6, ]; }
# 2.2 String Interpolation
- 스트링 인터폴래이션은 text에 변수를 추가하는 방법이다.
- 자바스크립트의 템플릿 리터럴과 비슷하다.
- 큰따옴표(”) 혹은 작은 따옴표(’) 사이에 변수를 넣고 싶다면 $변수명을, 무언가를 연산하고 싶다면, ${표현식} 형태로 적어주면 된다.
void main() { var name = "James"; var age = 10; var greeting = "hello $name, I'm ${age + 5}"; print(greeting); //hello James, I'm 15 }
# 2.3 Collection For
- Dart에서는 for문을 사용하여 List의 컬렉션을 구성할 수 있다.
-
List 안에서 for 문을 사용한다는 점에서, Python의 리스트 컴프리헨션 + for 문의 조합과 비슷하다.
void main() { var oldFriends = ["Nico", "Lynn"]; var newFriends = [ "James", "Han", for (var friend in oldFriends) "❤️ $friend" ]; print(newFriends); // [James, Han, ❤️ Nico, ❤️ Lynn] }
# 2.4 Maps
- Dart의 Map은 자바스크립트의 Object, 파이썬의 딕셔너리 같은 해시맵이다.
- Object 타입은 Dart에서 사실상 모든 타입(any)에 가깝다. 왜냐하면, 객체 지향 프로그래밍 언어인 Dart는 모든 게 객체, 즉 Object이기 때문이다. Map의 key 혹은 value 가 여러 타입을 가질 때, 컴파일러는 Object 타입임을 유추해낸다.
- Map을 생성하는 법은 크게 두 가지가 있다.
- var 키워드
var gift = { 'first': 'partridge', 'second': 'turtledoves', 'fifth': 'golden rings' }
- Map 생성자
Map<String, int> gift = { 'first': 1, 'second': 2, 'fifth': 3 }
- var 키워드
# 2.5 Sets
- 중괄호({})를 통해 정의할 수 있다.
- 중복을 허용하지 않는다. 순서가 있다.
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
# 3 FUNCTIONS
# 3.0 Defining a Function
- Dart 의 함수는 반환값의 타입, 이름, 매개변수, 실행문으로 이루어져있다.
- 매개변수도 타입을 각각 지정해줘야 한다.
-
Dart에서 함수는 두 가지 방법으로 정의할 수 있다.
-
실행문이 한 줄 이상일 때
num plus(num a, num b) { return a + b; }
-
실행문이 한 줄일 때
num plus(num a, num b) => a + b;
-
# 3.1 Named Parameters
- 함수를 선언할 때 정한 위치에 따른 파라미터를 입력하는 것이 아닌(Positional Parameters), 함수를 정의할 때 정한 파라미터의 이름과, 매개변수로 입력할 값을 각각 key와 value 로 하는 해시맵 형태로 입력하는 것.
- 이를 사용하기 위해서는, 파라미터들을 중괄호로 감싸주어야 한다.
-
Dart 의 Null Safety 기능은 입력된 파라미터가 Null 일 가능성에 대해 경고하기 때문에, 파라미터에 null이 아닌 기본값을 지정하거나, required 제어자를 사용한다.
String sayHello({required String name, required int age, required String country}) { return "${name} / ${age} / ${country}"; } String sayHello({String name = "James", int age = 20, String country}) { return "${name} / ${age} / ${country}"; } void main() { print(sayHello(name: "sugar", age: 10, country: "Korea")); }
# 3.3 **Optional Positional Parameters**
-
Positional Parameter 가 Optional 함을 표현하기 위해서는, 대괄호와 ? 를 사용한다.
String sayHello(String name, int age, [String? country = ""]) { return 'Hello ${name}, You are ${age} from the ${country}'; } void main() { var result = sayHello("sugar", 10); print(result); }
# 3.4 QQ Operator
- 자바스크립트에서도 같은 기능이 있다. 널 병합 연산자, 널 할당 연산자라고 칭한다.
- ?? (QQ Operator)
- a ?? b 에서 a가 null 이라면, b를 반환하고, null이 아니라면 a를 반환한다.
String capitalizeName(String? name) { return name?.toUpperCase() ?? ""; }
- a ?? b 에서 a가 null 이라면, b를 반환하고, null이 아니라면 a를 반환한다.
- ??= (QQ equals, QQ Assigment Operator)
- 좌항에 할당된 값이 null이라면, 우항의 값을 할당한다.
void main() { String? name; name ??= "sugar"; name = null; name ??= "js"; print(name); // js }
- 좌항에 할당된 값이 null이라면, 우항의 값을 할당한다.
# 3.5 Typedef
- 타입스크립트의 type alias 와 비슷하다.
-
자료형에 이름을 붙여 변수처럼 사용할 수 있게 한다.
typedef ListOfInts = List; ListOfInts reverseListOfNumbers(ListOfInts list) { var reversedList = list.reversed.toList(); return reversedList; }
# 4 CLASSES
# 4.0 Your First Dart Class
- class 에서 property 를 선언할 때는 반드시 타입을 명시해야 한다. (var 키워드 X)
- 인스턴스를 생성할 땐, 클래스이름() 으로 생성하면 된다.
-
클래스 메서드 내에서는 this 를 사용하지 않는 것을 권장한다.
class Player { final String name = 'James'; final int age = 99; void sayName(){ print("Hi my name is $name") } void main() { var player = Player(); }
# 4.1 Constructors
- constructor method 를 생성하는 법은, 클래스 이름과 같은 이름의 메서드를 클래스 내에서 선언하면 된다.
- 인스턴스 생성 시에 전달받을 값들은 인스턴스가 생성되고 각 변수에 할당될 것이기 때문에 late 를 사용한다.
-
그러나 위와 같은 작업은 비효율적이다.
class Player { late final String name; late final int age; Player(String name){ this.name = name; } } void main(){ var player = Player("James", 99); }
- 더 짧은 constructor method 를 작성하는 법은 다음과 같다.
class Player { late final String name; late final int age; **Player(this.name, this.age);** }
# 4.2 Named Constructors Parameters
-
함수와 마찬가지로, 중괄호({}) + required를 활용하면 된다.
class Player { String name; int xp; String team; int age; Player({ required this.name, required this.xp, required this.team, required this.age. }); void main() { var player = Player( name: "James", xp: 1200, team: "blue", age: 99, ); }
# 4.3 Named Constructors
- 다르게 동작하는 Constructor 를 갖고 싶을 때 사용한다.
-
이름은 자유롭게 지을 수 있다.
class Player { String name; int xp; String team; int age; Player.createBluePlayer({ required String name, required int age, }) : this.age = age, this.name = name, this.team = 'blue', this.xp = 0; Player.createBluePlayer({ required String name, required int age, }) : this.age = age, this.name = name, this.team = 'red', this.xp = 0; } void main() { var redplayer = Player.createRedPlayer( name: "James", age: 99, ); }
-
여기에는 추가적인 syntactic sugar(편의 문법)이 존재한다.
// 일반적인 방법 Player.createRed(String name, int xp) : this.name = name, this.xp = xp, this.team = 'red'; // 간소화된 방법 Player.createRed( this.name, this.xp, [this.team = 'red'] );
# 4.5 Cascade Notation
-
인스턴스의 프로퍼티에 할당된 값을 업데이트하고 싶을 때 사용하는 편의문법이다.
class Player { String name; int xp; String team; int age; Player({ required this.name, required this.xp, required this.team, required this.age. }); void main() { var player = Player( name: "James", xp: 1200, team: "blue", age: 99, ); **player.name = "Han"; player.xp = 20; player.team = "red"; player.age = 99; // 반복되는 구조** }
-
위 예시에서 인스턴스의 프로퍼티를 업데이트할 때, 반복적인 구조가 나타난다.
-
.. (마침표 두 개) 를 활용하여 위 예시의 player 를 곧바로 참조할 수 있다.
var player = Player( name: "James", xp: 1200, team: "blue", age: 99, ); .**.name = "Han"; ..xp = 20; ..team = "red"; ..age = 99;**
# 4.6 Enums
- 오타에서 개발자들을 지켜주는 기능!
-
선택의 폭을 줄여준다.
**enum Team { red, blue }** class Player { String name; **Team team;** Player({ required this.name, required this.team, }); void main() { var player = Player( name: "James", **team: Team.blue,** ); }
# 4.7 Abstract Classes
- 추상 클래스는 인스턴스를 생성할 수 없다.
- 메소드의 반환값과 함수 이름을 모아둔다.
- extends 키워드로 상속해서 사용한다.
-
추상 클래스에서 상속받은 메서드는 반드시 구현해야 한다.
abstract class Human{ void walk(); } class Player extends Human { void walk() { print("I am walking"); } } class Player2 extends Human { void sayHello() { print("Hello!"); } } // 에러 발생
# 4.8 Inheritance
- 어떤 클래스가 다른 클래스를 상속하고 생성자 메서드를 만들 , super 클래스(상위 클래스)의 생성자 메서드의 매개변수도 고려해야 한다.
- 여기서 상위 클래스의 생성자 메서드에 매개변수를 전달하는 방법은, 하위 클래스의 생성자 메서드 뒤에 콜론(:) 을 쓰고, super(매개변수 이름) 을 적어준다. 위치 매개변수와 이름 매개변수 둘 다 사용가능하다.
-
하위 클래스에서 super 객체를 활용해 상위 클래스를 참조할 수 있다.
class Human { final String name; Human(this.name); void sayHello(){ print("Hello! $name"); } } class Player extends Human { Player({ required this.team, required String name }) : super(name: name); }
- 만약 상위 클래스에 있는 메서드를, 하위 클래스에서 override 하고 싶을 땐, @override 데코레이터를 쓴다.
// 생략 @override void sayHello(){ super.sayHello(); }
# 4.9 Mixins
- Mixins 는 생성자가 없는 클래스이다. 그저 프로퍼티를 포함하는 용도로 사용한다.
-
다른 클래스에서 with 와 함께 사용하면, 해당 Mixins 의 프로퍼티 (변수, 함수) 를 클래스에 포함시킨다.
class Handsome{ final double height= "190.00" } class Human with Handsome{ // 생략 }