context.read, context.watch, context.select

샤워실의 바보·2024년 2월 21일
0

Provider Flutter

목록 보기
2/3
post-thumbnail

이 코드는 Flutter의 provider 패키지를 사용하여 상태 관리를 수행하는 방법을 보여줍니다. 특히, context 매개변수를 활용하여 상태 객체에 접근하는 여러 방법을 사용합니다. provider 패키지는 데이터를 효율적으로 관리하고 앱 전체에 걸쳐 쉽게 접근할 수 있도록 돕습니다. 코드 내 각 부분에 대한 설명과 주석을 추가해 보겠습니다.

기존 방식

기존에는 Provider.of<T>(context)를 사용하여 context에서 원하는 데이터 타입의 객체를 검색했습니다. listen: false 매개변수를 추가하면 위젯 트리가 해당 데이터의 변화에 따라 재빌드되지 않도록 설정할 수 있었습니다.

final myModel = Provider.of<MyModel>(context, listen: false);

개선된 방식

provider 4.1 이후부터는 BuildContext의 extension methods를 사용하여 더 간단하게 데이터에 접근할 수 있습니다. 예를 들어, context.read<T>()Provider.of<T>(context, listen: false)와 동일한 역할을 하지만, 더 간결하게 표현됩니다. read<T>()는 데이터를 읽을 때 사용되며, 데이터의 변경을 감지하여 위젯을 재빌드하지 않습니다.

read<T>() 사용 예시

final myModel = context.read<MyModel>();

이 방식은 특히 이벤트 핸들러나 생명주기 메서드 내에서 상태를 변경할 때 유용하며, UI를 재빌드할 필요가 없을 때 사용됩니다.

watch<T>() 사용 예시

데이터의 변화를 감지하고 해당 변화에 반응하여 위젯을 재빌드해야 할 때는 watch<T>()를 사용할 수 있습니다. 이는 기존의 Provider.of<T>(context) 호출과 유사하지만, listen: true를 기본값으로 가지며 더 간결한 문법을 제공합니다.

final myModel = context.watch<MyModel>();

provider 4.1 이상에서 도입된 BuildContext의 extension methods는 코드를 더 간결하고 읽기 쉽게 만들어 줍니다. read<T>()watch<T>() 메서드는 각각 상태를 읽거나 상태 변화에 따른 리액션을 구현할 때 사용되며, 기존 방식에 비해 더 효율적이고 직관적인 코드 작성을 가능하게 합니다. 이러한 변화는 Flutter 개발자들이 더 깔끔하고 세련된 방식으로 상태 관리를 할 수 있도록 돕습니다.에서는 Dog 클래스의 인스턴스를 앱 전체에서 접근 가능하게 만듭니다.

context.watch<T>()

  • context.watch<T>()는 주어진 타입 T의 객체가 변경될 때마다 위젯을 다시 빌드하도록 합니다. 이 방법은 데이터의 변화를 UI에 실시간으로 반영하고자 할 때 유용합니다.

Provider.of<T>(context)와 동일하다.

context.select<T, R>(R Function(T) selector)

  • context.select<T, R>() 메서드는 특정 타입 T의 객체에서 R 타입의 값을 선택하여 사용합니다. 이 메서드는 객체의 일부분만 필요할 때 유용하며, 선택된 부분이 변경될 때만 위젯을 다시 빌드합니다. 이는 성능 최적화에 도움이 됩니다.

context.read<T>()

  • context.read<T>() 메서드는 타입 T의 객체를 읽어오되, 위젯의 재빌드를 트리거하지 않습니다. 주로 이벤트 핸들러 내에서 상태를 변경할 때 사용됩니다.

Provider.of<T>(context, listen: false)와 동일하다.

아래는 코드에 추가한 주석입니다:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'models/dog.dart'; // Dog 모델을 포함하는 외부 파일을 임포트합니다.

