[Dart] Dart를 시작해보자

강경서·2024년 5월 30일
0
post-thumbnail

Dart

Dart는 구글에서 개발한 오픈 소스 프로그래밍 언어로, 모바일 앱부터 웹 및 IoT 애플리케이션까지 다양한 플랫폼을 지원하며, 정적 타입, 생산성, 멀티 플랫폼 개발 등의 특징을 갖고 있습니다.

  • UI 최적화 : 사용자 인터페이스 생성 요구에 특화된 프로그래밍 언어로 개발
  • 생산적인 개발: 리로드를 사용하여 실행 중인 앱에서 즉시 결과 확인
  • 모든 플랫폼에서 빠름: 모바일, 데스크톱 및 백엔드용 ARM 및 x64 머신 코드로 컴파일, 웹용 JavaScript로 컴파일

Variables

The Var Keyword

Dart에서 변수를 만드는 방법은 2가지가 있습니다.

void main() {
	var name = 'Paul'; // 방법 1
    String name = 'Paul'; // 방법 2
}

함수 및 메서드 내부에서 지역 변수를 선언할 때는 var 키워드를 사용해서 변수를 생성하고 class 에서 변수 및 프로퍼티를 선언할 때는 명시적 타입을 이용하여 변수를 생성합니다.

Dynamic Type

Dynamic Type은 여러가지 타입을 가질 수 있는 변수에 쓰는 키워드 입니다. 주로 해당 변수의 타입을 알 수 없을 때 사용합니다. 변수를 선언할때 dynamic 키워드를 사용하거나 변수 선언 시 값을 지정하지 않으면 자동적으로 Dynamic Type을 가지게 됩니다.

void main() {
	dynamic name; 
    var name; 
}

Nullable Variables

DartNull Safety를 지원하고 있습니다. 기본적으로 모든 변수는 null이 될 수 없고 ? 키워드를 사용하여 null이 참조될 수 있음을 알려줍니다.

void main() {
	String? name = 'Paul';
    name = null;
}

Null Safety란 프로그래밍 언어나 프레임워크에서 null 참조로 인한 오류를 방지하기 위한 기능입니다. null 참조로 인한 오류는 많은 소프트웨어 버그의 주요 원인 중 하나이며, 디버깅이 어렵고 예측 불가능한 동작을 유발할 수 있습니다.

Final Variables

final 변수는 재할당을 할 수 없는 변수로 만들어줍니다.

void main() {
	final name = 'Paul';
}

Late Variables

late 키워드를 사용하여 변수의 값을 나중에 할당 할 수 있습니다. 이는 class 내의 인스턴스 변수의 값을 나중에 할당 할 떄 유용합니다.

void main() {
	late final String name;
    name = 'Paul';
}

Constant Variables

Dart에서 const 변수는 Compile Time Constant를 만들어줍니다. 이는 컴파일 시점에 바뀌지 않는 값을 사용해야합니다. final 변수와 유사해 보일 수 있지만 컴파일 시점에 값이 바뀐다면 (ex. API를 통해 받아온 값, 사용자의 입력값) final을 사용하고 그렇지 않다면 const를 사용합니다.

void main() {
	const key = 'keyValue'; // 컴파일 시점에 바뀌지 않는 값
    final data = fetchAPI(); // 컴파일 시점에 바뀌는 값
}

Data Types

Basic Data Types

Dart에서 자료형은 모두 객체입니다. String, bool, int, double 모두 class입니다.
따라서 import할 필요 없이 해당 자료형이 가지는 모든 method를 사용할 수 있습니다.
그 중, intdoublenum이라는 자료형을 상속받은 자료형입니다.

void main() {
	String name = "tom"; 
	bool isPlay = true;
	int age = 10;
	double money = 52.55;
	num x = 12;
	num y = 1.2;
}

Lists

Dart에서 List를 선언하는 것은 두 가지 방법이 있습니다.

void main(){
	var case1 = [1,2,3,4,5]; // 방법 1
	List case2 = [1,2,3,4,5]; // 방법 2
}

