Value as Attribute and Property with Vue.js
Tags: html, javascript, vue
Published: 2020-05-17 01:49:01

Attribute and property are confusing concepts in web development, and for value, because of its particularity, are more easy to puzzle us, I'm trying to distinguish them with examples
<!-- more -->

## Concept of Attribute and Property

To put it simplely, attribute is for element tag, and property is for element object, such as:
```
<input id="input" value="test value">
<script>
let input = document.getElementById('input');
console.log(input.getAttribute('value')); // test value
console.log(input.value); // test value
</script>
```
- Value attribute of input is defined through `value="test value"` of tag, and can be gotten via `input.getAttribute('value')`, and can be updated via `input.setAttribute('value', 'New Value')`
- Value property of input can be gotten and updated via `input.value`，and the initial value is the same as attribute


## Binding of Attribute and Property

At the very beginning, if the attribute value is updated, the property value will be changed too.

But if property value is updated (input in the input element or assign new value to `input.value`), the attribute value will not be changed, further more, if attribute value is updated again now, the property value will not be changed with it, such as this [animation][gif-value] shown, and you can also try to operate in this [page][codepen-value]

Actually, the [dirty value flag][dirty-value-flag] is taking effect. the initial value of `dirty value flag` is `false`, namely update of attribute value will be changed, but once property value is updated by user interaction, value of dirty value flag will become true, namely update of attribute value will not affect corresponding property value

So in the real world project, we're generally handling value as property


## Value handled by Vue.js 

### Usually use :value

`v-bind` of Vue.js, is handling attribute normally, if need to be handled as property, we need to add `.prop`

But `v-bind:value` are mostly handling property value by default, because it's transformed forcibly
```
<input id="input" :value="'test value'" >
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let input = new Vue({
  el: '#input',
  mounted () {
    console.log(this.$el.getAttribute('value')); // null
    console.log(this.$el.value); // test value
    console.log(this._vnode.data) // {attrs: {id: "input"}, domProps: {value: "test value"}}
  }
});
</script>
```
Visible, Vue.js treat value as domProps of VNode's data, not attrs, so it will become value as property after mounted

In the source code of Vue.js, the handling of forcibly transfer property is as following
```
// src/compiler/parser/index.js
function processAttrs (el) {
...
        if ((modifiers && modifiers.prop) || (
          !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
        )) {
          addProp(el, name, value, list[i], isDynamic)
        } else {
          addAttr(el, name, value, list[i], isDynamic)
        }
```
inside it, definition of `platformMustUseProp` in web platform is as following:
```
// src/platforms/web/util/attrs.js
const acceptValue = makeMap('input,textarea,option,select,progress')
export const mustUseProp = (tag: string, type: ?string, attr: string): boolean => {
  return (
    (attr === 'value' && acceptValue(tag)) && type !== 'button' ||
    (attr === 'selected' && tag === 'option') ||
    (attr === 'checked' && tag === 'input') ||
    (attr === 'muted' && tag === 'video')
  )
}
```
From the foregoing, value of input whose type is not button, textarea, option, select and progress will forcibly to be property, without the need to set `:value.prop`

For example, [textarea][mdn-textarea] tab does not support value attribute, so the value of following code will not shown in the textarea
```
<textarea value="test value"></textarea>
```
But in Vue.js, the following code can successfully bind to value property and show in the textarea
```
<textarea :value="'test value'"></textarea>
```

### Explicitly use :value.prop

One more thing deserves attention in the above Vue.js source code is that, to be treated forcibly as property, `!el.component` need to be met, namely it's not a dynamical component, because el.component of dynamical component will be its `is` attribute value

Namely `v-bind` of dynamical component will be treated as attribute, if need to be as property, the `.prop` need to be added, such as:
```
<div id="app">
  <component :is="element" :value.prop="'test value'"></component>
  <button @click="change">Change</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let app = new Vue({
  el: '#app',
  data: {
    element: 'input'
  },
  methods: {
    change () {
      this.element = 'input' === this.element ? 'textarea' : 'input';
    }
  }
});
</script>
```
If in the above `component`, substitute `:value.prop` with `.prop`, when switch to `textarea`, the value will not show in the textarea, you can click to switch tab to see it in this [page][codepen-textarea]

## Summary
- The binding relationship of value as attribute and property will invalid after property value update by user interaction
- Vue.js usually use `:value` to treat value as property
- Dynamic component of Vue.js need to use `:value.prop` to treat value as property

[gif-value]: https://chanvinxiao.com/cn/blog/value-as-attribute-and-property-with-vuejs/value.gif
[codepen-value]: https://codepen.io/chanvin/pen/RwPzdMd?editors=1010
[mdn-textarea]: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/textarea
[codepen-textarea]: https://codepen.io/chanvin/pen/KKpjYmy?editors=1010
[dirty-value-flag]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty