Vue - 3 - 组件化(概念、语法、父子)

  • 上一篇:《Vue - 2 - 【归档】、基本概念、【生命周期】、模板语法》
  • 下一篇:《Vue - 4 - 组件化 - 插槽(slot)》

Vue - 3 - 组件化(概念、语法、父子)_第1张图片

文章目录

    • # 组件化
      • ## 不用组件的两个【灾难性问题】
        • 1、如==全局变量同名==问题
        • 2、又如 代码的编写方式对js文件的依赖顺序几乎是强制性的
      • ## Vue 组件化思想
      • ## 注册组件
        • 注册组件的基本步骤
        • 基本使用
        • 全局组件 、 局部组件
        • 父组件和子组件
        • 语法糖:组件注册
      • ## 技巧:模板分离写法
      • ## 组件访问实例数据 【无法访问的】
      • ## 为什么 组件内部的 data 属性值必须是个 函数???
      • ## 父子组件之间通讯
        • 通过 props 向子组件传递数据
        • 通过 props 向子组件传递数据 - 验证数据类型 - type
        • 通过 props 向子组件传递数据 - 验证数据类型 - validator + required
        • 通过 props 向子组件传递数据 - 默认值 - default
        • 通过 props 向子组件传递数据 - 驼峰标识
        • **【误区】**props 双向绑定
        • 通过 事件 向父组件发送消息
        • 父子通信案例:
      • ## 子组件最顶层只能有一个element
      • ## 父子组件之间 - 访问方式
        • $children - 父访子 - 【不常用,常用 ref】
        • ref - 父访子
        • `$parent` - 子访问父
        • $root 访问根组件

# 组件化

## 不用组件的两个【灾难性问题】

随着ajax异步请求的出现,慢慢形成了前后端的分离
客户端需要完成的事情越来越多,代码量也是与日俱增。
为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护。
但是这种维护方式,依然不能避免一些灾难性的问题

1、如全局变量同名问题

Vue - 3 - 组件化(概念、语法、父子)_第2张图片

2、又如 代码的编写方式对js文件的依赖顺序几乎是强制性的

但是当js文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较同时的事情。
而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生。

## Vue 组件化思想

官方 - 《组件基础》 - https://cn.vuejs.org/v2/guide/components.html

  • 我们希望开发出一个个独立可复用的小组件来构造我们的应哟

  • 那样, 任何都会被抽象成一颗组件数
    Vue - 3 - 组件化(概念、语法、父子)_第3张图片
    (其中,连线指的是下级对上级组件的依赖)

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

Vue - 3 - 组件化(概念、语法、父子)_第4张图片

## 注册组件

注册组件的基本步骤

  • 创建组件构造器
    调用 Vue.extend() 方法创建组件构造器
  • 注册组件
    调用 Vue.component() 方法 注册组件
  • 使用组件
    在Vue实例的作用域范围内使用组件

基本使用

<body>
  <div id='app'>
    
    <my-cpn>my-cpn>
  div>

  <script src="../dist/vue.js">script>
  <script>
    // 1.创建组件的构造器对象
    const cpnC = Vue.extend({
      template: `
      

我是标题

我是内容

`
}) // 2. 注册组件 (全局组件,意味着可以在多个Vue的实例下使用) Vue.component('my-cpn', cpnC) const app = new Vue({ el: '#app', data: { } })
script> body>

Vue - 3 - 组件化(概念、语法、父子)_第5张图片

全局组件 、 局部组件

  • 全局组件,意味着可以在多个Vue的实例下使用(例子如上)
  • 局部组件
