728x90

배열.fold()는 배열의 값들을 하나씩 커내 하나의 값으로 변환하는 함수이다.

- curr 는 elememt 이다.

- prev 는 이전값. 최초에는 prev에 0이 들어가고, 다음부터는 return 의 결과값을 prev 에 넣는다.

void main() {
  int totalPrice = [12345].fold(0, (prev, curr) {
    print("$prev / $curr");
    return prev + curr;
  });
  print(totalPrice);
}

 

'Flutter 앱 > Dart 언어' 카테고리의 다른 글

Dart List.where  (0) 2024.01.16
[Dart 고급] Dart asMap, entries  (0) 2024.01.16
Dart mixin  (0) 2023.12.11
getter & setter  (0) 2023.12.09
Dart 직렬화(Serialization) 및 역직렬화(Deserialization)  (0) 2023.12.08
블로그 이미지

Link2Me

,
728x90

가장 기본적인 상태관리를 하는 방법이다.

부모위젯

- StatefulWidget으로 생성한다.

- 모든 공유 상태를 가지고 있다.

- 상태 변경시 setState()로 자식 위젯들을 갱신한다.

 

자식 위젯

-  부모 위젯의 공유 상태를 전달받는다.

- 이벤트 발생시, 부모 위젯에게 전달하여 공유 상태를 변경한다.

 

import 'package:flutter/material.dart';
 
void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyApp(),
    ),
  );
}
 
class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}
 
class _MyAppState extends State<MyApp> {
  // 공유 상태
  int state = 0;
 
  // 공유 상태 업데이트
  void increaseState() {
    setState(() {
      state += 1;
    });
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("state : $state"),
            Child(
              state: state, // 부모로부터 공유 상태를 전달 받는다.
              onPressed: increaseState, // 이벤트를 부모에게 전달한다.
            ),
          ],
        ),
      ),
    );
  }
}
 
class Child extends StatelessWidget {
  const Child({
    required this.state,
    required this.onPressed,
  });
 
  final int state;
  final void Function() onPressed;
 
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text("Child : $state"),
    );
  }
}
 

 

 

 

블로그 이미지

Link2Me

,
728x90

Flutter 에서는 named parameter 기반 생성자를 사용한다.

Android Studio 가 기본으로 제공하는 것에서는 name parameter 기반으로 생성되지 않기 때문에 불편하다.

이를 해결할 방법이다.

 

 

Plugins 탭을 누르고 MarketPlace를 선택한 다음에 dart data class 를 입력하면 검색 결과가 나온다.

 

여기까지 하면 준비는 된 것이다.

 

실제 data class 를 생성하고 나서 시도해 보자.

필요로 하는 변수를 선언하고 나서

  1. Windows/Linux: Alt + Enter
  2. MacOS: ⌘ + Enter

단축키를 누른다.

 

 

OK버튼을 누르면 아래와 같이 코드가 자동으로 추가된다.

 

 

 

Copy는 Copywith를 자동으로 생성해주고,

toMap() and fromMap()은 자동 생성한 후에 toJson() 과 fromJson 으로 용어 변경을 해주고 세부적인 사항은 수정해주면 된다.

 

 

 

 

블로그 이미지

Link2Me

,
728x90

family를 이용하면 Provider에 매개변수를 추가할 수 있다.

.family를 붙이고, 매개변수 타입을 명시한다.

 

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
 
final counterProvider = NotifierProvider.family<Counter, intint>(() {
  return Counter();
});
 
class Counter extends FamilyNotifier<intint> {
  @override
  int build(int initValue) => initValue;
 
  void increment() => state++;
}
 
 
void main() {
  runApp(
    const ProviderScope(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        home: MyApp(),
      ),
    ),
  );
}
 
