Análisis de principios del marco de gestión de estado de Get in Flutter y proveedor

¿Por qué es necesaria la gestión estatal?

En primer lugar, ¿por qué se necesita la gestión de estado? Esto se debe a que Flutter crea una interfaz de usuario basada en un  estilo declarativo  . Uno de los propósitos de usar la gestión de estado es resolver los problemas causados ​​por el desarrollo "declarativo".

El desarrollo "declarativo" es una forma diferente al desarrollo nativo, por lo que nunca hemos oído hablar de la gestión estatal en el desarrollo nativo ¿Cómo entendemos el desarrollo "declarativo"?

Análisis "declarativo" VS "imperativo"

Analiza con el contraejemplo más clásico

// 一、定义展示的内容
private int mCount =0;
 
// 二、中间展示数字的控件 TextView
private TextView mTvCount;
 
// 三、关联 TextView 与 xml 中的组件
mTvCount = findViewById(R.id.tv_count)
 
// 四、点击按钮控制组件更新
private void increase( ){ 
	mCount++;
	mTvCounter.setText(mCount.toString()); 
}

En Flutter, solo necesitamos llamar después de que se incremente la variable  setState((){}) . setState Se actualizará toda la página, lo que hará que cambie el valor que se muestra en el medio.

// 一、声明变量
int _counter =0; 

// 二、展示变量 
Text('$_counter')

//  三、变量增加,更新界面
setState(() {
   _counter++; 
});

Se puede encontrar que solo  _counter las propiedades se modifican en Flutter, y no se realizan operaciones en el componente de texto.Toda la interfaz cambia a medida que cambia el estado.

Así que hay un dicho en Flutter:  UI = f(state) :

 

En el ejemplo anterior, el estado es  _counter el valor de y   se llama setState al método de compilación del controlador  para generar una nueva interfaz de usuario.f

Entonces, ¿cuáles son las ventajas del declarativo y qué problemas trae?

Ventajas: permita que los desarrolladores se deshagan del engorroso control de los componentes y se concentren en el procesamiento del estado

Después de acostumbrarse al desarrollo de Flutter, regrese al desarrollo de plataforma nativa, encontrará que cuando varios componentes están relacionados entre sí, es muy problemático controlar View.

En Flutter, solo necesitamos lidiar con el estado (la complejidad se transfiere al mapeo de estado -> UI, es decir, la construcción de Widget). Los últimos desarrollos, incluidos Jetpack Compose, Swift y otras tecnologías, también están evolucionando en la dirección de lo "declarativo".

Problemas con el desarrollo declarativo

Cuando se desarrolla directamente sin usar la administración estatal, se encuentran tres problemas:

  1. La lógica está acoplada con la interfaz de usuario de la página, lo que da como resultado la imposibilidad de reutilizar/prueba unitaria, confusión de modificaciones, etc.
  2. Dificultad para acceder a los datos a través de los componentes (páginas cruzadas)
  3. No se puede controlar fácilmente el rango de actualización (el cambio de la página setState provocará el cambio de la página global)

A continuación, lo llevaré a comprender estos problemas uno por uno. El próximo capítulo describirá en detalle cómo el marco de gestión estatal resuelve estos problemas.

1) La lógica está acoplada con la interfaz de usuario de la página, lo que da como resultado la imposibilidad de reutilizar/prueba unitaria, confusión de modificaciones, etc.

Cuando el negocio no era complicado al principio, todos los códigos se escribían directamente en el widget. A medida que el negocio iteraba, los archivos se hacían cada vez más grandes, lo que dificultaba que otros desarrolladores entendieran intuitivamente la lógica comercial interna. Y alguna lógica general, como el procesamiento del estado de la solicitud de red, paginación, etc., se pega de un lado a otro en diferentes páginas.

Este problema también existe en el original, y más tarde se derivaron ideas como el patrón de diseño MVP para resolverlo.

2) Dificultad para acceder a los datos entre componentes (páginas cruzadas)

El segundo punto es la interacción entre componentes. Por ejemplo, en la estructura del Widget, si un componente secundario quiere mostrar los campos en el componente principal  name , es posible que deba pasarse capa por capa.