<body>
  <div id='app'>
    
    <my-cpn>my-cpn>
  div>

  <script src="../dist/vue.js">script>
  <script>
    // 1.创建组件的构造器对象
    const cpnC = Vue.extend({
      template: `
      

我是标题

我是内容

`
}) const app = new Vue({ el: '#app', data: { }, components: { // 2. 注册局部组件 'my-cpn': cpnC } })
script> body>

Vue - 3 - 组件化(概念、语法、父子)_第6张图片

父组件和子组件

<body>
  <div id='app'>
    <cpn2>cpn2>
  div>

  <script src="../dist/vue.js">script>
  <script>
    // 1.创建第一个组件
    const cpn1 = Vue.extend({
      template: `
      

我是内容 cpn1

`
}) // 2.创建第一个组件 const cpn2 = Vue.extend({ template: `

我是标题 cpn2

`
, components: { cpn1: cpn1 } }) const app = new Vue({ el: '#app', data: { }, components: { // 2. 注册局部组件 cpn2: cpn2 } })
script> body>

cpn2包含了 cpn1,所以cpn2是父组件

Vue - 3 - 组件化(概念、语法、父子)_第7张图片

语法糖:组件注册

省略 Vue.extend ,直接在 component里面定义 组件构造器

<body>
  <div id='app'>
    <cpn>cpn>
  div>

  <script src="../dist/vue.js">script>
  <script>
    Vue.component('cpn', {
      template: `
      

我是标题

我是内容,哈哈哈

`
}) const app = new Vue({ el: '#app', data: { } })
script> body>

Vue - 3 - 组件化(概念、语法、父子)_第8张图片

## 技巧:模板分离写法

把模板写在 标签内

<body>
  <div id='app'>
    <cpn>cpn>
  div>


  
  <script type="text/x-template" id='cpn'>
    <div>
      <h2>我是标题</h2>
      <p>我是内容,哈哈哈</p>
    </div>
  script>

  <script src="../dist/vue.js">script>
  <script>
    Vue.component('cpn', {
      template: '#cpn'
    })
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  script>
body>

或者这样

<body>
  <div id='app'>
    <cpn>cpn>
  div>
  
  <template id='cpn'>
    <div>
      <h2>我是标题h2>
      <p>我是内容,哈哈哈p>
    div>
  template>
  <script src="../dist/vue.js">script>
  <script>
    Vue.component('cpn', {
      template: '#cpn'
    })
    const app = new Vue({
      el: '#app',
      data: {}
    })
  script>
body>

## 组件访问实例数据 【无法访问的】

如果组件内容需要动态显示,需要在组件内定义数据(数据不能放在顶级实例内,组件是无法访问到的

另外:
组件内部的 data 属性值必须是个 函数

## 为什么 组件内部的 data 属性值必须是个 函数???

因为,Vue为了防止多个同一种组件之间的数据相互影响
详细看 : https://cn.vuejs.org/v2/guide/components.html#data-必须是个函数

即,写法要如下

<body>
  <div id='app'>
    <cpn>cpn>
  div>
  
  <template id='cpn'>
    <div>
      <h2>{{title}}h2>
      <p>我是内容,哈哈哈p>
    div>
  template>
  <script src="../dist/vue.js">script>
  <script>
    Vue.component('cpn', {
      template: '#cpn',
      data() {
        return {
          title: '我是个标题'
        }
      }
    })
    const app = new Vue({
      el: '#app',
      data: {}
    })
  script>
body>

Vue - 3 - 组件化(概念、语法、父子)_第9张图片

## 父子组件之间通讯

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

Vue - 3 - 组件化(概念、语法、父子)_第10张图片

通过 props 向子组件传递数据

<body>
  <div id='app'>
    <cpn v-bind:cmovies='movies' :cmessage='message'>cpn>
  div>

  <template id="cpn">
    <div>
      <h2>{{cmessage}}h2>
      <div>{{cmovies}}div>
    div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    const cpn = {
      template: '#cpn',
      props: ['cmovies', 'cmessage']
    }
    const app = new Vue({
      el: '#app',
      data: {
        movies: ['明日花绮罗', '上三悠亚', '斋藤飞鸟'],
        message: '你好啊'
      },
      components: {
        cpn: cpn
      }
    })
  script>
body>

Vue - 3 - 组件化(概念、语法、父子)_第11张图片

通过 props 向子组件传递数据 - 验证数据类型 - type

Vue - 3 - 组件化(概念、语法、父子)_第12张图片

<body>
  <div id='app'>
    <cpn v-bind:cmovies='movies' :cmessage='message'>cpn>
  div>

  <template id="cpn">
    <div>
      <h2>{{cmessage}}h2>
      <div>{{cmovies}}div>
    div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    const cpn = {
      template: '#cpn',
      props: {
        cmovies: Array,
        cmessage: String
      }
    }
    const app = new Vue({
      el: '#app',
      data: {
        movies: ['明日花绮罗', '上三悠亚', '斋藤飞鸟'],
        message: '你好啊'
      },
      components: {
        cpn: cpn
      }
    })
  script>
body>

如果偷偷把 message 类型改一下(如下)

    const app = new Vue({
      el: '#app',
      data: {
        movies: ['明日花绮罗', '上三悠亚', '斋藤飞鸟'],
        message: ['你好啊']
      },
      components: {
        cpn: cpn
      }
    })

会尽量将数据转为 相依类型显示
同时报错
Vue - 3 - 组件化(概念、语法、父子)_第13张图片

通过 props 向子组件传递数据 - 验证数据类型 - validator + required

  • 官方 api - https://cn.vuejs.org/v2/guide/components-props.html#Prop-验证1

通过 props 向子组件传递数据 - 默认值 - default

<body>
  <div id='app'>
    <cpn v-bind:cmovies='movies' :cmessage='message'>cpn>
  div>

  <template id="cpn">
    <div>
      <h2>{{cmessage}}h2>
      <div>{{cmovies}}div>
    div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    const cpn = {
      template: '#cpn',
      props: {
        cmovies: Array,
        cmessage: {
          type: String,
          default: '你好骚啊'
        }
      }
    }
    const app = new Vue({
      el: '#app',
      data: {
        movies: ['明日花绮罗', '上三悠亚', '斋藤飞鸟'],
        //message: ['你好啊']
      },
      components: {
        cpn: cpn
      }
    })
  script>
body>

Vue - 3 - 组件化(概念、语法、父子)_第14张图片

通过 props 向子组件传递数据 - 驼峰标识

结论:

  • 在不用脚手架情况下,写html标签时候,必须不能用驼峰(html不区分大小写)
  • 用了脚手架,随意()

Vue - 3 - 组件化(概念、语法、父子)_第15张图片

【误区】props 双向绑定

如果我们在子组件内做 props 的双向绑定,打开控制台,发现会报错(如下图)

Vue - 3 - 组件化(概念、语法、父子)_第16张图片

<body>

  
  <div id='app'>
    <cpn :num1='num1'>cpn>
  div>

  
  <template id="cpn">
    <div>
      <h2>{{num1}}h2>
      <input type="text" v-model="num1">
    div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    // 1.子组件
    const cpn = {
      template: '#cpn',
      props: {
        num1: String
      }
    }
    // 2.父组件
    const app = new Vue({
      el: '#app',
      data: {
        num1: '1'
      },
      components: {
        cpn: cpn
      },
    })
  script>
body>

看报错信息,知道是官方不推荐。

一个很大的原因是 :这样违背了 组件的独立性(如,子组件1改了值 ⇒ 父组件值也改 ⇒ 子组件2值也改了 ⇒ 【组件1和组件2的值绑定了(不独立)】)

报错信息里面也说了处理方法

Vue - 3 - 组件化(概念、语法、父子)_第17张图片

Instead, use a data or computed property based on the prop's value. Prop being mutated: "num1"
翻译:用一个 data 或者 计算属性,接收传过来的 prop 值作为初始值。

解决方案如下

<body>

  
  <div id='app'>
    
    <cpn :father-num='fatherNum'>cpn>
  div>

  
  <template id="cpn">
    <div>
      <h2>{{num}}h2>
      <input type="text" v-model="num">
    div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    // 1.子组件
    const cpn = {
      template: '#cpn',
      props: {
        fatherNum: String
      },
      data() {
        return {
          num: this['fatherNum']
        }
      }
    }
    // 2.父组件
    const app = new Vue({
      el: '#app',
      data: {
        fatherNum: '1'
      },
      components: {
        cpn: cpn
      },
    })
  script>
body>

通过 事件 向父组件发送消息

注意 驼峰标识 问题

<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="../dist/vue.js">script>
  <script>
    // 1.子组件
    const cpn = {
      template: '#cpn',
      data() {
        return {
          categories: [{
              id: 73944,
              name: 'Route'
            },
            {
              id: 72827,
              name: 'generating'
            },
            {
              id: 52405,
              name: 'Intelligent RAM Books'
            },
            {
              id: 86224,
              name: 'Usability'
            },
          ]
        }
      },
      methods: {
        btnClick(item) {
          this.$emit('item-click', item);
        }
      }
    }
    // 2.父组件
    const app = new Vue({
      el: '#app',
      data: {},
      components: {
        cpn: cpn
      },
      methods: {
        cpnClick(value) {
          console.log(value)
        }
      },
    })
  script>
body>

Vue - 3 - 组件化(概念、语法、父子)_第18张图片

父子通信案例:

  • 子元素接收父元素初始值
  • 子元素 data双向绑定
  • 父元素接收子元素修改值
<body>

  
  <div id='app'>
    <h2>fatherNum:{{fatherNum}}h2>
    
    <cpn :father-num='fatherNum' @num-changed='numChangedHandler'>cpn>
  div>

  
  <template id="cpn">
    <div>
      <h2>num:{{num}}h2>
      回车把值返回父组件:<input type="text" v-model="num" v-on:keyup.enter="numHandler(num)">
    div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    // 1.子组件
    const cpn = {
      template: '#cpn',
      props: {
        fatherNum: String
      },
      data() {
        return {
          num: this['fatherNum']
        }
      },
      methods: {
        numHandler(num) {
          this.$emit('num-changed', num);
        }
      },
    }
    // 2.父组件
    const app = new Vue({
      el: '#app',
      data: {
        fatherNum: '1'
      },
      components: {
        cpn: cpn
      },
      methods: {
        numChangedHandler(value) {
          this.fatherNum = value;
        }
      },
    })
  script>
body>

## 子组件最顶层只能有一个element

否则报错(如下)
Vue - 3 - 组件化(概念、语法、父子)_第19张图片

(下面)才是正确的
Vue - 3 - 组件化(概念、语法、父子)_第20张图片

## 父子组件之间 - 访问方式

有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。

  • 父组件访问子组件:使用 $children$refs
  • 子组件访问父组件:使用 $parent

$children - 父访子 - 【不常用,常用 ref】

我们先来看下 $children 的访问
this.$children 是一个数组类型,它包含所有子组件对象。
我们这里通过一个遍历,取出所有子组件的 message 状态。

<body>

  
  <div id='app'>
    <cpn>cpn>
    <cpn>cpn>
    <cpn>cpn>
    <button @click='btnClick'>打印子组件button>
  div>

  
  <template id="cpn">
    <div>我是子组件div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {},
      methods: {
        btnClick() {
          console.log('展示子组件:', this.$children)
          this.$children[0].showMessage();
        }
      },
      components: {
        cpn: {
          template: '#cpn',
          methods: {
            showMessage() {
              console.log('showMessage');
            }
          },
        }
      }
    })
  script>
body>

Vue - 3 - 组件化(概念、语法、父子)_第21张图片

ref - 父访子

$children 的缺陷:

  • 通过 $children 访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
  • 当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
  • 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用 $refs

$refs 的使用:

  • $refsref 指令通常是一起使用的。
  • 首先,我们通过ref给某一个子组件绑定一个特定的ID。
  • 其次,通过 this.$refs.ID 就可以访问到该组件了。
<body>

  
  <div id='app'>
    <cpn>cpn>
    <cpn ref="cpn">cpn>
    <cpn>cpn>
    <button @click='btnClick'>打印子组件button>
  div>

  
  <template id="cpn">
    <div>我是子组件div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {},
      methods: {
        btnClick() {
          console.log('展示子组件:', this.$children)
          console.log('展示 $refs.cpn :', this.$refs['cpn'])
          console.log('展示 $refs.cpn.$el :', this.$refs['cpn'].$el)
          this.$refs.cpn.showMessage();
        }
      },
      components: {
        cpn: {
          template: '#cpn',
          methods: {
            showMessage() {
              console.log('showMessage');
            }
          },
        }
      }
    })
  script>
body>

Vue - 3 - 组件化(概念、语法、父子)_第22张图片

$parent - 子访问父

如果我们想在子组件中直接访问父组件,可以通过 $parent

注意事项:

  • 尽管在Vue开发中,我们允许通过 $parent 来访问父组件,但是在真实开发中尽量不要这样做。
  • 子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了
  • 如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
  • 另外,更不好做的是通过 $parent`` 直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
<body>

  
  <div id='app'>
    <cpn>cpn>
  div>

  
  <template id="cpn">
    <div>
      <button @click='btnClick'>点击 - 控制台打印父组件button>
    div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    const cpn = {
      template: '#cpn',
      methods: {
        btnClick() {
          console.log(this.$parent);
        }
      },
    }
    const app = new Vue({
      el: '#app',
      data: {},
      components: {
        cpn: cpn
      }
    })
  script>
body>

Vue - 3 - 组件化(概念、语法、父子)_第23张图片

$root 访问根组件

根组件也就是 我们 new 出来的 Vue

用 vue dev-tool 看腋下
Vue - 3 - 组件化(概念、语法、父子)_第24张图片
Vue - 3 - 组件化(概念、语法、父子)_第25张图片

上面界面 代码如下

<body>

  
  <div id='app'>
    <p>rootp>
    <cpn>cpn>
    <p>rootp>
  div>

  
  <template id="cpn">
    <div>
      <button @click='btnClick'>点击 - 控制台打印【 根 root 】组件button>
    div>
  template>

  <script src="../dist/vue.js">script>
  <script>
    const cpn = {
      template: '#cpn',
      methods: {
        btnClick() {
          console.log(this.$root);
        }
      },
    }
    const app = new Vue({
      el: '#app',
      data: {},
      components: {
        cpn: cpn
      }
    })
  script>
body>

我们点击一下,就能访问到 root 组件了(跟上面例子结果一样,是因为这个的父组件就是根组件,感兴趣套多层组件看看)
Vue - 3 - 组件化(概念、语法、父子)_第26张图片

文章目录

    • # 组件化
      • ## 不用组件的两个【灾难性问题】
        • 1、如==全局变量同名==问题
        • 2、又如 代码的编写方式对js文件的依赖顺序几乎是强制性的
      • ## Vue 组件化思想
      • ## 注册组件
        • 注册组件的基本步骤
        • 基本使用
        • 全局组件 、 局部组件
        • 父组件和子组件
        • 语法糖:组件注册
      • ## 技巧:模板分离写法
      • ## 组件访问实例数据 【无法访问的】
      • ## 为什么 组件内部的 data 属性值必须是个 函数???
      • ## 父子组件之间通讯
        • 通过 props 向子组件传递数据
        • 通过 props 向子组件传递数据 - 验证数据类型 - type
        • 通过 props 向子组件传递数据 - 验证数据类型 - validator + required
        • 通过 props 向子组件传递数据 - 默认值 - default
        • 通过 props 向子组件传递数据 - 驼峰标识
        • **【误区】**props 双向绑定
        • 通过 事件 向父组件发送消息
        • 父子通信案例:
      • ## 子组件最顶层只能有一个element
      • ## 父子组件之间 - 访问方式
        • $children - 父访子 - 【不常用,常用 ref】
        • ref - 父访子
        • `$parent` - 子访问父
        • $root 访问根组件

你可能感兴趣的:(#,vue)