class MyApp extends ConsumerWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    int counter = ref.watch(counterProvider(10));
    return Scaffold(
      body: Center(
        child: Text(
          "$counter",
          style: const TextStyle(
            fontSize: 24,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: ref.read(counterProvider(10).notifier).increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}
 

 

counterProvider(10)과 같이 원하는 초기값을 전달할 수 있다.
ref.read(counterProvider(10))에서도 처음 전달한 매개 변수와 동일한 값을 전달해야 한다.

 

'Flutter 앱 > 상태관리' 카테고리의 다른 글

Flutter Riverpod 예제 : Bucket List  (0) 2023.12.27
Flutter StatefulWidget  (0) 2023.12.13
Flutter Riverpod - NotifierProvider  (0) 2023.12.11
Flutter riverpod - StateProvider  (0) 2023.11.23
Flutter GetX (반응형 상태관리)  (0) 2022.06.24
블로그 이미지

Link2Me

,
728x90

Notifier is a replacement of StateNotifier + ChangeNotifier.

 

아래 예제는 하나의 파일로 작성되었지만 별도의 파일로 Provider 를 추가하는 것을 권장한다.

- 클래스를 구현하고, Notifier 를 상속받고 제네릭 타입을 명시한다.

- 아래 예시는 숫자를 증가시키는 Provider 이므로 counterProvider 를 명명한다.

 

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
 
final counterProvider = NotifierProvider<Counter, int>(() {
  return Counter();
});
 
// Notifier를 상속 받은 클래스만 NotifierProvider에 등록 가능
 
class Counter extends Notifier<int> {
  @override
  int build() => 0// state 초기값
 
  void increment() => state++;
}
 
 
void main() {
  runApp(
    const ProviderScope(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        home: MyApp(),
      ),
    ),
  );
}
 
class MyApp extends ConsumerWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    int counter = ref.watch(counterProvider);
    return Scaffold(
      body: Center(
        child: Text(
          "$counter",
          style: const TextStyle(
            fontSize: 24,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: ref.read(counterProvider.notifier).increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}
 

 

ref.watch는 provider를 입력값으로 받고, state를 리턴하는 함수다.

 

https://riverpod.dev/docs/providers/notifier_provider 를 참조하면 도움된다.

 

 

'Flutter 앱 > 상태관리' 카테고리의 다른 글

Flutter StatefulWidget  (0) 2023.12.13
Flutter Riverpod - NotifierProvider.family  (0) 2023.12.11
Flutter riverpod - StateProvider  (0) 2023.11.23
Flutter GetX (반응형 상태관리)  (0) 2022.06.24
Flutter GetX (단순 상태관리)  (0) 2022.06.23
블로그 이미지

Link2Me

,

Dart mixin

Flutter 앱/Dart 언어 2023. 12. 11. 09:23
728x90

Mixin은 " Mixins are a way of defining code that can be reused in multiple class hierarchies. (여러 클래스 계층에서 코드 정의한 부분을 재사용하기 위한 방법 중 하나이다.)" 공식문서에 표현되어 있다. 

https://dart.dev/language/mixins

Dart 언어에는 extends, implements이 주로 사용되며 extends는 클래스를 상속할 때, implements는 추상화 클래스를 만들 때 사용한다.

상속(extends)할 때에는 클래스의 메소드를 override(재정의)하지 않으나, implements를 한 경우에는 반드시 override(재정의)가 필요하다.

Dart에서는 extends를 통한 다중 상속이 지원되지 않는다. mixin은 다중 상속해야 하는 경우에 주로 사용된다.

 

mixin class Scanner {
  // mixin class 키워드는 Dart 3.0.0 버전 이상부터 사용할 수 있다.
  void scanning() => print("scanning...");
}
 
mixin class Printer {
  void printing() => print("printing...");
}
 
class Machine with Printer, Scanner {}
 
void main() {
  final machine = Machine();
  machine.printing();
  machine.scanning();
}
 
/***
 * class Scanner {}       // mixin 불가능 & 인스턴스 생성 가능
 * mixin Scanner {}       // mixin 가능 & 인스턴스 생성 불가능
 * mixin class Scanner {} // mixin 가능 & 인스턴스 생성 가능
 */

 

 

 

 

 

 

 

'Flutter 앱 > Dart 언어' 카테고리의 다른 글

[Dart 고급] Dart asMap, entries  (0) 2024.01.16
Dart fold 함수  (2) 2023.12.15
getter & setter  (0) 2023.12.09
Dart 직렬화(Serialization) 및 역직렬화(Deserialization)  (0) 2023.12.08
Dart 3.0 records  (0) 2023.10.19
블로그 이미지

Link2Me

,
728x90

get 키워드를 사용한 함수를 Getter 라고 부른다.

// 일반 형태
반환타입 get 이름 {
    return 반환값;
}
 
// 화살표 구문 사용
반환타입 get 이름 => 반환값;

 

 

set 키워드를 사용하는 함수를 Setter 라고 부른다.

set 이름(값) {
    // 할당시 수행하고 싶은 로직
}

 

 

 

'Flutter 앱 > Dart 언어' 카테고리의 다른 글

Dart fold 함수  (2) 2023.12.15
Dart mixin  (0) 2023.12.11
Dart 직렬화(Serialization) 및 역직렬화(Deserialization)  (0) 2023.12.08
Dart 3.0 records  (0) 2023.10.19
Dart Collection  (0) 2022.06.28
블로그 이미지

Link2Me

,
728x90

https://link2me.tistory.com/2346 게시글에서 Live Templete를 추가한 것을 이용하여 코드를 작성한다.

Android Studio 에서 파일명을 생성하고 나서 frf 를 입력하면 자동 완성 포멧이 만들어진다.

여기에서 Class Name을 입력하고, part 파일명을 소문자로 입력하면 된다.

import 'package:json_annotation/json_annotation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
 
part 'person.freezed.dart';
part 'person.g.dart';
 
@freezed
class Person with _$Person {
  factory Person({
    required String name,
    required int age,
  }) = _Person;
 
  factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}

 

위 코드를 추가한 후에

터미널창에서 dart run build_runner build 를 하면 자동으로 2개의 파일이 추가된다.

변경될 때마다 자동으로 파일이 추가/수정되도록 하고 싶다면 dart run build_runner watch 명령어를 입력한다.

 

상속이 포함된 Class 작성에는 어려움이 있다고 하니 참고

 

 

import 'dart:convert';
import 'package:dart_ex/model/person.dart';
 
void main() {
  Person a = Person(name: '철수', age: 10);
  Person b = a; // 얕은 복사
 
  // a.name = '영희'; // 불변 객체이므로 name 수정 불가
 
  // copyWith()
  a = a.copyWith(name: '영희'); // 깊은 복사
 
  // toString()
  print("toString() : $a");
  print("toString() : $b");
 
  // 값 비교(Value Equality)
  bool valueEquality = Person(name: "철수", age: 1== Person(name: "철수", age: 1);
  print("값 비교 : $valueEquality");
 
  // JSON 직렬화(Serialization)
  Map<String, dynamic> map = a.toJson();
  print("toJson() : $map");
  String jsonString = jsonEncode(map);
 
  // JSON 역직렬화(Deserialization)
  Map<String, dynamic> jsonMap = jsonDecode(jsonString);
  Person person = Person.fromJson(jsonMap);
  print("fromJson() : $person");
}

 

블로그 이미지

Link2Me

,
728x90

1. 터미널을 열고 다음 명령어를 실행하여 필요한 패키지들을 설치한다. dev_dependency 설치

    dart pub add -d freezed build_runner json_serializable

 

2. 터미널에서 다음 명령어를 실행한다. dependency 설치

    dart pub add freezed_annotation json_annotation

 

그러면 아래와 같이 pubspec.yaml 파일에 자동으로 추가될 것이다.

name: dart_ex
description: "A new Flutter project."
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 
version: 1.0.0+1
 
environment:
  sdk: '>=3.2.2 <4.0.0'
 
dependencies:
  flutter:
    sdk: flutter
 
  cupertino_icons: ^1.0.2
  equatable: ^2.0.5
  freezed_annotation: ^2.4.1
  json_annotation: ^4.8.1
 
dev_dependencies:
  flutter_test:
    sdk: flutter
 
  flutter_lints: ^2.0.0
  freezed: ^2.4.5
  build_runner: ^2.4.7
  json_serializable: ^6.7.1
 
flutter:
 
  uses-material-design: true

 

 

이제 LiveTemplete 를 설정해보자.

Android Studio 에서 아래와 같은 번호 순서대로 하면 된다.

 

 

 

5번 이름이 나타나도록 지정해준다.

 

8번에 붙여넣을 코드이다.

import 'package:json_annotation/json_annotation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part '$NAME$.freezed.dart';
part '$NAME$.g.dart';

@freezed
class $CAP_NAME$ with _$$$CAP_NAME$ {
  factory $CAP_NAME$({
    $END$
  }) = _$CAP_NAME$;
  
  factory $CAP_NAME$.fromJson(Map<String, dynamic> json) => _$$$CAP_NAME$FromJson(json); 
}

 

 

아래와 같이 Change 로 변경해줘야 한다.

 

여기까지 하면 자동완성하는 코드가 완성된 것이다.

 

실제 사용하는 예제를 유투브에서 가져왔다.

https://www.youtube.com/watch?v=i5p6wXLAX7I

'Flutter 앱 > 환경설정' 카테고리의 다른 글

flutter db test 에러  (0) 2024.01.08
Flutter Dart Data Class 자동 생성  (0) 2023.12.13
Flutter Null Safety 확인 및 Migration  (0) 2022.06.25
IntelliJ IDEA Community 에 Dart 설치  (0) 2022.06.24
Flutter Upgrade  (0) 2022.06.17
블로그 이미지

Link2Me

,
728x90

 

JSON(JavaScript Object Notation)은 데이터를 표현하는 규칙이다.

  • 문자열에서 단따옴표('문자열')가 아닌 쌍따옴표("문자열")을 사용해야 한다.
  • 네트워크를 통해 다른 컴퓨터로 데이터 전송할 때 일련의 바이트(문자열)로 전달한다.

역직렬화(Deserialization) : 일련의 문자열 → Dart 클래스
직렬화(Serialization) : Dart 클래스 → 일련의 문자열
역직렬화(Deserialization) 진행 순서

  • JSON 포맷 String → Map<String, dynamic>
  • Map<String, dynamic> → Person 클래스

JSON 포맷 문자열이기 때문에 jsonDecode() 함수를 이용하면 Map<String, dynamic>으로 손쉽게 변경할 수 있다.

 

import 'dart:convert';
 
class Person {
  final String name;
  final int age;
 
  const Person({
    required this.name,
    required this.age,
  });
 
  // factory 키워드를 붙여 생성자 메소드로 만들 수 있으며, 다음 규칙을 지켜야 한다.
  // 클래스 인스턴스를 반환해야 한다.
  // 메소드명을 클래스명.메소드명() 형태로 작성해야 한다.
  factory Person.fromJson(Map<String, dynamic> json) {
    return Person(
      name: json['name'],
      age: json['age'],
    );
  }
 
  // 클래스의 인스턴스를 Map<String, dynamic>으로 변경하는 함수는 
// 일반적으로 클래스에 toJson() 메소드를 만들어 진행한다.
  Map<String, dynamic> toJson() {
    return {
      "name": name,
      "age": age,
    };
  }
}
 
void main() {
  // 네트워크 응답 문자열
  String jsonString = '{"name": "철수", "age": 10}';
 
  // JSON 포맷 String -> Map<String, dynamic>
  Map<String, dynamic> jsonMap = jsonDecode(jsonString);
  print(jsonMap);
 
  // Map<String, dynamic> -> Person
  Person person = Person.fromJson(jsonMap);
  print(person);
 
  // Person -> Map<String, dynamic>
  Map<String, dynamic> personMap = person.toJson();
  print(personMap);
 
  // jsonEncode() 함수를 이용하면 Map<String, dynamic>을 
// JSON 포맷 String으로 변경할 수 있다.
  // Map<String, dynamic> -> JSON 포맷 String
  String personString = jsonEncode(personMap);
  print(personString);
}
 

 

 

'Flutter 앱 > Dart 언어' 카테고리의 다른 글

Dart mixin  (0) 2023.12.11
getter & setter  (0) 2023.12.09
Dart 3.0 records  (0) 2023.10.19
Dart Collection  (0) 2022.06.28
Dart Class(클래스)  (0) 2022.06.27
블로그 이미지

Link2Me

,
728x90

9.1.1 버전에서는 정상 동작한다.

GoRouterState.of(context).location

 

10.0.0 버전부터는 아래와 같이 변경해줘야 한다.

GoRouterState.of(context).uri.toString()

 

 

GoRouterState.of(context).queryParameters

GoRouterState.of(context).uri.queryParameters

로 변경되었다.

블로그 이미지

Link2Me

,
728x90

Riverpod 라이브러리를 이용한 상태관리 방법에 대해 알아보고 적어둔다.

 

Provider는 상태를 저장하고, 자손 위젯에서 상태에 접근할 수 있도록 제공해주는 InheritedWidget를 래핑한 라이브러리이다. Provider 개발자가 단점을 보완하여 재작성된 라이브러리가 Riverpod 상태 관리 라이브러리이다.

 

1. 먼저 https://pub.dev/packages/riverpod 사이트에서 flutter_riverpod 의 최신버전을 확인하여 pubspec.yaml 에 추가한다.

 

name: riverpod_ex
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 
version: 1.0.0+1
 
environment:
  sdk: '>=3.1.5 <4.0.0'
 
dependencies:
  flutter:
    sdk: flutter
 
  cupertino_icons: ^1.0.2
  flutter_riverpod: ^2.4.8
 
dev_dependencies:
  flutter_test:
    sdk: flutter
 
  flutter_lints: ^2.0.0
 
 
flutter:
 
  uses-material-design: true
 

 

 

2. main.dart 파일에서 아래와 같이

_MyApp을 ProviderScope 로 감싸준다.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_ex/screen/home_screen.dart';
 
void main() {
  runApp(ProviderScope(
    child: _MyApp(),
  ));
}
 
class _MyApp extends StatelessWidget {
  const _MyApp({Key? key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomeScreen(),
    );
  }
}

 

 

3. state_provider.dart

상태관리를 할 StateProvider를 구현한다.

import 'package:flutter_riverpod/flutter_riverpod.dart';
 
final numberProvider = StateProvider<int>((ref) => 0);

 

 

4.state_provider_screen.dart

ConsumerWidget 을 상속하고, Widget build(BuildContext context) 를 Widget build(BuildContext context, WidgetRef ref) 로 변경해준다.

Riverpod에서 정의된 추상클래스 WidgetRef를 통해서 Provider에 접근 가능하고,
ref.watch를 통해서 변경을 감지하면 해당 위젯을 다시 build한다.

 

final provider = ref.watch(numberProvider); 를 추가하여 상태 변화를 감지한다.

Provider의 값이 변경되면 자체적으로 다시 build된다.

 

WidgetRef의 read 메소드를 호출(Provider의 값을 읽어오기만 함)하며, 상태를 변경할 StateProvider의 notifier를 전달한다.

ref.read(numberProvider.notifier).update((state) => state + 1); 로 상태를 증가시키거나,

ref.read(numberProvider.notifier).state = ref.read(numberProvider.notifier).state - 1; 와 같은 방법으로 상태를 감소/증가 시킬 수 있다.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
 
import '../layout/default_layout.dart';
import '../riverpod/state_provider.dart';
 
class StateProviderScreen extends ConsumerWidget {
  const StateProviderScreen({Key? key}) : super(key: key);
 
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final provider = ref.watch(numberProvider);
 
    return DefaultLayout(
      title: 'Basic Provider',
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              provider.toString(),
              textAlign: TextAlign.center,
            ),
            ElevatedButton(
              onPressed: () {
                ref.read(numberProvider.notifier).update((state) => state + 1);
              },
              child: Text('UP'),
            ),
            ElevatedButton(
              onPressed: () {
                ref.read(numberProvider.notifier).state =
                    ref.read(numberProvider.notifier).state - 1;
              },
              child: Text(
                'DOWN',
              ),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(builder: (_) => _NextScreen()),
                );
              },
              child: Text(
                'Next Screen',
              ),
            ),
          ],
        ),
      ),
    );
  }
}
 