O es necesario compartir datos de cribado entre dos páginas, y no existe un mecanismo muy elegante para resolver este acceso de datos entre páginas.

3) Es imposible controlar fácilmente el rango de actualización (el cambio de página setState conducirá al cambio de la página global)

El último problema también es la ventaja mencionada anteriormente, en muchos escenarios, solo modificamos parte del estado, como el color del botón. Pero toda la página  setState hará que se reconstruyan otros lugares que no necesitan cambiarse, lo que generará una sobrecarga innecesaria.

Proveedor, obtener análisis de diseño del marco de gestión de estado

El núcleo del marco de gestión de estado en Flutter radica en las soluciones a estos tres problemas. Echemos un vistazo a cómo Provider y Get los resuelven:

Resuelva el problema de acoplamiento de la lógica y la interfaz de usuario de la página.

Este problema también existe en el desarrollo nativo tradicional. Los archivos de actividad también pueden volverse difíciles de mantener con las iteraciones. Este problema se puede desacoplar a través del modo MVP.

En pocas palabras, es extraer el código lógico en la Vista a la capa Presentador, y la Vista solo es responsable de la construcción de la vista.

 

Esta es también la solución para casi todos los marcos de gestión de estado en Flutter.Puede pensar que el Presentador en la imagen de arriba está en Get  GetController, Provider  ChangeNotifier o Bloc  Bloc. Vale la pena mencionar que el enfoque específico es diferente entre Flutter y el marco MVP nativo.

Sabemos que en el modo MVP clásico, generalmente View y Presenter definen su propio comportamiento (acción) con interfaces y  mantienen las interfaces para llamarse entre sí  .

 

Sin embargo, esto no es adecuado para Flutter. Desde la perspectiva de la relación Presentador → Vista, Vista corresponde a Widget en Flutter, pero en Flutter, Widget es solo la configuración de la IU declarada por el usuario, y no es una buena práctica. para controlar directamente la instancia del Widget.

En la relación de Vista → Presentador, Widget puede sostener directamente al Presentador, pero esto traerá el problema de la comunicación de datos difícil .

Diferentes marcos de gestión de estado tienen diferentes soluciones a este punto.Desde la perspectiva de la implementación, se pueden dividir en dos categorías:

  • Resuelto por el mecanismo de árbol de Flutter, como Provider;
  • A través de inyección de dependencia, como Get.

1) Procesar la adquisición de V → P a través del mecanismo de árbol Flutter

 

abstract class Element implements BuildContext { 
	/// 当前 Element 的父节点
	Element? _parent; 
}

abstract class BuildContext {
	/// 查找父节点中的T类型的State
	T findAncestorState0fType<T extends State>( );

	/// 遍历子元素的element对象
	void visitChildElements(ElementVisitor visitor);

	/// 查找父节点中的T类型的 InheritedWidget 例如 MediaQuery 等
	T dependOnInheritedWidget0fExactType<T extends InheritedWidget>({ 
		Object aspect });
	……
} 

 

Element implementa el método de manipulación de la estructura de árbol en la clase principal BuildContext

Sabemos que hay tres árboles en Flutter, Widget, Element y RenderObject. El llamado  árbol de widgets es en realidad solo una forma de describir la relación de anidamiento de los componentes, y es una estructura virtual . Sin embargo, Element y RenderObject realmente existen en tiempo de ejecución.Puede ver que el componente Element contiene  _parent propiedades para almacenar su nodo principal. E implementa  BuildContext la interfaz, incluidos muchos métodos para operaciones de estructura de árbol, por ejemplo  findAncestorStateOfType, buscar el nodo principal o  visitChildElements atravesar los nodos secundarios.

En el ejemplo inicial, podemos   encontrar los objetos Element requeridos capa por capa, obtener el Widget o el Estado y luego recuperar las variables requeridas.context.findAncestorStateOfType

 

El proveedor también utiliza este mecanismo para completar la adquisición de View -> Presenter. Obteniendo  Provider.of el objeto Presente en el componente Proveedor de nivel superior. Obviamente, todos los nodos de Widget debajo del Proveedor pueden acceder al Presentador en el Proveedor a través de su propio contexto, lo que resuelve el problema de la comunicación entre componentes.

