Verbesserung der Leistung von Vue-Anwendungen

Wir haben ein Wiki bei TeamHood. Es gibt eine Reihe von Empfehlungen, darunter, wie die Leistung eines umfangreichen Frontends auf vue.js verbessert werden kann. Die Leistung musste verbessert werden, da unsere Hauptbildschirme aufgrund der Besonderheiten keine Paginierung aufweisen. Es gibt Kunden, die mehr als tausend dieser Karten auf einem Kanban / Gantt-Brett haben. All dies sollte ohne Verzögerungen funktionieren.





wiki, .





. vue2 , vue3. vue3 production-ready. vuex4 , ( , vue2+vuex3). javascript, vue-class-component, typescript, typed-vuex , .





1. (Deep) Object Watchers

- deep , watch . . items , vuex store, , item . isChecked , item, getter, :





export const state = () => ({
  items: [{ id: 1, name: 'First' }, { id: 2, name: 'Second' }],
  checkedItemIds: [1, 2]
})

export const getters = {
  extendedItems (state) {
    return state.items.map(item => ({
      ...item,
      isChecked: state.checkedItemIds.includes(item.id)
    }))
  }
}
      
      



, items , . - :





export default class ItemList extends Vue {
  computed: {
    extendedItems () { return this.$store.getters.extendedItems },
    itemIds () { return this.extendedItems.map(item => item.id) }
  },
  watch: {
    itemIds () {
      console.log('Saving new items order...', this.itemIds) 
    }
  }
}
      
      



item . - , . checkedItemIds extendedItems ( ), itemIds. -, , . , javascript, [1,2,3] != [1,2,3]. : example1.





- watcher . watcher computed . , {id, title, userId}



items, :





computed: {
  itemsTrigger () { 
    return JSON.stringify(items.map(item => ({ 
      id: item.id, 
      title: item.title, 
      userId: item.userId 
    }))) 
  }
},
watch: {
  itemsTrigger () {
    //    JSON.parse -    this.items; 
  }
}
      
      



, watcher, , .

watcher - , deep watcher - . deep - . , , - , - deep - .





- .. ( ).. , , , $emit('reinit'), $nextTick. .





2. Object.freeze

Object.freeze TeamHood 2 . , StarBright, nuxt . Nuxt , . vuex store ( ). , vuex. this.$store.dispatch('fetch', …), vuex .





, vuex . , , autocomplete , store . , vue (). , .





// 
state: () => ({
  items: []
}),
mutations: {
  setItems (state, items) {
    state.items = items
  },
  markItemCompleted (state, itemId) {
    const item = state.items.find(item => item.id === itemId)
    if (item) {
      item.completed = true
    }
  }
}

// 
state: () => ({
  items: []
}),
mutations: {
  setItems (state, items) {
    state.items = items.map(item => Object.freeze(item))
  },
  markItemCompleted (state, itemId) {
    const itemIndex = state.items.find(item => item.id === itemId)
    if (itemIndex !== -1) {
      //    item.completed = true ( ),    ;
      const newItem = {
        ...state.items[itemIndex],
        completed: true
      }
      state.items.splice(itemIndex, 1, Object.freeze(newItem))
    }
  }
}
      
      



: example2. , build- ( development).





3.

. . items.find :





// Vuex: 
getters: {
  itemById: (state) => (itemId) => state.items.find(item => item.id === itemId)
}
...
// Some <Item :item-id="itemId" /> component:
computed: {
  item () { return this.$store.getters.itemById(this.itemId) }
}
      
      



itemsByIds :





getters: {
  itemByIds: (state) => state.items.reduce((out, item) => {
    out[item.id] = item
    return out
  }, {})
}
// Some <Item :item-id="itemId" /> component:
computed: {
  item () { return this.$store.getters.itemsByIds[this.itemId] }
}
      
      



: example3.





4.

- vue. (shouldComponentUpdate) . - : - , .





, , - , , , . () :





// Store:
export const getters = {
  extendedItems (state) {
    return state.items.map(item => ({
      ...item,
      isChecked: state.checkedItemIds.includes(item.id)
    }))
  },
  extendedItemsByIds (state, getters) {
    return getters.extendedItems.reduce((out, extendedItem) => {
      out[extendedItem.id] = extendedItem
      return out
    }, {})
  }
}

