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.
// 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>