2) Resolver V → P mediante inyección de dependencia

El mecanismo del árbol es bueno, pero depende del contexto, lo que a veces puede ser enloquecedor. Sabemos que Dart es un modelo de subproceso único, por lo que no existe una condición de carrera para el acceso a objetos en subprocesos múltiples. En función de este Get, use un mapa de singleton global para almacenar objetos. A través de la inyección de dependencia, se realiza la adquisición de la capa Presenter. De esta forma, el Presentador se puede obtener en cualquier clase.

 

La clave correspondiente a este Map es  runtimeType +  tag, donde tag es un parámetro opcional, y value corresponde  Object, es decir, podemos almacenar cualquier tipo de objeto y obtenerlo en cualquier posición.

Resuelva el problema de que es difícil acceder a los datos entre componentes (páginas cruzadas)

Este problema es básicamente similar al pensamiento de la parte anterior, por lo que podemos resumir las características de las dos soluciones:

Proveedor 

  • El mecanismo del árbol de dependencia debe basarse en el contexto.
  • Proporciona la capacidad para que los subcomponentes accedan a la capa superior

Conseguir

  • Singleton global, accesible desde cualquier lugar
  • Hay duplicación de tipos y problemas de recuperación de memoria.

 

Resuelva el problema de la actualización innecesaria causada por setState de alto nivel

Finalmente,  setState el problema de refresco innecesario causado por el alto nivel que mencionamos, Flutter lo soluciona adoptando el modo observador, la clave está en dos pasos:

  1. El observador se suscribe al objeto observado;
  2. El objeto observado notifica al observador.

 

El sistema también proporciona  ValueNotifier la implementación de otros componentes: 

/// 声明可能变化的数据
ValueNotifier<int> _statusNotifier = ValueNotifier(0); 

ValueListenableBuilder<int>(
	// 建立与 _statusNotifier 的绑定关系 
	valueListenable: _statusNotifier, 
	builder: (c, data, _) {
		return Text('$data'); 
})

///数据变化驱动 ValueListenableBuilder 局部刷新 
_statusNotifier.value += 1;

Después de comprender el patrón de observador más básico, observe los componentes provistos en diferentes marcos:

Por ejemplo, el proveedor proporciona  ChangeNotifierProvider:

 

class Counter extend ChangeNotifier { 
	int count = 0;

	/// 调用此方法更新所有观察节点
	void increment() {
		count++;
		notifyListeners(); 
	}
}

void main() { 
	runApp(
		ChangeNotifierProvider(
			///  返回一个实现 ChangeNotifier 接口的对象 
			create: (_) => Counter(),
			child: const MyApp( ), 
		),
	);
 }

///  子节点通过 Consumer 获取 Counter 对象 
Consumer<Counter>(
	builder:(_, counter, _) => Text(counter.count.toString()) 

Aún siendo el ejemplo de contador anterior, aquí  Counter se hereda  ChangeNotifier a través del proveedor de nivel superior para el almacenamiento. El nodo secundario puede obtener la instancia a través de Consumer, después de llamar  increment al método, solo cambiará el componente Text correspondiente.

La misma función, en Get, solo necesita llamar  Get.put al objeto de almacenamiento del método  Counter por adelantado y  GetBuilder especificarlo como un tipo genérico para el componente  Counter . Dado que Get se basa en un singleton,  GetBuilder el objeto almacenado se puede obtener directamente a través de genéricos y exponerse en el método de creación. De esta  Counter forma, se establece una relación de seguimiento con el componente, y  Counter los cambios posteriores solo impulsarán  GetBuilder la actualización del componente que lo utiliza como genérico.

 

class Counter extends GetxController { 
	int count = 0;

	void increase() { 
		count++;
		update(); 
	}
}

/// 提前进行存储
final counter = Get.put(Counter( )); 

/// 直接通过泛型获取存储好的实例
GetBuilder<Counter>(
	builder: (Counter counter) => Text('${counter.count}') ); 

Preguntas frecuentes en la práctica

En el proceso de uso de estos marcos, puede encontrar los siguientes problemas:

El nivel de contexto en Provider es demasiado alto

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

  @override
  Widget build(BuildContext context) {
    return Provider(
      create: (_) => const Count(),
      child: MaterialApp(
        home: Scaffold(
          body: Center(child: Text('${Provider.of<Counter>(context).count}')),
        ),
      ),
    );
  }
}

 

 