class _NextScreen extends ConsumerWidget {
  const _NextScreen({Key? key}) : super(key: key);
 
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final provider = ref.watch(numberProvider);
 
    return DefaultLayout(
      title: 'Basic Provider',
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              provider.toString(),
              textAlign: TextAlign.center,
            ),
            ElevatedButton(
              onPressed: () {
                ref.read(numberProvider.notifier).update((state) => state + 1);
              },
              child: Text('UP'),
            ),
          ],
        ),
      ),
    );
  }
}

 

블로그 이미지

Link2Me

,
728x90

아래 코드는 factory 생성자를 일일이 타이핑을 해야 하는 번거로움이 있다.

import 'package:rest/restaurant/component/restaurant_card.dart';
 
import '../../common/const/data.dart';
 
enum RestaurantPriceRange {
  expensive,
  medium,
  cheap,
}
 
class RestaurantModel {
  final String id;
  final String name;
  final String thumbUrl;
  final List<String> tags;
  final RestaurantPriceRange priceRange;
  final double ratings;
  final int ratingsCount;
  final int deliveryTime;
  final int deliveryFee;
 
  RestaurantModel({
    required this.id,
    required this.name,
    required this.thumbUrl,
    required this.tags,
    required this.priceRange,
    required this.ratings,
    required this.ratingsCount,
    required this.deliveryTime,
    required this.deliveryFee,
  });
 
