Vue组件的使用

197 阅读2分钟

全局组件

    1. 创建组件构造器对象
    1. 注册组件
    1. 使用组件
    <div id="app">
      <!--3.使用组件-->
      <my-cpn></my-cpn>
      <div>
        <div>
         <!--3.使用组件-->
          <my-cpn></my-cpn>
        </div>
      </div>
    </div>
    <div id="app2">
      <!--3.使用组件-->
      <my-cpn></my-cpn>
    </div>
     <!--在此处使用组件无效,因为每在Vue实例挂载的范围内,脱离了Vue的管理-->
    <my-cpn></my-cpn>
    
    <script src="../js/vue.js"></script>
    <script>
      // 1.创建组件构造器对象
      const cpnC = Vue.extend({
        template: `
          <div>
            <h2>我是标题</h2>
            <p>我是内容, 哈哈哈哈</p>
            <p>我是内容, 呵呵呵呵</p>
          </div>`
      })
    
      // 2.注册全局组件,因此在被挂载的#app和#app2中都可以使用
      Vue.component('my-cpn', cpnC)
    
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        }
      })
      const app2 = new Vue({
        el: '#app2'
      })
    </script>
    

局部组件

<div id="app">
  <!--3.使用组件-->
  <my-cpn></my-cpn>
  <div>
    <div>
     <!--3.使用组件-->
      <my-cpn></my-cpn>
    </div>
  </div>
</div>
<div id="app2">
   <!--在此处使用组件无效-->
  <my-cpn></my-cpn>
</div>
 <!--在此处使用组件无效-->
<my-cpn></my-cpn>

<script src="../js/vue.js"></script>
<script>
  // 1.创建组件构造器对象
  const cpnC = Vue.extend({
    template: `
      <div>
        <h2>我是标题</h2>
        <p>我是内容, 哈哈哈哈</p>
        <p>我是内容, 呵呵呵呵</p>
      </div>`
  })


  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      // 2.注册局部组件,因此只能在被该Vue实例挂载下的dom使用
      myCpn: cpnC
    }
  })
  const app2 = new Vue({
    el: '#app2'
  })
</script>

父组件与子组件

  • 父子组件错误用法:以子标签的形式在Vue实例中使用
    • 因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
    • 该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
    • <child-cpn></child-cpn>是只能在父组件中被识别的
    • 类似这种用法,<child-cpn></child-cpn>是会被浏览器忽略的
  • 正确用法
<div id="app">
  <cpn2></cpn2>
  <!--<cpn1></cpn1>-->
</div>

<script src="../js/vue.js"></script>
<script>
  // 1.创建第一个组件构造器(子组件)
  const cpnC1 = Vue.extend({
    template: `
      <div>
        <h2>我是标题1</h2>
        <p>我是内容, 哈哈哈哈</p>
      </div>
    `
  })

  // 2.创建第二个组件构造器(父组件)
  const cpnC2 = Vue.extend({
    template: `
      <div>
        <h2>我是标题2</h2>
        <p>我是内容, 呵呵呵呵</p>
        <cpn1></cpn1>
      </div>
    `,
    components: {
      cpn1: cpnC1
    }
  })

  // root组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn2: cpnC2
    }
  })
