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

https://www.dartpad.dev/




# 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"
      
  • 변수를 재할당(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
      }
      




# 2.5 Sets

  • 중괄호({})를 통해 정의할 수 있다.
  • 중복을 허용하지 않는다. 순서가 있다.
    var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
    




# 3 FUNCTIONS




# 3.0 Defining a Function

  • Dart 의 함수는 반환값의 타입, 이름, 매개변수, 실행문으로 이루어져있다.
  • 매개변수도 타입을 각각 지정해줘야 한다.
  • Dart에서 함수는 두 가지 방법으로 정의할 수 있다.

    1. 실행문이 한 줄 이상일 때

      num plus(num a, num b) {
      	return a + b;
      }
      
    2. 실행문이 한 줄일 때

      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() ?? "";
      }
      
  • ??= (QQ equals, QQ Assigment Operator)
    • 좌항에 할당된 값이 null이라면, 우항의 값을 할당한다.
      void main() {
      	String? name;
      	name ??= "sugar";
      	name = null;
      	name ??= "js";
      	print(name); // js
      }
      




# 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{
    // 생략
    }