实例属性

Vue 实例提供了一系列属性,用于访问实例的数据、DOM 元素、组件关系等。这些属性都以 $ 开头,与用户定义的属性区分。

数据相关属性

$data

访问实例的数据对象:

const vm = new Vue({
  data: {
    message: 'Hello',
    count: 0
  }
})

console.log(vm.$data)
console.log(vm.$data.message)

vm.$data.message = 'World'

$data 是响应式的,修改它会触发视图更新。

$props

访问组件接收的 props:

Vue.component('my-component', {
  props: ['title', 'content'],
  mounted() {
    console.log(this.$props)
    console.log(this.$props.title)
  }
})

$attrs

包含父组件传递的非 prop 属性:

Vue.component('my-input', {
  props: ['value'],
  inheritAttrs: false,
  template: `
    <div>
      <input v-bind="$attrs" :value="value">
    </div>
  `,
  mounted() {
    console.log(this.$attrs)
  }
})

使用示例:

<my-input 
  v-model="text" 
  placeholder="Enter text" 
  class="form-input"
/>

$attrs 会包含 placeholder 和 class。

$listeners

包含父组件传递的事件监听器:

Vue.component('my-button', {
  template: '<button v-on="$listeners"><slot></slot></button>',
  mounted() {
    console.log(this.$listeners)
  }
})

使用示例:

<my-button @click="handleClick" @mouseenter="handleEnter">
  Click me
</my-button>

DOM 相关属性

$el

访问实例的根 DOM 元素:

const vm = new Vue({
  template: '<div class="container">{{ message }}</div>',
  data: {
    message: 'Hello'
  }
}).$mount()

console.log(vm.$el)
console.log(vm.$el.className)
console.log(vm.$el.textContent)

注意:$el 在 mounted 之前是 undefined。

$refs

访问模板中的引用元素或组件:

new Vue({
  template: `
    <div>
      <input ref="input" type="text">
      <child-component ref="child"></child-component>
    </div>
  `,
  mounted() {
    this.$refs.input.focus()
    this.$refs.child.someMethod()
  }
})

注意:$refs 只在渲染完成后可用,不是响应式的。

$slots

访问插槽内容:

Vue.component('my-component', {
  template: `
    <div>
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
    </div>
  `,
  mounted() {
    console.log(this.$slots.header)
    console.log(this.$slots.default)
  }
})

$scopedSlots

访问作用域插槽:

Vue.component('list-component', {
  template: `
    <ul>
      <li v-for="item in items" :key="item.id">
        <slot :item="item"></slot>
      </li>
    </ul>
  `,
  props: ['items'],
  mounted() {
    console.log(this.$scopedSlots.default)
  }
})

组件关系属性

$root

访问根 Vue 实例:

Vue.component('deep-child', {
  mounted() {
    console.log(this.$root)
    console.log(this.$root.$options.name)
  }
})

new Vue({
  name: 'RootApp',
  el: '#app'
})

$parent

访问父组件实例:

Vue.component('child', {
  mounted() {
    console.log(this.$parent)
    this.$parent.someMethod()
  }
})

注意:过度使用 $parent 会使组件耦合度增加。

$children

访问子组件实例数组:

new Vue({
  template: `
    <div>
      <child ref="child1"></child>
      <child ref="child2"></child>
    </div>
  `,
  mounted() {
    console.log(this.$children)
    this.$children.forEach(child => {
      child.reset()
    })
  }
})

注意:children不保证顺序,推荐使用children 不保证顺序,推荐使用 refs。

实例配置属性

$options

访问实例的初始化选项:

const vm = new Vue({
  customOption: 'foo',
  data: {
    message: 'Hello'
  },
  mounted() {
    console.log(this.$options)
    console.log(this.$options.customOption)
    console.log(this.$options.data)
  }
})

可用于访问自定义选项:

const vm = new Vue({
  myOption: {
    required: true,
    default: 'default value'
  },
  created() {
    const option = this.$options.myOption
    console.log(option)
  }
})

其他属性

$isServer

判断是否在服务端运行:

new Vue({
  created() {
    if (this.$isServer) {
      console.log('Running on server')
    } else {
      console.log('Running on client')
    }
  }
})

$vnode

当前组件的虚拟 DOM 节点:

Vue.component('my-component', {
  mounted() {
    console.log(this.$vnode)
    console.log(this.$vnode.tag)
    console.log(this.$vnode.data)
  }
})

$createElement

渲染函数的别名:

new Vue({
  render(createElement) {
    return createElement('div', 'Hello')
  }
})

new Vue({
  render() {
    return this.$createElement('div', 'Hello')
  }
})

属性访问示例

动态属性访问

const vm = new Vue({
  data: {
    message: 'Hello',
    count: 0
  }
})

const key = 'message'
console.log(vm[key])
console.log(vm.$data[key])

遍历数据

const vm = new Vue({
  data: {
    user: { name: 'John', age: 30 },
    items: ['a', 'b', 'c']
  }
})

Object.keys(vm.$data).forEach(key => {
  console.log(key, vm.$data[key])
})

访问父组件数据

Vue.component('child', {
  template: '<div>{{ parentMessage }}</div>',
  computed: {
    parentMessage() {
      return this.$parent.message
    }
  }
})

new Vue({
  data: {
    message: 'Hello from parent'
  }
})

实际案例

表单组件

Vue.component('form-component', {
  props: ['initialData'],
  data() {
    return {
      form: { ...this.initialData }
    }
  },
  methods: {
    reset() {
      this.form = { ...this.$options.propsData.initialData }
    },
    submit() {
      this.$emit('submit', this.$data.form)
    }
  }
})

调试组件

Vue.component('debug-panel', {
  template: `
    <div class="debug-panel">
      <h3>Debug Info</h3>
      <pre>{{ debugInfo }}</pre>
    </div>
  `,
  computed: {
    debugInfo() {
      return JSON.stringify({
        data: this.$data,
        props: this.$props,
        attrs: this.$attrs,
        $route: this.$route ? this.$route.path : 'N/A'
      }, null, 2)
    }
  }
})

透传组件

Vue.component('transparent-wrapper', {
  inheritAttrs: false,
  template: `
    <div class="wrapper">
      <input v-bind="$attrs" v-on="$listeners">
    </div>
  `
})

最佳实践

优先使用 refs而非refs 而非 children

this.$refs.child.method()

this.$children[0].method()

避免直接修改 $data

this.message = 'new value'

this.$data.message = 'new value'

谨慎使用 $parent

this.$emit('update', data)

this.$parent.update(data)

使用 attrsattrs 和 listeners 透传

Vue.component('base-input', {
  inheritAttrs: false,
  template: `
    <label>
      {{ label }}
      <input v-bind="$attrs" v-on="$listeners">
    </label>
  `,
  props: ['label']
})