</script>
  • 语法糖

    主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替

    <div id="app">
      <cpn1></cpn1>
      <cpn2></cpn2>
    </div>
    
    <script src="../js/vue.js"></script>
    <script>
      // 1.全局组件注册的语法糖
      // 1.创建组件构造器
      // const cpn1 = Vue.extend()
    
      // 2.注册组件
      Vue.component('cpn1', {
        template: `
          <div>
            <h2>我是标题1</h2>
            <p>我是内容, 哈哈哈哈</p>
          </div>
        `
      })
    
      // 2.注册局部组件的语法糖
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        },
        components: {
          'cpn2': {
            template: `
              <div>
                <h2>我是标题2</h2>
                <p>我是内容, 呵呵呵</p>
              </div>
        `
          }
        }
      })
    </script>
    
  • 组件模板的分离写法

    使用<template>标签

    <div id="app">
      <cpn></cpn>
      <cpn></cpn>
      <cpn></cpn>
    </div>
    
    <!-- 1.script标签, 注意:类型必须是text/x-template-->
    <!--<script type="text/x-template" id="cpn">-->
    <!--<div>-->
      <!--<h2>我是标题</h2>-->
      <!--<p>我是内容,哈哈哈</p>-->
    <!--</div>-->
    <!--</script> -->
    
    <!--2.template标签-->
    <template id="cpn">
      <div>
        <h2>我是标题</h2>
        <p>我是内容,呵呵呵</p>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
    
      // 1.注册一个全局组件
      Vue.component('cpn', {
        template: '#cpn'
      })
    
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        }
      })
    </script>
    
  • 数据存放问题

    组件是一个单独功能模块的封装,有属性自己的数据data(还有methods等属性,原型就是Vue实例),不能直接访问父组件中的data

    <div id="app">
      <cpn></cpn>
      <cpn></cpn>
      <cpn></cpn>
    </div>
    
    <!--1.script标签, 注意:类型必须是text/x-template-->
    <!--<script type="text/x-template" id="cpn">-->
    <!--<div>-->
      <!--<h2>我是标题</h2>-->
      <!--<p>我是内容,哈哈哈</p>-->
    <!--</div>-->
    <!--</script>-->
    
    <!--2.template标签-->
    <template id="cpn">
      <div>
        <h2>{{title}}</h2>
        <p>我是内容,呵呵呵</p>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
    
      // 1.注册一个全局组件
      Vue.component('cpn', {
        template: '#cpn',
        data() {
          return {
            title: 'abc'
          }
        }
      })
    
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊',
          // title: '我是标题'
        }
      })
    </script>
    
  • 组件中的data为什么是函数,且返回一个对象,对象内部保存着数据

    如果不是函数,则会导致不同的组件操作数据时会导致其他组件的数据一起变动,而函数返回的数据属于该组件自己,和其他组件无关

    <!--组件实例对象-->
    <div id="app">
      <cpn></cpn>
      <cpn></cpn>
      <cpn></cpn>
    </div>
    
    <template id="cpn">
      <div>
        <h2>当前计数: {{counter}}</h2>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
      </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
      // 1.注册组件
      const obj = {
        counter: 0
      }
      Vue.component('cpn', {
        template: '#cpn',
        // 使用这样的data,则每个组件有独立的数据,每个组件实例都会接受到一个新的对象,对应一块新的内存地址
        data() {
          return {
            counter: 0
          }
        },
        // 使用这样的data,每个组件共享数据,因为obj会传回一个地址,故每个组件实例接受到的是一个地址
        // data() {
        //   return obj
        // },
        methods: {
          increment() {
            this.counter++
          },
          decrement() {
            this.counter--
          }
        }
      })
    
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        }
      })
    </script>
    
  • 父组件向子组件传递数据

    通过props向子组件传递数据

    • 方式一:字符串数组,数组中的字符串就是传递时的名称
    • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等
    <div id="app">
      <!--<cpn v-bind:cmovies="movies"></cpn>-->
      <!--<cpn cmovies="movies" cmessage="message"></cpn>-->
    
      <cpn :cmessage="message" :cmovies="movies"></cpn>
    </div>
    
    <template id="cpn">
      <div>
        <ul>
          <li v-for="item in cmovies">{{item}}</li>
        </ul>
        <h2>{{cmessage}}</h2>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      // 父传子: props
      const cpn = {
        template: '#cpn',
        // props: ['cmovies', 'cmessage'],
        props: {
          // 1.类型限制
          // cmovies: Array,
          // cmessage: String,
    
          // 2.提供一些默认值, 以及必传值
          cmessage: {
            type: String,
            default: 'aaaaaaaa',
            required: true //如果使用子组件每传递值给cmessage时,会报错
          },
          // 类型是对象或者数组时, 默认值必须是一个函数
          cmovies: {
            type: Array,
            default() {
              return []
            }//默认值时数组或者对象时 必须用函数返回,否则会报错
          },
          cmessage: {
            // 自定义验证函数
            validtor: function (value){
              return ['你好啊', '你好吗'].indexOf(value) !== -1
            }
          },
        },
        data() {
          return {}
        },
        methods: {
    
        }
      }
    
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊',
          movies: ['海王', '海贼王', '海尔兄弟']
        },
        components: {
          cpn
        }
      })
    </script>
    
  • props中的驼峰标识&template下有多个标签的话,需要有一个根节点,否则会报错

    v-bind:class不能正确识别驼峰标识,因此传入props时,下面需要用 c-info替换cInfo,child-my-message替换childMyMessage

    <div id="app">
      <!-- 下面不能使用驼峰标识符 -->
      <cpn :c-info="info" :child-my-message="message" v-bind:class></cpn>
    </div>
    
    <template id="cpn">
      <div><!-- 模板里面有多个属性的话,需要用一个dom元素包裹起来,否则会报错 -->
        <h2>{{cInfo}}</h2>
        <h2>{{childMyMessage}}</h2>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      const cpn = {
        template: '#cpn',
        props: {
          cInfo: {
            type: Object,
            default() {
              return {}
            },
            required:true
            // 如果用了上面这个属性则一定要调用,否则会报错
          },
          childMyMessage: {
            type: String,
            default: ''
          }
        }
      }
    
      const app = new Vue({
        el: '#app',
        data: {
          info: {
            name: 'CodingKid',
            age: 18,
            height: 1.88
          },
          message: 'Hello, Vue.js!'
        },
        components: {
          cpn
        }
      })
    </script>
    
  • 子组件向父组件传递自定义事件

    在发送事件同时,可以传递参数到父组件

    <!--父组件模板-->
    <div id="app">
      <!-- 接受子组件发射的事件,传递过来的参数会默认传递给cpnClick,就如同$event,如果在这里使用$event,传递过来就是item,而不是事件本身 -->
      <!-- 在这里不能按照驼峰的方式写,但在脚手架里面可以 -->
      <cpn @item-click="cpnClick"></cpn>
    </div>
    
    <!--子组件模板-->
    <template id="cpn">
      <div>
        <button v-for="item in categories"
                @click="btnClick(item)">
          {{item.name}}
        </button>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
    
      // 1.子组件
      const cpn = {
        template: '#cpn',
        data() {
          return {
            categories: [
              {id: 'aaa', name: '热门推荐'},
              {id: 'bbb', name: '手机数码'},
              {id: 'ccc', name: '家用家电'},
              {id: 'ddd', name: '电脑办公'},
            ]
          }//必须返回一个对象
        },
        methods: {
          btnClick(item) {
            // 发射事件: 自定义事件
            this.$emit('item-click', item)//(发射事件的名称,传递的参数)
          }
        }
      }
    
      // 2.父组件
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        },
        components: {
          cpn
        },
        methods: {
          cpnClick(item) {
            console.log('cpnClick', item);
          }
        }
      })
    </script>
    
  • 父子组件通信案例

