Vue 的组件化思想:

  • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
  • 任何的应用都会被抽象成一颗组件树。

树形示意图

组件使用的基本步骤

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

简单示例:

<div id="app">
  <my-cpn></my-cpn>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  const cpn = Vue.extend({
    template: `
    <div>
      <h1>title</h1>
    </div>
    `,
  })

  // 注册组件(全局组件,可以在多个Vue的实例下使用
  Vue.component("my-cpn", cpn)
  const app = new Vue({
    el: "#app",
    data: {},
    methods: {},
  })
</script>

事实上,这神写法在 Vue2.x 的文档中已经看不到了,它会直接使用一些语法糖,但是在很多资料还是提到这种方式,而且这种方式是学习后面方式的基础。

全局组件和局部组件

  • Vue.component("my-cpn", cpn) 注册组件(全局组件,可以在多个 Vue 的实例下使用
  • new Vue({components:{cpn: cpn}}) 在 Vue 实例初始化时传入的对象中创建的组件为局部组件,只能在当前实例绑定的挂载的里面生效

父组件和子组件的区分

const cpnSon = Vue.extend({
  template: `
  <div>
    <h4>footer</h4>
  </div>
  `,
})

// 父组件
const cpn = Vue.extend({
  template: `
  <div>
    <h1>title</h1>
    <cpnSon></cpnSon>
  </div>
  `,
  components: {
    // 注册子组件,它的作用域是在 父组件下的,不能直接在组件下使用<cpnSon>,除非也在根节点注册
    cpnSon: spcSon
  }
})
// Vue 实例也是一个组件,根组件

当前作用域下组件的查找也是根据就近原则的,先查找其自身的子组件,没有查找全局组件

组件的语法糖

直接创建并注册

Vue.component("my-cpn", {
  template: `
  <div>
    <h4>footer</h4>
  </div>
  `,
  components: {
    cpnSon: {}
  }
})

模板的分离写法

Vue 提供了两种方案来定义 HTML 模块内容:

  • 使用< script>标签
  • 使用< template>标签
<!-- script标签,类型必须为text/x-template -->
<script type="text/x-template" id="cpn3">
  <div>
    <h2>cpn3</h2>
  </div>
</script>
<!-- 直接使用 template标签 -->
<template id="cpn4">
  <div>
    <h2>cpn4</h2>
  </div>
</template>

<script>
  Vue.component(cpn4, {
      template: '#cpn4'
    }
  })
</script>

组件的 data

组件不能直接访问 Vue 实例里面的数据

组件的原型也是指向 Vue 实例的,所以 Vue 实例有的东西(生命周期,计算属性...)它都支持

组件的 data 为什么是一个函数:

  • 确保组件每次使用是使用的数据是函数的返回值,而非是 data 里面的数据。确保了组件里数据有自己的作用域。
<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <!-- 每个 相同组件里面的data数据有自己的作用域,更改第一个cpn,不会影响第二个 -->
</div>

<template id="cpn">
  <div>
    <button @click="counter > 0 ? counter-- : ''" :disabled="counter < 1">-</button>
    <span>{{counter}}</span>
    <button @click="counter++">+</button>
  </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  Vue.component("cpn", {
    template: "#cpn",
     // data:{} 组件中的data属性不能是一个对象
    data() {
      // 必须是一个函数,这个函数返回一个对象,对象内部保存这数据
      return {
        counter: 0,
      }
    },
  })

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

Tips: 组件的模板必须被一个根节点包裹,<div>content</div>

父子组件间通信

子组件是不能引用父组件或者 ue 实例的数据的。但是,在开发中,往往一些数据确实需要从上层传递到下层.

组件间传值的方式

Tips: vue 实例和子组件的通信和父组件和子组件的通信过程是一样的。

父组件传值给子组件通过 props 来声明

props 的值有两种方式:

  • 字符串数组,数组中的字符串就是传递时数据的名称。
  • 对象,对象可以设置传递时的类型,也可以设置默认值等。
  • props 的值来源于调用组件是时的属性 <cpn :msg="msg"></cpn>
<body>
  <!-- 父组件 -->
  <div id="app">
    <!-- 接受参数 -->
    <cpn :msg="msg"></cpn>
  </div>

  <template id="cpn">
    <section>
      <h2>{{msg}}</h2>
      <p>{{valmovies}}</p>
    </section>
  </template>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const cpn = {
      template: "#cpn",
      // 字符串数组,数组中的字符串就是传递时数据的名称。
      // props: ["valmovies", "msg"],
      // 对象可以设置传递时的类型,也可以设置默认值等。
      props: {
        msg: String,
        valmovies: {
          type: Array,
          default() {
            // 如果是设置数组或者对象的默认值,必须使用函数返回值的形式
            return []
          },
          // 必须传入此参数
          // required: true,
        },
      },
      data() {
        return {}
      },
    }

    const app = new Vue({
      el: "#app",
      data: {
        msg: "hello!",
        movies: ["航海王", "22", "肖申克的救赎"],
      },
      components: {
        cpn,
      },
      methods: {},
    })
  </script>