Como se muestra en el código, cuando anidamos directamente el proveedor y el componente en el mismo nivel, el  Provider.of(context) tiempo de ejecución en el código lo arroja  ProviderNotFoundException. Debido a que el contexto que usamos aquí proviene de MyApp, pero el nodo del elemento Provider se encuentra debajo de MyApp, por lo que  Provider.of(context) no se puede obtener el nodo Provider. Hay dos formas de solucionar este problema, como se muestra en el siguiente código:

Mejora 1: al anidar el componente Builder, use el contexto del nodo secundario para acceder a:

 

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

  @override
  Widget build(BuildContext context) {
    return Provider(
      create: (_) => const Count(),
      child: MaterialApp(
        home: Scaffold(
          body: Center(
            child: Builder(builder: (builderContext) {
              return Text('${Provider.of<Counter>(builderContext).count}');
            }),
          ),
        ),
      ),
    );
  }
}

Mejora 2: llevar al proveedor al nivel superior:

void main() {
  runApp(
    Provider(
      create: (_) => Counter(),
      child: const MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(child: Text('${Provider.of<Counter>(context).count}')),
      ),
    );
  }
}

Get tiene problemas con los singletons globales

Como se mencionó anteriormente, Get utiliza un singleton global, que almacena objetos como claves runtimeType de forma algunos escenarios, los objetos obtenidos pueden no cumplir con las expectativas, como saltar entre las páginas de detalles del producto. Dado que diferentes instancias de la página de detalles corresponden a la misma Clase, son  runtimeType lo mismo. Si no agrega el parámetro de etiqueta, llamar a una página determinada  Get.find obtendrá los objetos que se han almacenado en otras páginas. Al mismo tiempo, debe prestar atención al reciclaje de objetos en Get, de lo contrario, es probable que se produzcan pérdidas de memoria. Hágalo  dispose manualmente en el momento de la página  delete o use completamente el componente provisto en Obtener, por ejemplo  GetBuilder, se lanzará  dispose en .

 

 GetBuilder El reciclaje se realiza en  dispose fases:

@override
void dispose() {
  super.dispose();
  widget.dispose?.call(this);
  if (_isCreator! || widget.assignId) {
    if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
      GetInstance().delete<T>(tag: widget.tag);
    }
  }

  _remove?.call();

  controller = null;
  _isCreator = null;
  _remove = null;
  _filter = null;
}

Resumen de ventajas y desventajas de Get y Provider

A través de este artículo, les presenté la necesidad de la administración de estado, qué problemas resuelve en el desarrollo de Flutter y cómo resolverlos. Al mismo tiempo, también resumí problemas comunes en la práctica, etc., vea aquí. Es posible que todavía tenga algunos dudas, ¿necesitas usar la gestión estatal?

En mi opinión, los marcos existen para resolver problemas. Entonces depende de si también estás pasando por esos temas que se plantearon al principio. Si lo hay, puede intentar usar la gestión de estado para resolverlo; si no, no hay necesidad de diseñarlo en exceso, utilícelo para su uso.

En segundo lugar, si se utiliza la gestión estatal, ¿cuál es mejor, Get o Provider?

Estos dos marcos tienen sus propias ventajas y desventajas. Creo que si usted o su equipo son nuevos en Flutter, usar Provider puede ayudarlo a comprender el mecanismo central de Flutter más rápido. Y si ya comprende los principios de Flutter, las funciones enriquecidas y la API concisa de Get pueden ayudarlo a mejorar la eficiencia de su desarrollo.

 

Supongo que te gusta

Origin blog.csdn.net/RreamigOfGirls/article/details/130323536
Recomendado
Clasificación