Dart 언어 기초

Dart 언어에 대한 기초지식을 정리해봤습니다.읽는데 9분 정도 걸려요.

제가 생각하기에 너무 기초지식이다 하는 부분은 과감하게 제외했습니다.
다만, 기초지식이다 하더라도 개념적으로 매우 중요하거나 많이들 알려주지 않는 내용은 포함하였습니다.

variables

변수 네이밍 방법은 다음과 같습니다.

.dart
  <제어자> <타입> <변수이름> = <>;

타입은 int, String 과 같은 타입들이 오거나, var 와 같은 타입 추론 키워드도 사용할 수 있습니다.
제어자에는 final, const, late, static 와 같은 키워드가 오게 됩니다.

var

dart에선 var는 타입 추론 키워드로 사용되는 예약어입니다.
따라서 <값>에 어떤 데이터가 오는지에 따라 변수의 타입이 결정됩니다.
단, 값을 지정하지 않을 때는 dynamic 타입으로 자동 결정됩니다.

dynamic?
타입 안정성이 보장되지 않지만, 모든 타입의 데이터를 할당할 수 있도록 해줍니다.
보통 json 파일을 파싱할 때 임시로 데이터 형식을 지정할 때 많이 사용합니다.

final vs const

finalconst 둘 모두 데이터의 불변성을 보장하고 싶을 때 사용하는 키워드 라는 점에서는 공통점을 갖습니다.
또한, 이 키워드를 사용한다면 타입을 굳이 지정하지 않아도 알아서 var로 타입을 지정하는 효과가 있습니다.
하지만 둘 사이의 중요한 차이점이 있습니다.

final은 런타임에 값이 지정됩니다.
따라서 메모리상의 heap 영역에 데이터가 저장됩니다.
그렇기에 생명주기는 스코프 내부로 한정되기 떄문에 flutter 에서 build 메서드가 호출될 때 이 부분은 반복적으로 메모리에 올라왔다 내려갔다를 반복하게 됩니다.

const는 컴파일타임에 값이 지정됩니다.
특이한 점인 const 키워드로 지정된 데이터는 메모리상에 별도로 적재되지 않고, 컴파일된 코드의 상수 풀(constant pool)상에 저장됩니다.
따라서 별도의 인스턴스가 생성되지 않고, 생명주기도 프로그램 수명과 동잃하기에 flutter 에서 build 메서드가 호출될 때 이 부분은 다시 랜더링되지 않습니다.

late

dart는 null-safety 언어입니다.
따라서 <타입>? 와 같이 ?로 null값이 가능하다고 지정해 주지 않는한 변수 선언시(혹은 인스턴스 생성시) 반드시 값을 지정해야 합니다.
하지만, late의 경우에는 인스턴스 생성 시점에 null값을 허용하지만, 이후에 단 한번 값을 할당할 수 있도록 해주는 키워드입니다.

물론, null값이 임시 허용이 되는만큼 사용에 주의가 필요합니다.

static

static은 클래스가 로드될 때 값이 지정됩니다.
따라서 heap영역이 아닌 클래스 자체 메모리 영역에 저장됩니다.
그렇기에 일반 메서드에선 static 변수에 접근할 수 있지만, static 메서드에선 일반 변수에 접근할 수 없습니다.
하지만 그렇기에 클래스를 인스턴스화 하지 않아도 바로 접근이 가능합니다.


function

dart는 사실 js를 대체하기 위해 나온 언어라고 합니다.
그래서일까요, dart의 모든 데이터 타입도 사실은 모두 Object입니다.

뿐만 아니라, 변수에 함수를 할당하거나, 익명 함수, 람다 함수 모두 사용 가능합니다.

named parameter

보통 함수의 파라미터는 아래와 같이 작성하곤 합니다.

.dart
  void myFunction(int num) {
    ...
  }
  ...
  myFunction(1);

하지만 입력받고자 하는 파라미터가 많아진다면, 인자를 넘겨줄 때 어느 자리에 어느 데이터를 넘겨야 할지 알기가 어렵습니다.
특히 flutter에서는 위젯을 만들 때 인자를 십수개씩 넘기는 경우가 많죠...

그렇기에 named parameter 기능을 제공합니다. (마치 js에서 객체를 인자로 넘기 듯)

.dart
  void myFunction({required int myNum}) {
    ...
  }
  ...
  myFunction(myNum: 1);

이렇게 하면 함수 호출이나, 클래스 생성자 호출시 어느 인자에 어느 데이터를 넘겨야 하는지 명확해집니다.

물론, 위 두 방식을 혼합하여 사용할 수도 있습니다.

.dart
  void myFunction(String str, {required int val}) {
    ...
  }
  ...
  myFunction("string", myNum: 1);

class

constructor

.dart
class Car {
  final String color;
  int wheels;
  String? name;

  Car({
    required this.color,
    required this.wheels,
    this.name,
  });
}

기본적으로 생성자는 위와 같이 정의할 수 있습니다.
named parameter 형식으로도 사용이 가능합니다. (대부분의 flutter 위젯은 위와 같이 정의합니다)

named constructor

.dart
Car {
  final String color;
  late int wheels;
  String? name;

  Car.fourWheels({required this.color, this.name}) {
    this.wheels = 4;
  }
}

클래스를 생성할 때 특별한 기능을 붙인채로 인스턴스화 할 수 있도록 named constructor를 지원합니다.
보통 초기상태를 위한 .init, 모델링을 위한 .fromJson와 같이 쓰이곤 합니다.
(참고로 이 때 factory 키워드를 이용해서 싱글톤 패턴으로 많이 구현하는데 이는 다음에 소개하겠습니다)

initializer list

위에 wheels을 별도로 초기화하기 위해 late 한정자를 사용했는데요, 보기가 안좋습니다.
final 멤버변수는 생성자 body에서 값을 지정할 수 없기에 저런식으로 표현했는데요,
initializer list를 이용하면 final 멤버변수도 생성자 호출시 별도로 처리할 수 있습니다.

.dart
class Car {
  final String color;
  final int wheels;
  String? name;

  Car.prototype({required this.color}) : wheels = 4 {
    this.name = 'proto';
  }
}

:를 붙여 : wheels = 4와 같이 사용할 수 있습니다.
또한, 뒤에 body를 붙여 추가작업 또한 할 수 있습니다.

initializer list에서 알 수 있듯 wheels = 4 와 같은 initializer를 ,를 이용해서 여려개 연달아 초기화할 수 있습니다.

redirecting constructor

.dart
class Car {
  final String color;
  final int wheels;
  String? name;

  Car({
    required this.color,
    required this.wheels,
    this.name,
  });

  Car.fourWheels({required String color, String? name})
      : this(
          color: color,
          wheels: 4,
          name: name,
        );
}

기존의 생성자를 this로 호출하여 named constructor를 구현한 방식입니다.
다만, 이 경우에는 생성자의 body를 구현할 수 없습니다.