Przejdź bezpośrednio do treści

Właściwości computed

Podstawowy przykład

Wyrażenia w szablonie są bardzo wygodne, ale są przeznaczone do prostych operacji. Umieszczanie zbyt dużej ilości logiki do szablonów może sprawić, że staną się one przeładowane i trudne do utrzymania. Na przykład, jeśli mamy obiekt z zagnieżdżoną tablicą:

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  }
}
js
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

I chcemy wyświetlić różne komunikaty w zależności od tego, czy author ma już jakieś książki, czy nie:

template
<p>Czy wydał jakieś książki:</p>
<span>{{ author.books.length > 0 ? 'Tak' : 'Nie' }}</span>

W tym momencie szablon zaczyna być nieco przeładowany. Trzeba przez chwilę na niego popatrzeć, zanim zrozumiemy, że wykonuje on obliczenia w zależności od author.books. Co ważniejsze, prawdopodobnie nie chcemy powtarzać tego samego obliczenia w kilku miejscach w szablonie.

Dlatego w przypadku bardziej złożonej logiki, która obejmuje dane reaktywne, zaleca się użycie właściwości computed. Oto ten sam przykład, ale zrefaktoryzowany:

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  },
  computed: {
    // getter właściwości computed
    publishedBooksMessage() {
      // `this` odnosi się do instancji komponentu
      return this.author.books.length > 0 ? 'Tak' : 'Nie'
    }
  }
}
template
<p>Czy wydał jakieś książki:</p>
<span>{{ publishedBooksMessage }}</span>

Wypróbuj w playground

Tutaj zadeklarowaliśmy właściwość computed publishedBooksMessage.

Spróbuj zmienić wartość tablicy books w danych aplikacji, a zobaczysz, jak publishedBooksMessage zmienia się odpowiednio.

Możesz powiązać właściwości computed w szablonach tak samo, jak zwykłe właściwości. Vue wie, że this.publishedBooksMessage zależy od this.author.books, więc zaktualizuje wszelkie powiązania zależne od this.publishedBooksMessage, gdy this.author.books się zmieni.

Zobacz także: Typowanie wartości computed

vue
<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// computed ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Tak' : 'Nie'
})
</script>

<template>
  <p>Czy wydał jakieś książki:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

Wypróbuj w playground

Tutaj zadeklarowaliśmy właściwość computed publishedBooksMessage. Funkcja computed() oczekuje funkcji getter, a zwrócona wartość to computed ref. Podobnie jak normalne refy, możesz uzyskać dostęp do wyniku obliczeń jako publishedBooksMessage.value. Computed refy są także automatycznie rozpakowywane w szablonach, więc możesz się do nich odwoływać bez .value w wyrażeniach szablonu.

Właściwość computed automatycznie śledzi swoje reaktywne zależności. Vue wie, że obliczenie publishedBooksMessage zależy od author.books, więc zaktualizuje wszystkie powiązania zależne od publishedBooksMessage, gdy author.books się zmieni.

Zobacz także: Typowanie właściwości computed

Computed caching vs. metody

Być może zauważyłeś, że ten sam rezultat możemy osiągnąć, wywołując metodę w wyrażeniu:

template
<p>{{ calculateBooksMessage() }}</p>
js
// w komponencie
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Tak' : 'Nie'
  }
}
js
// w komponencie
function calculateBooksMessage() {
  return author.books.length > 0 ? 'Tak' : 'Nie'
}

Zamiast właściwości computed możemy zdefiniować tę samą funkcję jako metodę. W ostatecznym wyniku oba podejścia dają dokładnie ten sam efekt. Jednak różnica polega na tym, że właściwości computed są cache'owane na podstawie ich reaktywnych zależności. Właściwość computed zostanie ponownie obliczona tylko wtedy, gdy zmienią się jej reaktywne zależności. Oznacza to, że tak długo, jak author.books nie ulegnie zmianie, każde kolejne odwołanie do publishedBooksMessage zwróci natychmiast wcześniej obliczony wynik, bez konieczności ponownego uruchamiania funkcji zwracającej wartość.

To także oznacza, że następująca obliczona właściwość nigdy się nie zaktualizuje, ponieważ Date.now() nie jest reaktywną zależnością:

js
computed: {
  now() {
    return Date.now()
  }
}
js
const now = computed(() => Date.now())

Dla porównania, wywołanie metody zawsze uruchomi funkcję za każdym razem, gdy nastąpi ponowne renderowanie.

