03组件化开发父子组件通信

Posted shawnyue-08

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了03组件化开发父子组件通信相关的知识,希望对你有一定的参考价值。

组件化开发

组件化的基本使用过程

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div>
  <h2>我是标题</h2>
  <p>我是内容,哈哈哈哈哈</p>
  <p>我是内容,哈哈哈哈哈</p>

  <h2>我是标题</h2>
  <p>我是内容,哈哈哈哈哈</p>
  <p>我是内容,哈哈哈哈哈</p>

  <h2>我是标题</h2>
  <p>我是内容,哈哈哈哈哈</p>
  <p>我是内容,哈哈哈哈哈</p>

  <h2>我是标题</h2>
  <p>我是内容,哈哈哈哈哈</p>
  <p>我是内容,哈哈哈哈哈</p>
</div>
<script src="../js/vue.js"></script>
<script>

</script>
</body>
</html>

容易造成大量重复性代码,所以需要把页面中需要多次使用的代码提取出来,作为一个组件(Component)。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <!-- 组件必须挂载在某个实例下,否则不会生效 -->
  <my-component></my-component>
  <div>
    <my-component></my-component>
  </div>
</div>
<!-- 不会生效 -->
<my-component></my-component>
<script src="../js/vue.js"></script>
<script>
  //1、创建一个组件构造器对象
  const component = Vue.extend({
    template: `
    <div>
      <h2>我是标题</h2>
      <p>我是内容,哈哈哈哈哈</p>
      <p>我是内容,哈哈哈哈哈</p>
    </div>`
    /* template:自定义组件的模板 */
    /* ES6新增一种字符串的表示方式,``,优点在于可以换行 */
  });
  //2、注册组件
  Vue.component(‘my-component‘, component);

  const app = new Vue({
    el: ‘#app‘,
    data: {

    }
  })
</script>
</body>
</html>

技术图片

事实上,这种写法在Vue 2.x的文档中几乎已经看不到了,他会直接使用我们下面的语法糖。

全局组件和局部组件

全局组件:可以在多个Vue的实例下使用;

局部组件:在哪个Vue实例下注册,就只能在哪个Vue实例绑定的组件中使用。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <my-component></my-component>
  <demo></demo>
</div>
<!-- 全局组件:可以在多个Vue实例下使用 -->
<div id="test">
  <my-component></my-component>
  <!-- 在app的Vue实例下注册的局部组件,不能在test中使用 -->
  <demo></demo>
</div>
<script src="../js/vue.js"></script>
<script>
  const component = Vue.extend({
    template: `
      <div>
        <h2>全局组件的标题</h2>
        <p>我是内容,哈哈哈哈</p>
        <p>我是内容,哈哈哈哈</p>
      </div>
    `
  });
  //2、注册组件(全局组件)
  Vue.component(‘my-component‘, component);


  const demo = Vue.extend({
    template: `
      <div>
        <h2>局部组件的标题</h2>
        <p>我是内容,嘻嘻嘻嘻</p>
        <p>我是内容,嘻嘻嘻嘻</p>
      </div>
    `
  });

  const app = new Vue({
    el: ‘#app‘,
    components: {
      /* 注册局部组件,在某个Vue实例下注册,只能在该实例绑定的组件下使用 */
      ‘demo‘: demo
    }
  });

  const test = new Vue({
    el: ‘#test‘
  });
</script>
</body>
</html>

技术图片

注册组件的语法糖写法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <component1></component1>

  <my-component></my-component>
</div>

<script src="../js/vue.js"></script>
<script>
  //全局组件注册的语法糖
  Vue.component(‘component1‘,{
    template: `
      <div>
        <h2>我是全局组件的标题</h2>
        <p>我是内容,哈哈哈哈</p>
      </div>
    `
  });


  const app = new Vue({
    el: ‘#app‘,
    components: {
      //局部组件注册的语法糖
      ‘my-component‘: {
        template: `
          <div>
            <h2>我是局部组件的标题</h2>
            <p>我是内容,呵呵呵呵</p>
          </div>
        `
      }
    }
  })
</script>
</body>
</html>

组件模板抽离的写法

在组件中写template模板的HTML代码是一件十分麻烦的事,特别不方便。

Vue提供了两种方案写HTML代码

  • 使用script标签(不常用)
  • 使用template标签
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <my-component></my-component>
</div>

<!-- 使用template标签 -->
<template id="my-component-template">
  <div>
    <h2>我是全局组件的标题</h2>
    <p>我是内容,哈哈哈哈</p>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  /* 注册全局组件:组件与模板分离 */
  Vue.component(‘my-component‘, {
    template: ‘#my-component-template‘,
  });

  const app = new Vue({
    el: ‘#app‘,
  })
</script>
</body>
</html>

组件的数据存放问题

组件可以访问Vue实例的数据吗?

  • 组件内部不能访问Vue实例中的数据

Vue组件应该有自己保存数据的地方。

组件有一个data属性,(必须是一个函数),这个函数返回一个对象,对象内部保存着数据。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<div id="app">
  <my-component></my-component>
