mirror of
https://gitee.com/vuejs/vue.git
synced 2024-12-02 03:57:36 +08:00
feat: auto cache inline prop literals to avoid child re-render
This commit is contained in:
parent
f493715f39
commit
996eb00a0a
@ -69,6 +69,7 @@ declare interface Component {
|
||||
_staticTrees: ?Array<VNode>; // v-once cached trees
|
||||
_hasHookEvent: boolean;
|
||||
_provided: ?Object;
|
||||
_inlineComputed: ?{ [key: string]: Watcher }; // inline computed watchers for literal props
|
||||
|
||||
// private methods
|
||||
|
||||
@ -129,6 +130,8 @@ declare interface Component {
|
||||
_k: (eventKeyCode: number, key: string, builtInAlias?: number | Array<number>, eventKeyName?: string) => ?boolean;
|
||||
// resolve scoped slots
|
||||
_u: (scopedSlots: ScopedSlotsData, res?: Object) => { [key: string]: Function };
|
||||
// create / return value from inline computed
|
||||
_a: (id: number, getter: Function) => any;
|
||||
|
||||
// SSR specific
|
||||
_ssrNode: Function;
|
||||
|
@ -29,16 +29,20 @@ const argRE = /:(.*)$/
|
||||
const bindRE = /^:|^v-bind:/
|
||||
const modifierRE = /\.[^.]+/g
|
||||
|
||||
const literalValueRE = /^(\{.*\}|\[.*\])$/
|
||||
|
||||
const decodeHTMLCached = cached(he.decode)
|
||||
|
||||
// configurable state
|
||||
export let warn: any
|
||||
let literalPropId
|
||||
let delimiters
|
||||
let transforms
|
||||
let preTransforms
|
||||
let postTransforms
|
||||
let platformIsPreTag
|
||||
let platformMustUseProp
|
||||
let platformIsReservedTag
|
||||
let platformGetTagNamespace
|
||||
|
||||
type Attr = { name: string; value: string };
|
||||
@ -66,9 +70,11 @@ export function parse (
|
||||
options: CompilerOptions
|
||||
): ASTElement | void {
|
||||
warn = options.warn || baseWarn
|
||||
literalPropId = 0
|
||||
|
||||
platformIsPreTag = options.isPreTag || no
|
||||
platformMustUseProp = options.mustUseProp || no
|
||||
platformIsReservedTag = options.isReservedTag || no
|
||||
platformGetTagNamespace = options.getTagNamespace || no
|
||||
|
||||
transforms = pluckModuleFunction(options.modules, 'transformNode')
|
||||
@ -529,6 +535,15 @@ function processAttrs (el) {
|
||||
)
|
||||
}
|
||||
}
|
||||
// optimize literal values in component props by wrapping them
|
||||
// in an inline watcher to avoid unnecessary re-renders
|
||||
if (
|
||||
!platformIsReservedTag(el.tag) &&
|
||||
el.tag !== 'slot' &&
|
||||
literalValueRE.test(value.trim())
|
||||
) {
|
||||
value = `_a(${literalPropId++},function(){return ${value}})`
|
||||
}
|
||||
if (isProp || (
|
||||
!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
|
||||
)) {
|
||||
|
28
src/core/instance/render-helpers/create-inline-computed.js
Normal file
28
src/core/instance/render-helpers/create-inline-computed.js
Normal file
@ -0,0 +1,28 @@
|
||||
/* @flow */
|
||||
|
||||
import { noop } from 'shared/util'
|
||||
import Watcher from 'core/observer/watcher'
|
||||
|
||||
/**
|
||||
* This runtime helper creates an inline computed property for component
|
||||
* props that contain object or array literals. The caching ensures the same
|
||||
* object/array is returned unless the value has indeed changed, thus avoiding
|
||||
* the child component to always re-render when comparing props values.
|
||||
*
|
||||
* Installed to the instance as _a, requires special handling in parser that
|
||||
* transforms the following
|
||||
* <foo :bar="{ a: 1 }"/>
|
||||
* to:
|
||||
* <foo :bar="_a(0, function(){return { a: 1 }})"
|
||||
*/
|
||||
export function createInlineComputed (id: string, getter: Function): any {
|
||||
const vm: Component = this
|
||||
const watchers = vm._inlineComputed || (vm._inlineComputed = {})
|
||||
const cached = watchers[id]
|
||||
if (cached) {
|
||||
return cached.value
|
||||
} else {
|
||||
watchers[id] = new Watcher(vm, getter, noop, { sync: true })
|
||||
return watchers[id].value
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import { bindObjectProps } from './bind-object-props'
|
||||
import { renderStatic, markOnce } from './render-static'
|
||||
import { bindObjectListeners } from './bind-object-listeners'
|
||||
import { resolveScopedSlots } from './resolve-slots'
|
||||
import { createInlineComputed } from './create-inline-computed'
|
||||
|
||||
export function installRenderHelpers (target: any) {
|
||||
target._o = markOnce
|
||||
@ -27,4 +28,5 @@ export function installRenderHelpers (target: any) {
|
||||
target._e = createEmptyVNode
|
||||
target._u = resolveScopedSlots
|
||||
target._g = bindObjectListeners
|
||||
target._a = createInlineComputed
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ export function proxy (target: Object, sourceKey: string, key: string) {
|
||||
|
||||
export function initState (vm: Component) {
|
||||
vm._watchers = []
|
||||
vm._inlineComputed = null
|
||||
const opts = vm.$options
|
||||
if (opts.props) initProps(vm, opts.props)
|
||||
if (opts.methods) initMethods(vm, opts.methods)
|
||||
|
@ -529,4 +529,38 @@ describe('Options props', () => {
|
||||
expect(`Invalid key "reqquired" in validation rules object for prop "value".`).toHaveBeenWarned()
|
||||
expect(`Invalid key "deafult" in validation rules object for prop "count".`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('should not trigger re-render on non-changed inline literals', done => {
|
||||
const updated = jasmine.createSpy('updated')
|
||||
const vm = new Vue({
|
||||
data: {
|
||||
n: 1,
|
||||
m: 1
|
||||
},
|
||||
template: `
|
||||
<div id="app">
|
||||
{{ n }} {{ m }} <foo :a="{ n: 1 }" :b="{ n: n }"/>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
foo: {
|
||||
props: ['a', 'b'],
|
||||
updated,
|
||||
template: `<div>{{ a.n }} {{ b.n }}</div>`
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
|
||||
expect(vm.$el.textContent).toContain('1 1 1 1')
|
||||
vm.n++ // literals that actually contain changed reactive data should trigger update
|
||||
waitForUpdate(() => {
|
||||
expect(vm.$el.textContent).toContain('2 1 1 2')
|
||||
expect(updated.calls.count()).toBe(1)
|
||||
}).then(() => {
|
||||
vm.m++ // changing data that does not affect any literals should not trigger update
|
||||
}).then(() => {
|
||||
expect(vm.$el.textContent).toContain('2 2 1 2')
|
||||
expect(updated.calls.count()).toBe(1)
|
||||
}).then(done)
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user