Dart 문법 공부 - 2
https://dart.dev/guides/language/language-tour
A tour of the Dart language
A tour of all the major Dart language features.
dart.dev
위 문서로 공부하며 기억해둘만한 내용만 정리
DartPad
dartpad.dev
실습 사이트
부가적인 내용이나 다른 코드가 섞여있을 수 있습니다.
- Booleans
boolean 값을 나타내기 위해 bool 타입 사용.
bool 타입은 true와 false 두 타입만 있고, 컴파일 타임 상수.
bool test = true;
- Lists
Dart에서 배열은 List 객체.
대괄호 내에서 쉼표로 구분된다.
var list = [1, 2, 3,];
print(myList.length);
print(myList[0]);
List<int>로 추론된다. 따라서 정수가 아닌 객체를 삽입할 수 없다.
리스트의 끝에 쉼표를 추가해둘 수도 있다.
List의 인덱스는 0에서부터 시작한다.
컴파일 타임 상수인 List를 만들 수도 있다.
var myList = const [1, 2, 3,];
myList[0] = 3; // Uncaught Error: Unsupported operation: indexed set
Dart 2.3에서는 spread 연산자(...)와 널을 인식하는 spread 연산자(?...)를 도입했다.
여러 값을 컬렉션에 삽입하는 간결한 방법을 제공한다.
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
spread 연산자 우측의 표현식이 null일 수 있으면, ?...를 이용하여 예외를 피할 수 있다.
List<int>? list = null;
List<int> list2 = [0, ...?list];
print(list2); // [0]
Dart는 조건 또는 반복을 이용하여 컬렉션을 빌드하는 collection if와 collection for를 제공한다.
bool test = false;
var list = [1, 2, 3, if(test) 4];
print(list); // [1, 2, 3]. 조건에 따라 [1, 2, 3, 4]가 될 수도 있다.
var list = [1, 2];
var nextList = ['#0', for(var i in list) '#$i'];
print(nextList);
- Sets
Dart는 고유한 아이템의 순서 없는 컬렉션을 지원한다.
다트는 set 리터럴 {}과 Set 타입을 이용하여 set을 지원한다.
void main() {
var mySet = {'1', '2', '3'}; // Set<String> 추론. 잘못된 타입을 넣으면 에러가 발생한다.
var emptySet = <String>{};
Set<String> emptySet2 = {};
var emptySet3 = {}; // map을 생성한다. set이 아니다.
print(emptySet3.runtimeType); // JsLinkedHashMap<dynamic, dynamic>
}
Set도 리스트와 동일하게 spread 연산자, if, for를 지원한다.
- Maps
map은 키와 값을 연결하는 컬렉션이다. 키와 값은 모든 타입의 객체일 수 있다.
map 리터럴 {}와 Map 타입으로 지원한다.
var myMap = {1:'hi', 2:3};
print(myMap.runtimeType); // JsLinkedHashMap<int, Object>
var myMap2 = {1:'hi', 2:'3'};
print(myMap2.runtimeType); // JsLinkedHashMap<int, String>
map도 타입을 추론하고, 다른 타입을 넣으면 에러가 발생한다.
var myMap = Map<int, String>();
myMap[1] = '3';
myMap[4] = '5';
다음과 같이 생성할 수도 있다. Dart에서는 자바 등과 달리 new 키워드가 optional이다.
map에서 key를 찾을 수 없으면, null을 반환한다.
var myMap = {1 : 10};
print(myMap[2]); // null
컴파일 타임 상수 map을 만들기 위해, map 리터럴 앞에 const를 사용할 수 있다.
Map도 리스트와 동일하게 spread 연산자, if, for를 지원한다.
- Runes and grapheme clusters
유니코드 문자를 표현하기 위해 \uXXXX를 사용할 수 있다.
4글자 이상의 16진수로 표현하고 싶다면, \u{XXXX}를 사용할 수 있다.
개별 유니코드 문자를 읽고 싶다면, characters 패키지에 의해 String에 정의된 characters getter를 사용하면 된다.
- Symbols
Symbol 객체는 Dart 프로그램에 선언된 연산자 또는 식별자를 나타낸다.
식별자에 대한 symbol을 얻으려면, 식별자 앞에 symbol 리터럴 #을 사용하면 된다.
- Functions
Dart는 진정한 객체지향 언어이므로, 함수도 객체이고, Function이라는 타입을 가진다.
이는 함수가 변수로서 저장되거나 인자로 다른 함수에 넘겨질 수 있음을 의미한다.
Dart 클래스의 인스턴스를 마치 함수처럼 호출할 수도 있다.
bool foo(int num) {
return num > 2;
}
void main() {
var func = foo;
print(func(2));
}
다음과 같이 반환형을 생략할 수도 있다.
foo(int num) {
return num > 2;
}
다음과 같이 화살표 함수로 표현할 수도 있다.
bool foo(int num) => num > 2;
foo(int num) => num > 2;
var func = (num) => num > 2;
- Parameters
함수는 필요한 위치에 파라미터를 가질 수 있다.
이들 뒤에는 named 파라미터 또는 선택적인 위치 파라미터가 올 수 있다.
Named parameters : required로 명시되지 않는 한 선택적이다.
함수를 정의할 때, named parameter를 지정하기 위해 {param1, param2}를 사용한다.
bool foo({bool? one, bool? two}) {
return true;
}
void main() {
print(foo(two: true, one: false)); // 인자에 파라미터 명을 지정할 수 있다.
}
Tip : 매개변수가 optional이지만 null일 수 없는 경우, 디폴트 값을 지정하자.
named parameter는 optional parameter지만, required를 선언하여 사용자가 반드시 값을 제공해야함을 나타낼 수 있다.
bool foo({required bool? one, bool? two}) {
return true;
}
void main() {
print(foo(two: true));
}
Error: Required named parameter 'one' must be provided.
Optional positinal parameters : []에 함수 파라미터 세트를 래핑하면, optional positional parameters로 표시된다.
void foo(String hello, [String? world]) {
print(hello);
if(world != null) {
print(world);
}
}
void main() {
foo('hello');
foo('hello', 'world');
}
Default parameter values : =를 사용하여 선택적 파라미터(named, positional 모두)에 디폴트 값을 지정할 수 있다. 디폴트 값은 컴파일 타임 상수여야 한다. 디폴트 값이 제공되지 않으면, 디폴트 값은 null이 된다.
void foo([String? hello = 'hello', String? world]) {
print(hello); // hello
print(world); // null
}
void main() {
foo();
}
named parameter에서도 동일하게 사용할 수 있다.
=대신 :를 사용하는 코드는 deprecated 이므로 =를 사용하자.
디폴트 값으로 Map, List, Set 등의 컬렉션도 지정할 수 있다.
void foo([
List<int>? one = const [1, 2, 3],
Map<String, int>? two = const {"1":1}]) {
...
}
- main() function
앱의 진입점 역할을 하는 최상위 함수.
void 반환 타입, 선택적으로 인자에 List<String>을 가진다.
커맨드 라인 인자를 정의하고 파싱하기 위해 args 라이브러리를 사용할 수 있다.
- 일급 객체로서의 함수
함수를 다른 함수의 파라미터로 전달할 수 있다.
함수를 값에 넣을 수 있다.
void foo(var bar) {
bar();
}
void main() {
var func = () => 1;
foo(func);
foo(() => 2);
}
- Anonymous functions(익명 함수)
대부분의 함수는 이름을 가지지만, Anonymous function 이라고 하는 이름 없는 함수(lambda 또는 closure)를 만들 수 있다.
익명 함수는 변수에 할당할 수 있고, 컬렉션에 넣거나 제거할 수 있다.
([[Type] param1[, …]]) {
codeBlock;
};
void main() {
var list = [1, 2, 3];
list.forEach((item) {
print(item);
});
}
타입없는 파라미터로 익명 함수를 정의한 예시다.
var list = [1, 2, 3];
list.forEach((item) => print(item));
하나의 표현식이거나 반환문만 있는 경우, 화살표 표기를 이용할 수도 있다.
- Lexical scope
Dart는 변수의 범위가 코드 레이아웃에 의해 정적으로 결정된다.
중첩된 함수 내에서는 상위 레벨에 있는 변수를 사용할 수 있다.
- Lexical closures
closure는 함수가 원래 범위 외부에서 사용되는 경우에도 변수에 접근할 수 있는 함수 객체다.
함수는 주변 범위에 정의된 변수를 닫을 수 있다.
Function add(int addBy) {
return (i) => i + addBy;
}
void main() {
var addTwo = add(2);
var addFour = add(4);
print(addTwo(1)); // 3
print(addFour(1)); // 5
}
파라미터로 받은 addBy를 캡쳐하고, 반환된 함수는 이를 기억한다.
- Testing functions for equailty
최상위 함수, 스태틱 메소드, 인스턴스 메소드에 대한 equality를 확인해본다.
void baz() { }
class A {
static void foo() { }
void bar() { }
}
void main() {
var x = A();
var y = A();
print(A.foo == A.foo); // true
print(x.bar == y.bar); // false
var z = baz;
print(z == baz); // true
}
서로 다른 인스턴스인 경우, 인스턴스 메소드 equality가 false로 나타난다.
- 반환값이 지정되지 않으면, 함수 바디에 return null;이 암시적으로 추가된다.
foo() { }
void main() {
print(foo()); // null
}
- Operators
https://dart.dev/guides/language/language-tour#operators
type test operators : as, is, is! 연산자는 런타임에 타입을 확인하는데 편리하다.
is : obj가 T로 지정된 인터페이스 T를 구현한 경우, obj is T는 true이다. 예를 들어, obj is Object?는 항상 true.
as : 객체가 해당 타입이 확실한 경우에 객체를 해당 타입으로 캐스팅한다.
객체가 T 타입인지 확실하지 않으면, 객체를 사용하기 전에 is 를 이용하여 확인하자.
as에서 타입이 다르면 예외를 던진다.
??= : 대상 변수가 null인 경우에만 할당하려면 사용한다.
void main() {
int? a = 1;
int? b = 3;
b ??= a;
print(b); // 3
}
void main() {
int? a = 1;
int? b = null;
b ??= a;
print(b); // 1
}
compound assignment operator는 operation과 assignment를 결합한다.
=, +=, /=, >>>=, ...
- Conditional expressions
condition ? expr1 : expr2
condition이 true면 expr1, false면 expr2가 실행 또는 반환된다.
void main() {
var cond = false;
var val1 = cond ? '1' : 2;
print(val1.runtimeType); // int
var val2 = !cond ? '1' : 2;
print(val2.runtimeType); // String
}
표현식에 서로 다른 타입을 넣을 수도 있었다.
expr1 ?? expr2
expr1이 null이 아니면 expr1을 실행 또는 반환하고, null이면 expr2를 실행 또는 반환한다.
String foo(String? str) => str ?? 'default';
void main() {
print(foo('string')); // string
print(foo(null)); // default
}
- Cascade notiation
Cascade(.., ?..) 를 사용하면 동일한 객체에 대해 일련의 작업을 수행할 수 있다.
인스턴스 멤버에 접근하는 것 외에도, 동일한 객체에서 인스턴스 메소드를 호출할 수 있다.
이를 통해 임시 변수 생성 단계를 줄이고, 보다 유동적인 코드를 작성할 수 있다.
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
생성자 Paint()는 Paint 객체를 반환한다.
cascade 표기를 한 코드는, 반환될 수 있는 값을 무시하며 객체에 대해서 동작한다.
위 코드는 아래 코드와 동일하다.
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;
cascade 동작하는 코드가 null일 수 있는 경우, 첫 연산자에 대해 ?..를 사용하면 된다.
?..로 시작하면, null 객체에 대해 cascade 연산이 수행되지 않음을 보장한다.
querySelector('#confirm') // Get an object.
?..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'))
..scrollIntoView();
위 코드는 아래 코드와 동일하다.
var button = querySelector('#confirm');
button?.text = 'Confirm';
button?.classes.add('important');
button?.onClick.listen((e) => window.alert('Confirmed!'));
button?.scrollIntoView();
?.. 문법은 2.12버전 이상에서 제공된다.
cascade를 중첩해서 사용할 수도 있다.
final addressBook = (AddressBookBuilder()
..name = 'jenny'
..email = 'jenny@example.com'
..phone = (PhoneNumberBuilder()
..number = '415-555-0100'
..label = 'home')
.build())
.build();
실제 객체를 반환할 때 cascade를 구성하도록 주의하자.
다음 코드에서 sb.write()의 반환형은 void이기 때문에 실패한다.
var sb = StringBuffer();
sb.write('foo')
..write('bar'); // Error: method 'write' isn't defined for 'void'.
엄밀히 말하면 .. 표기법은 연산자가 아니라 Dart 구문의 일부일 뿐이다.
- Other operators
() : 함수 호출
[] : 재정의 가능한 [] 연산자 호출을 나타낸다. 예를 들어, fooList[1]은 int 1을 fooList에 전달하여 인덱스 1번 요소에 접근한다.
?[] : []와 비슷하지만, []의 가장 왼쪽 피연산자는 null일 수 있다. 예를 들어, fooList[1]은 fooList가 null이 아닌 경우 int 1을 fooList에 전달하여 인덱스 1번 요소에 접근한다. (fooList가 null이면, 표현식은 null로 평가된다.)
. : 표현식의 속성을 참조한다. 예를 들어, foo.bar는 표현식 foo의 bar 속성을 선택한다.
?. : .와 비슷하지만, .의 가장 왼쪽 피연산자는 null일 수 있다. 예를 들어, foo?.bar는 foo가 null이 아닌경우 표현식 foo의 bar 속성을 선택한다. (foo가 null이면, 표현식을 null로 평가된다.)
! : nullable하지 않은 형식으로 표현식을 캐스팅하고, 캐스팅이 실패하면 런타임 예외를 던진다. 예를 들어, foo!.bar는 foo가 null이 아님을 단언하고, foo가 null이 아니라면 bar 속성을 선택한다. foo가 null이면 런타임 예외가 발생한다.