Vue学习笔记之10-组件化开发

什么是组件化

  • 将一个复杂的问题, 拆解为很多个可以处理的小问题, 再将其放到整体当中,大问题就可以迎刃而解 其实就是 动态规划问题

  • 组件化也是类似的思想

    • 如果将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂, 而且不利于后续的管理以及扩展, 不利于维护
    • 如果将一个页面拆分成一个个效地功能块, 每个功能块完成属于自己的部分的独立功能, 那么之后整个页面的管理和维护就变得非常容易了
  • 组件化是Vue.js中的重要思想, 它提供了一种抽象, 让我们开发出一个个独立的可以复用的小组件来组成我们的页面

  • 任何的应用都可以被抽象成一个组件树

  • 组件化思想的应用

    • 有了组件化的思想, 在以后的开发中就要充分地利用组件化
    • 尽可能地将页面拆分成一个个小的, 可以复用的组件
    • 这样可以方便代码的管理和维护,代码的扩展性也会大大增强

注册组件的基本步骤

  • 组件的使用分成三个步骤

    • 创建组件构造器
      • 调用Vue.extend()方法创建组件构造器
    • 注册组件
      • 调用Vue.component()方法注册组件
    • 使用组件
      • 在Vue实例的作用范围内使用组件
  • 注意: 前两个步骤代码的编写要写在 创建 new Vue() 之前

注册组件步骤解析

    1. Vue.extend()
    • 调用Vue.extend() 创建的是一个组件构造器
    • 在创建组件构造器的时候, 传入template 代表自定义组件的模板
    • 改模板就是在使用到组件的地方, 要显示的html代码
    • 事实上,这种写法在Vue2.x的文档中几乎看不到了,都是使用语法糖
    1. Vue.component()
    • 调用Vue.component() 是将创建的组件构造器注册为一个组件, 并给它一个标签名称
    • Vue.component() 需要传递两个参数: 1. 注册组件的标签名 2. 组件构造器
    1. 组件必须挂载在某个Vue实例中, 否则不会生效
<div id="app">
    <!-- 3. 使用组件 -->
    <my-cpn></my-cpn>
  </div>
  <script>
    // 1. 创建组件构造器
    const cpn = Vue.extend({
      template: `
      <div>
      <h1>我是标题</h1>
      <p>我是内容, 你好你好</p>
      <p>我是内容, 你好你好</p>
      <p>我是内容, 你好你好</p>
      </div>
      `
    })

    // 2. 注册组件
    Vue.component('my-cpn', cpn)

    // 以上两步要写在 new Vue实例之前

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

全局组件和局部组件 (开发中常用的是局部组件)

  • 全局组件就是在全局注册的组件, 全局组件可以在多个Vue实例中使用
  • 局部组件就是在Vue实例对象中注册的组件, 局部组件只能在当前的Vue实例下面使用, 在其他的Vue实例下使用会报错
    • 局部组件是在Vue实例下的一个叫 components 的 option下注册的, components值是一个对象那个, 里面写入 组件名(就是在html中使用的标签名) : 组件构造器 即可注册组件
  • 注意, 组件构造器和全局注册组件的代码要写在 创建Vue实例的代码之前
<div id="app1">
    <mycpn1></mycpn1>
    <mycpn2></mycpn2>
  </div>
  <div id="app2">
    <mycpn1></mycpn1>
    <!-- 局部的组件 在这里无效 而且会报错-->
    <!-- <mycpn2></mycpn2> -->
  </div>
  <script>
    // 1. 创建组件构造器
    const cpn1 = Vue.extend({
      template: `
      <div>
        <h2>我是标题111</h2>
        <p>我是内容111, 你好你好</p>  
      </div>`
    })

    const cpn2 = Vue.extend({
      template: `
      <div>
        <h2>我是标题222</h2>
        <p>我是内容222, 你好你好</p>  
      </div>`
    })

    // 2. 注册组件 (全局组件)
    // 全局组件可以在多个Vue实例下使用
    Vue.component('mycpn1', cpn1)

    const app1 = new Vue({
      el: "#app1",
      // 在实例化的Vue中注册组件就是 局部组件, 局部组件只能在当前的Vue实例中使用, 不能再其他的Vue实例中使用
      // 在 components option中定义局部组件
      components: {
        mycpn2: cpn2 // 组件名(就是在html中使用的标签名) : 组件构造器
      }
    })

    const app2 = new Vue({
      el: "#app2"
    })
  </script>

