props 父传子和 $emit 子传父

注意⚠️:

单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

步骤

父传子

  • 在父组件内,引入子组件并挂载
  • 在父组件挂载的子组件标签上,通过绑定属性 v-bind(缩写::)的方式,将数据传递给该子组件
  • 在子组件通过 props 来接收父组件传递的数据

子传父

  • 在子组件内,将(改变后的)数据通过 this.$emit 触发当前实例上的事件,附加参数都会传给监听器回调
  • 在父组件挂载的子组件标签上,通过绑定事件监听器 v-on(缩写:@)的方式,监听 this.$emit 触发的事件,在 methods 中定义监听器回调方法来获取数据

实例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>场景一:父子组件通信</title>
  <!-- 开发环境版本,包含了有帮助的命令行警告 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
  <div id="app">
    <!-- 父组件挂载到 #app 元素下 -->
    <m-self></m-self>
  </div>
</body>

<script>
  // 声明子组件
  Vue.component('m-son', {
    // 子组件接收父组件通过绑定属性(v-bind/:)绑定的属性
    // 注意⚠️:一般 props 接收的数据作为初始化数据
    props: {
      b: {
        type: String,
        default: ''
      }
    },
    // :value="b",而不用 v-model="b" 是因为 vue 中 prop 的使用,造成其父子 prop 之间形成了一个单向下行绑定,父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
    template: `<div>
    <input type="text" :value="b" @input="onInput" />
    </div>`,
    methods: {
      onInput(e) {
        let data = e.target.value
        // 子组件中更改了数据,通过 $emit 触发当前实例上的事件。附加参数都会传给监听器回调。
        this.$emit('onChangeB', data)
      }
    }
  })

  // 声明父组件,并将父组件挂载到 #app 上
  let mSelf = new Vue({
    el: '#app',
    data() {
      return {
        a: '将要传递给子组件的数据'
      }
    },
    // 父组件通过 v-bind(/:) 的方式,传递数据到子组件
    // 通过 v-on(/@) 的方式,监听子组件中 $emit 触发的事件
    template: `<div>
      <p>{{a}}</p>
      <m-son :b="a" @onChangeB="onChange"></m-son>
    </div>`,
    methods: {
      onChange(data) {
        this.a = data
      }
    }
  })
</script>

</html>

$parent 子访问/绑定父 和 $children 父访问/绑定子

注意⚠️:

节制地使用 $parent$children - 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信。

步骤

$parent 子访问/绑定父

  • 在子组件内,通过 this.$parent 获取父组件的实例(如果有父组件的话),实例上面当然可以获取到其挂载的数据
  • 在子组件内,通过 v-model 绑定父组件的数据,利用数据响应式完成子传父

$children 父访问/绑定子

  • 在子组件内,通过 this.$parent 拿到父组件的数据,并赋值给子组件,完成数据初始化
  • 在子组件内,通过 v-model 绑定子组件中的数据
  • 在父组件内,mounted 挂载后,通过 this.$children 获取到子组件实例数组,将某一子组件实例赋值给父组件的某个变量,利用对象的特性,实现数据响应式。

实例

$parent 子访问/绑定父

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>场景二:父子组件通信</title>
  <!-- 开发环境版本,包含了有帮助的命令行警告 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
  <div id="app">
    <!-- 父组件挂载到 #app 元素下 -->
    <m-self></m-self>
  </div>
</body>

<script>
  // 声明子组件
  Vue.component('m-son', {
    // 子组件中,通过 this.$parent 访问父组件实例
    template: `<div>
    <input type="text" v-model="$parent.a" />
    </div>`
  })

  // 声明父组件,并将父组件挂载到 #app 上
  let mSelf = new Vue({
    el: '#app',
    data() {
      return {
        a: '将要传递给子组件的数据'
      }
    },
    template: `<div>
      <p>{{a}}</p>
      <m-son></m-son>
    </div>`
  })
</script>

</html>

$children父访问/绑定子

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>场景二:父子组件通信</title>
  <!-- 开发环境版本,包含了有帮助的命令行警告 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
  <div id="app">
    <!-- 父组件挂载到 #app 元素下 -->
    <m-self></m-self>
  </div>
</body>

<script>
  // 声明子组件
  Vue.component('m-son', {
    data() {
      return {
        // 子组件中,通过 this.$parent 获取父组件实例,完成数据初始化
        b: this.$parent.a
      }
    },
    template: `<div>
    <input type="text" v-model="b" />
    </div>`
  })

  // 声明父组件,并将父组件挂载到 #app 上
  let mSelf = new Vue({
    el: '#app',
    data() {
      return {
        a: '将要传递给子组件的数据',
        children: null
      }
    },
    template: `<div>
      <p>{{children && children.b}}</p>
      <m-son></m-son>
    </div>`,
    mounted() {
      // 实例挂载后,将子组件实例赋值给父组件中定义的数据
      // 注意⚠️:$children 在挂载后才会取到值,而且不是响应式的。为了达到响应式的效果,这里采用重新赋值,利用了对象的特性
      this.children = this.$children && this.$children[0]
    }

  })
</script>

</html>

$root 请参考 $parent

v-bind="$attrs" 祖传孙和 v-on="$listeners" 孙传子

注意⚠️:

v-bind="$attrs"v-bind="$attrs" 的写法,不能采用简写。

步骤

祖传孙

  • 在祖组件内,通过绑定属性 v-bind (简写::) 的方式来传递数据
  • 在各级子组件内,绑定 $attrs,即 v-bind="$attrs",获取父级的绑定属性
  • 在最后的孙组件内,通过 $attrs 去获取祖组件传递的数据

孙传祖

  • 在祖组件内,通过绑定事件 v-on (简写:@) 的方式来接收孙组件回调数据
  • 在各级子组件内,绑定 $listenersv-on="$listeners",获取父级的事件监听器
  • 在最后的孙组件内,通过 this.$listeners 获取祖组件的事件监听器,在数据改变后,执行该监听器传递数据。或者采用 $emit 去触发

实例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>场景三:父子孙子等更高级别组件通信</title>
  <!-- 开发环境版本,包含了有帮助的命令行警告 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
  <div id="app">
    <!-- 父组件挂载到 #app 元素下 -->
    <m-self></m-self>
  </div>
</body>

<script>
  // 声明曾孙组件
  Vue.component('m-great-grandson', {
    // 孙组件通过 $attrs 获取祖组件传递的数据
    template: `<div>
    <input type="text" :value="$attrs.b" @input="onInput" />
    </div>`,
    methods: {
      onInput(e) {
        let data = e.target.value
        // 孙组件中更改了数据,通过 $emit 触发当前实例上的事件。附加参数都会传给监听器回调。
        // this.$emit('onChangeB', data)
        // 或者通过 $listeners 获取事件监听器,执行监听器
        this.$listeners.onChangeB(data)
      }
    }
  })

  // 声明孙组件
  Vue.component('m-grandson', {
    // 每层子组件都绑定 $attrs (v-bind="$attrs"),将数据传给子组件
    // 每层子组件都绑定 $listeners (v-on="$listeners"),将事件监听器传给子组件
    template: `<div>
    <m-great-grandson v-bind="$attrs" v-on="$listeners"></m-great-grandson>
    </div>`
  })

  // 声明子组件
  Vue.component('m-son', {
    // 每层子组件都绑定 $attrs (v-bind="$attrs"),将数据传给子组件
    // 每层子组件都绑定 $listeners (v-on="$listeners"),将事件监听器传给子组件
    template: `<div>
    <m-grandson v-bind="$attrs" v-on="$listeners"></m-grandson>
    </div>`
  })

  // 声明父组件,并将父组件挂载到 #app 上
  let mSelf = new Vue({
    el: '#app',
    data() {
      return {
        a: '将要传递给曾孙组件的数据'
      }
    },
    // 父组件内,通过 v-bind(/:) 绑定属性 (:b="a")。每层子组件都绑定 $attrs (v-bind="$attrs"),实现数据的祖传孙
    // 父组件内,通过 v-on(/@) 绑定事件 (@onChangeB="onChange")。每层子组件都绑定 $listeners (v-on="$listeners"),实现事件监听器的祖传孙
    template: `<div>
      <p>{{a}}</p>
      <m-son :b="a" @onChangeB="onChange"></m-son>
    </div>`,
    methods: {
      onChange(data) {
        this.a = data
      }
    }
  })
</script>

</html>

eventbus 中央事件管理

步骤

  • 声明两个子组件 a 组件和 b 组件,在同一父组件中引入
  • 声明 bus 中央事件管理, let bus = new Vue()
  • a 组件传值到 b 组件
  • 在 a 组件内,创建后,将数据赋值给 bus,同时开启事件监听器。触发事件后,回调处理参数
  • 在 b 组件内,创建后,将 bus 数据赋值给 b 组件,初始化数据。在数据改变后通过 bus.$emit 触发事件监听器,回调数据,完成数据传递

实例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>场景四:非父子组件通信</title>
  <!-- 开发环境版本,包含了有帮助的命令行警告 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
  <div id="app">
    <!-- 父组件挂载到 #app 元素下 -->
    <m-self></m-self>
  </div>
</body>