Listcollection ifcollection for를 지원합니다.
collection ifList를 만들 때, if를 통해 존재할 수도 안 할 수도 있는 요소를 가지고 만들 수 있습니다.

void main() {
	var giveMeFive = true;

	var item = [
		1,
		2,
		3,
		if (giveMeFive) 5, // giveMeFive가 true이면 10을 넣음
		];
    
    // 같은 로직입니다.
	if(giveMeFive){
		item.add(5);
	}
    
	print(item); // [1,2,3,5]
}

String Interpolation

Dart에서 변수를 사용하는 방법입니다. $ 기호를 붙이고 사용할 변수를 적어주면 됩니다.
만약 무언가를 계산하고 싶다면 ${ } 형태로 적어주면 됩니다.

void main(){
	var name = "tom";
	var age = 10;
	var greeting = "hello $name, I'm ${age + 5}";
}

Collection For

Dart는 조건문(if) 및 반복(for)을 사용하여 컬렉션을 구축하는 데 사용할 수 있는 컬렉션 if 및 컬렉션 for 도 제공합니다.

void main() {
	var oldFriends = ["nico", "lynn"];
	var newFriends = [
		"tom",
		"jon",
		for (var friend in oldFriends) "❤️ $friend"
	];

	print(newFriends); // [tom, jon, ❤️ nico, ❤️ lynn]
}

Maps

일반적으로 Mapkeyvalue를 연결하는 객체입니다. 키와 값 모두 모든 유형의 객체가 될 수 있습니다. 각 키는 한 번만 발생하지만 동일한 값을 여러 번 사용할 수 있습니다.

var gifts = {
	// Key: Value
	'first': 'partridge',
	'second': 'turtledoves',
	'fifth': 'golden rings'
};

// Map 생성자를 사용하여 동일한 객체를 만들 수 있습니다.
var gifts2 = Map();
gifts2['first'] = 'partridge';
gifts2['second'] = 'turtledoves';
gifts2['fifth'] = 'golden rings';

Sets

Set에 속한 모든 아이템들이 유니크해야될 때 사용합니다. 유니크할 필요가 없다면 List를 사용하면 됩니다.

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

Functions

Defining a Function

Dart는 진정한 객체 지향 언어이므로 함수도 객체이며 타입이 Function입니다. 이는 함수를 변수에 할당하거나 다른 함수에 인수로 전달할 수 있음을 의미합니다.

// 하나의 표현식만 포함하는 함수의 경우 아래와 같이 단축 구문을 사용할 수 있습니다.
String sayHello(String name) => "Hello ${name} nice to meet you.";

num plus(num a, num b) => a + b;

void main() {
	print(sayHello("sugar"));
}

여기서 sayHello는 이 function의 이름이며 sayHello 앞에는 return할 값의 자료형을 적어줍니다. 괄호 안에는 parameter(전달인자)를 적어줍니다.

Named Parameters

String sayHello(String name, int age, String country) {
	return "Hello $name, you are $age, and you come from $country;
}

void main() {
	print(sayHello('nico', 19, 'cuba'));
}

위의 코드에서는 매개변수가 3개인 sayHello 함수를 호출하여 출력한 것 입니다.
하지만 이 코드는 사용자가 요소들의 순서를 잊어버릴 수도 있고, 코드를 봤을 때 인자가 뜻하는 것이 무엇인지 바로 이해를 못할 수도 있습닏다. 이때 named argument를 사용하면 훨씬 편리합니다.

String sayHello({String name, int age, String country}) {
	return "Hello $name, you are $age, and you come from $country;
}

void main() {
	print(sayHello(
		age : 12,
		name : 'nico',
		country : cuba,
	));
}

이와 같이 함수의 매개변수 앞뒤로 중괄호를 붙여주면 함수를 호출할 때 매개변수의 이름과 값을 함께 입력할 수 있어 사용자에게 훨씬 편리합니다. 이때 요소들의 순서는 지키지 않아도 됩니다.

