vue入门笔记体系(六)vue组件

组件注册

  • 关于组件名:

*组件名可以是kebab-case (短横线分隔命名)或者PascalCase (驼峰式命名)
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case
当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。*

全局注册

//在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中
Vue.component('my-component-name', {
  // ... 选项 ...
})

局部注册

const component = {
    template: `
    <div>
      <input type="text" v-model="text">
    </div>
  `,
    data() {
        return {
            text: 123
        }
    },
    methods:{

    }

}
const app = new Vue({
    //el:'#root',
    components: {
        comp: component
    },
    template: '<comp></comp>',

}).$mount("#root")

模板的要求

注意:组件的模板只能有一个根元素。下面的情况是不允许的。

template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>
            <button>hello</button>`,

组件中的data必须是函数

当我们定义这个 <button-counter> 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:

data: {
  count: 0
}

取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:

data: function () {
  return {
    count: 0
  }
}

如果 Vue 没有这条规则,所有模板都会共享一份数据。

解析 DOM 模板时的注意事项

有些 HTML 元素,诸如 <ul>、<ol>、<table> 和 <select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>、<tr> 和 <option>,只能出现在其它某些特定的元素内部。

这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:

<table>
  <blog-post-row></blog-post-row>
</table>

这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is 特性给了我们一个变通的办法:

<table>
  <tr is="blog-post-row"></tr>
</table>

需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:

字符串 (例如:template: ‘…’)
单文件组件 (.vue)
<script type=”text/x-template”>

Prop

  • Prop 类型
//普通类型
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
//指定类型
props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object
}

单项数据流,传递过后不能修改
有两种常见的试图改变一个 prop 的情形:

//这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。
props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}
//这个 prop 以一种原始的值传入且需要进行转换
props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

Prop 验证

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 匹配任何类型)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组且一定会从一个工厂函数返回默认值
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

parent选项

类型:Vue instance

详细:

指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中。

//定义一个父组件
const parent = new Vue({
  name: 'parent'
})
//定义一个组件
const componet2 = {
  data () {
    return {
      text: 1
    }
  },
  mounted () {
    console.log(this.$parent.$options.name) //打印Root
    this.$parent.text = 1234; //也可以修改,节制地使用 $parent 和 $children - 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信
  }
}

//创建vue实例
new Vue({
  parent: parent,
  name: 'Root',
  el: '#root',
  mounted () {
    console.log(this.$parent.$options.name) //此时打印出'parent'
  },
  components: {
    Comp: componet2
  },
  data: {
    text: 23333
  },
  template: `
    <div>
      <span>{{text}}</span>
      <comp></comp>
    </div>
  `
})

组件继承

Vue.extend( options )
使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

//包含组件选项的对象
const compoent = {
  props: {
    active: Boolean,
    propOne: String
  },
  template: `
    <div>
      <input type="text" v-model="text">
      <span @click="handleChange">{{propOne}}</span>
      <span v-show="active">see me if active</span>
    </div>
  `,
  data () {
    return {
      text: 0
    }
  },
  mounted () {
    console.log('comp mounted')
  },
  methods: {
    handleChange () {
      this.$emit('change')
    }
  }
}

// 创建构造器
const CompVue = Vue.extend(compoent)

// 创建 CompVue实例,并挂载到一个元素上。
new CompVue({
  el: '#root',
  propsData: { //传递props属性
    propOne: 'xxx'
  },
  data: { //可以覆盖或者合并
    text: '123'
  },
  mounted () { //不会覆盖 ,都会被调用
    console.log('instance mounted')
  }
})

自定义双向绑定

父子组件传递进行绑定

// 1定义一个组件对象
const component = {
  props: ['value'],
  template: `
    <div>
      <input type="text" @input="handleInput" :value="value">  //绑定传递过来的value
    </div>
  `,
  methods: { //向父组件触发事件
    handleInput (e) {
      this.$emit('input', e.target.value)
    }
  }
}

//2定义一个实例
new Vue({
  components: {
    CompOne: component
  },
  el: '#root',
  data () {
    return {
      value: '123'
    }
  },
//接受事件并改变value进行属性传递
  template: `
    <div>
        <span>{{value}}</span>
       <comp-one :value="value" @input="value = arguments[0]"></comp-one>
    </div>
  `
})

v-model绑定

const component = {
//   model: {
//     prop: 'value1',
//     event: 'change'
//   },
  props: ['value'],
  template: `
    <div>
      <input type="text" @input="handleInput" :value="value">
    </div>
  `,
  methods: {
    handleInput (e) {
      this.$emit('input', e.target.value)
    }
  }
}

new Vue({
  components: {
    CompOne: component
  },
  el: '#root',
  data () {
    return {
      value: '123'
    }
  },
  template: `
    <div>
        <span>{{value}}</span>
       <comp-one v-model=value></comp-one> //直接使用v-model绑定
    </div>
  `
})

选项model

允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的。使用 model 选项可以回避这些情况产生的冲突。

import Vue from 'vue'

const component = {
  model: {
    prop: 'value1', //绑定value1    
    event: 'change'//绑定事件
  },
  props: ['value','value1'],
  template: `
    <div>
      <input type="text" @change="handleInput" :value="value1">
    </div>
  `,
  methods: {
    handleInput (e) {
      this.$emit('change', e.target.value)
    }
  }
}

new Vue({
  components: {
    CompOne: component
  },
  el: '#root',
  data () {
    return {
      value: '123'
    }
  },
  template: `
    <div>
        <span>{{value}}</span>
       <comp-one v-model=value></comp-one>
    </div>
  `
})

非父子组件间的传值

兄弟间的组件传递:

1.使用vue官方提供的vuex(有一定难度,学习成本较大)
2.发布订阅模式(总线机制,Bus)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id = "app">
        <child content="Dell"></child>
        <child content="lee"></child>

    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script>
        //增加一个bus属性,指向vue的实例
        Vue.prototype.bus = new Vue()


        Vue.component('child',{
            props:{
                content:String
            },
            data:function(){
                return {
                    seleContent:this.content
                }
            },
            template:'<div @click="handleClick">{{seleContent}}</div>',
            methods:{
                handleClick(){
                    this.bus.$emit('change',this.seleContent)
                }
            },
            mounted(){
                let _this = this;
                this.bus.$on('change',function(msg){
                    _this.seleContent = msg
                })
            }
        })

        new Vue({
            el: "#app"
        })
    </script>
</body>
</html>

插槽

插槽内容

const component = {
  template: `
    <div :style="style">
      <slot></slot> //vue的默认组件,放置插槽的地方
    </div>
  `,
  data () {
    return {
      style: {
        width: '200px',
        height: '200px',
        border: '1px solid #aaa'
      },
      value: 'component value'
    }
  }
}
new Vue({
  components: {
    CompOne: component
  },
  el: '#root',
  data () {
    return {
      value: '123'
    }
  },
  mounted () {

  },
  template: `
    <div>
      <comp-one>
        <span>this is contne</span> //传递插槽内容
      </comp-one>
    </div>
  `
})

具名插槽

有些时候我们需要多个插槽,可以进行命名;

import Vue from 'vue'

const component = {
  template: `
    <div :style="style">
      <div class="header">
        <slot name="header"></slot> //进行插槽命名
      </div>
      <div class="body">
        <slot name="body"></slot>
      </div>
    </div>
  `,
  data () {
    return {
      style: {
        width: '200px',
        height: '200px',
        border: '1px solid #aaa'
      },
      value: 'component value'
    }
  }
}

new Vue({
  components: {
    CompOne: component
  },
  el: '#root',
  data () {
    return {
      value: '123'
    }
  },
  mounted () {

  },
  template: `
    <div>
      <comp-one ref="comp">
        <span slot="header">this is header</span> ///向指定的内容
        <span slot="body">this is body</span>
      </comp-one>
    </div>
  `
})

插槽的默认内容

可以在 <slot> 标签内部指定默认的内容来做到这一点。

<button type="submit">
  <slot>Submit</slot>
</button>

如果父组件为这个插槽提供了内容,则默认的内容会被替换掉。

作用域插槽

import Vue from 'vue'

const component = {
  template: `
    <div :style="style">
      <slot :value="value"></slot> //向外传递的数据
    </div>
  `,
  data () {
    return {
      style: {
        width: '200px',
        height: '200px',
        border: '1px solid #aaa'
      },
      value: 'component value'
    }
  }
}

new Vue({
  components: {
    CompOne: component
  },
  el: '#root',
  data () {
    return {
      value: '123'
    }
  },
  mounted () {
    console.log(this.$refs.comp.value, this.$refs.span) //打印出组件实例和HTML标签
  },
  template: `
    <div>
      <comp-one ref="comp">
        <span>{{value}}</span> //此时没有作用域,访问到的不是组件内的value,而是外部的value
        //添加作用域,组件内部传递的数据都保存在props对象中
        <span slot-scope="props" ref="span">{{props.value}}</span>
      </comp-one>
    </div>
  `
})

动态组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id = "app">
        <!-- <child-one v-if="type ==='child-one'"></child-one>
        <child-two v-if="type ==='child-two'"></child-two> -->
        <component :is="type"></component>
        <button @click="handleBtn">change</button>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script>

        Vue.component('child-one',{
            template:'<div>child-one</div>'
        })
        Vue.component('child-two',{
            template:'<div>child-two</div>'
        })
        new Vue({
            el: "#app",
            data:{
                type:"child-one"
            },
            methods:{
                handleBtn(){
                    this.type=this.type==="child-one"?"child-two":"child-one"
                }
            }
        })
    </script>
</body>
</html>

provide / inject 跨级组件通信

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

import Vue from 'vue'

const ChildComponent = {
  template: '<div>child component:</div>',
  inject: ['yeye','value'], 
  mounted () {
    console.log(this.$parent.$options.name) //上一级
    console.log(this.yeye,this.value) //上上一级,接受到的数据,打印出上上一级的vue实例和123
  }
}

const component = {
  name: 'comp',
  components: {
    ChildComponent
  },
  template: `
    <div :style="style">
      <slot :value="value"></slot>
      <ChildComponent></ChildComponent>
    </div>
  `,
  data () {
    return {
      style: {
        width: '200px',
        height: '200px',
        border: '1px solid #aaa'
      },
      value: 'component value',
      value2: 'component222 value'
    }
  }
}

new Vue({
  components: {
    CompOne: component
  },
  provide () { //和data一样,可以调用相应的值,是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。
    return {
      yeye: this,
      value:this.value
    }
  },
  el: '#root',
  data () {
    return {
      value: '123'
    }
  },
  mounted () {
    console.log(this.$refs.comp.value2, this.$refs.span)
  },
  template: `
    <div>
      <comp-one ref="comp">
        <span slot-scope="props" ref="span">{{props.value}}</span>
      </comp-one>
    </div>
  `
})

provide 和 inject 绑定并不是可响应的。这是刻意为之的。provide 和 inject 绑定并不是可响应的。这是刻意为之的。

import Vue from 'vue'

const ChildComponent = {
  template: '<div>child component:{{data.value}}</div>',
  inject: ['yeye','data'],
  mounted () {
    console.log(this.$parent.$options.name) //上一级
    console.log(this.yeye,this.value) //上一级
  }
}

const component = {
  name: 'comp',
  components: {
    ChildComponent
  },
//   template: `
//     <div :style="style">
//       <div class="header">
//         <slot name="header"></slot>
//       </div>
//       <div class="body">
//         <slot name="body"></slot>
//       </div>
//     </div>
//   `,
  template: `
    <div :style="style">
      <slot :value="value"></slot>
      <ChildComponent></ChildComponent>
    </div>
  `,
  data () {
    return {
      style: {
        width: '200px',
        height: '200px',
        border: '1px solid #aaa'
      },
      value: 'component value',
      value2: 'component222 value'
    }
  }
}

new Vue({
  components: {
    CompOne: component
  },
  provide () { //和data一样,可以调用相应的值
    const data = {}

    // es5双向绑定实现原理
    Object.defineProperty(data, 'value', {
      get: () => this.value, //获取最新value,每次调用value相当于get()方法
      enumerable: true //可读取
    })

    return {
      yeye: this,
    //   value:this.value
      data
    }
  },
  el: '#root',
  data () {
    return {
      value: '123'
    }
  },
  mounted () {
    console.log(this.$refs.comp.value2, this.$refs.span)
  },
  template: `
    <div>
      <comp-one ref="comp">
        <span slot-scope="props" ref="span">{{props.value}}</span>
      </comp-one>
      <input v-model="value" />
    </div>
  `
})

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

渲染函数 & JSX

参考:渲染函数&JSX

    原文作者:sevencui
    原文地址: https://segmentfault.com/a/1190000015969998
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