void main() {
  runApp(const MyApp()); // 애플리케이션의 시작점입니다.
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Dog>(
      create: (context) => Dog(name: 'dog05', breed: 'breed05', age: 3), // Dog 객체를 생성하고 제공합니다.
      child: MaterialApp(
        title: 'Provider 05',
        debugShowCheckedModeBanner: false, // 디버그 배너를 비활성화합니다.
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(), // 앱의 홈 화면을 설정합니다.
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    // watch를 사용하여 Dog 객체의 name 속성에 변경이 있을 때마다 위젯을 다시 빌드합니다.
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider 05'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              '- name: ${context.watch<Dog>().name}',
              style: TextStyle(fontSize: 20.0),
            ),
            SizedBox(height: 10.0),
            BreedAndAge(),
          ],
        ),
      ),
    );
  }
}

class BreedAndAge extends StatelessWidget {
  const BreedAndAge({
    Key? key,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    // select를 사용하여 Dog 객체의 breed 속성만을 대상으로 변경이 있을 때만 위젯을 다시 빌드합니다.
    return Column(
      children: [
       

 Text(
          '- breed: ${context.select<Dog, String>((Dog dog) => dog.breed)}',
          style: TextStyle(fontSize: 20.0),
        ),
        SizedBox(height: 10.0),
        Age(),
      ],
    );
  }
}

class Age extends StatelessWidget {
  const Age({
    Key? key,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    // select를 사용하여 Dog 객체의 age 속성만을 대상으로 변경이 있을 때만 위젯을 다시 빌드합니다.
    return Column(
      children: [
        Text(
          '- age: ${context.select<Dog, int>((Dog dog) => dog.age)}',
          style: TextStyle(fontSize: 20.0),
        ),
        SizedBox(height: 20.0),
        ElevatedButton(
          // read를 사용하여 Dog 객체에 접근하고, 이벤트 핸들러 내에서 grow 메서드를 호출합니다. 이 경우 위젯은 재빌드되지 않습니다.
          onPressed: () => context.read<Dog>().grow(),
          child: Text(
            'Grow',
            style: TextStyle(fontSize: 20.0),
          ),
        ),
      ],
    );
  }
}

이 코드는 provider 패키지의 watch, select, 그리고 read 메서드를 사용하여 Flutter 앱에서 상태 관리를 어떻게 수행하는지 보여줍니다. watchselect는 위젯을 조건부로 다시 빌드하는 데 사용되며, read는 상태 변경 시 위젯의 재빌드 없이 상태를 읽거나 변경하는 데 사용됩니다. 이러한 방법은 효율적인 상태 관리와 성능 최적화를 위해 중요합니다.

context.select<T, R>(R Function(T) selector)

context.select<T, R>(R Function(T) selector) 메서드는 Flutter 앱에서 특정 Provider가 제공하는 데이터의 일부분만 관심이 있을 때 사용합니다. 즉, 전체 데이터 모델 중에서 특정 값이나 상태의 변화만을 감지하고 싶을 때 유용합니다. 이 방식은 필요한 데이터 부분만 구독함으로써 리소스 사용을 최적화하고, 앱의 성능을 향상시킬 수 있습니다.

Dog 모델을 고려해 보겠습니다. 이 모델에는 name, breed, age 등의 필드가 있을 수 있습니다. 만약 우리가 Dogage 필드 값의 변화만을 감지하고 싶다면, context.select() 메서드를 사용할 수 있습니다.

class Dog with ChangeNotifier {
  String name;
  String breed;
  int age;

  Dog({this.name = '', this.breed = '', this.age = 0});

  void grow() {
    age += 1;
    notifyListeners(); // 상태 변경을 알림
  }

  // 여기에 다른 메서드들이 있을 수 있습니다.
}

우리가 Dog 객체의 age 필드만 관심이 있고, 이 필드가 변할 때만 UI를 업데이트하고 싶다고 가정해 봅시다. 아래 예시 코드는 context.select()를 사용하여 이를 어떻게 할 수 있는지 보여줍니다.

class DogAgeWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    // context.select를 사용하여 Dog 객체의 age 필드 값의 변화만 감지합니다.
    final int dogAge = context.select<Dog, int>((Dog dog) => dog.age);

