Skip to content
Permalink
Browse files
fix(perf): avoid useless re-renders of component on parent update (#4825
)
  • Loading branch information
jacobmllr95 committed May 11, 2020
1 parent 260ef72 commit 2cb3fe0fa822a8284e023ccf71f8e451f124016a
Show file tree
Hide file tree
Showing 27 changed files with 931 additions and 334 deletions.
@@ -27,6 +27,7 @@ import { isLocaleRTL } from '../../utils/locale'
import { mathMax } from '../../utils/math'
import { toInteger } from '../../utils/number'
import { toString } from '../../utils/string'
import attrsMixin from '../../mixins/attrs'
import idMixin from '../../mixins/id'
import normalizeSlotMixin from '../../mixins/normalize-slot'
import {
@@ -56,7 +57,8 @@ export const STR_NARROW = 'narrow'
// @vue/component
export const BCalendar = Vue.extend({
name: NAME,
mixins: [idMixin, normalizeSlotMixin],
// Mixin order is important!
mixins: [attrsMixin, idMixin, normalizeSlotMixin],
model: {
// Even though this is the default that Vue assumes, we need
// to add it for the docs to reflect that this is the model
@@ -271,6 +273,27 @@ export const BCalendar = Vue.extend({
}
},
computed: {
valueId() {
return this.safeId()
},
widgetId() {
return this.safeId('_calendar-wrapper_')
},
navId() {
return this.safeId('_calendar-nav_')
},
gridId() {
return this.safeId('_calendar-grid_')
},
gridCaptionId() {
return this.safeId('_calendar-grid-caption_')
},
gridHelpId() {
return this.safeId('_calendar-grid-help_')
},
activeId() {
return this.activeYMD ? this.safeId(`_cell-${this.activeYMD}_`) : null
},
// TODO: Use computed props to convert `YYYY-MM-DD` to `Date` object
selectedDate() {
// Selected as a `Date` object
@@ -771,24 +794,28 @@ export const BCalendar = Vue.extend({
}
},
render(h) {
// If hidden prop is set, render just a placeholder node
// If `hidden` prop is set, render just a placeholder node
if (this.hidden) {
return h()
}

const { isLive, isRTL, activeYMD, selectedYMD, safeId } = this
const {
valueId,
widgetId,
navId,
gridId,
gridCaptionId,
gridHelpId,
activeId,
isLive,
isRTL,
activeYMD,
selectedYMD,
safeId
} = this
const hideDecadeNav = !this.showDecadeNav
const todayYMD = formatYMD(this.getToday())
const highlightToday = !this.noHighlightToday
// Pre-compute some IDs
// This should be computed props
const idValue = safeId()
const idWidget = safeId('_calendar-wrapper_')
const idNav = safeId('_calendar-nav_')
const idGrid = safeId('_calendar-grid_')
const idGridCaption = safeId('_calendar-grid-caption_')
const idGridHelp = safeId('_calendar-grid-help_')
const idActive = activeYMD ? safeId(`_cell-${activeYMD}_`) : null

// Header showing current selected date
let $header = h(
@@ -797,8 +824,8 @@ export const BCalendar = Vue.extend({
staticClass: 'form-control form-control-sm text-center',
class: { 'text-muted': this.disabled, readonly: this.readonly || this.disabled },
attrs: {
id: idValue,
for: idGrid,
id: valueId,
for: gridId,
role: 'status',
tabindex: this.disabled ? null : '-1',
// Mainly for testing purposes, as we do not know
@@ -885,11 +912,11 @@ export const BCalendar = Vue.extend({
{
staticClass: 'b-calendar-nav d-flex',
attrs: {
id: idNav,
id: navId,
role: 'group',
'aria-hidden': this.disabled ? 'true' : null,
'aria-label': this.labelNav || null,
'aria-controls': idGrid
'aria-controls': gridId
}
},
[
@@ -957,7 +984,7 @@ export const BCalendar = Vue.extend({
staticClass: 'b-calendar-grid-caption text-center font-weight-bold',
class: { 'text-muted': this.disabled },
attrs: {
id: idGridCaption,
id: gridCaptionId,
'aria-live': isLive ? 'polite' : null,
'aria-atomic': isLive ? 'true' : null
}
@@ -1077,7 +1104,7 @@ export const BCalendar = Vue.extend({
{
staticClass: 'b-calendar-grid-help border-top small text-muted text-center bg-light',
attrs: {
id: idGridHelp
id: gridHelpId
}
},
[h('div', { staticClass: 'small' }, this.labelHelp)]
@@ -1089,18 +1116,18 @@ export const BCalendar = Vue.extend({
ref: 'grid',
staticClass: 'b-calendar-grid form-control h-auto text-center',
attrs: {
id: idGrid,
id: gridId,
role: 'application',
tabindex: this.disabled ? null : '0',
'data-month': activeYMD.slice(0, -3), // `YYYY-MM`, mainly for testing
'aria-roledescription': this.labelCalendar || null,
'aria-labelledby': idGridCaption,
'aria-describedby': idGridHelp,
'aria-labelledby': gridCaptionId,
'aria-describedby': gridHelpId,
// `aria-readonly` is not considered valid on `role="application"`
// https://www.w3.org/TR/wai-aria-1.1/#aria-readonly
// 'aria-readonly': this.readonly && !this.disabled ? 'true' : null,
'aria-disabled': this.disabled ? 'true' : null,
'aria-activedescendant': idActive
'aria-activedescendant': activeId
},
on: {
keydown: this.onKeydownGrid,
@@ -1121,7 +1148,7 @@ export const BCalendar = Vue.extend({
staticClass: 'b-calendar-inner',
style: this.block ? {} : { width: this.width },
attrs: {
id: idWidget,
id: widgetId,
dir: isRTL ? 'rtl' : 'ltr',
lang: this.computedLocale || null,
role: 'group',
@@ -1133,9 +1160,9 @@ export const BCalendar = Vue.extend({
'aria-describedby': [
// Should the attr (if present) go last?
// Or should this attr be a prop?
this.$attrs['aria-describedby'],
idValue,
idGridHelp
this.bvAttrs['aria-describedby'],
valueId,
gridHelpId
]
.filter(identity)
.join(' ')
@@ -1,4 +1,5 @@
import Vue from '../../utils/vue'
import attrsMixin from '../../mixins/attrs'
import normalizeSlotMixin from '../../mixins/normalize-slot'

export const props = {
@@ -27,14 +28,24 @@ export const props = {
// @vue/component
export const BDropdownItemButton = /*#__PURE__*/ Vue.extend({
name: 'BDropdownItemButton',
mixins: [normalizeSlotMixin],
mixins: [attrsMixin, normalizeSlotMixin],
inheritAttrs: false,
inject: {
bvDropdown: {
default: null
}
},
props,
computed: {
computedAttrs() {
return {
...this.bvAttrs,
role: 'menuitem',
type: 'button',
disabled: this.disabled
}
}
},
methods: {
closeDropdown() {
if (this.bvDropdown) {
@@ -59,12 +70,7 @@ export const BDropdownItemButton = /*#__PURE__*/ Vue.extend({
[`text-${this.variant}`]: this.variant && !(this.active || this.disabled)
}
],
attrs: {
...this.$attrs,
role: 'menuitem',
type: 'button',
disabled: this.disabled
},
attrs: this.computedAttrs,
on: { click: this.onClick },
ref: 'button'
},
@@ -1,5 +1,6 @@
import Vue from '../../utils/vue'
import { requestAF } from '../../utils/dom'
import attrsMixin from '../../mixins/attrs'
import normalizeSlotMixin from '../../mixins/normalize-slot'
import { BLink, propsFactory as linkPropsFactory } from '../link/link'

@@ -8,7 +9,7 @@ export const props = linkPropsFactory()
// @vue/component
export const BDropdownItem = /*#__PURE__*/ Vue.extend({
name: 'BDropdownItem',
mixins: [normalizeSlotMixin],
mixins: [attrsMixin, normalizeSlotMixin],
inheritAttrs: false,
inject: {
bvDropdown: {
@@ -26,6 +27,14 @@ export const BDropdownItem = /*#__PURE__*/ Vue.extend({
default: null
}
},
computed: {
computedAttrs() {
return {
...this.bvAttrs,
role: 'menuitem'
}
}
},
methods: {
closeDropdown() {
// Close on next animation frame to allow <b-link> time to process
@@ -53,7 +62,7 @@ export const BDropdownItem = /*#__PURE__*/ Vue.extend({
[`text-${this.variant}`]: this.variant && !(this.active || this.disabled)
}
],
attrs: { ...this.$attrs, role: 'menuitem' },
attrs: this.computedAttrs,
on: { click: this.onClick },
ref: 'item'
},
@@ -6,6 +6,7 @@ import { isFile, isFunction, isUndefinedOrNull } from '../../utils/inspect'
import { File } from '../../utils/safe-types'
import { toString } from '../../utils/string'
import { warn } from '../../utils/warn'
import attrsMixin from '../../mixins/attrs'
import formCustomMixin from '../../mixins/form-custom'
import formMixin from '../../mixins/form'
import formStateMixin from '../../mixins/form-state'
@@ -26,7 +27,7 @@ const isValidValue = value => isFile(value) || (isArray(value) && value.every(v
// @vue/component
export const BFormFile = /*#__PURE__*/ Vue.extend({
name: NAME,
mixins: [idMixin, formMixin, formStateMixin, formCustomMixin, normalizeSlotMixin],
mixins: [attrsMixin, idMixin, formMixin, formStateMixin, formCustomMixin, normalizeSlotMixin],
inheritAttrs: false,
model: {
prop: 'value',
@@ -127,6 +128,22 @@ export const BFormFile = /*#__PURE__*/ Vue.extend({
? toString(this.fileNameFormatter(files))
: files.map(file => file.name).join(', ')
}
},
computedAttrs() {
return {
...this.bvAttrs,
type: 'file',
id: this.safeId(),
name: this.name,
disabled: this.disabled,
required: this.required,
form: this.form || null,
capture: this.capture || null,
accept: this.accept || null,
multiple: this.multiple,
webkitdirectory: this.directory,
'aria-required': this.required ? 'true' : null
}
}
},
watch: {
@@ -290,20 +307,7 @@ export const BFormFile = /*#__PURE__*/ Vue.extend({
},
this.stateClass
],
attrs: {
...this.$attrs,
type: 'file',
id: this.safeId(),
name: this.name,
disabled: this.disabled,
required: this.required,
form: this.form || null,
capture: this.capture || null,
accept: this.accept || null,
multiple: this.multiple,
webkitdirectory: this.directory,
'aria-required': this.required ? 'true' : null
},
attrs: this.computedAttrs,
on: {
change: this.onFileChange,
focusin: this.focusHandler,

0 comments on commit 2cb3fe0

Please sign in to comment.