analyse the code in the page and detect all points that can improve by understanding and seeing how watchers and computed properties work in Vue. this is Vue 2.7 so not vue 3 so it is less performant. How to make this more performant ? 
Right now when we scroll it is very jerky and there are a lot of lost frames. The console is spammed with "computeConfigData computeLayoutData computeRenderData" calls even when the items are all loaded. The loading itself is fast and all the images load pretty quickly but at the expense of the browser lagging out probably due to too many re-renders or forced reflows on top of the fact that the reactive system is always calling computed properties at every small change.

Make sure to give a detailed and the best possible solution without introducing unwanted side effects. Review and make changes if you think there's some things you could have overlooked.

At the end, give me code snippets that i should replace in my code. When i apply the changes, it should be solved and run smoothly without interruptions.

 @Web 
@https://srivastavaankita080.medium.com/vuejs-computed-watchers-explained-7b6416ebfa18 
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch, provide, reactive, PropType, nextTick, markRaw } from 'vue';
import {
    getGridGapDefault,
    getColumnCountDefault,
    getWindowMarginDefault,
    getItemRatioHeightDefault,
    debugLog,
} from './utils';
import { throttle } from 'lodash-es';
import PhotoBrowserController from '../../../../Web/src/Pages/Photos/PhotoBrowserController';

interface VirtualGridInterface extends Vue {
    resetGrid: () => void;
}

interface Item<P> {
    id: string;
    injected?: P;
    height: number;
    width?: number;
    columnSpan: number;
    newRow?: boolean;
    renderComponent: Component;
}

interface ContainerData {
    windowSize: ElementSize;
    windowScroll: ElementScroll;
    elementWindowOffset: number;
    elementSize: ElementSize;
}

interface ElementSize {
    width: number;
    height: number;
}

interface ElementScroll {
    x: number;
    y: number;
}

interface ConfigData<P> {
    windowMargin: number;
    gridGap: number;
    columnCount: number;
    entries: Item<P>[];
}

interface Cell<P> extends Item<P> {
    columnNumber: number;
    rowNumber: number;
    offset: number;
}

interface LayoutData<P> {
    totalHeight: number;
    cells: Cell<P>[];
}

interface RenderData<P> {
    cellsToRender: Cell<P>[];
    firstRenderedRowNumber: number | null;
    firstRenderedRowOffset: number | null;
}

const props = defineProps({
    items: { type: Array as () => Item<any>[], required: true },
    rowHeight: {type: Number, default: 150 },
    updateFunction: { type: Function as () => () => Promise<boolean>, default: () => () => Promise.resolve(true) },
    getGridGap: { type: Function, default: markRaw(getGridGapDefault) },
    getWindowMargin: { type: Function, default: markRaw(getWindowMarginDefault) },
    getItemRatioHeight: { type: Function, default: markRaw(getItemRatioHeightDefault) },
    scrollElement: { type: HTMLDivElement, default: null },
    updateTriggerMargin: { type: Number, default: 700 },
    loader: { type: Object as PropType<Component>, default: null },
    debug: { type: Boolean, default: false },
    itemComponent: { type: Object as PropType<Component>, required: true },
    headerComponent: { type: Object as PropType<Component>, required: true },
    photoBrowserController: { type: Object as PropType<PhotoBrowserController>, required: true }
});

const rowOffsets = ref([]);
const updateLock = ref(false);
// const itemsDirty = ref(false);
const bottomReached = ref(false);
const virtualGridRef = ref<Element | null>(null);

const windowSize = ref({ height: 0, width: 0 });
const elementWindowOffset = ref(0);
const elementHeight = ref(0);
const elementWidth = ref(0);

const structuredItems = computed<Item<P>[]>(() => computeItemStructure(props.items));
const configData = computed<ConfigData<any>>(() => computeConfigData(windowSize.value, elementWidth.value, structuredItems.value));
const layoutData = computed<LayoutData<any>>(() => computeLayoutData(configData.value));
const renderData = computed<RenderData<any>>(() => computeRenderData(configData.value, elementWindowOffset.value, windowSize.value, layoutData.value));

onMounted(() => {
    virtualGridRef.value = virtualGridRef.value as Element;
    initiliazeGrid();
    window.addEventListener('resize', resize);
    (props.scrollElement ?? window).addEventListener('scroll', scroll);
});

onBeforeUnmount(() => {
    window.removeEventListener('resize', resize);
    (props.scrollElement ?? window).removeEventListener('scroll', scroll);
});

watch(() => props.scrollElement, (newScrollElement, oldScrollElement) => {
    (oldScrollElement ?? window).removeEventListener('scroll', scroll);
    (newScrollElement ?? window).addEventListener('scroll', scroll);
});

watch(structuredItems, (newValue) => {
  console.log('structuredItems.value changed:', newValue);
});

watch(() => elementWidth.value, (newValue) => {
  console.log('elementWidth.value changed:', newValue);
});

watch(() => windowSize.value.width, (newValue) => {
  console.log('windowSize.value.width changed:', newValue);
});

// watch(() => props.items, () => {
//   nextTick(() => {
//     updateContainerSize();
//   });
// }, { deep: true });

const calculateAfterScroll = () => {
  if (virtualGridRef.value) {
    elementWindowOffset.value = getElementOffset(virtualGridRef.value);
  }
};

const calculateAfterResize = () => {
  windowSize.value = getWindowSize();
  const elementSize = getElementSize(virtualGridRef.value);
  elementWidth.value = elementSize.width;
  elementHeight.value = elementSize.height;
};

const calculateAfterNewContent = () => {
  if (virtualGridRef.value) {
    elementHeight.value = (virtualGridRef.value as Element).getBoundingClientRect().height;
  }
};

function computeContainerData(): void {
    if (virtualGridRef.value === null) {
        return;
    }
    calculateAfterResize();
    calculateAfterScroll();
    calculateAfterNewContent();
}

const resize = throttle(() => {
  calculateAfterResize();
  loadMoreData(true);
}, 500, { leading: false, trailing: true });

const scroll = throttle(() => {
  requestAnimationFrame(loadMoreData);
}, 250, { leading: false, trailing: true });

function initiliazeGrid(): void {
    nextTick(async () => {
      loadMoreData(true);
    });
}

function loadMoreData(fromResize = false): void {
    console.log('loadMoreData.');
    if (updateLock.value) return;
    loadMoreDataAsync(fromResize).then();
}

const loadMoreDataAsync = async (fromResize: boolean = false): Promise<void> => {
  if (fromResize) calculateAfterResize();
  calculateAfterScroll();
  calculateAfterNewContent();

  const windowBottom = windowSize.value.height;
  const bottomTrigger = Math.max(
      0,
      elementWindowOffset.value + elementHeight.value - props.updateTriggerMargin
  );
  if (!bottomReached.value && windowBottom >= bottomTrigger && !updateLock.value) {
    updateLock.value = true;
    const isLastBatch = await props.updateFunction();

    if (isLastBatch) {
      bottomReached.value = true;
    }

    updateLock.value = false;
    await loadMoreDataAsync();
  }
  return;
};

function computeItemStructure(items: Item<any>[]): Item<P>[] {
  console.log('computeItemStructure');
  return props.items.reduce((acc, item, index) => {
      const currentGroupKey = item.injected ? [item.injected.DisplayDate, item.injected.Store?.Name] : null;
      
      if (currentGroupKey && (!acc.lastGroupKey || currentGroupKey[0] !== acc.lastGroupKey[0] || currentGroupKey[1] !== acc.lastGroupKey[1])) {
        acc.entries.push({
          id: `${currentGroupKey[0]}-${currentGroupKey[1]}`,
          columnSpan: 0,
          newRow: true,
          height: 50,
          injected: { 
            dateStoreGroupInfo: { 
              date: currentGroupKey[0], 
              store: currentGroupKey[1] 
            } 
          }
        });
      }

      acc.lastGroupKey = currentGroupKey;
      acc.entries.push(item);
      return acc;
    }, { entries: [], lastGroupKey: null }).entries;
}