    return Text('Dog age: $dogAge');
  }
}

위의 DogAgeWidgetDog 객체의 age 필드 값이 변경될 때만 다시 빌드됩니다. 만약 Dog 객체의 다른 필드(name이나 breed)가 변경되어도, DogAgeWidget은 영향을 받지 않고 재빌드되지 않습니다. 이 방식은 특정 데이터 변화에만 반응하는 UI 부분을 효과적으로 만들 때 매우 유용합니다.

결론

context.select<T, R>(R Function(T) selector)는 특정 데이터의 변화만을 감지하고 싶을 때 사용하는 메서드입니다. 이는 앱의 성능을 최적화하는 데 도움을 줍니다. 전체 모델의 변화에 반응하여 모든 위젯을 다시 빌드하는 것이 아니라, 정말 필요한 부분의 변화에만 반응할 수 있게 해주기 때문입니다.

context.watch<Dog>().namecontext.select<Dog, String>((Dog dog) => dog.name)는 비슷해 보일 수 있지만, 그들이 작동하는 방식과 성능 최적화 측면에서 중요한 차이가 있습니다. 둘 다 Dog 객체의 name 속성의 변경을 감시하여 해당 변경이 있을 때 위젯을 다시 빌드하도록 합니다. 그러나, 내부적으로 Flutter 프레임워크에 의해 처리되는 방식이 다릅니다.

context.watch<T>()

context.watch<Dog>().name을 사용할 때, watch<T>() 메서드는 Dog 타입의 전체 객체에 대한 변경을 감시합니다. 즉, Dog 객체 내의 어떤 속성이든 변경되면 관련 위젯이 다시 빌드됩니다. 여기서 .name은 단지 Dog 객체에서 특정 필드를 접근하는 것이며, watch의 범위를 한정하지 않습니다.

  • 사용 사례: 위젯이 Dog 객체의 여러 속성 중 하나 또는 여러 개에 의존하고 있고, 이 중 어떤 것이든 변경될 때마다 위젯을 업데이트해야 하는 경우에 적합합니다.

context.select<T, R>(R Function(T) selector)

반면, context.select<Dog, String>((Dog dog) => dog.name)Dog 객체 내의 name 속성의 변경만을 감시합니다. select<T, R>(R Function(T) selector) 메서드는 특정 필드나 값의 변화에만 반응하여 위젯을 다시 빌드하도록 합니다. 이는 다른 속성의 변경이 있어도, name 속성이 변경되지 않는 한 위젯이 재빌드되지 않음을 의미합니다.

  • 사용 사례: 위젯이 Dog 객체 내의 name 속성의 변경에만 반응하면 충분한 경우에 이상적입니다. 이 방식은 필요하지 않은 위젯의 재빌드를 줄여 성능을 향상시킵니다.

성능 최적화와 사용 사례

context.select<T, R>(...)는 성능 최적화 측면에서 context.watch<T>()보다 더 세밀한 제어를 가능하게 합니다. select를 사용하면 관심 있는 특정 데이터의 변경에만 반응하여 불필요한 위젯의 재빌드를 방지할 수 있습니다. 이는 특히 대규모 애플리케이션 또는 상태가 자주 변경되는 애플리케이션에서 성능을 크게 향상시킬 수 있습니다.

결론적으로, context.watch<T>()context.select<T, R>(R Function(T) selector)는 유사한 결과를 제공할 수 있지만, select는 특정 필드의 변경에만 반응하는 더 세밀한 방식을 제공하여 성능 최적화에 유리합니다. 따라서 개발자는 각 위젯의 필요와 애플리케이션의 성능 요구 사항에 따라 적절한 메서드를 선택하여 사용해야 합니다.

profile
공부하는 개발자

0개의 댓글