# Dados Computados e Observadores

Esta seção usa a sintaxe de componente de single-file para exemplos de código

# Valores computados

Às vezes precisamos de um estado que depende de outro estado - no Vue isso é tratado com dados computados do componente. Para criar diretamente um valor computado, podemos usar a função computed: ela pega uma função getter e retorna um objeto reativo imutável ref para o valor retornado do getter.

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // erro
1
2
3
4
5
6

Alternativamente, ele pode receber um objeto com as funções get e set para criar um objeto ref modificável.

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0
1
2
3
4
5
6
7
8
9
10

# Depuração de Computados 3.2+

computed aceita um segundo argumento com as opções onTrack e onTrigger:

  • onTrack será chamado quando uma propriedade reativa ou ref for rastreada como uma dependência.
  • onTrigger será chamado quando o callback do observador for acionado pela mutação de uma dependência.

Ambos os callbacks receberão um evento depurador que contém informações sobre a dependência em questão. Recomenda-se colocar uma instrução debugger nesses callbacks para inspecionar interativamente a dependência:

const plusOne = computed(() => count.value + 1, {
  onTrack(e) {
    // acionado quando count.value é rastreado como uma dependência
    debugger
  },
  onTrigger(e) {
    // acionado quando count.value é modificado
    debugger
  }
})

// acessa plusOne, deve acionar onTrack
console.log(plusOne.value)

// muda count.value, deve acionar onTrigger
count.value++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

onTrack e onTrigger só funcionam no modo de desenvolvimento.

# watchEffect

Para aplicar e automaticamente reaplicar um efeito colateral baseado no estado reativo, podemos usar a função watchEffect. Ela executa uma função imediatamente enquanto rastreia de forma reativa suas dependências e a executa novamente sempre que as dependências são alteradas.

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> loga 0

setTimeout(() => {
  count.value++
  // -> loga 1
}, 100)
1
2
3
4
5
6
7
8
9

# Parando o Observador

Quando watchEffect é chamado durante a função setup() de um componente ou gatilhos do ciclo de vida, o observador é vinculado ao ciclo de vida do componente e será interrompido automaticamente quando o componente for desmontado.

Em outros casos, ele retorna um manipulador de parada que pode ser chamado para parar explicitamente o observador:

const stop = watchEffect(() => {
  /* ... */
})

// depois
stop()
1
2
3
4
5
6

# Invalidação de Efeito Colateral

Às vezes, a função do efeito observado executará efeitos colaterais assíncronos que precisam ser limpos quando forem invalidados (ou seja, estado alterado antes que os efeitos possam ser concluídos). A função de efeito recebe uma função onInvalidate que pode ser usada para registrar um callback de invalidação. Esse callback de invalidação é chamado quando:

  • o efeito está prestes a ser executado novamente
  • o observador é parado (ou seja, quando o componente é desmontado se watchEffect for usado dentro de setup() ou gatilhos de ciclo de vida)
watchEffect(onInvalidate => {
  const token = performAsyncOperation(id.value)
  onInvalidate(() => {
    // id foi alterado ou o observador está parado.
    // invalida a operação assíncrona pendente anteriormente
    token.cancel()
  })
})
1
2
3
4
5
6
7
8

Estamos registrando o callback de invalidação por meio de uma função passada em vez de retorná-lo do callback porque o valor retornado é importante para o tratamento de erros assíncronos. É muito comum que a função de efeito seja uma função assíncrona ao realizar a requisição de dados:

const data = ref(null)
watchEffect(async onInvalidate => {
  onInvalidate(() => {
    /* ... */
  }) // registramos a função de limpeza antes que a Promise seja resolvida
  data.value = await fetchData(props.id)
})
1
2
3
4
5
6
7

Uma função assíncrona retorna implicitamente uma Promise, mas a função de limpeza precisa ser registrada imediatamente antes que a Promise seja resolvida. Além disso, o Vue depende da Promise retornada para lidar automaticamente com possíveis erros na cadeia de Promises.

# Momento de Limpeza do Efeito

O sistema de reatividade do Vue armazena em buffer os efeitos invalidados e os libera de forma assíncrona para evitar invocação duplicada desnecessária quando há muitas mutações de estado acontecendo no mesmo "tick". Internamente, a função update de um componente também é um efeito observado. Quando um efeito de usuário é enfileirado, ele é invocado por padrão antes de todos os efeitos de update do componente:

<template>
  <div>{{ count }}</div>
</template>

<script>
export default {
  setup() {
    const count = ref(0)

    watchEffect(() => {
      console.log(count.value)
    })

    return {
      count
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Neste exemplo:

  • A contagem será registrada de forma síncrona na execução inicial.
  • Quando count for modificado, o callback será chamado antes de o componente ser atualizado.

Nos casos em que um efeito de observador precisa ser executado novamente após atualizações de componentes (ou seja, ao trabalhar com Refs de Template), nós podemos passar um objeto options adicional com a opção flush (o padrão é 'pre'):

// dispara após atualizações de componentes para que você possa acessar o DOM atualizado
// Nota: isso também adiará a execução inicial do efeito até
// a primeira renderização do componente ser finalizada.
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'post'
  }
)
1
2
3
4
5
6
7
8
9
10
11

A opção flush também aceita 'sync', o que força o efeito a sempre disparar de forma síncrona. No entanto, isso é ineficiente e raramente deve ser necessário.

No Vue >= 3.2.0, os apelidos watchPostEffect e watchSyncEffect também podem ser usados ​​para tornar a intenção do código mais óbvia.

# Depuração do Observador

As opções onTrack e onTrigger podem ser usadas para depurar o comportamento de um observador.

  • onTrack será chamado quando uma propriedade reativa ou ref for rastreada como uma dependência.
  • onTrigger será chamado quando o callback do observador for acionado pela mutação de uma dependência.

Ambos os callbacks receberão um evento depurador que contém informações sobre a dependência em questão. Recomenda-se colocar uma instrução debugger nesses callbacks para inspecionar interativamente a dependência:

watchEffect(
  () => {
    /* efeito colateral */
  },
  {
    onTrigger(e) {
      debugger
    }
  }
)
1
2
3
4
5
6
7
8
9
10

onTrack e onTrigger só funcionam no modo de desenvolvimento.

# watch

A API watch é o equivalente exato da propriedade watch do componente. watch requer a observação de uma fonte de dados específica e aplica efeitos colaterais em um callback separado. Também é preguiçoso por padrão - ou seja, o callback só é chamado quando a fonte observada foi alterada.

  • Comparado com watchEffect, watch nos permite:

    • Executar o efeito colateral preguiçosamente;
    • Ser mais específico sobre qual estado deve acionar o observador para executar novamente;
    • Acessar o valor anterior e atual do estado observado.

# Observando uma Única Fonte

Uma fonte de dados do observador pode ser uma função getter que retorna um valor ou diretamente um ref:

// observando um getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// observando diretamente um ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Observando Várias Fontes

Um observador também pode observar várias fontes ao mesmo tempo usando um array:

const firstName = ref('')
const lastName = ref('')

watch([firstName, lastName], (newValues, prevValues) => {
  console.log(newValues, prevValues)
})

firstName.value = 'John' // logs: ["John", ""] ["", ""]
lastName.value = 'Smith' // logs: ["John", "Smith"] ["John", ""]
1
2
3
4
5
6
7
8
9

No entanto, se você estiver alterando as duas fontes observadas simultaneamente na mesma função, o observador será executado apenas uma vez:









 
 
 
 
 




setup() {
  const firstName = ref('')
  const lastName = ref('')

  watch([firstName, lastName], (newValues, prevValues) => {
    console.log(newValues, prevValues)
  })

  const changeValues = () => {
    firstName.value = 'John'
    lastName.value = 'Smith'
    // logs: ["John", "Smith"] ["", ""]
  }

  return { changeValues }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Observe que várias alterações síncronas acionarão o observador apenas uma vez.

É possível forçar o observador a disparar após cada mudança usando a configuração flush: 'sync', embora isso geralmente não seja recomendado. Como alternativa, nextTick pode ser usado para aguardar a execução do observador antes de fazer outras alterações. por exemplo.:

const changeValues = async () => {
  firstName.value = 'John' // logs: ["John", ""] ["", ""]
  await nextTick()
  lastName.value = 'Smith' // logs: ["John", "Smith"] ["John", ""]
}
1
2
3
4
5

# Observando Objetos Reativos

Usar um observador para comparar valores de um array ou objeto que são reativos requer que ele tenha uma cópia feita apenas dos valores.

const numbers = reactive([1, 2, 3, 4])

watch(
  () => [...numbers],
  (numbers, prevNumbers) => {
    console.log(numbers, prevNumbers)
  }
)

numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]
1
2
3
4
5
6
7
8
9
10

A tentativa de verificar alterações de propriedades em um objeto ou array profundamente aninhado ainda exigirá que a opção deep seja verdadeira:

const state = reactive({
  id: 1,
  attributes: {
    name: ''
  }
})

watch(
  () => state,
  (state, prevState) => {
    console.log('not deep', state.attributes.name, prevState.attributes.name)
  }
)

watch(
  () => state,
  (state, prevState) => {
    console.log('deep', state.attributes.name, prevState.attributes.name)
  },
  { deep: true }
)

state.attributes.name = 'Alex' // Loga: "deep" "Alex" "Alex"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

No entanto, observar um objeto ou array reativo sempre retornará uma referência ao valor atual desse objeto para ambos o valor atual e anterior do estado. Para observar completamente objetos e arrays profundamente aninhados, pode ser necessária uma cópia profunda dos valores. Isso pode ser feito com um utilitário como lodash.cloneDeep (opens new window)

import _ from 'lodash'

const state = reactive({
  id: 1,
  attributes: {
    name: ''
  }
})

watch(
  () => _.cloneDeep(state),
  (state, prevState) => {
    console.log(state.attributes.name, prevState.attributes.name)
  }
)

state.attributes.name = 'Alex' // Loga: "Alex" ""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Comportamento Compartilhado com watchEffect

watch compartilha comportamento com watchEffect em termos de parada manual, invalidação de efeito colateral (com onInvalidate passado ao callback como o terceiro argumento), momento de limpeza e depuração.