  factory RestaurantModel.fromJson({
    required Map<String, dynamic> json,
  }) {
    return RestaurantModel(
      id: json['id'],
      name: json['name'],
      thumbUrl: 'http://$realIp${json['thumbUrl']}',
      tags: List<String>.from(json['tags']),
      priceRange: RestaurantPriceRange.values.firstWhere(
        (e) => e.name == json['priceRange'],
      ),
      ratings: json['ratings'],
      ratingsCount: json['ratingsCount'],
      deliveryTime: json['deliveryTime'],
      deliveryFee: json['deliveryFee'],
    );
  }
 
}

 

 

위 코드에서 factory 생성자 코드 부분을 자동 생성하는 방법으로 JSON Serialize 를 이용한다.

1. https://pub.dev/packages/json_serializable 에서 최신버전을 확인한다.

Readme 탭에서 Setup 부분의 example 을 누르면 pubspec.yaml 에 추가할 버전이 나온다.

아래의 라이브러리를 추가하고 flutter pub get 명령을 실행한다.

dependencies:
  json_annotation: ^4.8.0
 
dev_dependencies:
  build_runner: ^2.3.3
  json_serializable: ^6.7.1

 

 

Class 위에 @JsonSerializable() 을 추가하고, part 'restaurant_model.g.dart'; 코드를 추가한다.

그리고 나서 터미널 창을 열면 프로젝트 root 폴더가 된다.

root에서 dart run build_runner build 를 입력하고 실행한다.

매번 입력하기 귀찮다면 dart run build_runner watch 명령어를 실행하면 된다.

import 'package:json_annotation/json_annotation.dart';
import 'package:rest/common/utils/data_utils.dart';
 
part 'restaurant_model.g.dart';
 
/**
* 수정 사항이 생기면 터미널에서 dart run build_runner build 를 다시 실행한다.
 * 그러면 자동으로 g.dart 파일을 업데이트한다.
 */
 
enum RestaurantPriceRange {
  expensive,
  medium,
  cheap,
}
 