<script>
  // 中央事件管理
  let bus = new Vue()

  // 声明子组件 a
  Vue.component('m-son-a', {
    data() {
      return {
        a: '传给组件 b 数据'
      }
    },
    template: `<div>
    <p>{{a}}</p>
    </div>`,
    created() {
      //  将组件 a 数据赋值给 bus,并开启事件监听器。触发后,回调数据即为 组件 b 传递
      bus.a = this.a
      bus.$on('onChangeA', (data) => {
        this.a = data
      })
    },
    methods: {
    }
  })

  // 声明子组件 b
  Vue.component('m-son-b', {
    data() {
      return {
        b: ''
      }
    },
    template: `<div>
    <input type="text" :value="b" @input="onInput" />
    </div>`,
    created() {
      // 数据初始化
      this.b = bus.a
    },
    methods: {
      onInput(e) {
        let data = e.target.value
        // 组件 b 中更改了数据,通过 $emit 触发自定义事件。附加参数都会传给监听器回调。
        bus.$emit('onChangeA', data)
      }
    }
  })

  // 声明父组件,并将父组件挂载到 #app 上
  let mSelf = new Vue({
    el: '#app',
    template: `<div>
      <m-son-a></m-son-a>
      <m-son-b></m-son-b>
    </div>`
  })
</script>

</html>

对象,共用同一对象

注意⚠️:

共用同一对象,可能会引起一些数据的混乱。不理解的情况下会造成不是预期的变化。

步骤

  • 声明一个公用对象
  • 在涉及的组件内引入该对象,并在组件创建之后初始化数据
  • 绑定该数据到 view 视图

实例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>场景四:父子/非父子组件通信</title>
  <!-- 开发环境版本,包含了有帮助的命令行警告 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
  <div id="app">
    <!-- 父组件挂载到 #app 元素下 -->
    <m-self></m-self>
  </div>
</body>

<script>
  // 声明一个公用对象,利用对象的特性完成数据共享。也可以通过 es6 模块 导出导入对象。index.js 中 export const data = { a: ''} ,import { data } from './index.js'
  // 将对象赋值给某个变量,这个变量修改了对象中的属性,则原对象也会改变(对象是地址引用,引用类型)
  let data = {
    a: '传给组件 b 数据'
  }

  // 声明子组件 a
  Vue.component('m-son-a', {
    data() {
      return {
        data: {}
      }
    },
    template: `<div>
    <p>{{data && data.a}}</p>
    </div>`,
    created() {
      // 初始化数据
      this.data = data
    }
  })

  // 声明子组件 b
  Vue.component('m-son-b', {
    data() {
      return {
        data: {}
      }
    },
    template: `<div>
    <input type="text" v-model="data.a" />
    </div>`,
    created() {
      // 数据初始化
      this.data = data
    }
  })

  // 声明父组件,并将父组件挂载到 #app 上
  let mSelf = new Vue({
    el: '#app',
    template: `<div>
      <m-son-a></m-son-a>
      <m-son-b></m-son-b>
    </div>`
  })
</script>

</html>

provide/inject

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

实例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>场景三:父子孙子等更高级别组件通信</title>
  <!-- 开发环境版本,包含了有帮助的命令行警告 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
  <div id="app">
    <!-- 父组件挂载到 #app 元素下 -->
    <m-self></m-self>
  </div>
</body>

<script>
  // 声明曾孙组件
  Vue.component('m-great-grandson', {
    // inject 引入 provide 注入的属性
    inject: ['a', 'onChangeB'],
    // 孙组件通过 inject 获取祖组件传递的数据
    template: `<div>
    <input type="text" :value="a" @input="onInput" />
    </div>`,
    methods: {
      onInput(e) {
        let data = e.target.value
        // 孙组件中更改了数据,通过 $emit 触发当前实例上的事件。附加参数都会传给监听器回调。
        // this.$emit('onChangeB', data)
        // 或者
        this.onChangeB(data)
      }
    }
  })

  // 声明孙组件
  Vue.component('m-grandson', {
    template: `<div>
    <m-great-grandson></m-great-grandson>
    </div>`
  })

  // 声明子组件
  Vue.component('m-son', {
    template: `<div>
    <m-grandson></m-grandson>
    </div>`
  })

  // 声明父组件,并将父组件挂载到 #app 上
  let mSelf = new Vue({
    el: '#app',
    data() {
      return {
        b: '将要传递给曾孙组件的数据'
      }
    },
    // provide 可以是对象,也可以是返回一个对象函数,该对象包含可注入的属性
    provide() {
      return {
        a: this.b,
        onChangeB: this.onChange
      }
    },
    // 父组件内,通过 provide 声明注入属性
    // 父组件内,通过 v-on(/@) 绑定事件 (@onChangeB="onChange")
    template: `<div>
      <p>{{b}}</p>
      <m-son @onChangeB="onChange" ></m-son>
    </div>`,
    methods: {
      onChange(data) {
        this.b = data
      }
    }
  })
</script>

</html>

vuex

03-05 16:42