Vue组件的父组件和子组件

  • 子组件的注册组件代码可以写在父组件的组件构造器的名为components的option中, 子组件的注册完成后, 子组件的标签名就可以写入父组件的template中
  • 简单来说, 就是组件在哪里注册就只能在哪里使用, 浏览器解析组件标签的时候, 会先从当前的组件构造器中的components中查找是否有注册该标签名, 如果找到了就开始渲染, 如果没有找到就在全局的Vue.component中查找, 如果找到就渲染, 如果都没有找到的话就会报错
  • 注意, 子组件的组件构造器要写在 父组件的组件构造器之前, 否则按照浏览器解析代码的顺序, 会找不到子组件, 就会报错
<div id="app">
    <fathercpn></fathercpn>
  </div>
  <script>
    // 组件1
    const son = Vue.extend({
      template: `<div>
      <h2>我是子标题</h2>
      <p>我是子内容,呵呵呵呵</p>
      </div>`
    })

    // 组件2
    // 注意, 子组件的组件构造器要写在父组件的组件构造器之前
    const father = Vue.extend({
      template: `<div>
      <h2>我是父标题</h2>
      <p>我是父内容,哈哈哈哈</p>
      在这里使用子组件
      <soncpn></soncpn>
      </div>`,
      // 子组件可以在父组件的内部注册
      // 注册后就可以在父组件的template中使用
      components: {
        soncpn: son
      }
    })

    // 这个app可以看作是root组件(根组件)
    const app = new Vue({
      el: "#app",
      components: {
        fathercpn: father
      }
    })
  </script>

注册组件的语法糖

  • 传统注册组件的方式, 有些繁琐
  • Vue为了见过这个过程, 提供了注册的语法糖
  • 主要是省去了调用Vue.extend()的这个步骤, 可以将一个对象当作参数传入component中
<div id="app">
    <cpn1></cpn1>
    <cpn2></cpn2>
  </div>
  <script>
    // 注册组件的语法糖
    // 1. 全局注册组件的语法糖
    // 将组件构造器中调用Vue.extend方法的步骤省略, 而是直接将一个对象代替传入Vue.component中
    Vue.component('cpn1', {
      template: `<div>
        <h2>我是标题111</h2>
        <p>我是内容111, 你好你好</p>  
      </div>`
    })

    const app = new Vue({
      el: "#app",
      components: {
        // 2. 局部注册组件的语法糖
        // 跟全局注册组件的语法糖差不多, 也是直接将一个对象传入
        cpn2: {
          template: `<div>
        <h2>我是标题222</h2>
        <p>我是内容222, 你好你好</p>  
      </div>`
        }
      }
    })
  </script>

组件模板的分离写法

  • template模板如果写在js代码中不优雅不好看
  • 将其中的HTML代码分离出来,然后挂载到对应的组件上
  • Vue提供了两种方案来定义HTML模块内容
    • script标签
      • 注意script标签需要定义type类型为 text/x-template
      • 并且定义id属性来绑定组件的标签名
    • template标签
      • 定义id属性来绑定组件的标签名
<div id="app">
    <cpn1></cpn1>
    <cpn2></cpn2>
  </div>
  <!-- 组件模板的分离写法 -->
  <!-- 1. script标签写法 -->
  <script type="text/x-template" id="cpn1">
    <div>
      <h2>我是标题111</h2>
      <p>我是内容,哈哈哈哈哈</p>
    </div>
  </script>
  <!-- 2. template标签写法 -->
  <template id="cpn2">
    <div>
      <h2>我是标题222</h2>
      <p>我是内容,呵呵呵呵呵</p>
    </div>
  </template>

  <script>
    // 将定义的模板的id绑定到标签名中
    Vue.component('cpn1', {
      template: "#cpn1"
    })
    Vue.component('cpn2', {
      template: "#cpn2"
    })
    const app = new Vue({
      el: "#app"
    })
  </script>

组件中的数据存放问题

  • 组件可以访问Vue中的数据吗?
    • 不可以
    • 组件是一个单独功能模块的封装
    • 这个模块有属于自己的HTML模板, 也会有属于自己的数据data
  • 组件中的数据是保存在自身的一个data属性中的(组件也可以有自己的methods等等option,以后会用到)
    • 这个data属性必须是一个函数
    • 函数返回一个对象, 对象内部保存数据
<div id="app">
    <cpn1></cpn1>
  </div>
  <template id="cpn1">
    <div>
      <h2>我是标题</h2>
      <!-- 组件保存的数据也是用mustache语法引用 -->
      <p>我是内容,{{message}}</p>
    </div>
  </template>
  <script>
    Vue.component('cpn1', {
      template: "#cpn1",
      // 组件中的数据要存放在组件的注册的data中, 而且这个data必须是一个函数, 函数返回一个对象, 对象内写入数据
      data() {
        // 返回一个对象
        return {
          // 对象内写入数据
          message: "你好你好"
        }
      }
    })
    const app = new Vue({
      el: "#app"
    })
  </script>