<div id="app">
  <cpn :number1="num1"
      :number2="num2"
      @num1change="num1change"
      @num2change="num2change"/>
</div>

<template id="cpn">
  <div>
    <h2>props:{{number1}}</h2>
    <h2>data:{{dnumber1}}</h2>
    <!--<input type="text" v-model="dnumber1">-->
    <input type="text" :value="dnumber1" @input="num1Input">
    <h2>props:{{number2}}</h2>
    <h2>data:{{dnumber2}}</h2>
    <!--<input type="text" v-model="dnumber2">-->
    <input type="text" :value="dnumber2" @input="num2Input">
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      num1: 1,
      num2: 0
    },
    methods: {
      num1change(value) {
        this.num1 = parseFloat(value)
      },
      num2change(value) {
        this.num2 = parseFloat(value)
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        props: {
          number1: Number,
          number2: Number
        },
        data() {
          return {
            dnumber1: this.number1,
            dnumber2: this.number2
          }
        },
        methods: {
          num1Input(event) {
            // 1.将input中的value赋值到dnumber中
            this.dnumber1 = event.target.value;//这条语句不能缺少,

            // 2.为了让父组件可以修改值, 发出一个事件
            this.$emit('num1change', this.dnumber1)

            // 3.同时修饰dnumber2的值
            this.dnumber2 = this.dnumber1 * 100;
            this.$emit('num2change', this.dnumber2);
          },
          num2Input(event) {
            this.dnumber2 = event.target.value;
            this.$emit('num2change', this.dnumber2)

            // 同时修饰dnumber1的值
            this.dnumber1 = this.dnumber2 / 100;
            this.$emit('num1change', this.dnumber1);
          }
        }
      }
    }
  })
</script>
  • 利用watch属性v-model实现
<div id="app">
  <cpn :number1="num1"
       :number2="num2"
       @num1change="num1change"
       @num2change="num2change"/>
</div>

<template id="cpn">
  <div>
    <h2>props:{{number1}}</h2>
    <h2>data:{{dnumber1}}</h2>
    <input type="text" v-model="dnumber1">
    <h2>props:{{number2}}</h2>
    <h2>data:{{dnumber2}}</h2>
    <input type="text" v-model="dnumber2">
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      num1: 1,
      num2: 0
    },
    methods: {
      num1change(value) {
        this.num1 = parseFloat(value)
      },
      num2change(value) {
        this.num2 = parseFloat(value)
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        props: {
          number1: Number,
          number2: Number,
          name: ''
        },
        data() {
          return {
            dnumber1: this.number1,
            dnumber2: this.number2
          }
        },
        watch: {
          dnumber1(newValue) {
            this.dnumber2 = newValue * 100;
            this.$emit('num1change', newValue);
          },
          dnumber2(newValue) {
            this.number1 = newValue / 100;
            this.$emit('num2change', newValue);
          }
        }
      }
    }
  })
