• 4-vue组件化开发


    一、关于组件化

    1、什么是组件化

  • 人面对复杂问题的处理方式:任何一个人处理信息的逻辑能力都是有限的,但人有一种天生的能力,就是将问题进行拆解,
  • 如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,会发现大的问题也会迎刃而解;

  • 组件化也是类似的思想:如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于
  • 后续的管理以及扩展,但如果将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后

    整个页面的管理和维护就变得非常容易了;


    2、vue组件化思想

    组件化是 Vue.js 中的重要思想 ,它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用;

    任何的应用都会被抽象成一颗组件树:

    image


    有了组件化的思想,我们在之后的开发中就要充分的利用它,尽可能的将页面拆分成一个个小的、可复用的组件,

    这样让我们的代码更加方便组织和管理,并且扩展性也更强;


    二、注册组件

    1、基本使用

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

    1. 创建组件构造器
    2. 注册组件
    3. 使用组件

    image


    <body>
    <div id="app">
      <!--步骤三:使用组件两次(在Vue实例的作用范围内使用)-->
      <my-cpn></my-cpn>
      <my-cpn></my-cpn>
    </div>
    
    <script src="../js/vue.js"></script>
    <script>
      // 步骤一:创建组件构造器
      const cpnC = Vue.extend({
        // template使用的是反引号
        template: `
          <div>
            <h2>标题</h2>
            <p>内容</p>
          </div>`
      })
    
      //步骤二:注册组件,定义组件标签名称
      Vue.component('my-cpn', cpnC)
    
      const app = new Vue({
        el: '#app',
        data: {}
      })
    </script>
    </body>
    
    代码


    2、全局组件和局部组件

    当我们通过调用 Vue.component() 注册组件时,组件的注册是全局的 ,这意味着该组件可以在任意 Vue 实例下使用;

    如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件;


    上面的例子是全局组件案例,下面看一下局部组件:

    <body>
    <div id="app">
      <my-cpn></my-cpn>
      <my-cpn></my-cpn>
    </div>
    
    <script src="../js/vue.js"></script>
    <script>
      const cpnC = Vue.extend({
        template: `
          <div>
            <h2>标题</h2>
            <p>内容</p>
          </div>`
      })
    
      //在实例内部注册组件,为局部组件,其他实例不能使用
      const app = new Vue({
        el: '#app',
        data: {},
        components: {
          'my-cpn': cpnC
        }
      })
    </script>
    </body>
    局部组件代码


    3、父组件和子组件

    <body>
    <div id="app">
      <my-cpn2></my-cpn2>
    </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>
            <my-cpn1></my-cpn1>
          </div>
        `,
        //在组件2中注册组件1
        components: {
          'my-cpn1': cpnC1
        }
      })
    
      //root组件
      const app = new Vue({
        el: '#app',
        data: {},
        components: {
          'my-cpn2': cpnC2
        }
      })
    </script>
    </body>
    代码


    4、注册组件语法糖

    在上面注册组件的方式,可能会有些繁琐,Vue为了简化这个过程,提供了注册的语法糖,主要是省去了调用Vue.extend() 的步骤,直接使用一个对象来代替;

    <body>
    <div id="app">
      <my-cpn1></my-cpn1>
    </div>
    
    <script src="../js/vue.js"></script>
    <script>
      // 1. 注册全局组件的语法糖
      Vue.component('my-cpn1', {
        template: `
          <div>
          <h2>标题1</h2>
          <p>内容</p>
          </div>
          `
      })
    
      const app = new Vue({
        el: '#app',
        data: {},
        //2.注册局部组件的语法糖
        components: {
          'my-cpn2': {
            template: `
              <div>
              <h2>组件标题2</h2>
              <p>组件2中的一个段落内容</p>
              </div>
              `
          }
        }
      })
    </script>
    </body>
    代码


    5、模板分离写法

    使用 script 标签或 template 标签将模板内容从注册时的 template 中抽离出来;

    <body>
    <div id="app">
      <cpn-c1></cpn-c1>
    </div>
    
    <!--模板分离方式1:使用script标签-->
    <script type="text/x-template" id="cpn1">
      <div>
        <h3>标题1</h3>
        <p>内容</p>
      </div>
    </script>
    
    <!--模板分离方式2:使用template标签-->
    <template id="cpn2">
      <div>
        <h3>标题2</h3>
        <p>内容</p>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      Vue.component('cpnC1', {
        template: '#cpn2'
      })
    
      const app = new Vue({
        el: '#app',
        data: {}
      })
    </script>
    </body>
    
    代码


    三、组件的数据存放

    组件是一个单独功能模块的封装,这个模块有属于自己的HTML模板,也应该有属性自己的数据 data,那么组件中的数据是保存在哪里呢?顶层的Vue实例中吗?

    1、组件中能不能直接访问 Vue 实例中的 data

    组件不能直接访问 Vue 实例中的 data,而且即使可以访问,如果将所有的数据都放在 Vue 实例中,Vue 实例就会变的非常臃肿;


    2、组件数据的存放

    其实,组件对象也有一个 data 属性,只是这个 data 属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据;

    <body>
    <div id="app">
      <my-cpn1></my-cpn1>
    </div>
    
    <template id="cpn">
      <div>
        <h2>{{title}}</h2>
        <p>内容</p>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {},
        components: {
          'my-cpn1': {
            template: '#cpn',
            data() {
              return {
                title: '标题'
              }
            }
          }
        }
      })
    </script>
    </body>
    代码


    2、为什么 data 在组件中必须是一个函数呢?

    • 首先,如果不是一个函数,Vue直接就会报错
    • 其次,Vue 让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响


    四、父子组件的通信

    在上一个小节中,我们提到了子组件是不能引用父组件或者Vue实例的数据的。

    但是,在开发中,往往一些数据确实需要从上层传递到下层:

         比如在一个页面中,我们从服务器请求到了很多的数据。

         其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。

         这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。


    如何进行父子组件间的通信呢?Vue官方提到

         父传子:props

         子传父:自定义事件


    1、父传子:props

    props的值有两种方式:

         方式一:字符串数组,数组中的字符串就是传递时的名称。

         方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。


    (1)方式一:字符串数组

    首先有一个组件cpn,然后在父组件app里面注册,使用组件时用v-bind将父组件中的数据赋给了 cmovies,

    cmovies会将数据传递到props中,然后props又会将数据渲染到模板,最后呈现出来;

    <body>
    <div id="app">
      <cpn v-bind:cmovies="movies"></cpn>
    </div>
    
    <template id="cpn">
      <div>
        <ul>
          <li v-for="item in cmovies">{{item}}</li>
        </ul>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      //父传子 props
      const cpn = {
        template: '#cpn',
        props: ['cmovies'],
        data() {
          return {}
        }
      }
    
      //父
      const app = new Vue({
        el: '#app',
        data: {
          movies: ['one', 'two', 'three']
        },
        components: {
          cpn
        }
      })
    </script>
    </body>
    代码


    (2)方式二:对象

    除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。

    当我们有自定义构造函数时,验证也支持自定义的类型;

    支持的数据类型:

      String

      Number

      Boolean

      Array

      Object

      Date

      Function

      Symbol


    <body>
    <div id="app">
      <cpn v-bind:cmovies="movies"></cpn>
    </div>
    
    <template id="cpn">
      <div>
        <ul>
          <li v-for="item in cmovies">{{item}}</li>
        </ul>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      //父传子 props
      const cpn = {
        template: '#cpn',
        // props: ['cmovies'],
        props: {
          cmovies: {
            type: Array, //限制类型
            // default: 'abc',
            default () {  //默认值;类型是对象或者数组时,默认值必须是一个函数
              return []
            },
            required: true,  //是否必须
          }
        },
        data() {
          return {}
        }
      }
    
      //父
      const app = new Vue({
        el: '#app',
        data: {
          movies: ['one', 'two', 'three']
        },
        components: {
          cpn
        }
      })
    </script>
    </body>
    代码


    验证支持的数据类型:

    image


    自定义类型:

    image


    (3)props驼峰标识

    <body>
    <div id="app">
      <!--cMovies在这里要写成c-movies-->
      <cpn v-bind:c-movies="movies"></cpn>
    </div>
    
    <template id="cpn">
      <div>
        <ul>
          <li v-for="item in cMovies">{{item}}</li>
        </ul>
      </div>
    </template>
    
    <script src="../js/vue.js"></script>
    <script>
      //父传子 props
      const cpn = {
        template: '#cpn',
        // props: ['cmovies'],
        props: {
          // cMovies
          cMovies: {
            type: Array, //限制类型
            // default: 'abc',
            default () {  //默认值;类型是对象或者数组时,默认值必须是一个函数
              return []
            },
            required: true,  //是否必须
          }
        },
        data() {
          return {}
        }
      }
    
      //父
      const app = new Vue({
        el: '#app',
        data: {
          movies: ['one', 'two', 'three']
        },
        components: {
          cpn
        }
      })
    </script>
    </body>
    代码


    2、子级向父级传递数据

    自定义事件流程:

    1. 在子组件中,通过 $emit() 来触发事件
    2. 在父组件中,通过 v-on 来监听子组件事件


    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
    <!--父组件模板-->
    <div id="app">
      <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>
    </body>
    </html>
    代码


    3、双向绑定

    <body>
    <!-- 父组件模板 -->
    <div class="app">
      <cpn :number1="num1" :number2="num2" @num1change="num1change" @num2change="num2change"></cpn>
    </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中的数据只能父组件修改,通过使用标签动态绑定
            props: { // 父传子
              number1: Number,
              number2: Number
            },
            data() {
              return {
                dnumber1: this.number1,
                dnumber2: this.number2,
              }
            },
            methods: {
              num1Input(event) {
                // 将input中的vlaue赋值给dnumber1
                this.dnumber1 = event.target.value
                // 发射一个事件,让父组件可以修改值(子传父)
                this.$emit('num1change', this.dnumber1)
                // 修改dnumber2的值
                this.dnumber2 = this.dnumber1 * 100
                this.$emit('num2change', this.dnumber2)
              },
              num2Input(event) {
                this.dnumber2 = event.target.value
                this.$emit('num2change', this.dnumber2)
                this.dnumber1 = this.dnumber2 / 100
                this.$emit('num1change', this.dnumber1)
              }
            }
          }
        }
      })
    </script>
    </body>
    代码


    五、父子组件访问

    1、父组件访问子组件

    父组件访问子组件有两种方式:

    • $children(不常用)
    • $refs(常用)


    (1)$children

    this.$children 是一个数组类型,它包含所有子组件对象,可以通过遍历,取出所有子组件的信息;


    <body>
    <div id="app">
      <cpn></cpn>
      <cpn></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: {},
            methods: {
              btnClick() {
                console.log(this.$children);
                for (let c of this.$children) {
                  console.log(c.name)  //访问子组件中的属性
                  this.$children[0].showMessage()  //调用子组件中的方法
                }
              }
            },
            components: {
              cpn: {
                template: '#cpn',
                data() {
                  return {
                    name: '我是子组件的name'
                  }
                },
                methods: {
                  showMessage() {
                    console.log('子组件中showMessage方法')
                  }
                }
              },
            }
      })
    
    
    </script>
    </body>
    代码


    image

    $children 的缺陷:

    通过 $children 访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值,但是当子组件过多,我们需要拿到其中一个时,

    往往不能确定它的索引值,甚至还可能会发生变化;

    有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用 $refs


    (2)$refs

    $refs 和 ref 指令通常是一起使用的:

    首先,我们通过 ref 给某一个子组件绑定一个特定的 ID,然后通过 this.$refs.ID 就可以访问到该组件了;


    <body>
    <div id="app">
      <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',
        methods: {
          btnClick() {
            // 2.$refs => 对象类型, 默认是一个空的对象 ref='bbb'
            console.log(this.$refs.aaa.name);
          }
        },
        components: {
          cpn: {
            template: '#cpn',
            data() {
              return {
                name: '我是子组件的name'
              }
            },
            methods: {
              showMessage() {
                console.log('showMessage');
              }
            }
          },
        }
      })
    </script>
    </body>
    代码


    image


    2、子组件访问父组件(不常用)

    子组件访问父组件通过:$parent

    如果是向访问根组件,通过:$root


    <body>
    
    <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>
    
    </body>
    代码


    注意:

    • 尽管在 Vue 开发中,我们允许通过 $parent 来访问父组件,但是在真实开发中尽量不要这样做,因为这样耦合度太高了;
    • 子组件应该尽量避免直接访问父组件的数据,如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题 ;
    • 外,通过 $parent 直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于调试和维护 ;
    • 不常用 $root 来访问根组件(即 vue 实例),因为根组件中一般只存放路由等重要数据,不存放其他信息;
  • 相关阅读:
    C++ Node template typename T
    C++ BST Insert Print,compile via g++ ,the *.cpp asterisk can represent all the files end with cpp
    springmvc 请求数据 全局过滤器
    spring mvc页面跳转和回写
    Conda 换源
    d2l 安装,提示 "error:Microsoft Visual C++ 14.0 or greater is required.Get it with "Microsoft C++ Build Tools""
    Conda 安装配置
    UI卡顿假死问题
    System.NotSupportedException:“该类型的 CollectionView 不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改。”
    List<T>和ObservableCollection<T>的相互转换
  • 原文地址:https://www.cnblogs.com/weiyiming007/p/13594913.html
  • Copyright © 2020-2023  润新知