Os decoradores @Provide e @Consume são utilizados em ArkTS para sincronização bidirecional de dados entre componentes em diferentes níveis da hierarquia de interface do usuário. Eles permitem que variáveis de estado sejam compartilhadas entre componentes ancestrais e descendentes sem a necessidade de passagem explícita de parâmetros, simplificando a gestão de estado em aplicações complexas.
Em termos gerais, o @Provide é aplicado a uma variável em um componente ancestral, tornando-a disponível para todos os seus descendentes. O @Consume, por outro lado, é usado em componentes descendentes para acessar e sincronizar com a variável fornecida pelo @Provide. Essa relação estabelece uma sincronização bidirecional, onde alterações em qualquer um dos lados são refletidas automaticamente no outro.
Características Principais
A ligação entre @Provide e @Consume pode ser feita através do nome da variável ou de um apelido (alias). Recomenda-se que os tipos de dados sejam idênticos para evitar conversões implícitas que possam causar comportamentos inesperados. Exemplo de ligação:
// Ligação pelo nome da variável
@Provide idade: number = 0;
@Consume idade: number;
// Ligação por apelido
@Provide('identificador') codigo: number = 0;
@Consume('identificador') valor: number;
É importante notar que uma variável @Provide pode ser consumida por múltiplos @Consume, mas não é permitido declraar múltiplos @Provide com o mesmo nome ou apelido no mesmo componente ou seus filhos. Isso resultaria em um erro em tempo de execução.
Detalhes dos Decoradores
O @Provide segue as mesmas regras que o @State, mas atua como uma fonte de sincronização para múltiplos níveis de descendentes. Os tipos de dados permitidos incluem primitivos (string, number, boolean), objetos, classes, enums, arrays, Date, Map e Set (a partir da API version 11), além de tipos específicos do ArkUI como Length, ResourceStr e ResourceColor.
O @Consume, por sua vez, não permite inicialização local e deve ser inicializado exclusivamente através da correspondência com um @Provide existente na árvore de componentes. Se nenhum @Provide correspondente for encontrado, o framework emitirá um erro.
Exemplos de Uso
Sincronização com Tipo Date
Neste exemplo, uma data é compartilhada entre um componente pai e filho. Ambos podem modificar a data, e as alterações são sincronizadas automaticamente.
@Component
struct Filho {
@Consume dataSelecionada: Date;
build() {
Column() {
Button('Filho: incrementar dia')
.onClick(() => {
this.dataSelecionada.setDate(this.dataSelecionada.getDate() + 1)
})
Button('Filho: atualizar para nova data')
.margin(10)
.onClick(() => {
this.dataSelecionada = new Date('2023-09-09')
})
DatePicker({
start: new Date('1970-1-1'),
end: new Date('2100-1-1'),
selected: this.dataSelecionada
})
}
}
}
@Entry
@Component
struct Pai {
@Provide dataSelecionada: Date = new Date('2021-08-08')
build() {
Column() {
Button('Pai: incrementar dia')
.margin(10)
.onClick(() => {
this.dataSelecionada.setDate(this.dataSelecionada.getDate() + 1)
})
Button('Pai: atualizar para nova data')
.margin(10)
.onClick(() => {
this.dataSelecionada = new Date('2023-07-07')
})
DatePicker({
start: new Date('1970-1-1'),
end: new Date('2100-1-1'),
selected: this.dataSelecionada
})
Filho()
}
}
}
Sincronização com Tipo Map
A partir da API version 11, o Map é suportado. Neste caso, um mapa de números para strings é sincronizado entre componentes.
@Component
struct SubComponente {
@Consume mapaMensagens: Map<number string="">
build() {
Column() {
ForEach(Array.from(this.mapaMensagens.entries()), (item: [number, string]) => {
Text(`Chave: ${item[0]}, Valor: ${item[1]}`).fontSize(20)
})
Button('Sub: adicionar item')
.onClick(() => {
this.mapaMensagens.set(5, "novo_valor")
})
Button('Sub: limpar mapa')
.onClick(() => {
this.mapaMensagens.clear()
})
}
}
}
@Entry
@Component
struct ComponentePrincipal {
@Provide mapaMensagens: Map<number string=""> = new Map([[1, "alpha"], [2, "beta"]])
build() {
Column() {
Button('Principal: atualizar mapa')
.onClick(() => {
this.mapaMensagens = new Map([[1, "alpha"], [2, "beta"], [3, "gamma"]])
})
SubComponente()
}
}
}
</number></number>
Sincronização com Tipo Set
Semelhante ao Map, o Set também é suportado. Um conjunto de números é compartilhado e manipulado em ambos os componentes.
@Component
struct ComponenteFilho {
@Consume conjuntoNumeros: Set<number>
build() {
Column() {
ForEach(Array.from(this.conjuntoNumeros), (num: number) => {
Text(`Número: ${num}`).fontSize(20)
})
Button('Filho: adicionar número')
.onClick(() => {
this.conjuntoNumeros.add(10)
})
Button('Filho: remover primeiro')
.onClick(() => {
this.conjuntoNumeros.delete(0)
})
}
}
}
@Entry
@Component
struct ComponenteRaiz {
@Provide conjuntoNumeros: Set<number> = new Set([0, 1, 2, 3])
build() {
Column() {
Button('Raiz: reiniciar conjunto')
.onClick(() => {
this.conjuntoNumeros = new Set([4, 5, 6])
})
ComponenteFilho()
}
}
}
</number></number>
Suporte a Tipos Union
Os decoradores @Provide e @Consume podem lidar com tipos union, incluindo undefined e null. Neste exemplo, uma variável do tipo string | undefined é sincronizada.
@Component
struct Neto {
@Consume valorUniao: string | undefined;
build() {
Column() {
Text(`Valor atual: ${this.valorUniao}`)
Button('Neto: definir como "atualizado"')
.onClick(() => {
this.valorUniao = 'atualizado'
})
}
}
}
@Entry
@Component
struct Avo {
@Provide valorUniao: string | undefined = 'inicial';
build() {
Column() {
Button('Avô: definir como undefined')
.onClick(() => {
this.valorUniao = undefined
})
Neto()
}
}
}
Restrições e Cuidados Comuns
Algumas limitações importantes devem ser observadas:
- O apelido (alias) do @Provide deve ser uma string constante; caso contrário, ocorrerá um erro de compilação.
- Não é permitido inicializar localmente uma variável @Consume. Ela deve ser vinculada diretamente a um @Provide correspondente.
- Se um @Consume não encontrar um @Provide com a mesma chave durante a inicialização, um erro em tempo de execução será gerado.
- Não é possível decorar variáveis do tipo Function com @Provide ou @Consume.
Um erro comum ocorre quando se usa @BuilderParam com closures, onde o contexto (this) pode apontar para o componente errado, fazendo com que o @Provide não seja encontrado. A solução é garantir que o @Provide esteja declarado no componente correto, como no componente de entrada.
Outro ponto de atenção é que chamadas a métodos externos, como estáticos, que modificam objetos decorados com @Provide ou @Consume, podem não acionar atualizações na interface. Para contornar isso, é recomendável atribuir o objeto a uma variável temporária antes de chamar o método, preservando o proxy do framework.