组件中的data为什么一定要是一个对象

  • 就是为了防止多次引用组件的时候, 组件和组件之间共用一个data, 造成变量泄露
  • Vue已经考虑了这个问题, 所以这里必须写函数, 利用函数的作用域, 成为一个闭包, 防止变量泄露
<body>
  <div id="app">
    <counter></counter>
    <counter></counter>
    <counter></counter>
  </div>
  <template id="counter">
    <div>
      <h2>当前计数: {{num}}</h2>
      <button @click="decrement">-</button>
      <button @click="increment">+</button>
    </div>
  </template>
  <script>
    // 1. 注册组件
    Vue.component('counter', {
      template: "#counter",
      // 这里为什么要是一个函数?
      // 就是为了防止多次引用组件的时候, 组件和组件之间共用一个data, 造成变量泄露
      // Vue已经考虑了这个问题, 所以这里必须写函数, 利用函数的作用域, 成为一个闭包, 防止变量泄露
      data() {
        return {
          num: 0
        }
      },
      methods: {
        increment() {
          this.num++
        },
        decrement() {
          this.num--
        }
      }
    })

    const app = new Vue({
      el: "#app"
    })
  </script>

组件通讯-父组件向子组件传递数据

  • 在Vue中子组件是不可以直接引用父组件或者Vue实例中的数据的
  • 但是在开发中, 往往一些数据需要从上层传递到下层
    • 比如在一个页面中, 从服务器请求到很多数据, 包括大组件的数据和小组件的数据, 他们都存储在大组件的data中
    • 其中一部分数据, 并非是整个页面的大组件来展示的, 而是通过子组件来展示
    • 这时, 并不会让子组件再次发送一个网络请求, 这样会大大加大服务器的压力的, 此时会让大组件(父组件)将苏剧传递给小组件(子组件)
  • 父组件向子组件传递数据的方法
    • 通过props向子组件传递数据
      • props的写法也分有数组写法和对象写法 (我们一般用对象写法)
      • 对象的写法可以设置 传入的数据类型, 是否必须传入, 默认值等等
      • 对象的写法中, 如果type的值为0的时候, 则可以传入任何类型的值
<div id="app">
    <!-- 传递数据要子组件的标签中用 v-bind 链接数据 -->
    <cpn :sonmessage="message" :sonmovies="movies"></cpn>
  </div>

  <template id="cpn">
    <div>
      <!-- 在通过子组件标签链接数据后, 就可以在子组件的html模板中用mustache语法使用数据 -->
      <h2>{{sonmessage}}</h2>
      <ul>
        <li v-for="item in sonmovies">{{item}}</li>
      </ul>
    </div>
  </template>
  <script>
    // 这里是子组件
    const cpn =  {
      template: "#cpn",
      // 子组件向父组件拿数据, 用props
      // 这里用数组形式, 数组里面传入新的数据名
      // props: ["sonmessage", "sonmovies"]

      // 这里使用对象形式, 对象形式可以设置传入的数据的类型, 默认值, 是否必须传入等等
      props: {
        // 简单地设置数据类型
        /* sonmessage : String,
        sonmovies: Array */

        // 传入一个对象可以有更多的操作
        sonmessage: {
          // 设置数据的类型
          type: String,
          // 设置是否必须传入
          required: true, // true表示必须传入, false表示不是必须传入
          // 设置默认值
          default: "你好我是默认值"
        },

        sonmovies: {
          type: Array,
          // 注意如果传入的数据类型是数组或者对象的话, 设置默认值default必须是一个函数, 函数返回一个默认的数据
          default () {
            return ["我是默认的数据", "我也是默认的数据"]
          }
        }
      }
    }

    const app = new Vue({
      el: "#app",
      data: {
        // 这里是父组件的数据
        message: "你好你好",
        movies: ["海王", "海贼王", "海尔兄弟"]
      },
      components : {
        cpn
      }
    })
  </script>

父组件向子组件传递数据时props中的驼峰标识

  • 因为HTML代码时不区分大小写的, 所以不能使用驼峰命名法, 而JS代码时严格区分大小写的, 所以就会导致这个问题
  • 记住在HTML代码中用短线命名, 在JS代码中使用驼峰命名 就OK啦~