@JsonSerializable()
class RestaurantModel {
  final String id;
  final String name;
  @JsonKey(
    fromJson: DataUtils.pathToUrl,
  )
  final String thumbUrl;
  final List<String> tags;
  final RestaurantPriceRange priceRange;
  final double ratings;
  final int ratingsCount;
  final int deliveryTime;
  final int deliveryFee;
 
  RestaurantModel({
    required this.id,
    required this.name,
    required this.thumbUrl,
    required this.tags,
    required this.priceRange,
    required this.ratings,
    required this.ratingsCount,
    required this.deliveryTime,
    required this.deliveryFee,
  });
 
  factory RestaurantModel.fromJson(Map<String, dynamic> json)
  => _$RestaurantModelFromJson(json);
 
  Map<String, dynamic> toJson() => _$RestaurantModelToJson(this);
 
 
  // factory RestaurantModel.fromJson({
  //   required Map<String, dynamic> json,
  // }) {
  //   return RestaurantModel(
  //     id: json['id'],
  //     name: json['name'],
  //     thumbUrl: 'http://$realIp${json['thumbUrl']}',
  //     tags: List<String>.from(json['tags']),
  //     priceRange: RestaurantPriceRange.values.firstWhere(
  //       (e) => e.name == json['priceRange'],
  //     ),
  //     ratings: json['ratings'],
  //     ratingsCount: json['ratingsCount'],
  //     deliveryTime: json['deliveryTime'],
  //     deliveryFee: json['deliveryFee'],
  //   );
  // }
 
}

 

모델 클래스 내부에 fromJson과 toJson을 정의해 두면 해당 메서드를 호출할 때마다 오타를 걱정할 필요가 없어진다.

 

 

칼럼에서 별도 변경이 필요하면

  @JsonKey(
    fromJson: DataUtils.pathToUrl,
  )

와 같이 추가하고 나서 dart run build_runner build 를 다시 실행한다.

그러면 restaurant_model.g.dart 파일이 업데이트되어 자동 생성된다.

 

// GENERATED CODE - DO NOT MODIFY BY HAND
 
part of 'restaurant_model.dart';
 
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
 
RestaurantModel _$RestaurantModelFromJson(Map<String, dynamic> json) =>
    RestaurantModel(
      id: json['id'as String,
      name: json['name'as String,
      thumbUrl: DataUtils.pathToUrl(json['thumbUrl'as String),
      tags: (json['tags'as List<dynamic>).map((e) => e as String).toList(),
      priceRange:
          $enumDecode(_$RestaurantPriceRangeEnumMap, json['priceRange']),
      ratings: (json['ratings'as num).toDouble(),
      ratingsCount: json['ratingsCount'as int,
      deliveryTime: json['deliveryTime'as int,
      deliveryFee: json['deliveryFee'as int,
    );
 
Map<String, dynamic> _$RestaurantModelToJson(RestaurantModel instance) =>
    <String, dynamic>{
      'id': instance.id,
      'name': instance.name,
      'thumbUrl': instance.thumbUrl,
      'tags': instance.tags,
      'priceRange': _$RestaurantPriceRangeEnumMap[instance.priceRange]!,
      'ratings': instance.ratings,
      'ratingsCount': instance.ratingsCount,
      'deliveryTime': instance.deliveryTime,
      'deliveryFee': instance.deliveryFee,
    };
 
const _$RestaurantPriceRangeEnumMap = {
  RestaurantPriceRange.expensive: 'expensive',
  RestaurantPriceRange.medium: 'medium',
  RestaurantPriceRange.cheap: 'cheap',
};
 

 

 

블로그 이미지

Link2Me

,
728x90

FutureBuilder 와 Dio 라이브러리를 이용하여 서버에 있는 데이터를 가져와서 화면에 보여주는 ListView 예제이다.

 

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:rest/restaurant/component/restaurant_card.dart';
 
import '../../common/const/data.dart';
 
class RestaurantScreen extends StatelessWidget {
  const RestaurantScreen({Key? key}) : super(key: key);
 
  Future<List> pagenateRestaurant() async {
    final dio = Dio();
    final accessToken = await storage.read(key: ACCESS_TOKEN_KEY);
    //print('accessToken read ::: ${accessToken}');
 
    final resp = await dio.get(
      'http://$realIp/restaurant',
      options: Options(headers: {
        'authorization''Bearer $accessToken',
      }),
    );
 
    return resp.data['data'];
  }
 
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: FutureBuilder<List>(
            future: pagenateRestaurant(),
            builder: (context, AsyncSnapshot<List> snapshot) {
              //print(snapshot.error);
              //print(snapshot.data);
              if (!snapshot.hasData) {
                return Container();
              }
 
              return ListView.separated(
                itemCount: snapshot.data!.length,
                itemBuilder: (_, index) {
                  final item = snapshot.data![index];
                  return RestaurantCard(
                    image: Image.network(
                      'http://$realIp${item['thumbUrl']}',
                      fit: BoxFit.cover,
                    ),
                    name: item['name'],
                    tags: List<String>.from(item['tags']),
                    ratingsCount: item['ratingsCount'],
                    deliveryTime: item['deliveryTime'],
                    deliveryFee: item['deliveryFee'],
                    ratings: item['ratings'],
                  );
                },
                separatorBuilder: (_, index) {
                  return SizedBox(height: 16.0);
                },
              );
            },
          ),
        ),
      ),
    );
  }
}
 

 

 

위 코드를 아래와 같이 Model 화하여 수정한 코드이다.

 
enum RestaurantPriceRange {
  expensive,
  medium,
  cheap,
}
 
class RestaurantModel {
  final String id;
  final String name;
  final String thumbUrl;
  final List<String> tags;
  final RestaurantPriceRange priceRange;
  final double ratings;
  final int ratingsCount;
  final int deliveryTime;
  final int deliveryFee;
 
  RestaurantModel({
    required this.id,
    required this.name,
    required this.thumbUrl,
    required this.tags,
    required this.priceRange,
    required this.ratings,
    required this.ratingsCount,
    required this.deliveryTime,
    required this.deliveryFee,
  });
}
 

 

 

 
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:rest/restaurant/component/restaurant_card.dart';
import 'package:rest/restaurant/model/restaurant_model.dart';
 
import '../../common/const/data.dart';
 
class RestaurantScreen extends StatelessWidget {
  const RestaurantScreen({Key? key}) : super(key: key);
 
  Future<List> pagenateRestaurant() async {
    final dio = Dio();
    final accessToken = await storage.read(key: ACCESS_TOKEN_KEY);
    //print('accessToken read ::: ${accessToken}');
 
    final resp = await dio.get(
      'http://$realIp/restaurant',
      options: Options(headers: {
        'authorization''Bearer $accessToken',
      }),
    );
 
    return resp.data['data'];
  }
 
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: FutureBuilder<List>(
            future: pagenateRestaurant(),
            builder: (context, AsyncSnapshot<List> snapshot) {
              //print(snapshot.error);
              //print(snapshot.data);
              if (!snapshot.hasData) {
                return Container();
              }
 
              return ListView.separated(
                itemCount: snapshot.data!.length,
                itemBuilder: (_, index) {
                  final item = snapshot.data![index];
                  final pItem = RestaurantModel(
                    id: item['id'],
                    name: item['name'],
                    thumbUrl: 'http://$realIp${item['thumbUrl']}',
                    tags: List<String>.from(item['tags']),
                    priceRange: RestaurantPriceRange.values.firstWhere(
                      (e) => e.name == item['priceRange'],
                    ),
                    ratings: item['ratings'],
                    ratingsCount: item['ratingsCount'],
                    deliveryTime: item['deliveryTime'],
                    deliveryFee: item['deliveryFee'],
                  );
 
                  return RestaurantCard(
                    image: Image.network(
                      pItem.thumbUrl,
                      fit: BoxFit.cover,
                    ),
                    name: pItem.name,
                    tags: pItem.tags,
                    ratingsCount: pItem.ratingsCount,
                    deliveryTime: pItem.deliveryTime,
                    deliveryFee: pItem.deliveryFee,
                    ratings: pItem.ratings,
                  );
                },
                separatorBuilder: (_, index) {
                  return SizedBox(height: 16.0);
                },
              );
            },
          ),
        ),
      ),
    );
  }
}
 

 

 

