Flutter를 공부하기 전에 Flutter의 언어인 Dart를 먼저 공부해보고자 NomadCoder의 "Dart 시작하기" 영상을 보게 되었습니다.
AOT와 JIT 둘다 가능하다.
둘다 구글이 만듦.
var name
으로만 선언해두고 할당을 해두지 않으면 dynamic이다.Final을 사용하면 해당 변수는 수정할 수 없어진다.
final name ="nico";
name = 'niconiconi";
위의 경우엔 에러가 발생한다.
late final String name;
// do Something
print(name); // 컴파일에러난다. (할당전에 name에 접근하지말것)
name="nico";
name ="ninicoco"; // 컴파일에러난다. (한번만 할당가능함)
const name="nico";
null safety언어다 보니 런타임 중 발생할 수 있는 null관련 에러를 컴파일 과정에서 막아버린다. (ex. noSuchMethodError)
=> null이 필요한 경우엔 어떡하나요?
?를 사용하면 됩니다.
String nico = "nico";
nico=null;
에서는 에러가 나지만,
String? nico = "nico";
nico = null;
는 에러가 나지 않습니다.
nico?.isNotEmpty;
를 통해 nico가 null이 아니라면 isNotEmpty 속성을 반환하란 의미
String str = "String";
bool b = false;
int i = 3;
double d = 12.12;
num n = 14; // (많이 쓰진 않지만, int double의 부모클래스임)
: text에 변수를 추가하는 방법 (js랑 ``쓰는거랑 비슷하다)
var name = "nico";
var age = 12;
var greeting = 'hello everyone, my name is $name';
var greeting = 'I'm ${age+2} years old'; // 계산시엔 {}로 감싸줘야한다.
var numbers = [1,2,3,4,5];
List<int> numbers2 = [1,2,3,4];
var giveMeFive = true;
var numbers = [1,2,3,4, if(giveMeFive) 5];
var oldFriends = ['nico', 'lynn'];
var newFriends = [
'lewis','ralph','darren', for(var friends in oldFriends) "of $friend"
];
var player = {
'name' : 'nico',
'xp': 19.99,
'super': false,
};
다양한 자료형 쌍을 만들떈 아래와같은 방법을 써도 된다.
Map<int, bool> player = {
1: true,
2: false,
3: true,
}
Map<List<int>, bool> player = {
[1,2,3]: true,
[3,4]: false,
[5]: true,
}
Set<int> numbers = {1,2,3,4};
numbers.add(1);
void sayHello(String name){
print("hello $name nice to meet you!");
}
num plus (int a, int b) => a+b;
자료형(반환값이 없으면 void) 함수명(파라미터자료형 파라미터명){ ... }
형식을 갖는다.void main() {}
내외 상관없이 선언해둘 수 있다.String sayHello (String potato){
return "Hello $potato";
}
String sayHello (String potato) => "Hello $potato";
// before
String hello(String name, int age, String country){ ... }
...
hello("sujeong", 25, "korea");
// after
String hello(String name, int age, String country){ ... }
...
hello(name: "sujeong", country:"korea", age: 25);
String hello({String name ="dname", int age=0, String country="korea"}){ ... }
변수를 다 적지 않아도 default값이 들어간다. String hello({required String name, required int age, required String country}){ ... }
이 경우 변수를 다 적지 않으면 컴파일 에러가 발생한다.String hello(String name, int age, [String? country = "cuba"]){ ... }
String nameUpperCase(String? name){
return name.toUpperCase(); // null safety 위반
}
void main(){
nameUpperCase("nico");
nameUpperCase(null);
}
name이 null이 될 수 있는 상황에서 문제없이 값을 반환하고자 할때 qq Operator를 사용한다.
// 이렇게 해결할 수도 있지만,
String nameUpperCase(String? name){
return name!=null ? name.toUpperCase() : '없다';
}
// 이게 더 짧다. (js랑 동일함)
String nameUpperCase(String? name){
return name.toUpperCase() ?? '없다';
}
String? name;
// name이 null이면 nico를 할당하란 의미
name ??= "nico";
name ??= "이젠 할당되었으니 이건 실행될 일 없지";
typedef ListOfInts = List<int>;
List<int> reverseListOfNumbers(List<int> list){
var reversed = list.reversed;
return reversed.toList();
}
void main(){
reverseListOfNumbers([1,2,3]);
}
class Player {
late final String name;
int xp = 1500;
final String familyName = "못바꾸는이름"; // 밖에서 바꿀 수 없다.
// 기본형 constructor
Player(String name, int xp){
this.name = name;
this.xp = xp;
}
// 타입선언된 전역변수일 경우
Player(this.name, this.xp);
void sayHello(){
var xp = 2000;
print("Hi my name is $name, global vairable is ${this.xp} and local variable is $xp");
}
}
void main(){
var plater = Player("뿌뿌", 3000);
print(player.name);
player.sayHello();
}
class Player{
String name;
int xp;
String team;
int age;
// required를 넣어줘야 null-safety를 유지할 수 있다.
Player({required this.name, required this.xp, required this.team, required this.age});
...
}
void main(){
// Flutter에선 해당방식을 많이 사용한다.
var player = Player(name:"nico", xp:2000, team:"blue", age:21)
});
var player2 = Player("lynn", 2500, "blue", 12);
}
class Player {
String name;
int xp;
String team;
int age;
Player({required this.name, required this.xp, required this.team, required this.age});
// named Constructor (named)
Player.createBluePlayer({
required String name,
required int age
}) : this.age = age,
this.name = name,
this.team = "blue",
this.xp = 0;
// named Constructor (positional)
Player.createRedPlayer(String name, int age)
: this.age = age,
this.name= name,
this.team = "red",
this.xp = 0;
// enhanced case
Player.fromJson(Map<STring, dynamic> playerJson)
: name = playerJson['name'],
xp = playerJson['xp'],
team = playerJson['team'];
void sayHello(){
print("Hi my name is $name and xp is $xp");
}
}
void main() {
// use named Constructor (named)
var redPlayer = Player.createBluePlayer(name:"susu", age: 23);
// used named Constructor (positional)
var redPlayer = Player.createRedPlayer("titi", 30);
// enhanced case
var apiData = [
{
"name": "dodo",
"team": "blue",
"xp":0,
},
{
"name": "bubu",
"team": "red",
"xp":0,
},
{
"name": "babe",
"team": "red",
"xp":0,
},
];
apiData.forEach((playerJson) {
var p = Player.fromJson(playerJson);
p.sayHello();
});
}
// 기본 모양
void main(){
var nico = Player({name: 'nico', xp:1200, team: 'red');
nico.name="las";
nico.xp = 1200000;
nico.team = "blue";
}
위와 같은 기본모양의 코드가 다음과 같은 코드의 모양으로도 표현될 수 있다.
// cascade notation 모양 : Player선언에서 ;을 지우고, nico.name => ..name으로 변경된것이다.
void main(){
var nico = Player({name: 'nico', xp:1200, team: 'red')
..name="las"
..xp = 1200000
..team = "blue";
}
덕분에 데이터 저장을 위한 변수 생성을 피하고, 간결하고 축약된문법으로 값을 할당할 수 있다.
근데 왜 cascade notation일까?
cascade라고하면 css(Cascade Style Sheet)가 생각나는데, 이와 연관된 것일까?
- 순간 의문
chatGPT에게 물어봤더니, "연속적으로 ..을 이용해 값을 할당하는 것이 마치 소폭포(cascade)처럼 보이기 때문에, 이를 Cascading Notation이라고 부릅니다." 라는 해답을 얻었다.
// cascade notation 모양 : Player선언에서 ;을 지우고, nico.name => ..name으로 변경된것이다.
void main(){
var nico = Player({name: 'nico', xp:1200, team: 'red')
..name="las"
..xp = 1200000
..team = "blue";
}
꼭 인스턴스를 생성할 때 되는게 아니다. 인스턴스를 참조하는 변수에 위와같이 이어붙이더라도 정상적으로 값을 할당하고 실행할 수 있다.
이넘은 자바에서도 보았던 개념으로, 오타를 방지하여 작성하는데 크게 도움이 된다. 또한 선택의 폭을 줄여 개발자가 어떤 값을 넣을지 빠르게 알아챌 수 있게 한다.
enum Team { red, blue }
class Player {
String name;
Team team;
Player ({required this.name, required this.team})
}
...
void main(){
var player = Player({name="nico, team=Team.red})
}
Color같은 경우에 많이 사용되고, 생성보단 이미 만들어진 enum을 가져오는 경우가 많을 것 같다.
abstract class Human {
void walk();
}
class Player extends Human {
// 기존 Player 클래스 그대로 사용하되, walk라는 함수가 없으면 에러가 난다.
void walk(){
print("I'm walk")
}
}
class Coach extends Human {
...
void walk(){
print("the coach is walking");
}
}
abstract class는 만들 클래스의 청사진 정도로 보면 된다.
class Human{
final String name;
Human(this.name);
void sayHello(){
print("Hi my name is $name");
}
}
class Player extends Human {
final Team team;
// 아래와 같이 super를 이용해 부모 클래스의 생성자를 부를 수 있다.
Player({required this.name, required String name}) : super(name)
}
void main(){
var p = Player(team: Team.red, name'nico');
Human 클래스에 있는 내용을 Player에 그대로 가져오고자 사용되는 개념을 말한다. 이제 Human을 상속받은 Player에서는 sayHello함수가 없지만 함수를 실행할 수 있다.
...
class Player extends Human {
final Team team;
// 아래와 같이 super를 이용해 부모 클래스의 생성자를 부를 수 있다.
Player({required this.name, required Team team}) : super(name)
// 생성자를 아래와 같이 좀 더 이쁘게 적을 수도 있다.
Player({
required this.team,
required String name,
}):super(name);
@override
void sayHello(){
// super를 통해 부모의 sayHello를 부르고 이후작업을 실행할 수도 있다.
super.sayHello();
print("and I play for $team");
}
}
...
만약 상속받은 함수를 override하고 싶다면 어떻게 해야할까? 정답은 notation mark를 이용하여 @override를 적고, super를 사용하면 부모의 함수를 호출한뒤 이후 내 작업을 기술할 수 있고, 그렇지 않다면 완전히 오버라이드하여 내 작업을 실행할 수도 있다.
class Strong {
final double strengthLevel = 1500.99;
}
class QuickRunner {
void runQuick(){
print("ruuuuuuuun");
}
}
// `with` 뒤에 Mixin으로 만든 생성자가 없는 클래스들을 적어주면 된다.
class Player with Strong, QuickRunner {
final TEam