Skip to content

Commit e960cd1

Browse files
defccyyx990803
authored andcommitted
enable style merge behavior between parent-child components (fix #3997) (#4138)
* merge style between components * update test case * update style compiler * add paren to style binding code * update background property parsing * introduce interpolation warning and refactor var to const
1 parent 33cf113 commit e960cd1

File tree

9 files changed

+239
-68
lines changed

9 files changed

+239
-68
lines changed

flow/compiler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ declare type ASTElement = {
106106

107107
staticClass?: string;
108108
classBinding?: string;
109+
staticStyle?: string;
109110
styleBinding?: string;
110111
events?: ASTElementHandlers;
111112
nativeEvents?: ASTElementHandlers;

flow/vnode.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ declare interface VNodeData {
3737
tag?: string;
3838
staticClass?: string;
3939
class?: any;
40+
staticStyle?: string;
4041
style?: Array<Object> | Object;
4142
props?: { [key: string]: any };
4243
attrs?: { [key: string]: string };
Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,49 @@
11
/* @flow */
22

3+
import { parseText } from 'compiler/parser/text-parser'
34
import {
4-
getBindingAttr
5+
getAndRemoveAttr,
6+
getBindingAttr,
7+
baseWarn
58
} from 'compiler/helpers'
69

7-
function transformNode (el: ASTElement) {
10+
function transformNode (el: ASTElement, options: CompilerOptions) {
11+
const warn = options.warn || baseWarn
12+
const staticStyle = getAndRemoveAttr(el, 'style')
13+
if (staticStyle) {
14+
if (process.env.NODE_ENV !== 'production') {
15+
const expression = parseText(staticStyle, options.delimiters)
16+
if (expression) {
17+
warn(
18+
`style="${staticStyle}": ` +
19+
'Interpolation inside attributes has been removed. ' +
20+
'Use v-bind or the colon shorthand instead. For example, ' +
21+
'instead of <div style="{{ val }}">, use <div :style="val">.'
22+
)
23+
}
24+
}
25+
el.staticStyle = JSON.stringify(staticStyle)
26+
}
27+
828
const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
929
if (styleBinding) {
1030
el.styleBinding = styleBinding
1131
}
1232
}
1333

1434
function genData (el: ASTElement): string {
15-
return el.styleBinding
16-
? `style:(${el.styleBinding}),`
17-
: ''
35+
let data = ''
36+
if (el.staticStyle) {
37+
data += `staticStyle:${el.staticStyle},`
38+
}
39+
if (el.styleBinding) {
40+
data += `style:(${el.styleBinding}),`
41+
}
42+
return data
1843
}
1944

2045
export default {
46+
staticKeys: ['staticStyle'],
2147
transformNode,
2248
genData
2349
}

src/platforms/web/runtime/modules/style.js

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* @flow */
22

3-
import { cached, extend, camelize, toObject } from 'shared/util'
3+
import { cached, camelize, extend, looseEqual } from 'shared/util'
4+
import { normalizeBindingStyle, getStyle } from 'web/util/style'
45

56
const cssVarRE = /^--/
67
const setProp = (el, name, val) => {
@@ -31,45 +32,39 @@ const normalize = cached(function (prop) {
3132
})
3233

3334
function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
34-
if ((!oldVnode.data || !oldVnode.data.style) && !vnode.data.style) {
35+
const data = vnode.data
36+
const oldData = oldVnode.data
37+
38+
if (!data.staticStyle && !data.style &&
39+
!oldData.staticStyle && !oldData.style) {
3540
return
3641
}
42+
3743
let cur, name
3844
const el: any = vnode.elm
3945
const oldStyle: any = oldVnode.data.style || {}
40-
let style: any = vnode.data.style || {}
41-
42-
// handle string
43-
if (typeof style === 'string') {
44-
el.style.cssText = style
45-
return
46-
}
47-
48-
const needClone = style.__ob__
46+
const style: Object = normalizeBindingStyle(vnode.data.style || {})
47+
vnode.data.style = extend({}, style)
4948

50-
// handle array syntax
51-
if (Array.isArray(style)) {
52-
style = vnode.data.style = toObject(style)
53-
}
49+
const newStyle: Object = getStyle(vnode, true)
5450

55-
// clone the style for future updates,
56-
// in case the user mutates the style object in-place.
57-
if (needClone) {
58-
style = vnode.data.style = extend({}, style)
51+
if (looseEqual(el._prevStyle, newStyle)) {
52+
return
5953
}
6054

6155
for (name in oldStyle) {
62-
if (style[name] == null) {
56+
if (newStyle[name] == null) {
6357
setProp(el, name, '')
6458
}
6559
}
66-
for (name in style) {
67-
cur = style[name]
60+
for (name in newStyle) {
61+
cur = newStyle[name]
6862
if (cur !== oldStyle[name]) {
6963
// ie9 setting to null has no effect, must use empty string
7064
setProp(el, name, cur == null ? '' : cur)
7165
}
7266
}
67+
el._prevStyle = newStyle
7368
}
7469

7570
export default {
Lines changed: 12 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,19 @@
11
/* @flow */
2+
import { hyphenate } from 'shared/util'
3+
import { getStyle } from 'web/util/style'
24

3-
import { hyphenate, toObject } from 'shared/util'
4-
5-
function concatStyleString (former: string, latter: string) {
6-
if (former === '' || latter === '' || former.charAt(former.length - 1) === ';') {
7-
return former + latter
8-
}
9-
return former + ';' + latter
10-
}
11-
12-
function generateStyleText (node) {
13-
const staticStyle = node.data.attrs && node.data.attrs.style
14-
let styles = node.data.style
15-
const parentStyle = node.parent ? generateStyleText(node.parent) : ''
16-
17-
if (!styles && !staticStyle) {
18-
return parentStyle
19-
}
20-
21-
let dynamicStyle = ''
22-
if (styles) {
23-
if (typeof styles === 'string') {
24-
dynamicStyle += styles
25-
} else {
26-
if (Array.isArray(styles)) {
27-
styles = toObject(styles)
28-
}
29-
for (const key in styles) {
30-
dynamicStyle += `${hyphenate(key)}:${styles[key]};`
31-
}
32-
}
5+
function genStyleText (vnode: VNode): string {
6+
let styleText = ''
7+
const style = getStyle(vnode, false)
8+
for (const key in style) {
9+
styleText += `${hyphenate(key)}:${style[key]};`
3310
}
34-
35-
dynamicStyle = concatStyleString(parentStyle, dynamicStyle)
36-
return concatStyleString(dynamicStyle, staticStyle || '')
11+
return styleText.slice(0, -1)
3712
}
3813

39-
export default function renderStyle (node: VNodeWithData): ?string {
40-
const res = generateStyleText(node)
41-
if (res) {
42-
return ` style=${JSON.stringify(res)}`
14+
export default function renderStyle (vnode: VNodeWithData): ?string {
15+
const styleText = genStyleText(vnode)
16+
if (styleText) {
17+
return ` style=${JSON.stringify(styleText)}`
4318
}
4419
}

src/platforms/web/util/style.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/* @flow */
2+
3+
import { cached, extend, toObject } from 'shared/util'
4+
5+
const parseStyleText = cached(function (cssText) {
6+
const rs = {}
7+
if (!cssText) {
8+
return rs
9+
}
10+
const hasBackground = cssText.indexOf('background') >= 0
11+
// maybe with background-image: url(http://xxx) or base64 img
12+
const listDelimiter = hasBackground ? /;(?![^(]*\))/g : ';'
13+
const propertyDelimiter = hasBackground ? /:(.+)/ : ':'
14+
cssText.split(listDelimiter).forEach(function (item) {
15+
if (item) {
16+
var tmp = item.split(propertyDelimiter)
17+
tmp.length > 1 && (rs[tmp[0].trim()] = tmp[1].trim())
18+
}
19+
})
20+
return rs
21+
})
22+
23+
function normalizeStyleData (styleData: Object): Object {
24+
const style = normalizeBindingStyle(styleData.style)
25+
const staticStyle = parseStyleText(styleData.staticStyle)
26+
return extend(extend({}, staticStyle), style)
27+
}
28+
29+
export function normalizeBindingStyle (bindingStyle: any): Object {
30+
if (Array.isArray(bindingStyle)) {
31+
return toObject(bindingStyle)
32+
}
33+
34+
if (typeof bindingStyle === 'string') {
35+
return parseStyleText(bindingStyle)
36+
}
37+
return bindingStyle
38+
}
39+
40+
/**
41+
* parent component style should be after child's
42+
* so that parent component's style could override it
43+
*/
44+
export function getStyle (vnode: VNode, checkChild: boolean): Object {
45+
let data = vnode.data
46+
let parentNode = vnode
47+
let childNode = vnode
48+
49+
data = normalizeStyleData(data)
50+
51+
if (checkChild) {
52+
while (childNode.child) {
53+
childNode = childNode.child._vnode
54+
if (childNode.data) {
55+
data = extend(normalizeStyleData(childNode.data), data)
56+
}
57+
}
58+
}
59+
while ((parentNode = parentNode.parent)) {
60+
if (parentNode.data) {
61+
data = extend(data, normalizeStyleData(parentNode.data))
62+
}
63+
}
64+
return data
65+
}
66+

test/ssr/ssr-string.spec.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ describe('SSR: renderToString', () => {
102102
}
103103
}, result => {
104104
expect(result).toContain(
105-
'<div server-rendered="true" style="font-size:14px;color:red;background-color:black"></div>'
105+
'<div server-rendered="true" style="background-color:black;font-size:14px;color:red"></div>'
106106
)
107107
done()
108108
})
@@ -143,13 +143,13 @@ describe('SSR: renderToString', () => {
143143

144144
it('nested custom component style', done => {
145145
renderVmWithOptions({
146-
template: '<comp :style="style"></comp>',
146+
template: '<comp style="color: blue" :style="style"></comp>',
147147
data: {
148148
style: 'color:red'
149149
},
150150
components: {
151151
comp: {
152-
template: '<nested style="font-size:520rem"></nested>',
152+
template: '<nested style="text-align: left;" :style="{fontSize:\'520rem\'}"></nested>',
153153
components: {
154154
nested: {
155155
template: '<div></div>'
@@ -159,7 +159,7 @@ describe('SSR: renderToString', () => {
159159
}
160160
}, result => {
161161
expect(result).toContain(
162-
'<div server-rendered="true" style="color:red;font-size:520rem"></div>'
162+
'<div server-rendered="true" style="text-align:left;font-size:520rem;color:red"></div>'
163163
)
164164
done()
165165
})

0 commit comments

Comments
 (0)