From 6b6b0d2a5f54200aaf7fed7790d1a6454efe6f10 Mon Sep 17 00:00:00 2001 From: Yury Orlov Date: Wed, 18 Apr 2018 12:23:31 +0300 Subject: [PATCH] chore(vue-core): calculate getter values once (#943) --- .../dx-vue-core/src/plugin-based/action.js | 2 +- .../dx-vue-core/src/plugin-based/constants.js | 1 + .../dx-vue-core/src/plugin-based/getter.js | 51 ++++++++++++++++-- .../src/plugin-based/getter.test.js | 53 +++++++++++++++++++ .../dx-vue-core/src/plugin-based/helpers.js | 30 +++++++++-- .../src/plugin-based/template-connector.js | 2 +- 6 files changed, 131 insertions(+), 8 deletions(-) diff --git a/packages/dx-vue-core/src/plugin-based/action.js b/packages/dx-vue-core/src/plugin-based/action.js index a73c988381..b1b2c3266c 100644 --- a/packages/dx-vue-core/src/plugin-based/action.js +++ b/packages/dx-vue-core/src/plugin-based/action.js @@ -27,7 +27,7 @@ export const Action = { position: () => this.position(), [`${name}Action`]: (params) => { const { action } = this; - const getters = getAvailableGetters( + const { getters } = getAvailableGetters( pluginHost, getterName => pluginHost.get(`${getterName}Getter`, this.plugin), ); diff --git a/packages/dx-vue-core/src/plugin-based/constants.js b/packages/dx-vue-core/src/plugin-based/constants.js index 5a4b488965..d09aa6e352 100644 --- a/packages/dx-vue-core/src/plugin-based/constants.js +++ b/packages/dx-vue-core/src/plugin-based/constants.js @@ -3,3 +3,4 @@ export const POSITION_CONTEXT = Symbol('position'); export const TEMPLATE_HOST_CONTEXT = Symbol('templateHost'); export const RERENDER_TEMPLATE_EVENT = Symbol('rerenderTemplate'); +export const UPDATE_CONNECTION_EVENT = Symbol('updateConnection'); diff --git a/packages/dx-vue-core/src/plugin-based/getter.js b/packages/dx-vue-core/src/plugin-based/getter.js index ec205c307e..04fe41aea6 100644 --- a/packages/dx-vue-core/src/plugin-based/getter.js +++ b/packages/dx-vue-core/src/plugin-based/getter.js @@ -1,9 +1,13 @@ import { getAvailableGetters, + isTrackedDependenciesChanged, getAvailableActions, } from './helpers'; import { PLUGIN_HOST_CONTEXT, POSITION_CONTEXT } from './constants'; +const GLOBAL_SHIFT = 0xffff; + +let globalGetterId = 0; export const Getter = { name: 'Getter', props: { @@ -21,21 +25,50 @@ export const Getter = { pluginHost: { from: PLUGIN_HOST_CONTEXT }, position: { from: POSITION_CONTEXT }, }, + created() { + this.globalId = globalGetterId; + globalGetterId += 1; + this.internalId = 0; + this.generateId(); + }, beforeMount() { const { pluginHost, name } = this; + let lastComputed; + let lastTrackedDependencies = {}; + let lastResult; + let unwatch; + this.plugin = { position: () => this.position(), [`${name}Getter`]: (original) => { const { value, computed } = this; - if (value !== undefined) return value; + if (computed === null) return { id: this.id, value }; + const getGetterValue = getterName => ((getterName === name) ? original : pluginHost.get(`${getterName}Getter`, this.plugin)); - const getters = getAvailableGetters(pluginHost, getGetterValue); + if (computed === lastComputed && + !isTrackedDependenciesChanged(pluginHost, lastTrackedDependencies, getGetterValue)) { + return { id: this.id, value: lastResult }; + } + + const { getters, trackedDependencies } = getAvailableGetters(pluginHost, getGetterValue); const actions = getAvailableActions(pluginHost); - return computed(getters, actions); + + lastComputed = computed; + lastTrackedDependencies = trackedDependencies; + if (unwatch) unwatch(); + unwatch = this.$watch( + () => computed(getters, actions), + (newValue) => { + this.generateId(); + lastResult = newValue; + }, + { immediate: true }, + ); + return { id: this.id, value: lastResult }; }, }; @@ -47,4 +80,16 @@ export const Getter = { render() { return null; }, + methods: { + generateId() { + this.internalId = this.internalId + 1 < GLOBAL_SHIFT ? this.internalId + 1 : 0; + // eslint-disable-next-line no-bitwise + this.id = (this.globalId << GLOBAL_SHIFT) + this.internalId; + }, + }, + watch: { + value() { + this.generateId(); + }, + }, }; diff --git a/packages/dx-vue-core/src/plugin-based/getter.test.js b/packages/dx-vue-core/src/plugin-based/getter.test.js index cab1dbe6cb..0423bd293d 100644 --- a/packages/dx-vue-core/src/plugin-based/getter.test.js +++ b/packages/dx-vue-core/src/plugin-based/getter.test.js @@ -174,4 +174,57 @@ describe('Getter', () => { expect(wrapper.find('h1').text()) .toBe('new'); }); + + it('should return the same instance of the file value in several connections', () => { + const EncapsulatedPlugin = { + render() { + return ( + + + + ); + }, + }; + + const Test = { + props: ['text'], + render() { + let counter = 0; + return ( + + + + { + counter += 1; + return `${this.text}_${counter}`; + }} + /> + + ); + }, + }; + + const wrapper = mount({ + data() { + return { text: 'old' }; + }, + render() { + return ; + }, + }); + + expect(wrapper.find('h2').text()) + .toBe('old_1'); + }); }); diff --git a/packages/dx-vue-core/src/plugin-based/helpers.js b/packages/dx-vue-core/src/plugin-based/helpers.js index 18ee1f1c8f..a176276150 100644 --- a/packages/dx-vue-core/src/plugin-based/helpers.js +++ b/packages/dx-vue-core/src/plugin-based/helpers.js @@ -1,14 +1,38 @@ +import { shallowEqual } from '@devexpress/dx-core'; + export const getAvailableGetters = ( pluginHost, getGetterValue = getterName => pluginHost.get(`${getterName}Getter`), -) => - pluginHost.knownKeys('Getter') +) => { + const trackedDependencies = {}; + const getters = pluginHost.knownKeys('Getter') .reduce((acc, getterName) => { Object.defineProperty(acc, getterName, { - get: () => getGetterValue(getterName), + get: () => { + const boxedGetter = getGetterValue(getterName); + trackedDependencies[getterName] = boxedGetter && boxedGetter.id; + return boxedGetter && boxedGetter.value; + }, }); return acc; }, {}); + return { getters, trackedDependencies }; +}; + +export const isTrackedDependenciesChanged = ( + pluginHost, + prevTrackedDependencies, + getGetterValue = getterName => pluginHost.get(`${getterName}Getter`), +) => { + const trackedDependencies = Object.keys(prevTrackedDependencies) + .reduce((acc, getterName) => { + const boxedGetter = getGetterValue(getterName); + return Object.assign(acc, { + [getterName]: boxedGetter && boxedGetter.id, + }); + }, {}); + return !shallowEqual(prevTrackedDependencies, trackedDependencies); +}; export const getAvailableActions = ( pluginHost, diff --git a/packages/dx-vue-core/src/plugin-based/template-connector.js b/packages/dx-vue-core/src/plugin-based/template-connector.js index 0e7291864d..bcb53e64c8 100644 --- a/packages/dx-vue-core/src/plugin-based/template-connector.js +++ b/packages/dx-vue-core/src/plugin-based/template-connector.js @@ -10,7 +10,7 @@ export const TemplateConnector = { pluginHost: { from: PLUGIN_HOST_CONTEXT }, }, render() { - const getters = getAvailableGetters(this.pluginHost); + const { getters } = getAvailableGetters(this.pluginHost); const actions = getAvailableActions(this.pluginHost); return this.$scopedSlots.default({ getters, actions }); },