하지만 여기서 또 다른 문제가 발생합니다. Dartnull safety가 적용되는데, 인수 중 하나가 null 일 수도 있기 때문입니다. 다시 말해, 유저가 name과 age, country 3개의 인수 중 하나라도 빼고 보낼 수 있기 때문입니다

이때는 두 가지 옵션이 있습니다.
우선, default value를 지정해주면 됩니다.

String sayHello({String name = 'anon',
	int age = 99,
	String country = 'wakanda',
}) {
	return "Hello $name, you are $age, and you come from $country";
}

void main() {
	print(sayHello()); // 아무것도 전달하지 않아도 default value가 이미 있으므로 null safety에 걸릴 일이 없습니다.
}

두 번째는 required modifier를 이용하는 방벙입니다.

String sayHello({
	required String name,
	required int age,
	required String country,
}) {
	return "Hello $name, you are $age, and you come from $country";
}

void main() {
	print(sayHello()); // name, country, age가 모두 포함되어야 하기 때문에 dart에서 컴파일하지 않습니다.
}

위와 같이 매개변수 앞에 required를 적어주면, 함수가 호출될 때 반드시 required가 적힌 매개변수가 포함되어야 합니다.

Optional Positional Parameters

Dart에서 []optional, positional parameter를 명시할 때 사용됩니다.
name, age는 필수값이고 []를 통해 country를 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);
}

QQ Operator

?? 연산자를 이용하면 왼쪽 값이 null인지 체크해서 null이 아니면 왼쪽 값을 리턴하고 null이면 오른쪽 값을 리턴한다.

String capitalizeName(String? name) {
	return name?.toUpperCase() ?? "";
}

??= 연산자를 이용하면 변수 안에 값이 null일 때를 체크해서 값을 할당해줄 수 있습니다.

void main() {
	String? name;
	name ??= "sugar";
	name = null;
	name ??= "js";
	print(name); // js
}

Typedef

typedef를 이용하면 자료형을 간편하게 작성할 수 있습니다. (자료형 이름의 별명을 만들 때 사용합니다.)

// 전
List reverseListOfNumbers(List list) {
	var reversed = list.reversed;
	return reversed.toList();
}
// 후
typedef ListOfInts = List;

ListOfInts reverseListOfNumbers(ListOfInts list) {
	var reversed = list.reversed;
	return reversed.toList();
}

CLASSES

Class

Class에서 property를 선언할 때는 타입을 사용해서 정의합니다.

class Player {
	final String name = 'kks';
	final int age = 30;
	void sayName(){
	// dart에서 class method 내부에서 this를 쓰지 않는 것을 권장합니다.
	print("Hi my name is $name")
	}
}

void main(){
	// new를 꼭 붙이지 않아도 됩니다.
	var player =Player();
}

Constructors

생성자(constructor)함수는 Class 이름과 같아야 합니다.

class Player {
	// 이럴 때 late를 사용합니다.
	late final String name;
	late final int age;
	// 생성자(constructor)함수는 Class 이름과 같아야 합니다.
	Player(String name, int age){
		this.name = name;
        this.age = age;
	}
}

void main(){
	// Player 클래스의 인스턴스 생성!
	var player = Player("kks", 1500);
}

위의 생성자 함수는 다음과 같이 줄일 수 있습니다.

// 생략
Player(this.name, this.age);

Named Constructor Parameters

class가 거대해질 경우 위와 같이 생성자 함수를 만드는 것은 비효율적입니다.

class Team {
	final String name;
	int age;
	String description;

	Team(this.name, this.age, this.description);
}

void main(){
	// 인자의 올바른 위치를 기억하기 어렵습니다.
	// 각 인자의 의미를 알 수 없습니다.
	var myTeam = Team("jisoung", 17, "Happy coding is end coding");
}

이를 해결하기 위해 Named Constructor Parameters를 사용합니다.

class Player {
	final String name;
	int age;
	String description;

	Player({this.name, this.age, this.description});
}

void main(){
	var player = Player(
		name: "kks",
		age: 30,
		description: "Hello"
	}
}