function computeConfigData(windowSize: ElementSize, elementWidth: number, items: Item<any>[]): ConfigData<any> {
  console.log('computeConfigData');
    if (!elementWidth || items.length === 0) {
      return {
        windowMargin: 0,
        gridGap: 0,
        columnCount: 1,
        entries: [],
      };
    }

    const windowMargin = props.getWindowMargin(windowSize.height);
    const gridGap = props.getGridGap(elementWidth, windowSize.height);
    const columnCount = getColumnCount(elementWidth);
    const columnWidth = getColumnWidth(columnCount, gridGap, elementWidth);

    const entries = items.map((item) => {
      if (item.injected?.dateStoreGroupInfo) {
        return { ...item, columnSpan: columnCount };
      }

      if (!item.width) return item;

      const imageWidth = columnWidth * item.columnSpan + gridGap * (item.columnSpan - 1);
      return {
        ...item,
        height: props.getItemRatioHeight(item.height, item.width, imageWidth),
        width: imageWidth,
      };
    });

    return {
        windowMargin,
        gridGap,
        columnCount,
        entries,
    };
};

const computeLayoutData = (configData: ConfigData<any>): LayoutData<any> => {
  console.log('computeLayoutData');
    if (configData === null) {
        return { cells: [], totalHeight: 0 };
    }

    let currentRowNumber = 1;
    let prevRowsTotalHeight = 0;
    let currentRowMaxHeight = 0;

    let columnShift = 0;

    const cells: Cell<any>[] = configData.entries.map((entry, index) => {
        const { columnCount, gridGap } = configData;

        let columnSpanRecompute = entry.columnSpan;
        let heightRecompute = entry.height;

        // if the column span is 0 or negative we assume it is full width
        if (columnSpanRecompute < 1) {
            columnSpanRecompute = columnCount;
        }

        const distanceToRowStart = (index + columnShift) % columnCount;
        if (entry.newRow && distanceToRowStart !== 0) {
            columnShift += columnCount - distanceToRowStart;
        }

        const shiftedIndex = index + columnShift;
        const columnNumber = (shiftedIndex % columnCount) + 1;
        const rowNumber = Math.floor(shiftedIndex / columnCount) + 1;

        // here we check that an image is not going out of the grid, if yes we resize it
        if (columnNumber + columnSpanRecompute > columnCount + 1) {
            const overlapNumber = columnNumber + columnSpanRecompute - columnCount - 1;
            const overlapRatio = overlapNumber / columnSpanRecompute;
            heightRecompute = heightRecompute * (1 - overlapRatio);

            columnSpanRecompute -= overlapNumber;
        }

        // we need to count the shift created by multiple column objects
        if (columnSpanRecompute > 1) {
            columnShift += columnSpanRecompute - 1;
        }

        if (rowNumber !== currentRowNumber) {
            currentRowNumber = rowNumber;
            prevRowsTotalHeight += currentRowMaxHeight + gridGap;
            currentRowMaxHeight = 0;
        }

        const offset = prevRowsTotalHeight;
        const height = Math.round(heightRecompute);

        currentRowMaxHeight = Math.max(currentRowMaxHeight, height);

        return { ...entry, columnNumber, rowNumber, offset, height, columnSpan: columnSpanRecompute };
    });

    const totalHeight = prevRowsTotalHeight + currentRowMaxHeight;

    return { cells, totalHeight };
};

const computeRenderData = (
    configData: ConfigData<any>,
    elementWindowOffset: number,
    windowSize: { height: number, width: number },
    layoutData: LayoutData<any>
): RenderData<any> => {
  console.log('computeRenderData');
    if (layoutData === null || configData === null) {
        return { cellsToRender: [], firstRenderedRowNumber: 0, firstRenderedRowOffset: 0 };
    }
    const cellsToRender: Cell<any>[] = [];
    let firstRenderedRowNumber: null | number = null;
    let firstRenderedRowOffset: null | number = null;

    if (elementWindowOffset !== null) {
        for (const cell of layoutData.cells) {
            const cellTop = elementWindowOffset + cell.offset;
            const cellBottom = cellTop + cell.height;

            const windowBottom = windowSize.height;

            const renderTop = 0 - configData.windowMargin;
            const renderBottom = windowBottom + configData.windowMargin;

            if (cellTop > renderBottom) {
                continue;
            }
            if (cellBottom < renderTop) {
                continue;
            }

            if (firstRenderedRowNumber === null) {
                firstRenderedRowNumber = cell.rowNumber;
            }

            if (cell.rowNumber === firstRenderedRowNumber) {
                firstRenderedRowOffset = firstRenderedRowOffset
                    ? Math.min(firstRenderedRowOffset, cell.offset)
                    : cell.offset;
            }

            cellsToRender.push(cell);
        }
    }

    return { cellsToRender, firstRenderedRowNumber, firstRenderedRowOffset };
};