</div>

<template id="my-component-template">
  <div>
    <span>{{message}}</span>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  Vue.component(‘my-component‘, {
    template: ‘#my-component-template‘,
    data() {
      return {
        message: ‘Hello Vue.js‘,
      }
    }
  })

  const app = new Vue({
    el: ‘#app‘,
    data: {

    }
  })
</script>
</body>
</html>

组件中的data为什么是函数?

单个页面复用一个组件时,组件的data域就不能是属性了,不然修改一个组件,其他组件都跟着变化。

把计数器封装为一个组件。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <my-counter></my-counter>
  <hr>
  <my-counter></my-counter>
  <hr>
  <my-counter></my-counter>
</div>

<template id="my-counter-template">
  <div>
    <h3>当前计数:{{counter}}</h3>
    <button v-on:click="increment">+</button>
    <button v-on:click="decrement">-</button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  Vue.component(‘my-counter‘, {
    template: ‘#my-counter-template‘,
    data() {
      return {
        counter: 0
      }
    },
    methods: {
      increment() {
        this.counter++;
      },
      decrement() {
        this.counter--;
      }
    }
  })

  const app = new Vue({
    el: ‘#app‘,
    data: {}
  })
</script>
</body>
</html>

技术图片

组件通信

父子通信父传子

Vue官方提供的父子组件通信方式

  • 通过props向子组件传递数据
  • 通过事件向父组件发送消息

在下面的代码中,直接将Vue实例作为父组件,并且其中包含子组件来简化代码。

props的值有两种方式:

  • 字符串数组:数组中的字符串就是传递时的名称(id);
  • 对象:功能丰富,常用

props是数组类型

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <!-- 子组件不能直接使用父组件/Vue实例的数据,可以通过Vue实例/父组件向子组件通信的方式来传递数据 -->
  <my-component-son v-bind:books="books" v-bind:message="message"></my-component-son>
</div>

<template id="my-component-son-template">
  <div>
    <ul>
      <li v-for="item in books">{{item}}</li>
    </ul>
    <span>{{message}}</span>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  const app = new Vue({
    el: ‘#app‘,
    data: {
      message: ‘你好啊‘,
      books: [‘三体‘, ‘平凡的世界‘, ‘春‘, ‘白夜行‘]
    },
    components: {
      ‘my-component-son‘: {
        template: ‘#my-component-son-template‘,
        data() {
          return {

          }
        },
        /* props是一个字符串数组 */
        props: [‘books‘, ‘message‘]
      }
    }
  })
</script>
</body>
</html>

props是对象类型

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <!-- 没有绑定number,显示默认值1 -->
  <my-component-son v-bind:books="books" :message="message"></my-component-son>
</div>

<template id="my-component-son-template">
  <div>
    <ul>
      <li v-for="item in books">{{item}}</li>
    </ul>
    <span>{{message}}</span>
    <span>{{number}}</span>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  const app = new Vue({
    el: ‘#app‘,
    data: {
      message: ‘你好啊‘,
      books: [‘三体‘, ‘解忧杂货铺‘, ‘白夜行‘, ‘时间简史‘],
      number: 100,
    },
    components: {
      ‘my-component-son‘: {
        template: ‘#my-component-son-template‘,
        /* props是对象,可以指定变量的类型,并且提供默认值 */
        props: {
          /*books: Array,
          message: String,
          number: Number,*/
          books: {
            type: Array,
            /* 对象或数组默认值必须从一个工厂函数获取 */
            default() {
              return [];
            },
            required: true
          },
          message: {
            type: String,
            default: ‘‘,
            required: true
          },
          number: {
            type: Number,
            default: 1,
            required: false
          }
        }
      }
    }
  })
</script>
</body>
</html>

props驼峰标识

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <!-- son-info -->
  <my-component-son v-bind:son-info="info"></my-component-son>
</div>

<template id="my-component-son-template">
  <div>
    <ul>
      <!-- sonInfo -->
      <li v-for="(value, key, index) in sonInfo">{{index + 1}}---{{key}}:{{value}}</li>
    </ul>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>


  const app = new Vue({
    el: ‘#app‘,
    data: {
      info: {
        name: ‘张三‘,
        age: 22,
        gender: ‘男‘
      }
    },
    components: {
      ‘my-component-son‘: {
        template: ‘#my-component-son-template‘,
        props: {
          /* sonInfo */
          sonInfo: {
            type: Object,
            default() {
              return {};
            },
            required: true
          }
        }
      }
    }
  })
</script>
</body>
</html>

父子通信子传父

Vue官方提供的父子组件通信方式

  • 通过props向子组件传递数据
  • 通过事件向父组件发送消息
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <!-- v-on监听自定义事件,cpnClick不传递参数默认会把item传递到父组件 -->
  <my-component-son v-on:item-click="cpnClick"></my-component-son>
</div>