'Flutter 앱 > Network' 카테고리의 다른 글

Flutter Login with PHP Session #1  (0) 2023.12.24
Flutter AES256 with PHP  (0) 2023.12.19
Flutter Login Example  (0) 2022.07.25
Session vs JWT  (0) 2022.07.22
Flutter Login 로직 구현 예제 (오류 포함)  (0) 2022.07.22
블로그 이미지

Link2Me

,
728x90

미세먼지 공공데이터 신청 방법이다.

 

먼저 https://www.data.go.kr/index.do 에 접속한다.

회원가입이 안되어 있다면 회원가입부터 해야 한다. 회원가입은 네이버 로그인 정보 연동으로 가입했다.

 

미세먼지를 검색어에 입력하고 나서 오픈 API 탭을 누른다.

 

 

 

4번 한국환경공단_에어코리아_대기오염정보통계 현황을 누르면

인증키 정보가 나온다. postman 에서 사용할 인증키는 Encoding 인증키를 사용하면 된다.

인증키를 복사하고 나서 5번 상세정보를 클릭한다.

 

 

 

 

다음으로는 postman 을 구글 검색으로 다운로드 받아서 회원가입(구글로그인 연동)하고 나서 팝업되는 창에 입력을 한다.

운영체제에 맞는 버전을 다운로드 하여 설치한다.

 

위 그림에 나온 요청주소와 요청변수를 입력하고 Send 키를 누른다.

 

결과는 아래와 같이 나온다.

 

위 그림에서 나온 정보를 복사하여 플러터(Flutter) 코드에서 가공하여 수정하여 활용하면 된다.

items 에 나열된 시도 명칭은 행정순서와는 무관하게 나와 있으니 순서를 맞추기보다는 그냥 활용하는 편이 나을 듯 싶다.

블로그 이미지

Link2Me

,
728x90

Flutter 에서 로컬 데이터베이스 패키지 중 하나인 Drift 에 대해 알아보자.
Drift 는 Sqlflite 와 다르게 ORM 방식의 데이터베이스이다.
ORM 은 Object Relational Mapping (객체-관계 매핑), 쉽게 말해서 객체와 관계형 데이터베이스의 데이터를 연결해주는 것이다.

Drift로 SQLite DB 생성부터 관리
- 테이블 정의하기
- DB 생성하기
- DB 관리파일 생성(g.dart)
- CRUD 코드 추가하기

 

먼저 pubspec.yaml 파일에 추가할 내용은 https://drift.simonbinder.eu/docs/getting-started/ 에 나와 있다.

 

name: drift_ex
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 
version: 1.0.0+1
 
environment:
  sdk: '>=3.1.5 <4.0.0'
 
dependencies:
  flutter:
    sdk: flutter
 
  cupertino_icons: ^1.0.2
  drift: ^2.13.0
  sqlite3_flutter_libs: ^0.5.0
  path_provider: ^2.0.0
  path: ^1.8.3
 
dev_dependencies:
  flutter_test:
    sdk: flutter
 
  flutter_lints: ^2.0.0
  drift_dev: ^2.13.0
  build_runner: ^2.4.6
 
flutter:
 
  uses-material-design: true
 
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #

 

 

테이블 정의하기

import 'package:drift/drift.dart';
 
/***
 * CONTENT, DATE, STARTTIME, ENDTIME, COLORID, CREATEDAT
 */
class Schedules extends Table {
  IntColumn get id => integer().autoIncrement()(); // PRIMARY KEY
 
  TextColumn get content => text()(); // 내용
 
  DateTimeColumn get date => dateTime()(); // 일정날짜
 
  IntColumn get startTime => integer()(); // 시작 시간
  IntColumn get endTime => integer()();  // 종료 시간
 