// App.vue:
<ItemById for="id in $store.state.ids" :key="id" :item-id="id />

// Item.vue:
<template>
  <div>{{ item.title }}</div>
</template>

<script>
export default {
  props: ['itemId'],
  computed: {
    item () { return this.$store.getters.extendedItemsByIds[this.itemId] }
  },
  updated () {
    console.count('Item updated')
  }
}
</script>
      
      



: example4p1. item <Item>. , <Item> extendedItemsByIds, item.





vue - , virtual DOM (memoization). - - dry run props $store. - , .





store . normalizr , . ids. , getter, . , :





// Store:
export const state = () => ({
  ids: [],
  itemsByIds: {},
  checkedIds: []
})

export const getters = {
  extendedItems (state, getters) {
    return state.ids.map(id => ({
      id,
      item: state.itemsByIds[id],
      isChecked: state.checkedIds.includes(id)
    }))
  }
}

export const mutations = {
  renameItem (state, { id, title }) {
    const item = state.itemsByIds[id]
    if (item) {
      state.itemsByIds[id] = Object.freeze({
        ...item,
        title
      })
    }
  },
  setCheckedItemById (state, { id, isChecked }) {
    const index = state.checkedIds.indexOf(id)
    if (isChecked && index === -1) {
      state.checkedIds.push(id)
    } else if (!isChecked && index !== -1) {
      state.checkedIds.splice(index, 1)
    }
  }
}

// Item.vue:
computed: {
  item () {
    return this.$store.state.itemsByIds[this.itemId]
  },
  isChecked () {
    return this.$store.state.checkedIds.includes(this.itemId)
  }
}
      
      



, renameItem state.itemsByIds, . rename : example4p2. isChecked state.checkedIds ( ), - <Item>.





, <Item> :





<Item
  v-for="extendedItem in extendedItems"
  :key="extendedItem.id"
  :item="extendedItem.item"
  :is-checked="extendedItem.isChecked"
/>
      
      



: example4p3.





5. IntersectionObserver

DOM- . . , gantt , , viewport. . , intersection observer. vuetify v-intersect , , IntersectionObserver , , .





, : example5. 100 ( 10), , . IntersectionObserver , ., - IntersectionObserver:





export default {
  inserted (el, { value: observer }) {
    if (observer instanceof IntersectionObserver) {
      observer.observe(el)
    }
    el._intersectionObserver = observer
  },
  update (el, { value: newObserver }) {
    const oldObserver = el._intersectionObserver
    const isOldObserver = oldObserver instanceof IntersectionObserver
    const isNewObserver = newObserver instanceof IntersectionObserver
    if (!isOldObserver && !isNewObserver) || (isOldObserver && (oldObserver === newObserver)) {
      return false
    }
    if (isOldObserver) {
      oldObserver.unobserve(el)
      el._intersectionObserver = undefined
    }
    if (isNewObserver) {
      newObserver.observe(el)
      el._intersectionObserver = newObserver
    }
  },
  unbind (el) {
    if (el._intersectionObserver instanceof IntersectionObserver) {
      el._intersectionObserver.unobserve(el)
    }
    el._intersectionObserver = undefined
  }
}
      
      



, , , . , , - vue , . , . - . , css:





<template>
  <div 
    v-for="i in 100" 
    :key="i" 
    v-node-intersect="intersectionObserver"
    class="rr-intersectionable"
  >
    <Heavy />
  </div>
</template>

<script>
export default {
  data () {
    return {
      intersectionObserver: new IntersectionObserver(this.handleIntersections)
    }
  },
  methods: {
    handleIntersections (entries) {
      entries.forEach((entry) => {
        const className = 'rr-intersectionable--invisible'
        if (entry.isIntersecting) {
          entry.target.classList.remove(className)
        } else {
          entry.target.classList.add(className)
        }
      })
    }
  }
}
</script>

<style>
.rr-intersectionable--invisible .rr-heavy-part
  display: none
</style>
      
      






All Articles