Skip to content

Commit c700c67

Browse files
committed
docs (#149): add attribute coercion section to migration guide
1 parent 60f7dfd commit c700c67

File tree

2 files changed

+136
-1
lines changed

2 files changed

+136
-1
lines changed

src/.vuepress/config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ const sidebar = {
100100
'migration/fragments',
101101
'migration/render-function-api',
102102
'migration/slots-unification',
103-
'migration/keycode-modifiers'
103+
'migration/keycode-modifiers',
104+
'migration/attribute-coercion'
104105
]
105106
},
106107
{
+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Attribute Coercion Behavior
2+
3+
::: info Info
4+
This is a low-level internal API change and does not affect most developers.
5+
:::
6+
7+
## Overview
8+
9+
Here is a high level summary of the changes:
10+
11+
- Drop the internal concept of enumerated attributes and treat those attributes the same as normal non-boolean attributes
12+
- **BREAKING**: No longer removes attribute if value is boolean false. Instead, it's set as attr="false" instead. To remove the attribute, use null or undefined.
13+
14+
For more information, read on!
15+
16+
## 2.x Syntax
17+
18+
In 2.x, we had the following strategies for coercing `v-bind` values:
19+
20+
- For some attribute/element pairs, Vue is always using the corresponding IDL attribute (property): [like `value` of `<input>`, `<select>`, `<progress>`, etc](https://github.com/vuejs/vue/blob/bad3c326a3f8b8e0d3bcf07917dc0adf97c32351/src/platforms/web/util/attrs.js#L11-L18).
21+
22+
- For “[boolean attributes](https://github.com/vuejs/vue/blob/bad3c326a3f8b8e0d3bcf07917dc0adf97c32351/src/platforms/web/util/attrs.js#L33-L40)” and [xlinks](https://github.com/vuejs/vue/blob/bad3c326a3f8b8e0d3bcf07917dc0adf97c32351/src/platforms/web/util/attrs.js#L44-L46), Vue removes them if they are “falsy” ([`undefined`, `null` or `false`](https://github.com/vuejs/vue/blob/bad3c326a3f8b8e0d3bcf07917dc0adf97c32351/src/platforms/web/util/attrs.js#L52-L54)) and adds them otherwise (see [here](https://github.com/vuejs/vue/blob/bad3c326a3f8b8e0d3bcf07917dc0adf97c32351/src/platforms/web/runtime/modules/attrs.js#L66-L77) and [here](https://github.com/vuejs/vue/blob/bad3c326a3f8b8e0d3bcf07917dc0adf97c32351/src/platforms/web/runtime/modules/attrs.js#L81-L85)).
23+
24+
- For “[enumerated attributes](https://github.com/vuejs/vue/blob/bad3c326a3f8b8e0d3bcf07917dc0adf97c32351/src/platforms/web/util/attrs.js#L20)” (currently `contenteditable`, `draggable` and `spellcheck`), Vue tries to [coerce](https://github.com/vuejs/vue/blob/bad3c326a3f8b8e0d3bcf07917dc0adf97c32351/src/platforms/web/util/attrs.js#L24-L31) them to string (with special treatment for `contenteditable` for now, to fix [vuejs/vue#9397](https://github.com/vuejs/vue/issues/9397)).
25+
26+
- For other attributes, we remove “falsy” values (`undefined`, `null`, or `false`) and set other values as-is (see [here](https://github.com/vuejs/vue/blob/bad3c326a3f8b8e0d3bcf07917dc0adf97c32351/src/platforms/web/runtime/modules/attrs.js#L92-L113)).
27+
28+
The following table describes how Vue coerce “enumerated attributes” differently with normal non-boolean attributes:
29+
30+
| Binding expr. | `foo` <sup>normal</sup> | `draggable` <sup>enumerated</sup> |
31+
| ------------------- | ----------------------- | --------------------------------- |
32+
| `:attr="null"` | / | `draggable="false"` |
33+
| `:attr="undefined"` | / | / |
34+
| `:attr="true"` | `foo="true"` | `draggable="true"` |
35+
| `:attr="false"` | / | `draggable="false"` |
36+
| `:attr="0"` | `foo="0"` | `draggable="true"` |
37+
| `attr=""` | `foo=""` | `draggable="true"` |
38+
| `attr="foo"` | `foo="foo"` | `draggable="true"` |
39+
| `attr` | `foo=""` | `draggable="true"` |
40+
41+
We can see from the table above, current implementation coerces `true` to `'true'` but removes the attribute if it's `false`. This also led to inconsistency and required users to manually coerce boolean values to string in very common use cases like `aria-*` attributes like `aria-selected`, `aria-hidden`, etc.
42+
43+
## 3.x Syntax
44+
45+
We intend to drop this internal concept of “enumerated attributes” and treat them as normal non-boolean HTML attributes.
46+
47+
- This solves the inconsistency between normal non-boolean attributes and “enumerated attributes”
48+
- It also makes it possible to use values other than `'true'` and `'false'`, or even keywords yet to come, for attributes like `contenteditable`
49+
50+
For non-boolean attributes, Vue will stop removing them if they are `false` and coerce them to `'false'` instead.
51+
52+
- This solves the inconsistency between `true` and `false` and makes outputing `aria-*` attributes easier
53+
54+
The following table describes the new behavior:
55+
56+
| Binding expr. | `foo` <sup>normal</sup> | `draggable` <sup>enumerated</sup> |
57+
| ------------------- | -------------------------- | --------------------------------- |
58+
| `:attr="null"` | / | / <sup>†</sup> |
59+
| `:attr="undefined"` | / | / |
60+
| `:attr="true"` | `foo="true"` | `draggable="true"` |
61+
| `:attr="false"` | `foo="false"` <sup>†</sup> | `draggable="false"` |
62+
| `:attr="0"` | `foo="0"` | `draggable="0"` <sup>†</sup> |
63+
| `attr=""` | `foo=""` | `draggable=""` <sup>†</sup> |
64+
| `attr="foo"` | `foo="foo"` | `draggable="foo"` <sup>†</sup> |
65+
| `attr` | `foo=""` | `draggable=""` <sup>†</sup> |
66+
67+
<small>†: changed</small>
68+
69+
Coercion for boolean attributes is left untouched.
70+
71+
## Migration Strategy
72+
73+
### Enumerated attributes
74+
75+
The absence of an enumerated attribute and `attr="false"` may produce different IDL attribute values (which will reflect the actual state), described as follows:
76+
77+
| Absent enumerated attr | IDL attr & value |
78+
| ---------------------- | ------------------------------------ |
79+
| `contenteditable` | `contentEditable` &rarr; `'inherit'` |
80+
| `draggable` | `draggable` &rarr; `false` |
81+
| `spellcheck` | `spellcheck` &rarr; `true` |
82+
83+
To keep the old behavior work, and as we will be coercing `false` to `'false'`, in 3.x Vue developers need to make `v-bind` expression resolve to `false` or `'false'` for `contenteditable` and `spellcheck`.
84+
85+
In 2.x, invalid values were coerced to `'true'` for enumerated attributes. This was usually unintended and unlikely to be relied upon on a large scale. In 3.x `true` or `'true'` should be explicitly specified.
86+
87+
### Coercing `false` to `'false'` instead of removing the attribute
88+
89+
In 3.x, `null` or `undefined` should be used to explicitly remove an attribute.
90+
91+
### Comparison between 2.x & 3.x behavior
92+
93+
<table>
94+
<thead>
95+
<tr>
96+
<th>Attribute</th>
97+
<th><code>v-bind</code> value <sup>2.x</sup></th>
98+
<th><code>v-bind</code> value <sup>3.x</sup></th>
99+
<th>HTML output</th>
100+
</tr>
101+
</thead>
102+
<tbody>
103+
<tr>
104+
<td rowspan="3">2.x “Enumerated attrs”<br><small>i.e. <code>contenteditable</code>, <code>draggable</code> and <code>spellcheck</code>.</small></td>
105+
<td><code>undefined</code>, <code>false</code></td>
106+
<td><code>undefined</code>, <code>null</code></td>
107+
<td><i>removed</i></td>
108+
</tr>
109+
<tr>
110+
<td>
111+
<code>true</code>, <code>'true'</code>, <code>''</code>, <code>1</code>,
112+
<code>'foo'</code>
113+
</td>
114+
<td><code>true</code>, <code>'true'</code></td>
115+
<td><code>"true"</code></td>
116+
</tr>
117+
<tr>
118+
<td><code>null</code>, <code>'false'</code></td>
119+
<td><code>false</code>, <code>'false'</code></td>
120+
<td><code>"false"</code></td>
121+
</tr>
122+
<tr>
123+
<td rowspan="2">Other non-boolean attrs<br><small>eg. <code>aria-checked</code>, <code>tabindex</code>, <code>alt</code>, etc.</small></td>
124+
<td><code>undefined</code>, <code>null</code>, <code>false</code></td>
125+
<td><code>undefined</code>, <code>null</code></td>
126+
<td><i>removed</i></td>
127+
</tr>
128+
<tr>
129+
<td><code>'false'</code></td>
130+
<td><code>false</code>, <code>'false'</code></td>
131+
<td><code>"false"</code></td>
132+
</tr>
133+
</tbody>
134+
</table>

0 commit comments

Comments
 (0)