  IntColumn get colorId => integer()(); // Category Color Table ID
 
  DateTimeColumn get createAt => dateTime().clientDefault(() => DateTime.now(),)();
}
 

 

import 'package:drift/drift.dart';
 
class CategoryColors extends Table {
  // PRIMARY KEY
  IntColumn get id => integer().autoIncrement()();
 
  // 색상 코드
  TextColumn get hexCode => text()();
}

 

 

DB 생성하기

schemaVersion은 일반적으로 1부터 시작하고 Table의 변화가 있을때 1씩 올려준다.

import 'dart:io';
 
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
 
import '../model/category_color.dart';
import '../model/schedule.dart';
 
part 'schedule_database.g.dart';
 
@DriftDatabase(
  tables: [
    Schedules,
    CategoryColors,
  ],
)
 
class LocalDatabase extends _$LocalDatabase {
  LocalDatabase() : super(_openConnection());
 
  @override
  int get schemaVersion => 1;
}
 
LazyDatabase _openConnection() {
  // LazyDatabase에서는 db파일이 저장될 폴더 위치를 지정해준다.
  return LazyDatabase(() async {
    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'db.sqlite'));
    return NativeDatabase(file);
  });
}
 

 

 

g.dart 코드 자동 생성하기

아래와 같이 터미널에서 flutter pub run build_runner build  명령어를 입력한다.

 

 

 

CRUD 코드 추가하기

class LocalDatabase extends _$LocalDatabase {
  LocalDatabase() : super(_openConnection());
 
  Future<int> createSchedule(SchedulesCompanion data) =>
      into(schedules).insert(data);
 
  Future<int> createCategoryColor(CategoryColorsCompanion data) =>
      into(categoryColors).insert(data);
 
  Future<List<CategoryColor>> getCategoryColors() =>
      select(categoryColors).get();
 
  // getSingle : 하나의 데이터만 가져와라.
  Future<Schedule> getScheduleById(int id) =>
      (select(schedules)..where((tbl) => tbl.id.equals(id))).getSingle();
 
  Future<int> updateScheduleById(int id, SchedulesCompanion data) =>
      (update(schedules)..where((tbl) => tbl.id.equals(id))).write(data);
 
  // 삭제한 ID의 int 값을 리턴 받는다.
  Future<int> removeSchedule(int id) =>
      (delete(schedules)..where((tbl) => tbl.id.equals(id))).go();
 
  Stream<List<ScheduleWithColor>> watchSchedules(DateTime date) {
    final query = select(schedules).join([
      innerJoin(categoryColors, categoryColors.id.equalsExp(schedules.colorId))
    ]);
 
    query.where(schedules.date.equals(date));
    query.orderBy(
      [
        // asc -> ascending 오름차순
        // desc -> descending 내림차순
        OrderingTerm.asc(schedules.startTime),
      ],
    );
 
    return query.watch().map(
          (rows) => rows
              .map(
                (row) => ScheduleWithColor(
                  schedule: row.readTable(schedules),
                  categoryColor: row.readTable(categoryColors),
                ),
              )
              .toList(),
        );
  }
 
  @override
  int get schemaVersion => 1;
}
 

 

 

블로그 이미지

Link2Me

,
728x90

https://dart.dev/language/records 에 공식적인 설명이 되어 있다.

 

Records는 익명성, 불변성, 집계성을 가진 타입이며
다른 컬렉션 유형(List, Set, Map 등등)과 마찬가지로 여러 개체를 하나의 개체로 묶을 수 있다.
하지만 다른 컬렉션 유형과 달리 Record는 크기와 유형이 고정되어 있다.

void main() {
  final result = person({
    'name''홍길동'
    'age'25
  });
  
  print(result); // (홍길동, 25)
  
  print(result.$1); // 홍길동
  
  print(result.$2); // 25
  
  
 
  var record = ('first', a: 2, b: true'last');
 
  print(record.$1); // Prints 'first'
  print(record.a); // Prints 2
  print(record.b); // Prints true
  print(record.$2); // Prints 'last'
 
 
}
 
