[Dart] 다트 기초

JH Cho·2023년 1월 19일
0

Flutter

목록 보기
1/8

Variables

Hello Dart!

  • 모든 Dart 앱은 main() 함수를 시작점으로 가지고 있음.
  • 콘솔에 텍스트를 표시하기 위해선 print()함수를 사용.
void main() {
  print('Hello, World!');
}

var

  • Dart에서 변수는 var 키워드를 사용하여 자동 타입 지정 가능
  • 명시적 타입 지정도 가능 (예: String)
  • 세미콜론(;)을 작성하는 것을 주의 (자동 포매팅 없음)
// 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 Type

  • 선언방법
    • 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은 타입이 고정되지 않는다.

Nullable Variables

  • Null Safety는 개발자가 null 값을 참조하지 못하도록 하는 것
  • null 값을 참조하면 런타임 에러가 발생함.

Without null safety

// 컴파일러가 에러를 캐치하지 못하고 
// 런타임 때 에러가 발생하게 됨.
bool isEmpty(String string) => string.length == 0;

main(){
  isEmpty(null); 
  // err: NoSuchMethodError
  //null이 인자로 보내지고 null.length가 실행되는데
  // null에는 length 메서드가 없기 때문에 
  // 위와 같은 에러가 발생하게 된다.
}

null은 아무 값도 없음을 나타내는 중요한 값.
따라서 dart에서는 null safety를 도입함.

with 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를 적용할 수 있지만 옵셔널 연산자를 이용하면
훨씬 간단하게 구현 가능하다.

final(const)

void main() {
  // JS의 const 선언과 같다.
  final name = "이름";
  // 타입 지정도 가능 final String name = '이름'
  // 필수는 아님.
  
  name = '창씨개명'; // error
}

late Variable

  • 값 할당을 미루어 준다.
void main() {
  late final String name;
  //사용예) 무언가 실행되면 api에서 데이터를 불러와 값을 할당
  
  //할당 하기 전 참조 시 할당되지 않음을 이유로 에러 발생
  print(name);
  
  name = 'api에서 불러온 이름';
  name = 'dd'; // 마찬가지로 상수이므로 에러 발생
  
}

대부분의 경우 필요한 데이터를 알고 있기 때문에
final name = '이름'; 의 경우처럼 초기에 값을 할당할 것이다.
하지만 그렇지 않은 경우도 있기 때문에 이 때 late가 유용하며,
할당 전까지 접근이 불가하기 때문에 에러 방지에도 유용하다.

Constant Variables

  • JS의 const 키워드는 Dart의 final과 같고 Dart의 const 키워드는 약간 다름
  • Dart의 const는 compile-time constant를 생성한다.
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(변수)

Data Types

Basic Data Types

  • String, bool, int, double, num
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 클래스 자료형의 자식 클래스!

Lists

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 마지막 요소 반환
}

collection if

//### 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]
  }
}

String Interpolation

void main(){
  var name = '이름';
  
  var greeting = 'Hello everyone, my name is $name';
  
  print(greeting);
  //Hello everyone, my name is 이름
}

String Interpolation

  • 단순 변수 : $변수명
  • 계산 등.. : ${변수명 + 2}
  • JS는 템플릿 리터럴 이용하지만 다트는 "" or '' 상관없음
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 $변수명

Collection For

  • 배열에 다른 배열 변형 추가 가능.
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 은 아주 혁명적임.

Maps

  • 객체의 키와 밸류 타입을 지정해줄 수 있다.
  • 배열에도 적용 가능
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가 있으므로 후에 배워보자.

Sets

  • 요소의 중복없는(unique) 배열을 만들기 위한.
  • JS의 Set과 같네
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와 같지만 모든 요소가 유니크(중복없음)해야 한다.

Function

함수 정의하기

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;

Named Parameter

  • 기존
String sayHello(String name, int age, String country){
  return "Hello $name, you are $age, from $country";
}

void main() {
  // 기존 파라미터 전달 방법.
  print(sayHello('이름', 100, "N.Korea"));
}
  • name parameter

// 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:'ㅇㅇ'));
}

optional positional parameter

  • 대괄호 감싸기 -> 디폴트 값 설정
String sayHello(String name, int age, [String country = '쿠바']) =>
    "hello $name $age $country";

void main() {
  print(sayHello("이름", 12));
  // hello 이름 12 쿠바
  
  print(sayHello("이름", 12,"한국"));
  //hello 이름 12 한국
}

QQ operator

  • 기존 방식
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); // 이러면 됨
}

typedef

  • 타입스크립트 처럼 타입 만들기 쌉가능.
// 기존
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를 사용하면 됨.

Classes

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 는 굳이 필요 없다.
필요한 경우는 메서드의 변수와 프로퍼티의 이름이 같을 경우 정도.

constructor

  • JS의 컨스트럭터와 비슷한데 약간 다르네
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); //하하
}

Named Constructor Parameter

  • class가 커져서 관리가 어려울 때
//기존 클라스
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,
  );
}

Named Constructor

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
}

복습 named constructor

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
   * */
}

Cascade Notation

  • 인스턴스 수정을 쉽게 할 수 있다.
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

  • enum은 리터럴 타입 지정과 비슷하다.
  • string으로 쓸 필요 없이 아래처럼 constrain하면 된다.
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

  • abstract class는 자식 클라스가 항상 가져야 하는 메서드를 정의할 수 있다.
  • abstract class의 메서드는 불변이 아니므로 자식클래스에서 마음대로 구현이 가능하다.
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');
  }
}

클래스 상속(inheritance)

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
}

Mixins

mixin

  • 생성자 없는 컨스터럭터를 의미.
  • 재사용 하는 메서드나 프로퍼티를 넣어두면 좋다.
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들의 메서드와 프로퍼티를 가져온 모습.

    주의점 : mixin은 컨스트럭터 없는 class여야 함.
    또한 부모 자식 상속 관계가 아닌 단순히 프로퍼티와 메서드를 긁어오는 것임.

profile
주먹구구식은 버리고 Why & How를 고민하며 프로그래밍 하는 개발자가 되자!

0개의 댓글