void main() {
print('Hello, World!');
}
// hello world
// dart의 main은 entry
// dart는 세미콜론을 자동으로 포매팅해주지 않기 때문에
// 세미콜론을 작성하는 것을 주의해라.
// void main() {
// print('hello world');
// }
void main() {
var name = '이름'; // 자동 타입 지정
String na = 'me'; // 명시적 타입 지정
print(na);
na = 'you'; // 업데이트 가능
}
보통 class내에서 변수나 프로퍼티를 선언할 땐 type 지정을 해주고
함수나 메서드 내에서 지역변수 등을 선얼할 때는 var를 쓰는 것을 권장한다.
dynamic 식별자 or var 식별자
void main() {
var name; //dynamic name
// 명시적 지정도 가능
// dynamic name;
name = '이름';
name = 12;
name = true;
// 이렇게 다른 타입을 할당해도 에러가 발생하지 않는다.
name.
// dart가 정확히 name의 타입이 지정되지 않음을 알고
// 관련 메서드를 나타내지 않는다.
if(name is String){
name. // string타입에 관한 메서드가 나옴.
}
}
다이내믹 타입은 정말 필요할 때만 사용하는 것을 권장
🤷🏻 var과 차이가 뭔데?
answer: var은 선언 후 값이 할당되면 타입이 고정된다.
하지만 dynamic은 타입이 고정되지 않는다.
// 컴파일러가 에러를 캐치하지 못하고
// 런타임 때 에러가 발생하게 됨.
bool isEmpty(String string) => string.length == 0;
main(){
isEmpty(null);
// err: NoSuchMethodError
//null이 인자로 보내지고 null.length가 실행되는데
// null에는 length 메서드가 없기 때문에
// 위와 같은 에러가 발생하게 된다.
}
null은 아무 값도 없음을 나타내는 중요한 값.
따라서 dart에서는 null safety를 도입함.
bool isEmpty(String string) => string.length == 0;
main() {
// '?' String 또는 null 입니다.
String? nico = '이름';
nico = null;
if(nico != null){
nico.isNotEmpty; // true
}
// 또는 옵셔널 연산자를 사용해도 좋다
nico?.isEmpty;
}
기본적으로 모든 변수는 non-nullable
변수가 null도 될 수 있음을 알려주기 위해 '?'를 사용
조건문으로 null safety를 적용할 수 있지만 옵셔널 연산자를 이용하면
훨씬 간단하게 구현 가능하다.
void main() {
// JS의 const 선언과 같다.
final name = "이름";
// 타입 지정도 가능 final String name = '이름'
// 필수는 아님.
name = '창씨개명'; // error
}
void main() {
late final String name;
//사용예) 무언가 실행되면 api에서 데이터를 불러와 값을 할당
//할당 하기 전 참조 시 할당되지 않음을 이유로 에러 발생
print(name);
name = 'api에서 불러온 이름';
name = 'dd'; // 마찬가지로 상수이므로 에러 발생
}
대부분의 경우 필요한 데이터를 알고 있기 때문에
final name = '이름';
의 경우처럼 초기에 값을 할당할 것이다.
하지만 그렇지 않은 경우도 있기 때문에 이 때late
가 유용하며,
할당 전까지 접근이 불가하기 때문에 에러 방지에도 유용하다.
void main(){
const name = '이름';
name = '11'; //final과 마찬가지로 상수
// 중요한 점: const는 compile-time에 알고 있는 값이어야 함.
// API를 예로 들어보면
const API = '1231318151';
// API 값은 절대 변하지 않고 컴파일 된 시점(유저가 사용하는)에도 유지됨.
// 앱스토어에 배포하기 전에 알고 있는 상수
final API2 = fetchAPI();
// 이 경우 컴파일 된 시점에 API2의 값이 fetchAPI로 데이터를 받아오거나
// 유저가 입력한 것에 따라 바뀌기 때문에 final나 var를 사용하는 것이 옳다.
// 앱스토어에 배포한 후 상화에 따라 변화되는 상수
// -------- 예시2 ---------
const max_allowed_price = 120000;
// 상황에 따라 변하지 않고 고정된 제한 값(상수) 이용
}
- compile 되기 전에 상수 값이 확실하다? -> const
- compile 된 후 상수 값이 유저의 상호작용이나 data fetch에 따라 할당되기 때문에 불확실하다 -> final(상수) or var(변수)
void main(){
String name = '이름';
bool alive = false;
int age = 300;
// 정수
double money = 5.55;
// 부동소수점 숫자
num x = 12;
x = 1.1;
// num클래스는 int와 double의 부모클래스기 때문에
// num 변수에는 int와 double 모두 할당 가능하다.
}
bool, String는 모두 super class (부모 클래스)
int, double은 num 클래스 자료형의 자식 클래스!
void main() {
var giveMeFive = true;
var numbers = [1, 2, 3, 4];
//List<int> numbers
//위와 같다.
List<int> numbers2 = [
1,
2,
3,
4,
if(giveMeFive) 5,
];
numbers2.add(1); //[1, 2, 3, 4, 1]
print(numbers2);
numbers2.first; //첫 요소 //or last 마지막 요소 반환
}
//### Lists(배열)
void main() {
var giveMeFive = true;
List<int> numbers2 = [
1,
2,
3,
4,
if(giveMeFive) 5,
];
//아래 코드와 같음.
if(giveMeFive){
numbers2.add(1); //[1, 2, 3, 4, 1]
}
}
void main(){
var name = '이름';
var greeting = 'Hello everyone, my name is $name';
print(greeting);
//Hello everyone, my name is 이름
}
void main(){
var name = '이름';
var age = 10;
var greeting = "Hello everyone, my name is $name and I'm ${age + 2}";
print(greeting);
//Hello everyone, my name is 이름 and I'm 12
}
추가 팁 ) 작은 따옴표 안에 작은 따옴표 쓰려면 이스케이프 사용
'i\'m $변수명
void main() {
var oldFriends = ['nico', 'lynn'];
var newFriends = [
'lewis',
'ralph',
'darren',
for (var friend in oldFriends) '🪓 $friend',
];
print(newFriends);
[lewis, ralph, darren, 🪓 nico, 🪓 lynn]
}
collection if / collection for 은 아주 혁명적임.
void main() {
var player = {
'name': 'nico',
'xp': 19.99,
'superpower': false,
};
//Map<String, Object> player
// 키: string, 값: object(any와 같은 의미)
var player2 = {
'name': 'nico',
'xp': '19.99',
'superpower': 'false',
};
//Map<String, String> player2
Map<int, bool> player = {
1: true,
2: false,
};
// 객체의 키와 밸류의 타입을 지정해줄 수 있다.
// var를 사용해도 알아서 타입을 유추해준다.
Map<List<int>, bool> player3 = {
[1, 2]: true,
[3, 4]: false,
};
List<Map<String, Object>> players = [
{
'name': '이름',
'xp': 200,
},
{
'name': '이름',
'xp': 200,
}
];
}
위와 같은 방법보다 더 효율적인 class가 있으므로 후에 배워보자.
void main() {
var numbers = {1,2,3,4};
//Set<int> numbers = {1,2,3,4}
//Set에 속한 아이템들은 unique함.
numbers.add(1);
numbers.add(5);
print(numbers);//{1, 2, 3, 4, 5}
// 배열의 경우
List<int> numbers2 = [1,2,3,4];
numbers2.add(1);
numbers2.add(1);
print(numbers2); //[1, 2, 3, 4, 1, 1]
}
Set은 순서가 있고(sequence)
List와 같지만 모든 요소가 유니크(중복없음)해야 한다.
void sayHello(String name) {
print("Hello $name nice to meet you!");
}
String sayHello2(String name) {
return "Hello, $name";
}
String sayHello3(String name) => "hello, $name";
void main() {
print(sayHello3('이름'));
//hello, 이름
}
// 1)
String sayHello(String name) {
//call api
// perform cal
return 'result';
}
------
// 2)
num plus(num a, num b) => a + b;
String sayHello(String name, int age, String country){
return "Hello $name, you are $age, from $country";
}
void main() {
// 기존 파라미터 전달 방법.
print(sayHello('이름', 100, "N.Korea"));
}
// 1. 중괄호로 감싸고 default 값 부여하기
String sayHello({String name = "익명", int age = 99, String country="usa"}) {
return "Hello $name, you are $age, from $country";
}
// 2. 또는 required 적용
String sayHello2({required String name ,required int age ,required String country}) {
return "Hello $name, you are $age, from $country";
}
void main() {
//리액트 props 전달할 때 {} 객체로 받는 것 개념 비슷
//named parameter
// 인수를 키와 값으로 전달.
// default value
print(sayHello(age: 12, country: "korea", name: "이름"));
//required
print(sayHello2(age:12,country:'kore', name:'ㅇㅇ'));
}
String sayHello(String name, int age, [String country = '쿠바']) =>
"hello $name $age $country";
void main() {
print(sayHello("이름", 12));
// hello 이름 12 쿠바
print(sayHello("이름", 12,"한국"));
//hello 이름 12 한국
}
- 기존 방식
String upperName(String? name) {
if (name != null) {
return name.toUpperCase();
}
return "no null";
}
void main() {
print(upperName("lower"));// LOWER
print(upperName(null));// no null
}
- 개선 방식 ( "??", "?")
String upperName(String? name) {
//return name != null ? name.toUpperCase() : 'No null';
return name?.toUpperCase() ?? "No null";
// A ?? B -> B
// A가 null이면 B
}
훨씬 짧아졌넹.
String upperName(String? name) {
// return name != null ? name.toUpperCase() : 'No null';
return name?.toUpperCase() ?? "No null";
//name? : name은 String or null이다.
// ?? : 좌항의 name이 null인 경우 우항을 리턴
}
void main() {
String? name;
name ??= '이름';
// 좌변 name은 절대 null이 될 수 없기 때문에
//우변 값이 할당 될 수 없음.
name ??= 'dd';
//따라서 'dd'가 할당되지 않음.
print(name); // 이름
//다시 null 을 부여하면 할당 됨.
name = null;
name ??= "이러면 됨";
print(name); // 이러면 됨
}
// 기존
List<int> reverseListOfNumbers(List<int> list){
var reversed = list.reversed;
return reversed.toList();
}
void main() {
print(reverseListOfNumbers([1,2,3,4,]));
// [4, 3, 2, 1]
}
// typedef 이용하기
typedef ListOfInts = List<int>;
ListOfInts reverseListOfNumbers(ListOfInts list){
var reversed = list.reversed;
return reversed.toList();
}
String sayHi(Map<String, String> userInfo){
return "Hi, ${userInfo['name']}";
}
void main(){
sayHi({"name":"뿅뿅"});
}
---- 객체 typedef 지정
typedef UserInfo = Map<String, String>;
String sayHi(UserInfo userInfo){
return "Hi, ${userInfo['name']}";
}
좀 더 키 식별자 등을 정형화한 객체를 만들고 싶으면
class를 사용하면 됨.
class Player {
String name = "이름";
int xp = 1500;
void sayHello(){
print("Hi my name is $name");
}
//this 키워드 안써도 되는데 굳이 써야하는 상황은
void sayBye(){
var name = "카ㅋㅋ";
// 메서드 내에 프로퍼티와 같은 이름의 변수가 있는 경우.
//속한 클래스의 변수 참조
print("Bye, ${this.name}"); // '이름'
//메서드의 변수 참조
print("$name"); // '카ㅋㅋ'
}
}
void main(){
var player = Player();
//JS와 달리 new player도 가능하지만 없어도 됨.
print(player);// instance of 'Player'
print(player.name); //이름
player.name = '바까';
print(player.name);//'바까'
// 못 바꾸게 하려면 final 적용하기
player.sayHello();
//Hi my name is 바까
}
메서드가 프로퍼티 참조할 때 this 는 굳이 필요 없다.
필요한 경우는 메서드의 변수와 프로퍼티의 이름이 같을 경우 정도.
class Player{
String name;
int xp;
//컨스트럭터 이용해서 이름 xp 유동적 바꾸기.
Player(this.name, this.xp);
// 중괄호 + 기본값 부여하는 방식
// Player({this.name='기본', this.xp= 0});
}
void main(){
// 컨스트럭터 이용
var player = Player('이름', 123);
print(player.name);// 이름
// 두 번째 방식 중괄호 기본값인 경우
var player2 = Player(name: '하하', xp: 120);
print(player2.name); //하하
}
//기존 클라스
class Player{
String name;
int xp;
String team;
int age;
Player(this.name, this.xp, this.team, this.age);
}
void main(){
// 인자가 무엇을 의미하는지 알기 어려워짐.
var player = Player('이름', 123,'red', 12);
}
class Player {
String name;
int xp;
String team;
int age;
// 1. 중괄호 쳐주기
Player({required this.name, required this.xp, required this.team, required this.age});
}
void main() {
// 2. 키밸류 쌍으로 파라미터 전달.
var player = Player(
name: '이름',
xp: 123,
team: 'red',
age: 12,
);
}
class Player {
String name, team;
int xp, age;
Player(
{required this.name,
required this.xp,
required this.team,
required this.age});
// named const param은 파라미터값을 프로퍼티에 할당해주는 역할
// 바로 값 부여하는 방식(constructor)
Player.createBluePlayer(this.name, this.age)
: team = 'blue',
xp = 0;
// 중괄호에 기본값 주는 방식(Named Constructor Parameter)
Player.createRedPlayer({this.name = '2', this.age = 1})
: team = 'red',
xp = 0;
}
void main() {
//named 문법 사용하는 경우
var player = Player.createBluePlayer(
'이름',
12,
);
print(player.name); //이름
//positional 문법 사용하는 경우.
var player2 = Player.createRedPlayer(name: '1', age: 12);
print(player2.name); // 1
print(player2.age); // 12
print(player2.team); //red
}
class Player {
final String name;
int xp;
String team;
//fromJson - named constructor
// playerJson 객체의 프로퍼티 값을 부여
Player.fromJson(Map<String, dynamic> playerJson) :
name = playerJson['name'],
xp = playerJson['xp'],
team = playerJson['team'];
void sayHello(){
print('hi, $name, $xp, $team');
}
}
void main() {
var apiData = [
{
"name": '일번',
"team": "red",
"xp": 0,
},
{
"name": '이번',
"team": "red",
"xp": 0,
},
{
"name": '삼번',
"team": "red",
"xp": 0,
},
];
apiData.forEach((playerJson){
var player = Player.fromJson(playerJson);
player.sayHello();
});
/*
* hi, 일번, 0, red
hi, 이번, 0, red
hi, 삼번, 0, red
* */
}
class Player {
String name;
int xp;
String team;
//fromJson - named constructor
Player({required this.name, required this.xp, required this.team});
void sayHello() {
print('hi, $name, $xp, $team');
}
}
void main() {
// 기존 인스턴스 값 바꾸는 방법
var CJ = Player(name: 'CJ', xp: 1200, team: 'red');
CJ.name = 'wf';
CJ.xp = 10;
CJ.team = 'blue';
// Cascade Notation 방법
var BJ = Player(name: 'BJ', xp:1000, team: 'red')
..name='WW'
..xp = 1000
..team = 'blue';
BJ.sayHello(); //hi, WW, 1000, blue
// 예시 하나 더
var HJ = BJ
..name = 'hoho'
..xp = 99
..team = 'white'
..sayHello(); //hi, hoho, 99, white
}
enum Team {red, blue}
enum XPLevel {beginner,medium,pro}
class Player {
String name;
XPLevel xp;
Team team;
// String team;
//fromJson - named constructor
Player({required this.name, required this.xp, required this.team});
void sayHello() {
print('hi, $name, $xp, $team');
}
}
void main() {
// 기존 인스턴스 값 바꾸는 방법
var cj = Player(name: 'CJ', xp: XPLevel.beginner, team: Team.blue);
cj.name = 'wf';
cj.xp = XPLevel.medium;
cj.team = Team.red;
// Cascade Notation 방법
var bj = Player(name: 'BJ', xp:XPLevel.beginner, team: Team.red)
..name='WW'
..xp = XPLevel.pro
..team = Team.blue;
bj.sayHello(); //hi, WW, XPLevel.pro, Team.blue
// 예시 하나 더
var hj = bj
..name = 'hoho'
..xp = 99 // 에러
..team = 'white' // 에러 Team.red나 Team.blue만 올 수 있음.
..sayHello(); //hi, hoho, 99, white
}
abstract class Human{
void walk();
}
enum Team {red, blue}
enum XPLevel {beginner,medium,pro}
// extends하니 Human의 walk메서드가 없다고 에러 띄움.
class Player extends Human {
String name;
XPLevel xp;
Team team;
Player({required this.name, required this.xp, required this.team});
void walk(){
print('im walking');
}
void sayHello() {
print('hi, $name, $xp, $team');
}
}
class coach extends Human{
void walk(){
print('the coach is walking');
}
}
class Human {
final String name;
Human({required this.name});
//방식2 : 또는 Human(this.name)
void sayHello() {
print('Hi my name is $name');
}
}
enum Team { blue, red }
class Player extends Human {
final Team team;
Player({
required this.team,
required String name,
}) : super(name: name);
// 방식2 : 또는 super(name);
// super()가 부모클라스의 Human constructor를 작동시킴
@override //부모가 상속한 sayHello를 대체하겠다.
void sayHello() {
// super는 상속한 부모 클래스의 프로퍼티에 접근이나 메서드 호출을 가능하게 한다.
super.sayHello();
print('and I play for ${team}');
}
}
void main() {
var player = Player(
team: Team.red,
name: '이름',
);
//override 하지 않으면 부모의 sayHello를 호출(체이닝?)
//Hi my name is 이름
// override를 할 경우 해당 인스턴스의 sayHello()가 호출됨.
player.sayHello();
//Hi my name is 이름
//and I play for Team.red
}
class Strong {
final double strengthLevel = 1500.99;
}
class QuickRunner {
void runQuick(){
print('run');
}
}
class Tall {
final double height = 1.99;
}
class Horse with Strong, QuickRunner{}
class Kid with QuickRunner{}
enum Team { blue, red }
// with : 다른 클래스의 프로퍼티와 메서드를 긁어온다.
class Player with Strong, QuickRunner, Tall {
final Team team;
Player({
required this.team,
});
}
void main() {
var player = Player(team:Team.blue);
print(player.team); //Team.blue
player.runQuick(); //run
print(player.strengthLevel); //1500.99
}
주의점 : mixin은 컨스트럭터 없는 class여야 함.
또한 부모 자식 상속 관계가 아닌 단순히 프로퍼티와 메서드를 긁어오는 것임.