// 튜플로 반환하여 Type과 순서를 보장받을 수 있다.
// 튜플은 소괄호를 사용한다.
(String, int) person(Map<String, dynamic> json) {
  return (json['name'as String, json['age'as int);
}
 

 

 

 
// Returns multiple values in a record:
(String, int) userInfo(Map<String, dynamic> json) {
  return (json['name'as String, json['age'as int);
}
 
final json = <String, dynamic>{
  'name''Dash',
  'age'10,
  'color''blue',
};
 
// Destructures using a record pattern:
var (name, age) = userInfo(json);
 
/* Equivalent to:
  var info = userInfo(json);
  var name = info.$1;
  var age  = info.$2;
*/

 

 

void main() {
  final result = getPersonWithType();  
 
  for(final item in result){
    print(item);
  }
  
  print('-----------------------');
  
  for(final item in result){
    print(item.$1);
    print(item.$2);
  }
 
}
 
List<(String name, int age)> getPersonWithType() {
  return [
    ('홍길동'25),
    ('강감찬'37),
    ('이순신'33)
  ];
}

 

 

void main() {
  final result = getPersonWithNameType(); 
 
  for(final item in result){
    print(item);
  }
  
  print('-----------------------');
  
  for(final item in result){
    print(item.name);
    print(item.age);
  }
 
}
 
List<({String name, int age})> getPersonWithNameType() {
  return [
    ( name: '홍길동', age: 25),
    ( name: '강감찬', age: 37),
    ( name: '이순신', age: 33)
  ];
}

 

 

'Flutter 앱 > Dart 언어' 카테고리의 다른 글

getter & setter  (0) 2023.12.09
Dart 직렬화(Serialization) 및 역직렬화(Deserialization)  (0) 2023.12.08
Dart Collection  (0) 2022.06.28
Dart Class(클래스)  (0) 2022.06.27
Dart Asynchronous programming(비동기 프로그래밍)  (0) 2022.06.23
블로그 이미지

Link2Me

,
728x90

자바스크립트 구조분해할당과 keys를 얻는 방법에 대해 간략하게 적어둔다.

 

 
const user = {
    name:'홍길동',
    age: 25,
    email:'link2me@gmail.com'
}
 
// 구조 분해 할당 : 필요한 변수만 지정할 수 있다.
const { name, age, email } = user;
console.log(`사용자의 이름은 ${name}입니다.`);
console.log(`${name}의 나이는 ${age}세 입니다.`);
console.log(`${name}의 이메일은 ${email}세 입니다.`);
 
/***
 * Object.keys(obj) – 객체의 키만 담은 배열을 반환.
 * Object.values(obj) – 객체의 값만 담은 배열을 반환
 * Object.entries(obj) – [키, 값] 쌍을 담은 배열을 반환
 */
const keys = Object.keys(user);
console.log(keys); // [ 'name', 'age', 'email' ]
 
console.log(user['email']);
 
const values = keys.map(key => user[key])
console.log(values); // [ '홍길동', 25, 'link2me@gmail.com' ]
 
for(let key in user){
    console.log(`${key} ${user[key]}`);
}
 

 

 

 

블로그 이미지

Link2Me

,
728x90

JavaScript의 Object.assign()메서드 사용법을 알아보자.

불변성(immutable)을 지켜야 된다면 Object.assign을 사용하는 것 보다 스프레드 연산자를 사용하는 것이 좋다.
Object.assign으로 불변성을 지키려면 target에 항상 {} 빈 객체를 인자로 전달해야 한다.

const userAge = {
    name:'홍길동',
    age:25
}
 
const userEmail = {
    name:'홍길동',
    email:'link2me@gmail.com'
}
 
/***
 * Object.assign(target, ...sources);
 * target : 출처 객체의 속성을 복사해 반영한 후 반환할 객체
 * source : 목표 객체에 반영하고자 하는 속성들을 갖고 있는 객체들
 * 목표 객체의 속성 중 출처 객체와 동일한 키를 갖는 속성의 경우,
 * 그 속성 값은 출처 객체의 속성 값으로 덮어쓴다.
 */
const target = Object.assign({}, userAge, userEmail);
console.log(target);
// { name: '홍길동', age: 25, email: 'link2me@gmail.com' }
console.log(userAge);
// { name: '홍길동', age: 25 }
console.log(target === userAge); // false
 
const a = { k: 123 }
const b = { k: 123 }
console.log(a === b); // false
 

 

 

 
const userAge = {
    name:'홍길동',
    age:25
}
 
const userEmail = {
    name:'홍길동',
    email:'link2me@gmail.com'
}
 
/***
 * Object.assign(target, ...sources);
 * target : 출처 객체의 속성을 복사해 반영한 후 반환할 객체
 * source : 목표 객체에 반영하고자 하는 속성들을 갖고 있는 객체들
 * 목표 객체의 속성 중 출처 객체와 동일한 키를 갖는 속성의 경우,
 * 그 속성 값은 출처 객체의 속성 값으로 덮어쓴다.
 */
const target = Object.assign(userAge, userEmail);
console.log(target);
// { name: '홍길동', age: 25, email: 'link2me@gmail.com' }
console.log(userAge);
// { name: '홍길동', age: 25, email: 'link2me@gmail.com' }
console.log(target === userAge); // true
 
const a = { k: 123 }
const b = { k: 123 }
console.log(a === b); // false (서로 다른 객체)
 

 

블로그 이미지

Link2Me

,
728x90

한 마디로 클로저란, 함수가 선언될(생성될) 그 당시에 주변의 환경과 함께 갇히는 것을 말한다.
함수가 속한 렉시컬 스코프(Lexical Environment)를 기억하여, 함수가 렉시컬 스코프 밖에서 실행될 때도 이 스코프에 접근할 수 있게 해주는 기능이다.
렉시컬 스코프란 함수가 선언이 되는 위치에 따라서 상위 스코프가 결정되는 스코프다.
내부함수는 외부함수의 지역변수에 접근할 수 있는데 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근할 수 있는 것

 

/***
 * 자바스크립트에는 클로저라는 기능이 있다.
 * 클로저는 함수와 그 함수를 둘러싸고 있는 주변의 상태를 기억하는 기능이다.
 * 클로저는 자바스크립트 고유의 개념이 아니라
 * 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다.
 * 클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다.
 */
 
function makeAdd2(v1) {
  // 함수 makeAdd 내에서 내부함수가 선언되고 호출되었다.
  return function (v2) {
    //  이때 내부함수는 자신을 포함하고 있는 외부함수 makeAdd 변수 v1 접근할 수 있다.
    return v1 + v2;
  };
}
 
function makeAdd(v1){
  // 함수 makeAdd 내에서 내부함수 innerFunc가 선언되고 호출되었다.
  let innerFunc = function (v2){
    //  이때 내부함수 innerFunc는 자신을 포함하고 있는 외부함수 makeAdd 변수 v1 접근할 수 있다.
    return v1 + v2;
  }
  return innerFunc;
}
 
const add3 = makeAdd(3);
console.log(add3); // [Function innerFunc]
console.log(add3(10));
const add7 = makeAdd(7);
console.log(add7(10));
 

 

 

 
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>closure</title>
    <script defer src="./main.js"></script>
</head>
<body>
<p>클로저를 사용한 Counting</p>
<button id="inclease">+</button>
<p id="count">0</p>
<script>
    const incleaseBtn = document.getElementById('inclease');
    const count = document.getElementById('count');
 
    const increase = (function () {
        // 카운트 상태를 유지하기 위한 변수
        let counter = 0;
        // 클로저를 반환
        return function () {
            return ++counter;
        };
    }());
 
    incleaseBtn.onclick = function () {
        count.innerHTML = increase();
    };
</script>
</body>
</html>

 

 

블로그 이미지

Link2Me

,