原创

优雅的给vue对象设置初始值


vue 中经常定义很多data ,在用户进行一些操作后,需要将data中的某个对象定义为初始值,最常见的为表单数据

初始化指定对象

form: {
	title: '',
	desc: '',
	url: ''
}

大众都会选择以下方法进行初始化值

this.form = {
	title: '',
	desc: '',
	url: ''
}

其实还可以这样

// Object.assign(this.form, this.$options.data().form)
this.form = this.$options.data().form

示例代码

<template>
  <div>
    <button @click="setData">setData</button>
    <div>
      title:
      <input type="text" v-model="form.title" placeholder="title">
    </div>
    <div>
      desc:
      <input type="text" v-model="form.desc" placeholder="desc">
    </div>
    <div>
      url:
      <input type="text" v-model="form.url" placeholder="url">
    </div>
    <div>
      <button @click="initFn1">初始化方法1</button>
      <button @click="initFn2">初始化方法2</button>
    </div>
  </div>
</template>
<script>
export default {
  name: 't3',
  data() {
    return {
      form: {
        title: '',
        desc: '',
        url: ''
      }
    }
  },
  methods: {
    setData(){
      this.form = {
        title: '零三的笔记',
        desc: 'web03',
        url: 'web03.cn'
      }
    },
    initFn1(){
      this.form = {
        title: '',
        desc: '',
        url: ''
      }
    },
    initFn2(){
      //Object.assign(this.form, this.$options.data().form)
	  this.form = this.$options.data().form
    }
  }
}
</script>
<style scoped>
div{
  padding: 5px 20px;
}
</style>

初始化整个data

对于初始化整个data,如果还是一个一个data属性进行赋值,那这难免太low了,可以直接使用Object.assign进行初始化整个data

关键代码

Object.assign(this.$data, this.$options.data())

示例

<template>
  <div>
    <button @click="setData">setData</button>
    <div>
      title:
      <input type="text" v-model="form.title" placeholder="title">
    </div>
    <div>
      desc:
      <input type="text" v-model="form.desc" placeholder="desc">
    </div>
    <div>
      url:
      <input type="text" v-model="form.url" placeholder="url">
    </div>
    <div>
      msg: <span>{{otherData.msg}}</span>
    </div>
    <div>
      <button @click="initFn">初始化data</button>
    </div>
  </div>
</template>
<script>
export default {
  name: 't3',
  data() {
    return {
      form: {
        title: '',
        desc: '',
        url: ''
      },
      otherData: {
        msg: ''
      }
    }
  },
  methods: {
    setData(){
      this.form = {
        title: '零三的笔记',
        desc: 'web03',
        url: 'web03.cn'
      }
      this.otherData.msg = 'msg data'
    },
    initFn(){
      Object.assign(this.$data, this.$options.data())
    }
  }
}
</script>
<style scoped>
div{
  padding: 5px 20px;
}
</style>

同样支持初始化form新增的属性

Objeact.assign初始化form的弊端

个人发现Objeact.assign在原data数据进行增删新属性之后,会出现数据遗留现象,以下案例

<template>
  <div>
    <button @click="setData">setData</button>
    <div>
      title:
      <input type="text" v-model="form.title" placeholder="title">
    </div>
    <div>
      desc:
      <input type="text" v-model="form.desc" placeholder="desc">
    </div>
    <div>
      url:
      <input type="text" v-model="form.url" placeholder="url">
    </div>
    <div v-if="form.attr">
      attr:{{form.attr}}
    </div>
    <div>
      <button @click="setAttribute">set attribute</button>
      <button @click="initFn">初始化data</button>
    </div>
  </div>
</template>
<script>
export default {
  name: 't3',
  data() {
    return {
      form: {
        title: '',
        desc: '',
        url: ''
      },
      otherData: {
        msg: ''
      }
    }
  },
  methods: {
    setData(){
      this.form = {
        title: '零三的笔记',
        desc: 'web03',
        url: 'web03.cn'
      }
      this.otherData.msg = "msg data"
    },
    setAttribute(){
      //this.form.attr = "我是动态的属性"
      // 或者
      this.$set(this.form, 'attr', "我是动态的属性")
    },
    initFn(){
      Object.assign(this.form, this.$options.data().form)
    }
  }
}
</script>
<style scoped>
div{
  padding: 5px 20px;
}
</style>

以上demo出现了attr遗留的问题,而我们并不希望让他存在

进行优化

- Object.assign(this.form, this.$options.data().form)
+ this.form = this.$options.data().form