<div id="app">
    <!-- 注意这里不能使用驼峰命名法了, 要将props中的驼峰命名的转换为短线命名 -->
    <cpn :son-message="message" :son-person="person"></cpn>
  </div>

  <template id="cpn">
    <div>
      <!-- 这里使用的数据, 要与props中的数据名一致 -->
      <h2>{{sonMessage}}</h2>
      <p>{{sonPerson}}</p>
    </div>
  </template>
  <script>
    const cpn = {
      template: "#cpn",
      props: {
        sonMessage: {
          type: String,
          default: "你好你好"
        },
        sonPerson: {
          type: Object,
          default () {
            return {}
          }
        }
      }
    }
    const app = new Vue({
      el: "#app",
      data: {
        message: "我是传入的数据",
        person: {
          name: "xiaoLam",
          age: 22
        }
      },
      components: {
        cpn
      }
    })
  </script>

子组件向父组件传递数据

  • 需要使用自定义事件传递
  • 什么时候需要自定义事件呢?
    • 当子组件需要向父组件传递数据的时候, 就要用到自定义事件了
    • v-on不仅仅可以监听DOM事件, 也可以用域监听组件间的自定义事件
  • 自定义事件的流程
    • 在子组件中, 通过$emit()来发射数据
      • $emit() 中有两个参数, 第一个参数是自定义事件的名字, 第二个参数是需要发送的数据
    • 在父组件中, 通过v-on来监听自定义事件, 接收数据
  <!-- 需求: 计数器 -->
  <!-- 操作在子组件中完成 -->
  <!-- 展示交给父组件完成 -->

  <!-- 父组件模版 -->
  <div id="app">
    <p>{{num}}</p>
    <!-- 在这里用v-on监听接收子组件发射数据的自定义事件 -->
    <btn @num-de="decreNum" @num-in="increNum"></btn>
  </div>

  <!-- 子组件模板 -->
  <template id="btn">
    <div>
      <!-- 设置点击后触发发射数据事件 -->
      <button @click="decrement">-</button>
      <button @click="increment">+</button>
    </div>
  </template>
  <script>
    // 子组件模块
    const btn = {
      template: "#btn",
      methods: {
        // 用$emit()发射数据
        decrement() {
          this.$emit("num-de")
        },
        increment() {
          this.$emit("num-in")
        }
      }
    }

    // 父组件模块
    const app = new Vue({
      el: "#app",
      data: {
        num: 0
      },
      components: {
        btn
      },
      methods: {
        // 处理接收来的数据
        increNum() {
          this.num++
        },
        decreNum() {
          this.num--
        }
      }
    })
  </script>

子组件接收来自父组件的数据, 通过子组件修改父组件的数据案例

  • 注意!!! 子组件不要直接修改props中来自父组件的数据, 会报错的
    • 正确做法是, 子组件修改子组件中data的数据, 然后将data中的数据通过自定义事件发送给父组件, 父组件处理接收的数据, 修改父组件自身的数据
    • 以下的代码请注意看注释
    • 以下代码实现的过程为, 从父组件通过props向子组件发送数据, 子组件接收数据, 子组件设置自己的data数据, 子组件修改自己的data数据, 子组件通过自定义事件向父组件发送自己的data数据, 父组件接收数据, 父组件修改自己的data数据, 同步修改了props向子组件发送的数据
<body>
  <!-- 根组件模板 -->
  <div id="app">
    <son :sonnum1="num1" :sonnum2="num2" @parchangenum1="parentnum1" @parchangenum2="parentnum2"></son>
  </div>

  <!-- 子组件模板 -->
  <template id="son">
    <div>
      <!-- 注意嗷!!! 子组件里不要直接修改props中来自父组件的数据 -->
      <!-- <input type="number" v-model="num1"> -->
      <input type="number" :value="num1" @input="changenum1">
      <!-- props 是接收的来自父组件的数据 -->
      <h2>props : {{sonnum1}}</h2>
      <!-- data 是子组件自身的数据 -->
      <h2>data : {{num1}}</h2>
      <!-- <input type="number" v-model="num2"> -->
      <input type="number" :value="num2" @input="changenum2">
      <h2>props : {{sonnum2}}</h2>
      <h2>data : {{num2}}</h2>
    </div>
  </template>
  <script>
    const son = {
      template: "#son",
      // 接收来自根组件的数据
      props: {
        sonnum1: {
          // 在做这个案例的时候, 发现了一个很有趣的现象, 如果将这里的type的值设置为 0 , 那么这个sonnum1 就可以接收任何类型的数据
          type: Number
        },
        sonnum2: {
          type: Number
        }
      },
      // 子组件的data必须是一个函数, 防止变量泄露
      data() {
        return {
          num1: this.sonnum1,
          num2: this.sonnum2
        }
      },
      methods: {
        changenum1(event) {
          // 处理num1, 然后向根组件发送
          this.num1 = event.target.value;
          this.$emit("parchangenum1", this.num1);
          // 处理num2, 然后向根组件发送
          this.num2 = this.num1 * 100;
          this.$emit("parchangenum2", this.num2);
        },
        changenum2(event) {
          this.num2 = event.target.value;
          this.$emit("parchangenum2", this.num2)
          this.num1 = this.num2 / 100;
          this.$emit("parchangenum1", this.num1)
        }
      }
    }

    const app = new Vue({
      el: "#app",
      data: {
        num1: 1,
        num2: 2
      },
      components: {
        son
      },
      methods: {
        // 父组件处理从子组件接收来的数据
        parentnum1(num) {
          this.num1 = parseFloat(num)
        },
        parentnum2(num) {
          this.num2 = parseFloat(num)
        }
      }
    })
  </script>
