flutter를 사용하기 위한 기본적인 dart 문법
#0.0 Welcome
Dart
Dart는 모든 플랫폼에서 빠른 앱을 위한 클라이언트 최적화 언어입니다.
1. UI 최적화용
(사용자 인터페이스 생성 요구에 특화된 프로그래밍 언어로 개발)
2. 생산적인 개발
(핫 리로드를 사용하여 실행 중인 앱에서 즉시 결과 확인)
3. 모든 플랫폼에서 빠름
(모바일, 데스크톱 및 백엔드용 ARM 및 x64 머신 코드로 컴파일합니다. 또는 웹용 JavaScript로 컴파일)
https://dart.dev/
Flutter 프레임워크는 인기 있는 다중 플랫폼 UI 툴킷으로 Dart 플랫폼으로 구동되며 iOS, Android, macOS, Windows, Linux 및 웹에서 실행되는 UI 경험을 빌드하기 위한 도구 및 UI 라이브러리를 제공합니다.
#0.1 Why Dart
Dart의 컴파일러 기술을 사용하면 다양한 방식으로 코드를 실행할 수 있습니다.
기본 플랫폼: 모바일 및 데스크톱 장치를 대상으로 하는 앱의 경우 Dart에는 JIT(Just-In-Time) 컴파일 기능이 있는 Dart VM과 기계 코드 생성을 위한 AOT(Ahead-of-Time) 컴파일러가 모두 포함되어 있습니다.
웹 플랫폼: 웹을 대상으로 하는 앱의 경우 Dart는 개발 또는 프로덕션 목적으로 컴파일할 수 있습니다. 웹 컴파일러는 Dart를 JavaScript로 변환합니다.
https://dart.dev/overview
#1.0 Hello World
main함수
main함수는 모든 Dart 프로그램의 Entry point이다.
main 함수에서 쓴 코드가 호출된다. (만약 main이 없다면 실행이 되지 않음)
dart는 자동으로 세미콜론을 붙여주지 않기 때문에 직접 붙여야 한다. (일부러 세미콜론을 안 쓸 때가 있기 때문)
void main(){
print("hello world");
}
#1 VARIABLES
1.1 The Var Keyword
변수를 만드는 2가지 방법
void main() {
var name = "pizza"; // 방법 1
String name = "chicken"; // 방법 2
name = "chicken ";
}
함수나 메소드 내부에 지역변수를 선언할 때는 var를 사용하고
class에서 변수나 property를 선언할 때는 타입을 지정해준다.
1.2 Dynamic Type
Dynamic 타입
여러가지 타입을 가질 수 있는 변수에 쓰는 키워드이다. (해당 변수의 타입을 알 수 없을 때 주로 사용)
변수를 선언할 때 dynamic을 쓰거나 값을 지정하지 않으면 dynamic 타입을 가진다.
void main(){
dynamic name;
var name2;
}
1.3 Nullable Variables
Null Safety
개발자가 null 값을 참조할 수 없도록 하는 것이다.
String뒤에 ?를 붙여줌으로서 name이 String 또는 null이 될 수 있다고 명시해준 것입니다.
기본적으로 모든 변수는 non-nullable(null이 될 수 없음)이다.
void main() {
String? name = "hello";
name = null;
}
1.4 Final Variables
final 변수
var대신 final로 변수를 만들게 되면 이 변수는 수정할 수 없게 된다. (딱 한 번만 설절될 수 있음)
자바스크립트의 const랑 비슷하다.
void main() {
final name = "pizza";
name = "ham"; // 수정 불가
final String username = "tom";
name = "tom2"; // 수정 불가
}
1.5 Late Variables
late 변수
초기 데이터 없이 먼저 변수를 생성하고 추후에 데이터를 넣을 때 주로 사용한다.
flutter로 data fecthing을 할 때 유용하다.
late 변수를 만들고, API에 요청을 보낸 뒤에 API에서 값을 보내주면 그 응답값을 late변수에 넣어 사용할 수 있다.
void main() {
late final String name;
print(name); // name 변수에 접근 불가
}
1.6 Constant Variables
const 변수
dart에서 const는 compile-time constant를 만들어준다.
const는 컴파일할 때 알고 있는 값을 사용해야 한다.
만약 어떤 값인지 모르고, 그 값이 API로부터 오거나 사용자가 화면에서 입력해야 하는 값이라면 그건 const가 아닌 final이나 var가 되어야 한다.
void main() {
const name = "tom"; // 컴파일 시점에 바뀌지 않는 값
final username=fetchAPI(); // 컴파일 시점에 바뀌는 값
}
const: 컴파일 시점에 바뀌지 않는 값 (상수)
final: 컴파일 시점에 바뀌는 값 (API에서 받아온 값, 사용자 입력값)
1.7 Recap
변수를 만드는 2가지 방법
dart
void main() {
var name = "pizza"; // 방법 1
name = "chicken ";
String name2 = "chicken"; // 방법 2
}
final: 값을 재할당하지 못하는 변수를 만듦
dynamic type: 어떤 타입의 데이터가 들어올 지 모를 때 사용함
const: 컴파일 할 때 값을 알고 있는 변수
final: 런타임 중에 만들어질 수 있는 변수
late: final, var, String같은 것들 앞에 써줄 수 있는 수식어로서 어떤 데이터가 올 지 모를 때 사용한다.
#2 DATA TYPES
2.0 Basic Data Types
기본 데이터 타입
아래 타입을 포함한 거의 대부분의 타입들이 객체로 이루어져 있다. (함수도 객체)
이것이 Dart가 진정한 객체 지향 언어로 불리는 이유이다.
void main() {
String name = "tom";
bool isPlay = true;
int age = 10;
double money = 52.55;
num x = 12;
num y = 1.2;
}
2.1 Lists
dark에서 lists를 선언하는 것은 두 가지 방법이 있다.
dart
void main(){
int case1 = [1,2,3,4,5];
List case2 = [1,2,3,4,5];
}
만약 vscode나 dartPad를 사용한다면 맨 끝을 쉽표로 마무리하면 유용하다.
dart
void main(){
int case1 = [
1,
2,
3,
4,
5,
];
}
dart의 유용한 점은 `collection if`와 `collection for`을 지원하는 것이다.
collection if를 사용하면 `존재할 수도 안할 수도 있는 요소를 가지고 올 수 있다.`
dart
void main(){
var giveMeSix = true;
int case1 = [
1,
2,
3,
4,
5,
if(giveMeSix) 6,
];
// 아래와 같은 기능이다.
if(giveMeSix){
case1.add(6);
}
}
2.2 String Interpolation
변수 사용하는 방법
$달러 기호를 붙이고 사용할 변수를 적어주면 된다.
만약 무언가를 계산하고 싶다면 ${ } 형태로 적어주면 된다.
void main(){
var name = "tom";
var age = 10;
var greeting = "hello $name, I'm ${age + 5}";
}
2.3 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]
}
https://dart.dev/guides/language/language-tour#collection-operators
2.4 Maps
일반적으로 맵은 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';
https://dart.dev/guides/language/language-tour#maps
2.5 Sets
Set에 속한 모든 아이템들이 유니크해야될 때 사용한다.
유니크할 필요가 없다면 List를 사용하면 된다.
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
https://dart.dev/guides/language/language-tour#sets
#3 FUNCTIONS
3.0 Defining a Functions
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"));
}
https://dart.dev/guides/language/language-tour#functions
3.1 Named parameters
Named parameters는 명시적으로 required로 표시되지 않는 한 선택 사항입니다. 기본값을 제공하지 않거나 Named parameters를 필수로 표시하지 않으면 해당 유형은 기본값이 null이 되므로 null을 허용해야 합니다.
String sayHello(
{required String name, required int age, required String country}) {
return "${name} / ${age} / ${country}";
}
void main() {
print(sayHello(name: "sugar", age: 10, country: "Korea"));
}
https://dart.dev/guides/language/language-tour#parameters
3.2 Recap
parameter
position parameter : function을 호출할 때 필요한 파라미터, 파라미터의 위치를 정확하게 알아야 함, 이 방식은 좋은 게 아님, position parameter은 두개에서 최대 세개까지는 가능한데 최대한 쓰지 않는 방식이 좋음
named argument: 각각의 이름에 해당 값을 파라미터로 보내는 방식, 바로 확인할 수 있어서 좋음, 순서도 상관이 없음, 그러나 해당 값을 보내지 않을 수 있기 때문에 function의 변수를 required로 바꿔 주거나 파라미터를 기본 값을 만드는 방법이 있다. 이렇게 하면 아무 값을 할당하지 않아도 호출할 수 있다.
몇몇 function들은 default value를 주는 것이 불가능할 수 있다.
사용자가 데이터를 전달하지 않으면 동작하지 않는 function들 같은 경우이다.
예를 들어, 사용자가 로그인을 할 때 이메일이나 비밀번호를 default value로 줄 수는 없다.
이런 경우에는 required modifier을 주면 된다.
3.3 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);
}
3.4 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
}
3.5 TypeDef
자료형에 사용자가 원하는 alias를 붙일 수 있게 해준다. (자료형 이름의 별명을 만들 때 사용)
typedef ListOfInts = List;
ListOfInts reverseListOfNumbers(ListOfInts list) {
var reversedList = list.reversed.toList();
return reversedList;
}
https://dart.dev/guides/language/language-tour#typedefs
#4 CLASSES
4.0 Your First Dart Class
dart에서 property를 선언할 때는 타입을 사용해서 정의한다.
dart
class Player {
final String name = 'jisoung';
final int age = 17;
void sayName(){
// class method안에서는 this를 쓰지 않는 것을 권장한다.
print("Hi my name is $name")
}
}
void main(){
// new를 꼭 붙이지 않아도 된다.
var player =Player();
}
4.1 Constructors
dart에서 생성자(constructor) 함수는 클래스 이름과 같아야 한다.
dart
class Player {
// 이럴 때 late를 사용한다.
late final String name;
late final int age;
// 클래스 이름과 같아야한다!
Player(String name){
this.name = name;
}
}
void main(){
// Player 클래스의 인스턴스 생성!
var player = Player("jisoung", 1500);
위의 생성자 함수는 다음과 같이 줄일 수 있다.
dart
// 생략
Player(this.name, this.age);
첫 번째 인자는 this.name으로 두 번째 인자는 this.age로 갈 것이다.
4.2 Named Constructor Parameters
클래스가 거대해질 경우 생성자 함수를 만드는 것은 비효율적일 것이다.
예를 들어보자
dart
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");
이 문제를 해결할려면 너무 간단하다.
첫 번째는 중괄호({})를 이용하는 것이다.
dart
class Team {
final String name;
int age;
String description;
Team({this.name, this.age, this.description});
}
void main(){
// 한 눈에 볼 수 있다.
var player = Player(
name: "jisoung",
age: 17,
description: "Happy coding is end coding"
}
}
두 번째는 name parameter를 사용하는 것이다.
dart
// 생략
Player(name:"jisoung", age: 17, description: "Happy coding is end coding");
하지만 여기에는 큰 문제가 있다.
변수가 null일 수도 있기 때문에 우리는 이걸 required를 이용하거나 기본 값을 줘서 처리해 줘야한다. 우리는 required를 사용할 것이다.
dart
// 생략
Team({
required this.name,
required this.age,
required this.description,
});,
훨씬 좋아졌다.
4.3 Named Constructors
만약 생성자(constructor) 함수를 여러개 만들고 싶다면 어떨까?
dart
// 생략
Player.createBluePlayer({
required String name,
required int age,
}) : this.age = age,
this.name = name,
this.team = 'blue',
this.xp = 0;
콜론(:)을 사용하면 특별한 생성자 함수를 만들 수 있다.
콜론을 넣음으로써 dart에게 여기서 객체를 초기화하라고 명령할 수 있다.
기존 생성자 코드와 비교해보자
dart
// 첫 번째 case
class Player {
late final String name;
Player({
required this.name
});
}
void main(){
var player = Player(
name: "jisoung",
);
}
dart
// 두 번째 case
Player({this.name, this.age, this.description});
dart
// 세 번째 case
class Player {
final String name;
Player.createUser({
required this.name
}): this.name = name;
}
void main(){
var player = Player.createUser(
name: "jisoung",
);
}
recap (누락된요약)
4.5 Cascade Notation
예제를 보자
dart
// 생략
void main(){
var jisoung = Player(name: "jisoung", age: 17, description: "Happy code is end coding");
jisoung.name = "nico";
jisoung = 20;
jisoung.description = "Best Project is End Project";
}
위를 보면 반복되는 부분이 있다. dart에서는 이걸 간단하게 ..으로 해결할 수 있다.
dart
// 생략
void main(){
var jisoung = Player(name: "jisoung", age: 17, description: "Happy code is end coding");
...name = "nico"
..age = 20
..description = "Best Project is End Project";
}
각 '..'들은 jisoung을 가리킨다. 매우 유용한 operator이다.
앞에 class가 있다면 그 클래스를 가리킨다.
4.6 enum은 우리가 실수하지 않도록 도와주는 타입이다.
dart에서 enum type을 만드는 법은 다음과 같다
dart
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 jisoung = Player(name: "jisoung", age: 17, team: Team.red);
var sushi = jisoung
..name = "sushi"
..age = 12
..team = Team.blue;
4.7 Abstract Classes
추상화 클래스는 다른 클래스들이 직접 구현 해야하는 메소드들을 모아놓은 일종의 `청사진`이라 보면 된다.
추상 클래스에서는 기능을 구현하지 않는다.
dart
abstract class Human {
void walk();
}
extends를 이용해 상속, 확장을 할 수 있다.
dart
abstract class Human {
void walk();
}
class Player extends Human {
// 생략
void walk(){
print("working!");
}
}
4.8 Inheritance
상속을 하고 super를 이용해 부모 클래스의 생성자를 호출할 수 있다.
dart
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);
// Human의 생성자 함수를 호출한다.
}
@override를 이용해 부모 클래스의 객체를 받아올 수 있다.
dart
// 생략
@override
void sayHello(){
super.sayHello();
}
4.9 Mixins
Mixin은 생성자가 없는 클래스를 의미한다.
Mixin 클래스는 상속을 할 때 extends를 하지 않고 with 를 사용한다.
Mixin의 핵심은 여러 클래스에 재사용이 가능하다는 점이다.
dart
class Tall {
final double tall = "190.00"
}
class Human with Tail {
// 생략
}
extends와 차이점은 extend를 하게 되면 확장한 그 클래스는 부모 클래스가 되지만 with는 부모의 인스턴스 관계가 된다. 단순하게 mixin 내부의 프로퍼티를 갖고 오는 거라고 생각하면 쉽다.