Análise de Princípio do Provedor e Get in Flutter State Management Framework

Por que o gerenciamento de estado é necessário?

Em primeiro lugar, por que o gerenciamento de estado é necessário? Isso ocorre porque o Flutter constrói a interface do usuário com base no  estilo declarativo  . Um dos propósitos de usar o gerenciamento de estado é resolver os problemas causados ​​pelo desenvolvimento "declarativo".

O desenvolvimento "declarativo" é uma forma diferente do desenvolvimento nativo, por isso nunca ouvimos falar de gerenciamento de estado no desenvolvimento nativo. Como entendemos o desenvolvimento "declarativo"?

Análise "Declarativa" VS "Imperativa"

Analise com o contra-exemplo mais clássico

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

No Flutter, só precisamos chamar depois que a variável for incrementada  setState((){}) . setState A página inteira será atualizada, fazendo com que o valor exibido no meio seja alterado.

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

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

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

Pode-se constatar que apenas  _counter as propriedades são modificadas no Flutter, e nenhuma operação é realizada no componente Text.Toda a interface muda conforme o estado muda.

Portanto, existe um ditado no Flutter:  UI = f(state) :

 

No exemplo acima, o estado é  _counter o valor de , e  setState o  f método de compilação do driver é chamado para gerar uma nova interface do usuário.

Então, quais são as vantagens do declarativo e quais problemas ele traz?

Vantagens: permite que os desenvolvedores se livrem do controle complicado de componentes e se concentrem no processamento de estado

Depois de se acostumar com o desenvolvimento do Flutter, retornar ao desenvolvimento da plataforma nativa, você descobrirá que, quando vários componentes estão relacionados entre si, é muito problemático controlar o View.

No Flutter, precisamos apenas lidar com o estado (a complexidade é transferida para o mapeamento do estado -> UI, ou seja, a construção do Widget). Os desenvolvimentos mais recentes, incluindo Jetpack Compose, Swift e outras tecnologias, também estão evoluindo na direção do "declarativo".

Problemas com desenvolvimento declarativo

Ao desenvolver diretamente sem usar o gerenciamento de estado, existem três problemas encontrados:

  1. A lógica é acoplada à interface do usuário da página, resultando na incapacidade de reutilização/teste de unidade, confusão de modificações, etc.
  2. Difícil acessar dados entre componentes (páginas cruzadas)
  3. Não é possível controlar facilmente o intervalo de atualização (a alteração da página setState causará a alteração da página global)

Em seguida, levarei você a entender esses problemas um por um.O próximo capítulo descreverá em detalhes como a estrutura de gerenciamento de estado resolve esses problemas.

1) A lógica é acoplada à interface do usuário da página, resultando na incapacidade de reutilização/teste de unidade, confusão de modificações, etc.

Quando o negócio não era complicado no início, todos os códigos eram escritos diretamente no widget. À medida que o negócio iterava, os arquivos se tornavam cada vez maiores, tornando difícil para outros desenvolvedores entender intuitivamente a lógica de negócios interna. E alguma lógica geral, como o processamento do status da solicitação de rede, paginação, etc., é colada para frente e para trás em páginas diferentes.

Esse problema também existe no original, e ideias como o padrão de projeto MVP foram derivadas posteriormente para resolvê-lo.

2) Difícil acessar dados entre componentes (páginas cruzadas)

O segundo ponto é a interação entre componentes, por exemplo, na estrutura Widget, se um componente filho quiser exibir os campos do componente pai  name , pode ser necessário passar camada por camada.

Ou é necessário compartilhar dados de triagem entre duas páginas, e não há um mecanismo muito elegante para resolver esse acesso de dados entre páginas.

3) É impossível controlar facilmente o intervalo de atualização (a alteração da página setState levará à alteração da página global)

O último problema também é a vantagem mencionada acima: em muitos cenários, modificamos apenas parte do estado, como a cor do botão. Mas a página inteira  setState fará com que outros locais que não precisam ser alterados sejam reconstruídos, trazendo sobrecarga desnecessária.

Provedor, obter análise de design de estrutura de gerenciamento de estado

O núcleo da estrutura de gerenciamento de estado no Flutter está nas soluções para esses três problemas. Vamos dar uma olhada em como o Provider e o Get os resolvem:

Resolva o problema de acoplamento da lógica e da interface do usuário da página

Esse problema também existe no desenvolvimento nativo tradicional. Os arquivos de atividade também podem se tornar difíceis de manter com as iterações. Esse problema pode ser desacoplado por meio do modo MVP.

Simplificando, é extrair o código lógico da View para a camada do Presenter, sendo que a View é responsável apenas pela construção da view

 

Esta também é a solução para quase todos os frameworks de gerenciamento de estado no Flutter.Você pode pensar no Presenter na figura acima como sendo Get  GetController, Provider  ChangeNotifier ou Bloc  Bloc. Vale ressaltar que a abordagem específica é diferente entre o Flutter e o framework MVP nativo.

Sabemos que no modo MVP clássico, geralmente o View e o Presenter definem seu próprio comportamento (ação) com as interfaces e  mantêm as interfaces para chamar uma à outra  .

 

No entanto, isso não é adequado para Flutter. Do ponto de vista do relacionamento Presenter → View, View corresponde a Widget no Flutter, mas no Flutter, Widget é apenas a configuração da interface do usuário declarada pelo usuário e não é uma boa prática para controlar diretamente a instância do Widget.

Na relação View → Presenter, o Widget pode de fato segurar o Presenter diretamente, mas isso trará o problema de difícil comunicação de dados .

Diferentes estruturas de gestão do estado têm soluções diferentes para este ponto. Do ponto de vista da implementação, elas podem ser divididas em duas categorias:

  • Resolvido pelo mecanismo de árvore do Flutter, como Provider;
  • Por meio de injeção de dependência, como Get.

1) Processar a aquisição de V → P através do mecanismo Flutter tree

 

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 });
	……
} 

 

O elemento implementa o método de manipulação da estrutura da árvore na classe pai BuildContext

Sabemos que existem três árvores em Flutter, Widget, Element e RenderObject. A chamada  árvore Widget é, na verdade, apenas uma maneira de descrevermos o relacionamento de aninhamento de componentes e é uma estrutura virtual . No entanto, Element e RenderObject realmente existem em tempo de execução.Você pode ver que o componente Element contém  _parent propriedades para armazenar seu nó pai. E implementa  BuildContext a interface, incluindo muitos métodos para operações de estrutura de árvore, por exemplo  findAncestorStateOfType, procurando o nó pai;  visitChildElements percorrendo os nós filhos.

No exemplo inicial, podemos   encontrar os objetos de Elemento necessários camada por camada, obter o Widget ou Estado e, em seguida, recuperar as variáveis ​​necessárias.context.findAncestorStateOfType

 

O provedor também usa esse mecanismo para concluir a aquisição de View -> Presenter. Obtendo  Provider.of o objeto Present no componente Provider de nível superior. Obviamente, todos os nós do Widget abaixo do Provider podem acessar o Presenter no Provider através de seu próprio contexto, o que resolve o problema de comunicação entre componentes.

2) Resolva V → P por meio de injeção de dependência

O mecanismo da árvore é bom, mas depende do contexto, que às vezes pode ser enlouquecedor. Sabemos que o Dart é um modelo de encadeamento único, portanto, não há condição de corrida para acesso a objetos em multiencadeamento. Com base nesse Get, use um mapa singleton global para armazenar objetos. Por meio da injeção de dependência, é realizada a aquisição da camada Presenter. Desta forma, o Presenter pode ser obtido em qualquer aula.

 

A chave correspondente a este Map é  runtimeType +  tag, onde tag é um parâmetro opcional, e value corresponde  Object, ou seja, podemos armazenar qualquer tipo de objeto e obtê-lo em qualquer posição.

Resolva o problema de que é difícil acessar dados entre componentes (páginas cruzadas)

Este problema é basicamente semelhante ao pensamento da parte anterior, pelo que podemos resumir as características das duas soluções:

Fornecedor 

  • O mecanismo da árvore de dependência deve ser baseado no contexto
  • Fornece a capacidade de subcomponentes acessarem a camada superior

Pegar

  • Singleton global, acessível em qualquer lugar
  • Há duplicação de tipos e problemas de recuperação de memória

 

Resolva o problema de atualização desnecessária causada por setState de alto nível