변수가 null일 수도 있기 때문에 required를 이용하거나 기본 값을 줘서 처리해 줘야합니다.

Player({
	required this.name,
	required this.age,
	required this.description,
});,

Named Constructors

생성자 함수를 만들때 다양한 패턴이 있습니다.
콜론 :을 사용하면 특별한 생성자 함수를 만들 수 있습니다. 콜론을 넣음으로써 dart에게 여기서 객체를 초기화하라고 명령할 수 있습니다.

Player.createBluePlayer({
	required String name,
	required int age,
	}) : this.age = age,
		this.name = name,
		this.team = 'blue',
		this.xp = 0;
        
 //
 var player = Player.createBluePlayer(name: "kks", age: 30)

이렇게 간소화된 방법으로 표현할 수 있습니다.

Player.createRedPlayer({
	required this.name,
	required this.xp,
	this.team = 'red',
    this.xp = 0,
});

이와 같은 Named Constructors를 사용한다면 실제 서비스에서 받는 JSON을 이용하여 인스턴스를 생성할 수 있습니다.

class Player {
	final String name;
    int xp;
    String team;
    
    Player.fromJson(Map<Strin, dynamic > playerJson)
    	: name = playerJson['name'];
          xp = playerJson['xp'];
          team = playerJson['team'];
    
    void sayHello() {
    	print("Hello my name is $name");
    }
}

void main() {
	var apiData = [...];
    
    apiData.forEach((playerJson) {
    	var player = Player.fromJson(playerJson);
        player.sayHello();
    })
} 

Cascade Notation

void main(){
	var player = Player(name: "player", age: 30, description: "Hello");
	player.name = "nico";
	player.age = 20;
	player.description = "Bye";
}

위와 같이 반복되는 부분을 .. 을 사용하여 단축할 수 있습니다. ; 를 빼주어야 합니다.

void main(){
	var player = Player(name: "player", age: 30, description: "Hello")
		...name = "nico"
		..age = 20
		..description = "Bye";
}

Enums

enum은 우리가 실수하지 않도록 도와주는 타입입니다.

enum Team {
	red,
	blue,
}

class Player {
	String name;
	int age;
	Team team;

	Player({
		required this.name,
		required this.age,
		required this.team,
	});
}

void main(){
	var player = Player(name: "kks", age: 30, team: Team.red);
	var newPlayer = player
		..name = "nico"
		..age = 20
		..team = Team.blue;

Abstract Classes

추상화 클래스는 다른 클래스들이 직접 구현 해야하는 메소드들을 모아놓은 일종의 청사진이라 볼 수 있습니다. 추상화 클래스에서는 기능을 구현하지 않습니다.

abstract class Human {
	void walk();
}

extends를 이용해 상속, 확장을 할 수 있습니다.

abstract class Human {
	void walk();
}

class Player extends Human {
	// 
	void walk(){
		print("working!");
	}
}

Inheritance

상속을 하고 super를 이용해 부모 클래스의 생성자를 호출할 수 있습니다.

class Human {
	final String name;
    	
	Human({required this.name});
    
	void sayHello(){
		print("Hello! $name");
	}
}

enum Team { blue, red }

class Player extends Human {
	final Team team; 

	Player({
		required this.team,
		required String name
	}) : super(name: name);
}

void main() {
	var player = Player(team : Team.red, name : 'kks');
}

@override를 메서드를 대체할 수 있습니다.

class Player extends Human {
	final Team team; 

	Player({
		required this.team,
		required String name
	}) : super(name: name);
    
     
    void sayHello() {
    	super.sayHello();
        print("and I play for ${team}")
    }
}

Mixins

Mixin은 생성자가 없는 클래스를 의미합니다. Mixin 클래스는 상속을 할 때 extends를 하지 않고 with 를 사용합니다. Mixin의 핵심은 여러 클래스에 재사용이 가능하다는 점입니다.

class Tall {
	final double tall = 180.00
}

class Strong {
	final double level = 10.00
}

class Human with Tail, Strong {
// 
}
profile
기록하고 배우고 시도하고

0개의 댓글