</body>

组件访问-父组件访问子组件, 通过$children 和 $refs

  • $children 是一个数组, 里面包含的的是父组件中包含的所有子组件
    • 想要通过children访问某一个特定的子组件只能通过数组的下标来访问(这样的方法很不灵活), 所以children访问子组件的方法很少用
  • $refs 是一个对象, 对象里面包含的是在父组件中 有ref属性注册的子组件, 没有ref属性的子组件不会被包含在内; 如果没有子组件设置ref属性, $refs就是一个空对象
    • 想要通过refs访问某一个特定的子组件, 需要在子组件的ref属性设置值, 通过这个设置值来访问这个子组件
<body>
  <div id="app">
    <son ref="son1"></son>
    <son></son>
    <son></son>
    <!-- 通过这个按钮来触发事件 -->
    <button @click="btnClick">按钮</button>
  </div>

  <template id="son">
    <div>
      <h2>我是子组件</h2>
    </div>
  </template>
  <script>
    const son = {
      template: "#son",
      data() {
        return {
          name: "子组件数据"
        }
      },
      methods: {
        showMessage() {
          console.log("子组件方法");
        }
      }
    }

    const app = new Vue({
      el: "#app",
      components: {
        son
      },
      methods: {
        btnClick() {
          // 1. 通过children获取所有的子组件
          // 获取的是所有的子组件组成的一个数组
          // 想要获取某个特定的子组件, 只能使用数组的下标获取(这样很不灵活, 所以使用children获取子组件的方法很少用)
          /* console.log(this.$children);
          this.$children[0].showMessage(); // 通过$children 使用某个下标的子组件的方法
          console.log(this.$children[0].name); // 通过$children 获取某个下标的子组件的数据 */

          // 2. 通过$refs 获取某个特定的子组件
          console.log(this.$refs); // $refs 获得的是一个对象, 里面包含通过标签属性ref注册的子组件 可以通过属性ref的值来获取特定的一个子组件
          this.$refs.son1.showMessage(); //通过refs调用ref值为son1的子组件的方法
          console.log(this.$refs.son1.name); // 通过refs获取ref值为son1的子组件中的数据

        }
      }

    })
  </script>
</body>

组件访问-子组件访问父组件, 通过$parent 和 $root

  • $parent 可以访问当前子组件上一级的父组件
  • $root 可以访问当前子组件的最上级根组件
  • 这两个方法不是很常用, 因为Vue最大的优点是能够组件化分离, 如果组件用了$parent 或者 $root 方法的话就大大地减低了Vue的组件性
<body>
  <div id="app">
    <son></son>
  </div>

  <template id="son">
    <div>
      <h2>我是son组件</h2>
      <button @click="sonBtnClick">我是son按钮</button>
      <sonchild></sonchild>
    </div>
  </template>

  <template id="sonchild">
    <div>
      <h2>我是sonchild组件</h2>
      <button @click="sonChildBtnClick">我是sonchild按钮</button>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: "#app",
      data: {
        message: "我是根组件的数据"
      },
      components: {
        son: {
          template: "#son",
          data() {
            return {
              message: "我是son组件的数据"
            }
          },
          methods: {
            sonBtnClick() {
              // 通过parent访问父组件
              console.log(this.$parent);
              // 通过parent访问父组件的数据
              console.log(this.$parent.message);
            }
          },
          components: {
            sonchild: {
              template: "#sonchild",
              methods: {
                sonChildBtnClick() {
                  // 通过 $root 访问根组件
                  console.log(this.$root);
                  console.log(this.$root.message);

                  // 通过$parent 访问父组件
                  console.log(this.$parent);
                  console.log(this.$parent.message);
                }
              }
            }
          }
        }
      }
    })
  </script>
</body>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值