/** Grid utils */



const rowHeight = 150;

function getColumnCount(gridWidth) { 
  // return memoize(() => {
  //   const columnWidth = rowHeight / 2;
  //   return 2 * Math.round((gridWidth / columnWidth) / 2);
  // });
  const columnWidth = rowHeight / 2;
  return 2 * Math.round((gridWidth / columnWidth) / 2);
}

function getColumnWidth(columnCount: number | null, gridGap: number | null, elementWidth: number | null) {
    if (columnCount === null || gridGap === null || elementWidth === null) {
        return 0;
    }

    const totalGapSpace = (columnCount - 1) * gridGap;
    const columnWidth = Math.round((elementWidth - totalGapSpace) / columnCount);

    return columnWidth;
}

function getGridRowStart(cell: Cell<any>, renderData: RenderData<any> | null) {
    if (renderData === null) {
        return undefined;
    }

    const offset = renderData.firstRenderedRowNumber !== null ? renderData.firstRenderedRowNumber - 1 : 0;
    const gridRowStart = cell.rowNumber - offset + rowOffsets.value.length;

    return `${gridRowStart}`;
}

/** For Parent Component */

function resetGrid(): void {
    bottomReached.value = false;
    loadMoreData();
}

/** Utils */

function isSameElementSize(a: ElementSize, b: ElementSize) {
    return a.width === b.width && a.height === b.height;
}

function getWindowSize(): ElementSize {
    return {
        width: window.innerWidth,
        height: window.innerHeight,
    };
}

function getElementSize(element: Element): ElementSize {
    const rect = element.getBoundingClientRect();
    return {
        width: rect.width,
        height: rect.height,
    };
}

function isSameElementScroll(a: ElementScroll, b: ElementScroll) {
    return a.x === b.x && a.y === b.y;
}

function getWindowScroll(): ElementScroll {
    return {
        x: window.scrollX,
        y: window.scrollY,
    };
}

function getElementOffset(element: Element) {
    return window.scrollY + element.getBoundingClientRect().top;
}

defineExpose({
    resetGrid
});
</script>


<template>
    <div
        ref="virtualGridRef"
        :style="{
            boxSizing: 'border-box',
            height: `${layoutData.totalHeight}px`,
            paddingTop: `${
                renderData !== null && renderData.firstRenderedRowOffset !== null
                    ? renderData.firstRenderedRowOffset + 'px'
                    : '0px'
            }`,
        }"
    >
        <div
            class="grid"
            :style="{
                'display': '-ms-grid',
                'display': 'grid',
                'align-items': 'center',
                'grid-template-columns': `repeat(${configData.columnCount}, 1fr)`,
                'gap': `${configData.gridGap}px`,
            }"
        >
            <div
                v-for="item in renderData.cellsToRender"
                :key="item.id"
                :class="`${item.injected.dateStoreGroupInfo ? 'banner grid-item-wrapper' : 'grid-item-wrapper'}`"
                :style="{
                    'height': `${item.height}px`,
                    'grid-column-start': item.columnNumber,
                    'grid-column-end': item.columnNumber + item.columnSpan,
                    'grid-row-start': getGridRowStart(item, renderData),
                }"
            >
                <component :is="item.injected.dateStoreGroupInfo ? props.headerComponent : props.itemComponent" :item="item" v-on="$listeners" />
            </div>
        </div>
    </div>
</template>

<style scoped>
.grid-item-wrapper {
  height: 150px !important;
}
.banner {
  display: block;
  height: 50px !important;
}
</style>