Dart는 구글에서 개발한 오픈 소스 프로그래밍 언어로, 모바일 앱부터 웹 및 IoT 애플리케이션까지 다양한 플랫폼을 지원하며, 정적 타입, 생산성, 멀티 플랫폼 개발 등의 특징을 갖고 있습니다.
- UI 최적화 : 사용자 인터페이스 생성 요구에 특화된 프로그래밍 언어로 개발
- 생산적인 개발: 리로드를 사용하여 실행 중인 앱에서 즉시 결과 확인
- 모든 플랫폼에서 빠름: 모바일, 데스크톱 및 백엔드용 ARM 및 x64 머신 코드로 컴파일, 웹용 JavaScript로 컴파일
Dart에서 변수를 만드는 방법은 2가지가 있습니다.
void main() {
var name = 'Paul'; // 방법 1
String name = 'Paul'; // 방법 2
}
함수 및 메서드 내부에서 지역 변수를 선언할 때는 var
키워드를 사용해서 변수를 생성하고 class
에서 변수 및 프로퍼티를 선언할 때는 명시적 타입을 이용하여 변수를 생성합니다.
Dynamic Type은 여러가지 타입을 가질 수 있는 변수에 쓰는 키워드 입니다. 주로 해당 변수의 타입을 알 수 없을 때 사용합니다. 변수를 선언할때 dynamic
키워드를 사용하거나 변수 선언 시 값을 지정하지 않으면 자동적으로 Dynamic Type을 가지게 됩니다.
void main() {
dynamic name;
var name;
}
Dart는 Null Safety를 지원하고 있습니다. 기본적으로 모든 변수는 null
이 될 수 없고 ?
키워드를 사용하여 null
이 참조될 수 있음을 알려줍니다.
void main() {
String? name = 'Paul';
name = null;
}
Null Safety란 프로그래밍 언어나 프레임워크에서
null
참조로 인한 오류를 방지하기 위한 기능입니다.null
참조로 인한 오류는 많은 소프트웨어 버그의 주요 원인 중 하나이며, 디버깅이 어렵고 예측 불가능한 동작을 유발할 수 있습니다.
final
변수는 재할당을 할 수 없는 변수로 만들어줍니다.
void main() {
final name = 'Paul';
}
late
키워드를 사용하여 변수의 값을 나중에 할당 할 수 있습니다. 이는 class
내의 인스턴스 변수의 값을 나중에 할당 할 떄 유용합니다.
void main() {
late final String name;
name = 'Paul';
}
Dart에서 const
변수는 Compile Time Constant를 만들어줍니다. 이는 컴파일 시점에 바뀌지 않는 값을 사용해야합니다. final
변수와 유사해 보일 수 있지만 컴파일 시점에 값이 바뀐다면 (ex. API를 통해 받아온 값, 사용자의 입력값) final
을 사용하고 그렇지 않다면 const
를 사용합니다.
void main() {
const key = 'keyValue'; // 컴파일 시점에 바뀌지 않는 값
final data = fetchAPI(); // 컴파일 시점에 바뀌는 값
}
Dart에서 자료형은 모두 객체입니다. String
, bool
, int
, double
모두 class
입니다.
따라서 import할 필요 없이 해당 자료형이 가지는 모든 method를 사용할 수 있습니다.
그 중, int
와 double
은 num
이라는 자료형을 상속받은 자료형입니다.
void main() {
String name = "tom";
bool isPlay = true;
int age = 10;
double money = 52.55;
num x = 12;
num y = 1.2;
}
Dart에서 List
를 선언하는 것은 두 가지 방법이 있습니다.
void main(){
var case1 = [1,2,3,4,5]; // 방법 1
List case2 = [1,2,3,4,5]; // 방법 2
}
List
는 collection if와 collection for를 지원합니다.
collection if는 List
를 만들 때, 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]
}
Dart에서 변수를 사용하는 방법입니다. $
기호를 붙이고 사용할 변수를 적어주면 됩니다.
만약 무언가를 계산하고 싶다면 ${ }
형태로 적어주면 됩니다.
void main(){
var name = "tom";
var age = 10;
var greeting = "hello $name, I'm ${age + 5}";
}
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]
}
일반적으로 Map
은 key와 value를 연결하는 객체입니다. 키와 값 모두 모든 유형의 객체가 될 수 있습니다. 각 키는 한 번만 발생하지만 동일한 값을 여러 번 사용할 수 있습니다.
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';
Set
에 속한 모든 아이템들이 유니크해야될 때 사용합니다. 유니크할 필요가 없다면 List를 사용하면 됩니다.
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
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(전달인자)를 적어줍니다.
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,
));
}
이와 같이 함수의 매개변수 앞뒤로 중괄호를 붙여주면 함수를 호출할 때 매개변수의 이름과 값을 함께 입력할 수 있어 사용자에게 훨씬 편리합니다. 이때 요소들의 순서는 지키지 않아도 됩니다.
하지만 여기서 또 다른 문제가 발생합니다. Dart는 null 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
가 적힌 매개변수가 포함되어야 합니다.
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);
}
??
연산자를 이용하면 왼쪽 값이 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
를 이용하면 자료형을 간편하게 작성할 수 있습니다. (자료형 이름의 별명을 만들 때 사용합니다.)
// 전
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();
}
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();
}
생성자(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);
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,
});,
생성자 함수를 만들때 다양한 패턴이 있습니다.
콜론 :
을 사용하면 특별한 생성자 함수를 만들 수 있습니다. 콜론을 넣음으로써 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();
})
}
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";
}
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 class Human {
void walk();
}
extends
를 이용해 상속, 확장을 할 수 있습니다.
abstract class Human {
void walk();
}
class Player extends Human {
//
void walk(){
print("working!");
}
}
상속을 하고 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}")
}
}
Mixin은 생성자가 없는 클래스를 의미합니다. Mixin 클래스는 상속을 할 때 extends
를 하지 않고 with
를 사용합니다. Mixin의 핵심은 여러 클래스에 재사용이 가능하다는 점입니다.
class Tall {
final double tall = 180.00
}
class Strong {
final double level = 10.00
}
class Human with Tail, Strong {
//
}