<template id="my-component-son-template">
  <div>
    <button v-for="item in categories" v-on:click="btnClick(item)">{{item.name}}</button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: ‘#app‘,
    components: {
      ‘my-component-son‘: {
        template: ‘#my-component-son-template‘,
        data() {
          return {
            categories: [
              {
                id: 1,
                name: ‘家用电器‘
              },
              {
                id: 2,
                name: ‘手机通讯‘
              },
              {
                id: 3,
                name: ‘电脑办公‘
              }
            ],
          }
        },
        methods: {
          btnClick(item) {
            /* 子组件发射事件,事件类型自定义为‘item-click‘ */
            this.$emit(‘item-click‘, item);
          }
        }
      }
    },
    methods: {
      /* 调用时没有传递参数,以前默认传递event,这里传递item */
      cpnClick(item) {
        console.log(‘cpnClick‘, item);
      }
    }
  });
</script>
</body>
</html>

技术图片

父子组件通信结合表单的双向绑定

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <my-component v-bind:number1="num1" :number2="num2"
                v-on:n1change="num1change" v-on:n2change="num2change"></my-component>
</div>

<template id="my-component-template">
  <div>
    <span>props:{{number1}}</span>
    <span>data:{{n1}}</span>
    <input type="number" id="n1" v-model.number="n1">
    <label for="n1"></label>
    <br>
    <span>props:{{number2}}</span>
    <span>data:{{n2}}</span>
    <input type="number" id="n2" v-model.number="n2">
    <label for="n2"></label>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  const app = new Vue({
    el: ‘#app‘,
    data: {
      num1: 1,
      num2: 0,
    },
    components: {
      ‘my-component‘: {
        template: ‘#my-component-template‘,
        props: {
          number1: {type: Number},
          number2: {type: Number},
        },
        data() {
          return {
            n1: this.number1,
            n2: this.number2
          }
        },
        watch: {
          n1(newValue) {
            //n1修改,n2=n1*100
            this.n2 = newValue * 100;
            this.$emit(‘n1change‘, newValue);
            this.$emit(‘n2change‘, this.n2);
          },
          n2(newValue) {
            //n2修改,n1=1/100*n2
            this.n1 = 1/100 * newValue;
            this.$emit(‘n2change‘, newValue);
            this.$emit(‘n1change‘, this.n1);
          }
        }
      }
    },
    methods: {
      num1change(value) {
        this.num1 = parseInt(value);
      },
      num2change(value) {
        this.num2 = parseInt(value);
      }
    }
  })
</script>
</body>
</html>

注意点:

1、template标签,一定要有一个根标签;

2、组件的data必须是一个函数;

3、组件的props最好用对象;

4、组件绑定模板用#。

父访问子

父组件访问子组件:$children或者$refs

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <!-- ref在实际开发中用的很多 -->
  <my-component-1 ref="one"></my-component-1>
  <my-component-1 ref="two"></my-component-1>
  <my-component-1 ref="three"></my-component-1>
  <button v-on:click="btnClick">按钮</button>
</div>

<template id="my-component-1-template">
  <div>
    <span>我是子组件1号</span>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  const app = new Vue({
    el: ‘#app‘,
    data: {

    },
    components: {
      ‘my-component-1‘: {
        template: ‘#my-component-1-template‘,
        methods: {
          showMessage() {
            console.log(‘showMessage‘);
          }
        },
        data() {
          return {
            name: ‘Hello Vue.js‘
          }
        }
      }
    },
    methods: {
      btnClick() {
        /*
        this.$children不常使用
        for (let item of this.$children) {
          item.showMessage();
          console.log(item.name);
        }
        */
        console.log(this.$refs.three.name);
        //Hello Vue.js
      }
    }
  })
</script>
</body>
</html>

子访问父

子组件访问父组件:$parent

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <my-component></my-component>
</div>

<template id="my-component-template">
  <div>
    <span>我是子组件</span>
    <button v-on:click="btnClick">按钮</button>
    <my-component-son></my-component-son>
  </div>
</template>

<template id="my-component-son-template">
  <div>
    <span>我是子组件的子组件</span>
    <button v-on:click="btnClick">按钮</button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: ‘#app‘,
    data: {
      message: ‘你好a‘
    },
    components: {
      ‘my-component‘: {
        template: ‘#my-component-template‘,
        methods: {
          btnClick() {
            console.log(this.$parent);
          }
        },
        data() {
          return {
            name: ‘我是my-component组件的name‘
          }
        },
        components: {
          ‘my-component-son‘: {
            template: ‘#my-component-son-template‘,
            methods: {
              btnClick() {
                /* 访问父组件 */
                console.log(this.$parent);
                console.log(this.$parent.name);
                /* 访问根组件 */
                console.log(this.$root);
                console.log(this.$root.message);
              }
            }
          }
        }
      }
    }
  })
</script>
</body>
</html>

以上是关于03组件化开发父子组件通信的主要内容,如果未能解决你的问题,请参考以下文章

12.组件化开发2-非父子组件之间通信-祖先和后代之间的通信

前端开发React 中父子组件之间的通信方式

vue组件之间的通信, 父子组件通信,兄弟组件通信

Vue父子组件通信

父子组件之间的通信

react初探之父子组件通信封装公共组件