</body>

验证都支持的数据类型:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • function
  • Symbol
  • null 匹配任何类型
  • 当有自定义构造函数时,验正也支持自定义的类型

Tips: html 标签属性不支持(大写)驼峰命名,所以在父组件传值子组件是如果传入的变量命名为驼峰式 props: ["valMovies",] 的话需要:<cpn :val-movies="[]"></cpn>. 将驼峰命名转为转为 - 链接形式.

不要直接在子组件中改变父组件传递过来的值, 需要修改最好是 在 data 中使用传递值的副本进行修改: data(){ return{ dMsg: this.msg}}, 同时通过自定义事件传递给父组件

子传父

<body>
  <div id="app">
    <!-- 通过自定义事件接受子组件传递过来的值, v-on用来监听自定义事件的 -->
    <!-- 注意转换为驼峰命名法,这里自定义事件是不支持的 itemClick != item-click -->
    <cpn @item-click="cpnClick"></cpn>
  </div>

  <template id="cpn">
    <section>
      <button v-for="item in goodsType" @click="btnClick(item)">{{item.name}}</button>
    </section>
  </template>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const cpn = {
      template: "#cpn",
      data() {
        return {
          goodsType: [
            { id: 0, name: "热门推荐" },
            { id: 1, name: "3C数码" },
            { id: 2, name: "日常百货" },
            { id: 4, name: "加用电器" },
          ],
        }
      },
      methods: {
        btnClick(item) {
          // 子组件发出一个事件, 自定义事件
          this.$emit("item-click", item)
        },
      },
    }

    const app = new Vue({
      el: "#app",
      data: {},
      components: {
        cpn,
      },
      methods: {
        cpnClick(item) {
          console.log(item)
        },
      },
    })
  </script>
</body>

Tips: html 标签属性不支持(大写)驼峰命名, 在在子组件相父组件传值时 如果自定义的事件名是 驼峰命名在此处是不被支持的. this.$emit('itemClick', item) Vue 不会将 @click="btnClick(item)" 转换为 itemClick. 同时 html 标签属性不支持(区分大小写) 所以此处并不会触发自定义事件.

父子组件间的访问

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

父组件访问子组件

  • $children 已使用的组件列表,实际开发一般不会使用此属性
  • $refs 对象类型,默认为空.通过在标签上添加 ref="",使其可以被 $refs 访问

子组件访问父

  • $parent 访问当前组件的父组件
  • $root 访问当前组件的 根组件,一般是 Vue 实例

<body>
  <div id="app">
    <cpn></cpn>      
    <cpn ref="cpn3"></cpn>
    <hr>
    <button @click="btnClick">访问子组件属性</button>
  </div>

  <template id="cpn">
    <section>
      <h2>子组件 cpn</h2>
      <soncpn></soncpn>  
    </section>
  </template>

  <template id="soncpn">
    <section>
      <h3>cpn - son</h3>
      <button @click="sonBtnClick">访问父组件</button>
    </section>
  </template>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: "#app",
      data: {},
      components: {
        cpn: {
          template: "#cpn",
          data() {
            return {
              dataMsg: "data msg",
            }
          },
          methods: {
            showMsg() {
              console.log("父访问子$children")
            },
          },

          components: {
            soncpn: {
              template: "#soncpn",
              methods: {
                sonBtnClick() {
                  // this.$parent 访问父组件 cpn
                  console.log(this.$parent)
                  console.log(this.$parent.dataMsg)
                  // 访问根组件 Vue 实例
                  console.log(this.$root)
                },
              },
            },
          },
        },
      },

      methods: {
        btnClick() {
          // 访问子组件 cpn
          console.log(this.$children) // array 已使用的组件列表
          this.$children[0].showMsg()
          console.log(this.$children[0].dataMsg)

          console.log(this.$refs)
          console.log(this.$refs.cpn3.dataMsg)
        },
      },
    })
  </script>
</body>

组件的插槽 slot

  • 组件的插槽也是为了让我们封装的组件更加具有扩展性
  • 让使用者可以决定组件内部的一些内容到底展示什么。

slot 的基本使用和具名插槽

  • 在子组件中,使用特殊的元素就可以为子组件开启一个插槽。
  • 该插槽插入什么内容取決于父组件如何使用。
  • \<slot>中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容

  • <slot name="title"></slot> 具名插槽可以在组件内部定义多个插槽,同时传入内容也要定义需要替换的参数名 <p slot="title"></p>.