</script>
  • 父组件访问子组件:$children$refs

    <div id="app">
      <cpn></cpn>
      <cpn></cpn>
    
      <cpn ref="aaa"></cpn>
      <button @click="btnClick">按钮</button>
    </div>
    
    <template id="cpn">
      <div>我是子组件</div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        },
        methods: {
          btnClick() {
            // 1.$children把所有使用的组件全拿过来组成一个数组
            // console.log(this.$children);是一个数组
            // for (let c of this.$children) {遍历每一个组件
            //   console.log(c.name);使用属性
            //   c.showMessage();使用方法
            // }
            // console.log(this.$children[3].name);
    
            // 2.$refs => 对象类型, 默认是一个空的对象
            console.log(this.$refs.aaa.name);//组件里面需要有ref属性和这里引用的属性匹配
          }
        },
        components: {
          cpn: {
            template: '#cpn',
            data() {
              return {
                name: '我是子组件的name'
              }
            },
            methods: {
              showMessage() {
                console.log('showMessage');
              }
            }
          },
        }
      })
    </script>
    
  • 子组件访问父组件:$parent$root

    • 尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做
    • 子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了,而组件化开发就是降低耦合度
    • 如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题
    • 另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,不知道是哪个子组件修改的,很不利于我的调试和维护
    <div id="app">
      <cpn></cpn>
    </div>
    
    <template id="cpn">
      <div>
        <h2>我是cpn组件</h2>
        <ccpn></ccpn>
      </div>
    </template>
    
    <template id="ccpn">
      <div>
        <h2>我是子组件</h2>
        <button @click="btnClick">按钮</button>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        },
        components: {
          cpn: {
            template: '#cpn',
            data() {
              return {
                name: '我是cpn组件的name'
              }
            },
            components: {
              ccpn: {
                template: '#ccpn',
                methods: {
                  btnClick() {
                    // 1.访问父组件$parent
                    // console.log(this.$parent);
                    // console.log(this.$parent.name);
    
                    // 2.访问根组件$root
                    console.log(this.$root);
                    console.log(this.$root.message);
                  }
                }
              }
            }
          }
        }
      })
    </script>
    

插槽的使用(已废弃的语法)

  • 插槽的基本使用

    • 插槽的基本使用 <slot></slot>
    • 插槽的默认值 <slot>默认值</slot>
    • 如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素
    <div id="app">
      <cpn></cpn>
      <div>-----------------------------</div><!--- 分割线 -->
      <cpn><span>哈哈哈</span></cpn>
      <div>-----------------------------</div>
      <cpn>
        <i>呵呵呵</i>
        <div>我是div元素</div>
        <p>我是p元素</p>
      </cpn>
    </div>
    
    
    <template id="cpn">
      <div>
        <h2>我是组件</h2>
        <slot><button>按钮</button></slot>
        <!--<button>按钮</button>-->
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        },
        components: {
          cpn: {
            template: '#cpn'
          }
        }
      })
    </script>
    
  • 具名插槽的使用

    <div id="app">
      <cpn>
        <span slot="center">标题</span>
        <button slot="left">返回</button>
      </cpn>
    </div>
    
    
    <template id="cpn">
      <div>
        <slot name="left"><span>左边</span></slot>
        <slot name="center"><span>中间</span></slot>
        <slot name="right"><span>右边</span></slot>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        },
        components: {
          cpn: {
            template: '#cpn'
          }
        }
      })
    </script>
    
  • 插槽的用法,其中涉及到编译作用域

    案例

    <div id="app">
      <cpn></cpn>
    
      <cpn>
        <!--目的是获取子组件中的pLanguages-->
        <div slot-scope="slot"><!--slot只是一个名字-->
          <!--<span v-for="item in slot.data"> - {{item}}</span>-->
          <span>{{slot.data.join(' - ')}}</span>
        </div>
      </cpn>
    
      <cpn>
        <!--目的是获取子组件中的pLanguages-->
        <div slot-scope="slot">
          <!--<span v-for="item in slot.data">{{item}} * </span>-->
          <span>{{slot.data.join(' * ')}}</span>
        </div>
      </cpn>
      <!--<cpn></cpn>-->
    </div>
    
    <template id="cpn">
      <div>
        <slot :data="pLanguages">
          <ul>
            <li v-for="item in pLanguages">{{item}}</li>
          </ul>
        </slot>
      </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊'
        },
        components: {
          cpn: {
            template: '#cpn',
            data() {
              return {
                pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
              }
            }
          }
        }
      })
    </script>
    
  • 关于插槽的新语法

写在最后

其中代码参考codewhy老师的vue.js的教学,以上内容均为个人笔记

码字不易,你懂的