Object.assign(this.$data, this.$options.data())支持初始化form新增的属性,且不支持this.$data=this.$options.data()

Objeact.assign初始化整个data弊端

发现在子组件使用Object.assign(this.$data, this.$options.data()),会将props的值清空,查看以下案例
// t3.vue

<template>
  <div>
    <child :parentDataObj="parentDataObj" :parentDataStr="parentDataStr"></child>
  </div>
</template>
<script>
import child from './child'
export default {
  name: 't3',
  components: {
    child
  },
  data(){
    return {
      parentDataObj: {
        name: '零三'
      },
      parentDataStr: '我是零三'
    }
  }
}
</script>

// child.vue

<template>
  <div>
    <button @click="setData">setData</button>
    <div>
      title:
      <input type="text" v-model="form.title" placeholder="title">
    </div>
    <div>
      desc:
      <input type="text" v-model="form.desc" placeholder="desc">
    </div>
    <div>
      url:
      <input type="text" v-model="form.url" placeholder="url">
    </div>
    <div v-if="myDataStr">
      myDataStr: <span>{{myDataStr}}</span>
    </div>
    <div v-if="myDataObj">
      myDataObj.name: <span>{{myDataObj.name}}</span>
    </div>
    <div>
      <button @click="initFn">初始化data</button>
    </div>
  </div>
</template>
<script>
export default {
  name: 'child',
  props: ['parentDataStr', 'parentDataObj'],
  data() {
    return {
      form: {
        title: '',
        desc: '',
        url: ''
      },
      myDataStr: this.parentDataStr,
      myDataObj: this.parentDataObj
    }
  },
  methods: {
    setData() {
      this.form = {
        title: '零三的笔记',
        desc: 'web03',
        url: 'web03.cn'
      }
    },
    initFn() {
      Object.assign(this.$data, this.$options.data())
    }
  }
}
</script>
<style scoped>
div {
  padding: 5px 20px;
}
</style>

解决

- Object.assign(this.$data, this.$options.data())
+ Object.assign(this.$data, this.$options.data.call(this))

分析问题原因

分析Vue实例的初始化逻辑源码,
new Vue的时候传了一个对象,把该对象记为options,Vue将options中自定义的属性和Vue构造函数中定义的属性合并为vm.$options,vm.$options.data()中的this指向vm.$options,而myDataStr和B并没有直接挂在vm.$options下,所以this.myDataStr和this.myDataObj为undefined。
源码地址:https://github.com/vuejs/vue/blob/dev/src/core/instance/init.js
关键源码

initMixin (Vue: Class<Component>) {
    const vm: Component = this
    // ...
    vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
    )
    // ...
}

将vm.$options.data映射到vm._data,使得可以通过vm._data访问数据,在映射过程中,通过call将data()的this指向当前的实例vm,并将data()的执行结果返回,因为prop和methods的初始化在data之前,所以这时vm上已有_props和_methods,可以拿到this.myDataObj和this.myDataStr。
源码地址:https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js
关键代码

initState (vm: Component) {
    // ...
    const opts = vm.$options
    if (opts.props) initProps(vm, opts.props)
    if (opts.methods) initMethods(vm, opts.methods)
    if (opts.data) {
        initData(vm) // 里面通过getData(data, vm)改变this
    }
    // ...
}
  
getData (data: Function, vm: Component): any {
    // ...
    try {
        return data.call(vm, vm) // this替换为vm
    }
    // ...
}

上面把属性映射到了vm._data里,可以通过vm._data.xxx访问数据,Vue再通过一个代理方法使得vm.xxx可以直接访问xxx
源码地址:https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js
关键代码

proxy(vm, `_data`, key);
  
proxy (target: Object, sourceKey: string, key: string) {
    sharedPropertyDefinition.proxyget = function proxyGetter () {
        return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

总结

  • 初始化没有动态属性的对象,可采用 Object.assign(this.form, this.$options.data().form)

  • 初始化有动态属性的对象一定要使用this.form = this.$options.data().form 【推荐】

  • 初始化data,data()中没有用this来访问props或methods,可不绑定this,使用Object.assign(this.$data, this.$options.data())

  • 初始化data,data()中若用了this来访问props或methods,一定要绑定this,使用Object.assign(this.$data, this.$options.data.call(this)) 【推荐】

vue
  • 作者:零三(联系作者)
  • 最后更新时间:2021-02-22 17:36
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 转载声明:来源地址 https://web03.cn
  • 评论