Dlaczego potrzebujemy cache'owania? Wyobraź sobie, że mamy kosztowną właściwość computed list, która wymaga przetworzenia ogromnej tablicy i wykonania wielu operacji obliczeniowych. Jeśli inne obliczone właściwości zależą od list, to bez cache'owania musielibyśmy uruchamiać funkcję zwracającą wartość list wiele razy więcej niż jest to konieczne! W przypadkach, w których nie chcesz korzystać z cache'owania, zamiast tego użyj wywołania metody.

Zapisywalne właściwości computed

Obliczone właściwości są domyślnie tylko do odczytu. Jeśli spróbujesz przypisać nową wartość do właściwości computed, otrzymasz ostrzeżenie w czasie działania. W rzadkich przypadkach, gdy potrzebujesz "zapisywalnej" właściwości computed, możesz ją utworzyć, definiując zarówno getter, jak i setter:

js
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      // getter
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // setter
      set(newValue) {
        // Zwróć uwagę, że używamy tutaj składni destrukturyzacji.
        [this.firstName, this.lastName] = newValue.split(' ')
      }
    }
  }
}

Teraz, gdy wykonasz this.fullName = 'Jan Kowalski', wywołany zostanie setter i this.firstName oraz this.lastName zostaną odpowiednio zaktualizowane.

vue
<script setup>
import { ref, computed } from 'vue'

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

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // Zwróć uwagę, że używamy tutaj składni destrukturyzacji.
    ;[firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

Teraz, gdy wykonasz fullName.value = 'Jan Kowalski', wywołany zostanie setter i firstName oraz lastName zostaną odpowiednio zaktualizowane.

Pobieranie poprzedniej wartości

  • Wsparcie tylko w 3.4+

Jeśli tego potrzebujesz, możesz pobrać poprzednią wartość zwróconą przez computed poprzez pierwszy argument z gettera:

js
export default {
  data() {
    return {
      count: 2
    }
  },
  computed: {
    // Ten computed zawsze zwraca wartość count gdy jest mniejsza lub równa 3.
    // Gdy count >= 4, ostatnia wartość, która spełniła nasz warunek zostanie zwrócona
    // dopóki count będzie mniejsze lub większe 3
    alwaysSmall(previous) {
      if (this.count <= 3) {
        return this.count
      }

      return previous
    }
  }
}
vue
<script setup>
import { ref, computed } from 'vue'

const count = ref(2)

// Ten computed zawsze zwraca wartość count gdy jest mniejsza lub równa 3.
// Gdy count >= 4, ostatnia wartość, która spełniła nasz warunek zostanie zwrócona
// dopóki count będzie mniejsze lub większe 3
const alwaysSmall = computed((previous) => {
  if (count.value <= 3) {
    return count.value
  }

  return previous
})
</script>

Gdy używasz zapisywalnej wartości computed:

js
export default {
  data() {
    return {
      count: 2
    }
  },
  computed: {
    alwaysSmall: {
      get(previous) {
        if (this.count <= 3) {
          return this.count
        }

        return previous;
      },
      set(newValue) {
        this.count = newValue * 2
      }
    }
  }
}
vue
<script setup>
import { ref, computed } from 'vue'

const count = ref(2)

const alwaysSmall = computed({
  get(previous) {
    if (count.value <= 3) {
      return count.value
    }

    return previous
  },
  set(newValue) {
    count.value = newValue * 2
  }
})
</script>

Najlepsze praktyki

Gettery powinny być wolne od efektów ubocznych

Należy pamiętać, że funkcje zwracające wartość właściwości computed powinny wykonywać jedynie czystą kalkulację i być wolne od efektów ubocznych. Nie modyfikuj innych stanów, nie wykonuj asynchronicznych żądań i nie manipuluj DOM-em wewnątrz getterów obliczonych właściwości! Traktuj obliczoną właściwość jako deklaratywny sposób opisania, jak obliczyć wartość na podstawie innych wartości – jej jedynym zadaniem powinno być obliczenie i zwrócenie tej wartości. Później w przewodniku omówimy, jak możemy wykonywać efekty uboczne w reakcji na zmiany stanu za pomocą obserwatorów (watchers).

Unikaj modyfikowania wartości obliczonych

Zwracana wartość z obliczonej właściwości jest stanem pochodnym. Traktuj ją jako tymczasowy zrzut danych – za każdym razem, gdy zmienia się stan źródłowy, tworzony jest nowy zrzut. Modyfikowanie takiego zrzutu nie ma sensu, dlatego wartość zwracana przez obliczoną właściwość powinna być traktowana jako tylko do odczytu i nigdy nie powinna być modyfikowana. Zamiast tego należy aktualizować stan źródłowy, od którego zależy obliczona właściwość, aby wywołać ponowne przeliczenie.

Właściwości computedJest załadowany