前端学习-Vue组件通信

182 阅读3分钟

父组件向子组件传值

  • 通过子组件的 props 选项接收父组件的传值。 注意:props 不要与 data 存在同名属性。

Props 命名规则

建议 prop 命名使用 camelCase,父组件绑定时使用 kebab-case。

div id="app">
    <my-component
      :item-title="item.title"
      :item-content="item.content"
    ></my-component>
</div>
<script>
    Vue.component('my-component', {
      props: ['itemTitle', 'itemContent'],
      template: `
        <div>
          <h3>{{ itemTitle }}</h3>
          <p>{{ itemContent }}</p>
        </div>
      `
    })
    new Vue({
      el: '#app',
      data: {
        item: {
          title: '这是示例标题',
          content: '这是示例内容'
        }
      }
    })
</script>

通过 v-for 遍历数据 items,创建组件并生成内容

<div id="app">
    <demo-item
      v-for="item in items"
      :item-title="item.title"
      :item-content="item.content"
      :key="item.title"

      :item="item"
    ></demo-item>
</div>
<script>
    Vue.component('demoItem', {
      props: ['itemTitle', 'itemContent', 'item'],
      template: `
        <div>
          <h3>{{ itemTitle }}</h3>
          <p> {{ itemContent }} </p>
        </div>
      `
    })

    new Vue({
      el: '#app',
      data: {
        // 准备给子组件使用的数据
        items: [
          {
            title: '示例标题1',
            content: '示例内容1'
          },
          {
            title: '示例标题2',
            content: '示例内容2'
          },
          {
            title: '示例标题3',
            content: '示例内容3'
          },
        ]
      }
    })
</script>

单向数据流

  • 父子组件间的所有 prop 都是单向下行绑定的。
  • 如果子组件要处理 prop 数据,应当存储在 data 中后操作。
  • 注意,如果 prop 为数组或对象时,子组件操作将会影响到父组件的状态。
<div id="app">
    <my-component
      :initial-title="title"
      :obj="obj"
    ></my-component>
</div>
<script>
    Vue.component('my-component', {
      props: ['initialTitle', 'obj'],
      template: `
        <div>
          {{ title }}
          <button @click="fn">按钮</button>
        </div>
      `,
      data () {
        return {
          title: this.initialTitle
        }
      },
      methods: {
        fn () {
          this.title = '这是新的标题';
          this.initialTitle = '这是新的标题'; // 不会影响父组件
          this.obj.name = 'jack';

        }
      }
    });

    new Vue({
      el: '#app',
      data: {
        title: '这是示例标题',
        obj: {
          name: 'william',
          age: 18
        }
      }
    });
</script>

Props 类型

  • Prop 可以设置类型检查,这时需要将 props 更改为一个带有验 证需求的对象,并指定对应类型。
  • Prop 还可以同时指定多个类型,通过数组方式保存即可。
<div id="app">
    <my-component
      :par-str="str"
      :par-num="num"
      :par-arr="arr"
      :par-obj="obj"
      :par-any="any"
      :par-data="str"
    ></my-component>
</div>
<script>
    Vue.component('MyComponent', {
      // 如果要设置 props 的具体规则,需要更改为对象写法
      props: {
        parStr: String,
        parNum: Number,
        parArr: Array,
        parObj: Object,
        parAny: undefined, // null
        parData: [String, Boolean] //同时指定多个类型
      },
      template: `
        <div>
          {{ parStr }}
          {{ parNum }}
          {{ parArr }}
          {{ parObj }}
          {{ parAny }}
          {{ parData }}
        </div>
      `
    })

    new Vue({
      el: '#app',
      data: {
        num: 100,
        str: 'abc',
        arr: [1, 2, 3],
        obj: {
          content1: '示例内容1',
          content2: '示例内容2'
        },
        any: [1, 2, 3]
      }
    });
</script>

Props 验证

  • 当 prop 需要设置多种规则时,可以将 prop 的值设置为选项对象。
  • 之前的类型检测功能通过 type 选项设置。
Vue.component('MyComponent', {
      props: {
        parStr: {
          type: String
        },
        parData: {
          type: [Array, Object]
        }
      },
      template: `<div></div>`
});

  • required 用于设置数据为必填项。
Vue.component('MyComponent', {
      props: {
        parStr: {
          type: String,
          required: true
        }
      },
      template: `<div> {{ parStr }} </div>`
});

  • default 用于给可选项指定默认值,当父组件未传递数据时生效。
  • 注意:当默认值为数组或对象时,必须为工厂函数返回的形式。
Vue.component('MyComponent', {
      props: {
        parNum: {
          type: Number,
          default: 100
        },
        parArr: {
          type: Array,
          default () {
            return [1, 2, 3];
          }
        }
      },
      template: `<div> {{ parArr }} </div>`
});

  • validator 用于给传入的 prop 设置校验函数,return 值为 false 时 Vue.js 会发出警告。
  • 注意:验证函数中无法使用实例的 data、methods 等功能。
Vue.component('MyComponent', {
      props: {
        parStr: {
          type: String,
          validator (value) {
            return value.startsWith('user');
          }
        }
      },
      template: `<div> {{ parStr }} </div>`
});

非 Props 属性

  • 当父组件给子组件设置了属性,但此属性在 props 中不存在,这时会自动绑定到子组件的根元素上。
  • 如果组件根元素已经存在了对应属性,则会替换组件内部的值。
  • class 与 style 是例外,当内外都设置时,属性会自动合并。
  • 如果不希望继承父组件设置的属性,可以设置 inheritAttrs: false,但只适用于普通属性,class 与 style 不受影响。
<div id="app">
    <my-component
      data-index="3"
      :title="'示例标题内容'"
      style="height: 200px;"
      class="colorRed"
    ></my-component>
</div>
<script>
    Vue.component('MyComponent', {
      inheritAttrs: false,
      template: `
      <div data-index="6"
           title="旧的title"
           class="abc" 
           style="width: 200px;">
        <p>这是组件的内容</p>
      </div>
      `
    });

    new Vue({
      el: '#app',
      data: {}
    });
</script>

子组件向父组件传值

  • 子向父传值需要通过自定义事件实现。
  • 子组件数据变化时,通过 $emit() 触发自定义事件。
  • 自定义事件名称建议使用 kebab-case。
  • 父组件监听子组件的自定义事件,并设置处理程序。
<div id="app">
    <h3>购物车</h3>
    <product-item
      v-for="product in products"
      :key="product.id"
      :title="product.title"
      <!--父组件在监听事件时需要接收子组件传递的数据-->
      @count-change="onCountChange"
    ></product-item>
    <p>商品总个数为:{{ totalCount }}</p>
</div>
<script>
    // 子组件
    Vue.component('ProductItem', {
      props: ['title'],
      template: `
        <div>
          <span>商品名称: {{ title }}, 商品个数: {{ count }}</span>
          <button @click="countIns1">+1</button>
          <button @click="countIns5">+5</button>
        </div>
      `,
      data () {
        return {
          count: 0
        }
      },
      methods: {
        countIns1 () {
          //子组件触发事件时可以向父组件传值
          this.$emit('count-change', 1);
          this.count++;
        },
        countIns5 () {
          this.$emit('count-change', 5);
          this.count += 5;
        }
      }
    });

    // 父组件
    new Vue({
      el: '#app',
      data: {
        products: [
          {
            id: 1,
            title: '苹果一斤'
          },
          {
            id: 2,
            title: '香蕉一根'
          },
          {
            id: 3,
            title: '橙子一个'
          }
        ],
        totalCount: 0
      },
      methods: {
        onCountChange (productCount) {
          this.totalCount += productCount;
        }
      }
    });
</script>

组件与 v-model

v-model 用于组件时,需要通过 props 与自定义事件实现。

<div id="app">
    <p>输入框内容为:{{ iptValue }}</p>
    <com-input v-model="iptValue"></com-input>
</div>
<script>
    // 子组件
    var ComInput = {
      props: ['value'],
      template: `
        <input
          type="text"
          :value="value"
          @input="onInput"
        >
      `,
      methods: {
        onInput (event) {
          this.$emit('input', event.target.value)
        }
      }
    }

    // 根实例
    new Vue({
      el: '#app',
      data: {
        iptValue: ''
      },
      components: {
        ComInput
      }
    });
</script>

非父子组件传值

非父子组件指的是兄弟组件或完全无关的两个组件。

兄弟组件传值

兄弟组件可以通过父组件进行数据中转

<div id="app">
    <!-- 父组件接收子组件A的数据 -->
    <com-a
      @value-change="value = $event"
    ></com-a>
    <!-- 父组件将数据传递给子组件B -->
    <com-b
      :value="value"
    ></com-b>
</div>
<script>
    // 子组件A:发送数据
    Vue.component('ComA', {
      template: `
        <div>
          组件A的内容: {{ value }}
          <button
            @click="$emit('value-change', value)"
          >发送</button>
        </div>
      `,
      data () {
        return {
          value: '这是组件A中的数据'
        }
      }
    });

    // 子组件B:接收数据
    Vue.component('ComB', {
      props: ['value'],
      template: `
        <div>
          组件B接收到: {{ value }}
        </div>  
      `
    });


    // 根实例(父组件)
    new Vue({
      el: '#app',
      data: {
        // 用于数据中转
        value: ''
      }
    })
</script>

EventBus

<div id="app">
    <h3>购物车</h3>
    <product-item></product-item>
    <product-total></product-total>
</div>

<script>
    var bus = new Vue();
    // 商品组件
    Vue.component('ProductItem', {
      template: `
        <div>
          <span>商品名称:苹果,商品个数:{{ count }}</span>
          <button
            @click="countIns"
          >+1</button>
        </div>
      `,
      data () {
        return {
          count: 0
        }
      },
      methods: {
        countIns () {
          // 给bus触发自定义事件,传递数据
          bus.$emit('countChange', 1);
          this.count++;
        }
      }
    });

    // 计数组件
    Vue.component('ProductTotal', {
      template: `
        <p>总个数为: {{ totalCount }}</p>
      `,
      data () {
        return {
          totalCount: 0
        }
      },
      created () {
        // 给 bus 注册事件,并接收数据
        bus.$on('countChange', (productCount) => {
          // 实例创建完毕,可以使用 data 等功能
          this.totalCount += productCount;
        });

      }
    })

    // 根实例
    new Vue({
      el: '#app',
      data: {

      }
    });
</script>

其他传值方式

$root

  • $root 用于访问当前组件树根实例,设置简单的 Vue 应用时可以 通过此方式进行组件传值。
  • 除了$root , Vue.js 中还提供了 $parent$children 用于 便捷访问父子组件。
<div id="app">
    <com-a></com-a>
</div>

<script>
    // 根实例的子组件A的子组件B
    var ComB = {
      template: `
        <div>
          组件B: {{ $root.count }}
          <button @click="clickFn">按钮</button>
        </div>
      `,
      methods: {
        clickFn () {
          this.$root.count = 200;
        }
      }
    };

    // 子组件A
    var ComA = {
      template: `
        <div>
          组件A: {{ $root.count }}
          <button @click="clickFn">按钮</button>
          <com-b></com-b>
        </div>
      `,
      methods: {
        clickFn () {
          this.$root.count = 100;
        }
      },
      components: {
        ComB
      }
    };


    // 根实例
    new Vue({
      el: '#app',
      data: {
        count: 0
      },
      components: {
        ComA
      }
    });
</script>

$refs

  • $refs 用于获取设置了 ref 属性的 HTML 标签或子组件。
  • 给普通 HTML 标签设置 ref 属性,$refs 可以获取 DOM 对象。
  • 给子组件设置 ref 属性,渲染后可通过 $refs 获取子组件实例。
<div id="app">
    <!-- 给 HTML 标签设置 ref 属性 -->
    <input type="text" ref="inp">
    <button @click="fn">按钮</button>

    <!-- 给子组件设置 ref 属性 -->
    <com-a ref="comA"></com-a>
</div>
  
<script>
    var ComA = {
      template: `<div>组件A的内容:{{ value }}</div>`,
      data () {
        return {
          value: '示例内容'
        }
      }
    }

    var vm = new Vue({
      el: '#app',
      methods: {
        fn () {
          // 点击后修改 HTML 标签焦点状态
          this.$refs.inp.focus();

          this.$refs.comA.value = '新的内容';
        }
      },
      components: {
        ComA
      },
      mounted () {
        console.log(this.$refs);
        this.$refs.comA.value = "修改后的内容";
      },
    });
</script>