一个不带 name 的 出口会带有隐含的名字“default”。未命名插槽,也就是默认插槽,捕获所有未被匹配的内容。

<div id="app">
  <!-- 使用默认值 -->
  <cpn></cpn>
  <cpn>
    <p>插槽 slot 接收组件调用时标签内部的内容,同时替换内部slot已有内容</p>
  </cpn>      
  <!-- 替换多个插槽的某个名为 footer的插槽 -->
  <cpn2><button slot="footer">替换后面</button></cpn2>
</div>

<template id="cpn">
  <section>
    <h2>组件 cpn</h2>
    <slot>
      <button>插槽默认值</button>
    </slot>
  </section>
</template>

<template id="cpn2">
  <section>
    <h2>插槽 cpn2</h2>
    <!-- 定义多个插槽, 使用name区分 -->
    <slot name="header"><div>上面</div></slot>
    <slot name="center"><div>中间</div></slot>     
    <slot name="footer"><div>下面</div></slot>   
  </section>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {},
    components: {
      cpn: {
        template: "#cpn",
      },
      cpn2 : {
        template: '#cpn2'
      }
    },
    methods: {},
  })
</script>

作用域插槽

编译作用域:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。

  • 作用域插槽:父组件替换插槽的标签,但是内容由子组件来提供(使用子组件的数据,替换原插槽内容)
<div id="app">
  <cpn3>
    <!-- 直接使用子组件的 languages 是不行的 -->
  </cpn3>
  <cpn3>
    <!-- 获取子组件的 languages -->
    <template slot-scope="slot">
      <!-- <span v-for="item in slot.val">{{item}} - </span> -->
      <span>{{slot.val.join(' - ')}}</span>
    </template>
  </cpn3>
</div>

<template id="cpn3">
  <section>
    <slot :val="languages">
      <ul>
        <li v-for="item in languages">{{item}}</li>
      </ul>
    </slot>
  </section>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {},
    components: {      
      cpn3: {
        template: "#cpn3",
        data() {
          return {
            languages: ["java", "javaScript", "c", "c++", "c++++", "golang"],
          }
        },
      },
    },
    methods: {},
  })
</script>

v-slot 插槽的新语法

Tips: 在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。规范对其的支持截至到 Vue3.新语法 v-slot

具名插槽的 v-slot
<!-- 比较与上面 -->
<div id="app">  
  <!-- 替换多个插槽的某个名为 footer的插槽 -->
  <cpn2>
    <template v-slot:header>
      <button slot="footer">替换后面</button>
    </template>    
  </cpn2>
</div>

<template id="cpn2">
  <section>
    <h2>插槽 cpn2</h2>
    <!-- 定义多个插槽, 使用name区分 -->
    <slot name="header"><div>上面</div></slot>
    <slot name="center"><div>中间</div></slot>     
    <slot name="footer"><div>下面</div></slot>   
  </section>
</template>

Tips: 注意 v-slot 只能添加在 <template> 上 (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。

作用域插槽:
<!-- 相较与上 -->
<cpn3>
  <!-- 获取子组件的 languages -->
  <template v-slot:default="slot">    
    <span>{{slot.val.join(' - ')}}</span>
  </template>
</cpn3>

<template id="cpn3">
  <section>
    <!-- 一个不带 name 的 <slot>出口会带有隐含的名字“default”。 -->
    <slot v-bind:val="languages">
      <ul>
        <li v-for="item in languages">{{item}}</li>
      </ul>
    </slot>
  </section>
</template>
独占插槽的默认写法(上面的例外)

当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:

<cpn3 v-slot:default="slot">
  <!-- 获取子组件的 languages --> 
  <span>{{slot.val.join(' - ')}}</span>  
</cpn3>

Tips: 不带参数的 v-slot 被假定对应默认插槽 v-slot="slot". 注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确.

Tips: 动态指令参数也可以用在 v-slot 上,来定义动态的插槽名, <template v-slot:[dynamicSlotName]>

Tips: v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header


相关推荐:

来自系列:Vue 笔记

分类 前端下文章:

微信小程序开发 days1 关注点:文件结构,json配置文件, 模板语法

小程序开发学习笔记 days2。 关注点:view, text, rich-text, button, image, navigator, icon, swiper, radio, checkbox

小程序学习笔记 days3。 关注点:生命周期,自定义组件

小程序学习笔记 days4。 关注点:scroll-view, Promise

css3 选择器,背景定义(定位), 盒子定位,弹性布局,栅格系统。还有一些常用的 css 属性。

更多...

评论([[comments.sum]])

发表

加载更多([[item.son.length-2]])...

发表

2020-11 By chuan.

Python-flask & bootstrap-flask

图片外链来自:fghrsh

互联网ICP备案号:蜀ICP备2020031846号