Por fim,  setState o problema de atualização desnecessária causado pelo alto nível que mencionamos, o Flutter resolve adotando o modo observador, a chave está em duas etapas:

  1. O observador se inscreve no objeto observado;
  2. O objeto observado notifica o observador.

 

O sistema também prevê  ValueNotifier a implementação de outros componentes: 

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

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

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

Depois de entender o padrão de observador mais básico, observe os componentes fornecidos em diferentes estruturas:

Por exemplo, o provedor fornece  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()) 

Ainda o exemplo do contador anterior, aqui  Counter ele é herdado  ChangeNotifier por meio do provedor de nível superior para armazenamento. O nó filho pode obter a instância através do Consumer, após chamar  increment o método, apenas o componente Text correspondente será alterado.

A mesma função, em Get, só precisa chamar  Get.put o  Counter objeto de armazenamento do método com antecedência e  GetBuilder especificá-lo como um tipo genérico para o componente  Counter . Como Get é baseado em um singleton,  GetBuilder o objeto armazenado pode ser obtido diretamente por meio de genéricos e exposto no método construtor. Desta  Counter forma, estabelece-se uma relação de monitorização com o componente, sendo que  Counter as alterações posteriores irão apenas conduzir  GetBuilder à atualização do componente que o 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}') ); 

Perguntas Frequentes na Prática

No processo de uso dessas estruturas, você pode encontrar os seguintes problemas:

O nível de contexto no provedor é muito 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}')),
        ),
      ),
    );
  }
}

 

 

Conforme mostrado no código, quando aninhamos diretamente o provedor e o componente no mesmo nível, o  Provider.of(context) tempo de execução no código o lança  ProviderNotFoundException. Como o contexto que usamos aqui vem de MyApp, mas o nó do elemento Provider está localizado abaixo de MyApp, portanto, o  Provider.of(context) nó Provider não pode ser obtido. Há duas maneiras de corrigir esse problema, conforme mostrado no código a seguir:

Melhoria 1: ao aninhar o componente Builder, use o contexto do nó filho para acessar:

 

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}');
            }),
          ),
        ),
      ),
    );
  }
}

Melhoria 2: Traga o Provedor para o nível 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 tem problemas com singletons globais

Conforme mencionado acima, Get usa o singleton global e o padrão runtimeType é armazenar objetos como chaves. Em alguns cenários, os objetos obtidos podem não atender às expectativas, como pular entre as páginas de detalhes do produto. Como diferentes instâncias da página de detalhes correspondem à mesma classe, elas são  runtimeType iguais. Se você não adicionar o parâmetro tag, chamar em uma determinada página  Get.find obterá os objetos que foram armazenados em outras páginas. Ao mesmo tempo, você deve prestar atenção à reciclagem de objetos em Get, caso contrário, é provável que cause vazamentos de memória. Faça  dispose isso manualmente no tempo da página  delete ou use totalmente o componente fornecido no Get, por exemplo  GetBuilder, ele será  dispose lançado no .

 

 GetBuilder A reciclagem é realizada em  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;
}

Resumo das vantagens e desvantagens de Get e Provider

Através deste artigo, apresentei a você a necessidade do gerenciamento de estado, quais problemas ele resolve no desenvolvimento do Flutter e como resolvê-los. Ao mesmo tempo, também resumi problemas comuns na prática, etc., veja aqui Você ainda pode ter alguns dúvidas, precisa usar gerenciamento de estado?

Na minha opinião, frameworks existem para resolver problemas. Então depende se você também está passando por essas questões que foram levantadas no início. Se houver, você pode tentar usar o gerenciamento de estado para resolvê-lo; caso contrário, não há necessidade de superdimensioná-lo, use-o para uso.

Em segundo lugar, se o gerenciamento de estado for usado, o que é melhor, Get ou Provider?

Essas duas estruturas têm suas próprias vantagens e desvantagens. Acho que, se você ou sua equipe são novos no Flutter, usar o Provider pode ajudá-lo a entender o mecanismo principal do Flutter mais rapidamente. E se você já tem uma compreensão dos princípios do Flutter, as funções avançadas e a API concisa do Get podem ajudá-lo a melhorar sua eficiência de desenvolvimento.

 

Acho que você gosta

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