3.Vue系列 组件化开发

认识组件化

将一个html拆解,分成很多个组件,每个组件实现页面的一个功能块,每个组件里面又可以细分,就是不断抽象的思想,不断把公共的、可以独立拆分出来的抽出来作为一个独立可复用的组件来向上提供调用,这样让我们的代码更加方便组织和管理,并且扩展性也更强。

注册组件

组件的使用有三步:

  1. script标签内,创建组件构造器对象,定义组件模板

    <script>
    	const cpnC = Vue.extend({
            template: `
                <div>
                    <h2>我是标题</h2>
                    <p>我是内容,11111</p>
                    <p>我是内容,22222</p>
                </div>`
        })
        ....
    </script>
    
  2. 注册组件(全局组件),在创建Vue实例前

    <script>
        ...
        Vue.component('mycpn', cpnC)
        const app = new Vue({
            el: "#app",
            data: {
                message: 'hello'
            }
        })
    </script>
    
  3. 在html中使用组件

    <div id="app">
        <!--3.使用组件-->
        <mycpn></mycpn>
        <mycpn></mycpn>
        <mycpn></mycpn>    
    
    </div>
    

    下面引用网上的一个图:

image

注册组件步骤解析

完整的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Vue Demo</title>
</head>
<body>
<div id="app">
    <!--3.使用组件-->
    <mycpn></mycpn>
    <mycpn></mycpn>
</div>
<mycpn></mycpn>   
<script src="../vue.js"></script>
<script>
    //1.创建组件构造器对象
    const cpnC = Vue.extend({
        template: `
            <div>
                <h2>我是标题</h2>
                <p>我是内容,11111</p>
                <p>我是内容,22222</p>
            </div>`
    })

    //2.注册组件
    Vue.component('mycpn', cpnC)

    const app = new Vue({
        el: "#app",
        data: {
            message: 'hello'
        }
    })
</script>
</body>
</html>
  1. Vue.extend():

调用Vue.extend()创建的是一个组件构造器。

通常在创建组件构造器时,传入template代表我们自定义组件的模板。

该模板就是在使用到组件的地方,要显示的HTML代码。

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

  1. Vue.component():

调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。

所以需要传递两个参数:1、注册组件的标签名 2、组件构造器

  1. 组件必须挂载在某个Vue实例下,否则它不会生效。(见下页)

我们来看下面我使用了三次<mycpn></mycpn>

而第三次其实并没有生效,因为他没有在new Vue挂载的实例#app下

image

组件其他补充

全局组件和局部组件

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

如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件,开发过程常用!

<script>
	const app = new Vue({
        el: "#app",
        data: {
            message: 'hello'
        },
        components: {
            mycpn: cpnC
        }
    })
</script>

父组件和子组件

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Vue Demo</title>
    
</head>
<body>
<div id="app">
    知识点1:
	组件构造器1 放在组件构造器2里面注册,而组件构造器2放在Vue实例里注册
    组件2是组件1的父组件
    <!--3.使用组件-->
    <cpn2></cpn2>
    <cpn1></cpn1>   //知识点3:这里会报错,如果要在这里使用cpn1,可以在Vue实例里再注册下cpn1

</div>
<cpn1></cpn1>   
<script src="../vue.js"></script>
<script>
    //1.创建第一个组件构造器(子组件)
    const cpnC1 = Vue.extend({
        template: `
            <div>
                <h2>我是标题1</h2>
                <p>我是内容,11111</p>
                <p>我是内容,22222</p>
            </div>`
    })
    //2.创建第二个组件构造器(父组件)
    const cpnC2 = Vue.extend({
        template: `
            <div>
                <h2>我是标题2</h2>
                <p>我是内容,11111</p>
                <p>我是内容,22222</p>
                <cpn1></cpn1>
            </div>
            `,
        components: {
            cpn1: cpnC1    //知识点2:cpn1只能在cpnC2组件里使用(作用域只是在当前组件模板使用),无法在外面(全局)使用,如果要在全局用,可以在Vue实例再注册cpn1即可
        }
    })

    // Vue实例也是一个组件, 相当于root组件
    const app = new Vue({
        el: "#app",
        data: {
            message: 'hello'
        },
        components: {
            cpn2: cpnC2
        }
    })

</script>
</body>
</html>

解析过程

在#app模板中使用<cpn2></cpn2>时,cpn2组件已经在组件cpnC2内部编译好了,Vue内部解析编译cpnC2的模板的时候,当解析到<cpn1></cpn1>,会在当前自己注册的组件内查找是否有注册cpn1组件,如果没找到,再去Vue实例中查找是否有注册cpn1组件,如果Vue实例里也没有,再去全局组件里找,最终找到后,会把cpn1标签替换为组件cpn1中定义的模板,替换后,cpn2组件会编译如下:

    const cpnC2 = Vue.extend({
        template: `
            <div>
                <h2>我是标题2</h2>
                	<p>我是内容,11111</p>
                	<p>我是内容,22222</p>
                <h2>我是标题1</h2>
                	<p>我是内容,11111</p>
                <p>我是内容,22222</p>
            </div>
            `,
        components: {
            cpn1: cpnC1    //cpn1只能在cpnC2组件里使用(作用域只是在当前组件模板使用),无法在外面(全局)使用
        }
    })

所以,对于Vue实例来说,Vue实例本身不关心cpn1组件的存在(因为他也不知道),所以当前示例在#app模板中使用cpn1,就会报错

vue.js:634 [Vue warn]: Unknown custom element: <cpn1> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

(found in <Root>)

注册组件语法糖

主要是省去调用Vue.extend()的步骤

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Vue Demo</title>
    
</head>
<body>
<div id="app">
    <cpn1></cpn1>
    <cpn2></cpn2>
</div>  
<script src="../vue.js"></script>
<script>
    //注册全局组件语法糖
    Vue.component('cpn1', {
        template: `
            <div>
                <h2>我是标题cpn1</h2>
                <p>我是内容,cpn1</p>
                <p>我是内容,cpn1</p>
                
            </div>`
    })

    //注册局部组件语法糖
    const app = new Vue({
        el: '#app',
        data: {
            message: 'hello'
        },
        components: {
            'cpn2': {
            template: `
                <div>
                    <h2>我是标题cpn2</h2>
                    <p>我是内容,cpn2</p>
                    <p>我是内容,cpn2</p>
                </div>
                `               
            }
        }
    })

</script>
</body>
</html>

模板的分离写法

这里的分离指的是:模板和组件分离

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<div id="app">
  <cpn></cpn>

</div>

<!--1.通过script标签,注意:类型必须是text/x-template-->
<!-- <script type="text/x-template" id="cpn">
<div>
    <h2>我是cpn1</h2>
</div>
</script> -->

<!--template标签-->
<template id="cpn">
    <div>
        <h2>我是cpn2</h2>
    </div>    
</template>


<script src="../vue.js"></script>
<script>

  // 注册一个全局组件
  Vue.component('cpn', {
    template: '#cpn'
  })

  const app = new Vue({
    el: '#app',
    data: {
      message: 'hello'
    }
  })
</script>

</body>
</html>

组件数据存放

模板里面不能直接访问Vue实例data数据, Vue组件的数据存放在组件里

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<div id="app">
  <cpn></cpn>
</div>

<!--模板里面不能直接访问Vue实例data数据, Vue组件的数据存放在组件里-->
<template id="cpn">
  <div>
    <h2>{{title}}</h2>
    <p>我是模板的数据</p>
  </div>
</template>

<script src="vue.js"></script>
<script>


Vue.component('cpn', {
  template: '#cpn',
  data() {
    return {
      title: '我是组件里的数据'
    }
  }
})

const app = new Vue({
  el: '#app',
  data: {
    message: 'hello',
    // title: '我是标题'
  }
})
</script>
</body>
</html>

为什么组件data必须是函数

主要是为了解决多个组件实例同时调用data的同一个数据的时候,不会相互影响:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <!--组件实例-->
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>

<template id="cpn">
  <div>
    <h2>当前计数: {{counter}}</h2>
    <button @click="decrement" :disabled="disabled"> - </button>
    <button @click="increment"> + </button>
  </div>
</template>
<script src="vue.js"></script>
<script>
  //注册组件
  Vue.component('cpn', {
    template: '#cpn',
    data() {
      return {
        counter: 0
      }
    },
    computed: {
      disabled(){
        return this.counter <= 0
      }
    },
    methods: {
      increment () {
        this.counter ++
      },
      decrement () {
        this.counter --
      }
    }
  })

  const app = new Vue({
    el: '#app',

  })
</script>
</body>
</html>

假设data这里不是函数,即:

Vue.component('cpn', {
  template: '#cpn',
  data: {
    counter: 0
  }
})

这时候就会有一个问题,比如有三个组件实例1,2,3,实例1把counter+1,实例2再去取counter的时候,就会取到counter=1而不是0,三个实例访问的变量counter在内存中是指向同一个地址。

<div id="app">
  <cpn></cpn>  //实例1
  <cpn></cpn>  //实例2
  <cpn></cpn>  //实例3
</div>

父子组件通信

父组件通过props向子组件传递数据,子组件通过自定义事件emit Event与父组件发送消息.

Vue实例和子组件的通信父组件和子组件的通信过程是一样的.

父级向子级传递

基本使用

最简单的props传递过程

1.Vue实例中data

data: {
message: 'hello world!'
},

2.子组件中的props

props: ['message']

3.通过:message="message"将data中的数据传递给props

<child_cpn :message="message"></child_cpn>

4.将props中的数据显示在子组件中

//子组件的模板
<template id="child_cpn">
  <div>
    <h2>{{message}}</h2>
  </div>
</template>

完整案例代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Title</title>
</head>
<body>

<div id="app">
  <child_cpn :message="message"></child_cpn>
</div>
//子组件的模板
<template id="child_cpn">
  <div>
    <h2>{{message}}</h2>
  </div>
</template>

<script src="vue.js"></script>
<script>

  const app = new Vue({
    el: '#app',
    data: {
      message: 'hello world!'
    },
    // 子组件
    components: {
      'child_cpn': {
        template: '#child_cpn',
        props: ['message']
      }
    }
  })
</script>

</body>
</html>

案例2:通过props传递数组

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Title</title>
</head>
<body>

<div id="app">
  <!--通过:cmovies="movies"将父组件的数据movies传递给子组件cpn的变量cmovies-->
  <cpn :cmovies="movies" :message="message"></cpn>  
</div>

<template id="cpn">
  <div>
    <h2>{{message}}</h2>
    <h2>{{cmovies}}</h2>
    <ul>
      <li v-for="item in cmovies">{{item}}</li>
    </ul>
  </div>
</template>

<script src="vue.js"></script>
<script>

  // 父传子
  const cpn = {
    template: '#cpn',  //挂载cpn
    props: ['cmovies', 'message'],  //props属性里面放变量cmovies,在模板里用
    data() {  //data必须是一个函数,函数就必须要有返回值,所以需要return一个对象
      return {

      }
    },
    methods: {

    }
  }

  //父组件
  const app = new Vue({
    el: '#app',  //挂载app
    data: {
      message: 'hello world!',
      movies: ['海王','海贼王','海尔兄弟']

    },
    // 子组件
    components: {
      cpn
    }
  })
</script>

</body>
</html>

props数据验证

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Title</title>
</head>
<body>

<div id="app">
  <!--通过:cmovies="movies"将父组件的数据movies传递给子组件cpn的变量cmovies-->
  <cpn :cmovies="movies" ></cpn>  
</div>

<template id="cpn">
  <div>
    <h2>{{cmessage}}</h2>
    <h2>{{cmovies}}</h2>
    <ul>
      <li v-for="item in cmovies">{{item}}</li>
    </ul>
  </div>
</template>

<script src="vue.js"></script>
<script>

  // 父传子
  const cpn = {
    template: '#cpn',  //挂载cpn
    props: {  //props属性里面放变量cmovies,在模板里用
      //1.类型限制, 变量名: 类型
      // cmovies: Array,  //定义了一个变量cmovies,且数据类型必须是array
      // cmessage: String,
      

      //2.定义类型,且提供一些默认值,以及必传值
      cmessage: {
        type: String,
        default: 'halo',  //演示默认值的时候要把父组件的数据注释掉或者在div里面不绑定(:cmessage="message"),父组件如果没有数据,在调用的时候会直接拿子组件定义的默认值
        required: true  //required为true,意味着div里面必须绑定:cmessage="message",也就是说在使用的时候必须要传message,否则报错
      },
      cmovies: {
        type: Array,
        // default: ['死亡笔记','扫毒','湄公河行动']  //在2.5.17往上这样写就会报错
        default() {
          return ['死亡笔记','扫毒','湄公河行动']  // 上面的写法Vue会有警告(提示必须是工厂函数),因为对象和数组默认值必须从一个工厂函数获取
        }
      }

    },  
    data() {  //data必须是一个函数,函数就必须要有返回值,所以需要return一个对象
      return {

      }
    },
    methods: {

    }
  }

  //父组件
  const app = new Vue({
    el: '#app',  //挂载app
    data: {
      message: 'hello world!',
      // movies: []

    },
    // 子组件
    components: {
      cpn
    }
  })
</script>

</body>
</html>

上面演示了props选项使用字符串、数组、对象类型的数据验证,props支持的数据类型还有:

  • String

  • Number

  • Boolean

  • Array

  • Object

  • Date

  • Function

  • Symbol

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
			}
		}
	}
})

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

function Person(first_name,last_name){
	this.first_name = first_name
	this.last_name = last_name
}

Vue.component('blog-post',{
	props: {
		author: Person
	}
})

父传子-props驼峰标识

这里有两个知识点:

第一个:当模板里有多个标签的时候,要有一个root element,比如这里的div

  <template id="cpn">  
    <div>
      <h2>{{cinfo}}</h2>
      <h2>{{childMyMessage}}</h2>
    </div>
  </template>

第二个:props的属性名如果用驼峰(childMyMessage),在绑定的时候需要使用-,比如下面这个例子,代码比较多,直接看childMyMessage数据流就好

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <cpn :cinfo="info" :child-my-message="childMyMessage"></cpn>
  </div>
  <template id="cpn">  
    <div>
      <h2>{{cinfo}}</h2>
      <h2>{{childMyMessage}}</h2>
    </div>
  </template>
<script src="vue.js"></script>
<script>
  //子
  const cpn = {
    template: '#cpn',
    props: {
      cinfo: {
        type: Object,
        default(){
        return {}
        }
      },
      childMyMessage: {
        type: String,
        default: '测试'
      }

    }
  }

  // 父
  const app = new Vue({
    el: "#app",
    data: {
      info: {
        name: 'kang',
        age: 18,
        height: 180
      },
      
    },
    components: {
      cpn
    }
  })
</script>
</body>
</html>

子级向父级传递

自定义事件的流程:

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

下面这个例子,演示子组件向父组件传递数据

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <!--父组件模板-->
  <div id="app">
    <!--2.父组件监听(接收)子组件发射的事件(item_click),cpn_click方法是对接收到的事件要做的处理-->
    <cpn @item_click="cpn_click"></cpn>  <!--cpn_click这里不写(item),系统会默认把item-->
  </div>
  <!--子组件模板-->
  <template id="cpn">  
    <div>
      <button v-for="item in categories" 
      @click="btn_click(item)">
        {{item.name}}
      </button>
    </div>
  </template>
<script src="vue.js"></script>
<script>
  //子
  const cpn = {
    template: '#cpn',
    data() {
      return {
        categories: [
          {id: '1010', name: '生鲜'},
          {id: '1020', name: '食百'},
          {id: '1030', name: '用品'},
          {id: '1040', name: '文具'},
        ]
      }
    },
    methods: {
      btn_click(item) {
        //1.子组件负责发送事件(item_click),自定义事件名字叫item_click,自定义事件的参数item
        this.$emit('item_click', item)
      }
    }
  }

  // 父
  const app = new Vue({
    el: "#app",
    data: {
      message: 'hello world'
    },
    components: {
      cpn
    },
    methods: {
      cpn_click(item){
        console.log('cpn_click', item);
      }
    }
  })
</script>
</body>
</html>

关键信息:

  1. 在子组件模板templatebutton标签点击后触发一个click事件,click事件对应的处理函数是子组件的btn_click

  2. btn_click函数传入一个参数item且自定义了一个名字叫item_click的事件,然后把入参item作为item_click事件的入参

  3. 然后父组件使用子组件模板cpn,监听子组件的item_click事件,以及监听到该事件后的处理函数cpn_click,该函数定义在父组件

另一个案例,两个事件都调同一个change_total函数

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <child-cpn @increment="change_total" @decrement="change_total"></child-cpn>
    <h2>点击次数:{{total}}</h2>
  </div>
  <template id="childCpn">
    <div>
      <button @click="increment">+1</button>
      <button @click="decrement">-1</button>
    </div>
  </template>
<script src="vue.js"></script>
<script>
  //父组件
  let app = new Vue({
    el: "#app",
    data: {
      total: 0
    },
    methods: {
      change_total(counter){
        this.total = counter
      }
    },
    //子组件
    components: {
      'child-cpn': {
        template: '#childCpn',
        data(){
          return {
            counter: 0
          }
        },
        methods: {
          increment(){
            this.counter++;
            this.$emit('increment', this.counter)
          },
          decrement(){
            this.counter--;
            this.$emit('decrement', this.counter)
          },          
        }
      }
    }
  })
</script>
</body>
</html>

父子组件通信

结合双向绑定案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
<div id="app">
  <cpn :number1="num1" 
       :number2="num2"
       @num1change="num1change"/>
</div>
<template id="cpn">
  <div>
    <h2>props: {{number1}}</h2>
    <h2>data: {{dnumber1}}</h2>
    在input输入值的时候,去初始化子组件data中的dnumber
    <!-- <input type="text" v-model="dnumber1"> -->
    在input输入值的时候,去初始化子组件data中的dnumber,且num1Input()实现同步修改父组件data的num1
    <input type="text" v-bind:value="dnumber1" v-on:input="num1Input">
    <h2>props: {{number2}}</h2>
    <h2>data: {{dnumber2}}</h2>
    <!-- <input type="text" v-model="dnumber2"> -->
    <!-- <input type="text" :value="dnumber2" @input="dnumber2=$event.target.value"> -->
    <input type="text" :value="dnumber2" @input="num2Input">
  </div>
</template>
<script src="vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      num1: 1,
      num2: 0
    },
    methods: {
      num1change(value){
        // this.num1 = value * 1  //*1 数据类型就转为Number,也可以通过parseInt\parseFloat
        this.num1 = parseFloat(value)
      },
      num2change(value){
        this.num2 = parseFloat(value)
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        props: {
          number1: {
            type: Number
          },
          number2: {
            type: Number
          }
        },
        // 要修改一定要放在data(){}里面
        data() {
          return {
            dnumber1: this.number1,
            dnumber2: this.number2
          }
        },
        methods: {
          num1Input(event){
            //1.将input中的value赋值到dnumber1中
            this.dnumber1 = event.target.value
            //2.为了让父组件可以修改值,发出一个事件
            this.$emit('num1change', this.dnumber1)
            //3.同时修改dnumber2的值
            this.dnumber2 = this.dnumber1 * 100;
            this.$emit('num2change', this.dnumber2);
          },
          num2Input(event){
            //1.将input中的value赋值到dnumber2中
            this.dnumber2 = event.target.value
            //2.为了让父组件可以修改值,发出一个事件
            this.$emit('num2change', this.dnumber2)
            //3.同时修改dnumber1的值
            this.dnumber1 = this.dnumber2 / 100;
            this.$emit('num1change', this.dnumber1);
          }
        }
      }
    }
  })
</script>
</body>
</html>

watch

父访问子-children-refs

父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件。

父组件访问子组件:使用\(children或\)refs

this.$children是一个数组类型,它包含所有子组件对象

$children的缺陷:

通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。

但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。

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

$refs的使用:

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

首先,我们通过ref给某一个子组件绑定一个特定的ID。

其次,通过this.$refs.ID就可以访问到该组件了。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn ref="aaa"></cpn>
    <button @click="btnClick">按钮</button>
  </div>

  <template id="cpn">
    <div>我是子组件</div>
  </template>
<script src="vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world!"
    },
    methods: {
      btnClick(){
        //1.通过$children[2]拿到子组件数组的某一个元素对象,不灵活,如果子组件索引改变就会取错值
        // console.log(this.$children);
        // this.$children[0].showMessage();
        // for(let c of this.$children){
        //   console.log("----"+c.name);
        //   c.showMessage();
        // console.log(this.$children[2].name);
        // }
        //2.$refs  常用,对象类型,默认为空对象,必须在组件上加上属性ref="aaa",aaa是一个组件对象的别名,可以通过$refs.aaa来获取子组件对象,通过$refs.aaa.name获取对象里面name的值
        console.log(this.$refs.aaa.name);
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        data(){
          return {
            name: '我是子组件的name'
          }
        },
        methods: {
          showMessage(){
            console.log('showMessage');
            return 1
          }
        }
      },
    }
  })
</script>
</body>
</html>

子组件访问父组件:使用$parent

子访问父-parent-root

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<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="vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world!"
    },
    components: {
      cpn: {
        template: '#cpn',
        data(){
          return {
            name: '我是cpn组件的name'
          }
        },
        components: {
          ccpn: {
            template: '#ccpn',
            methods: {
              btnClick(){
                //1.访问父组件$
                console.log(this.$parent);
                console.log(this.$parent.name);
                //2.访问根组件
                console.log(this.$root);
                console.log(this.$root.message);
              }
            }
          }
        }
      },
    }
  })
</script>
</body>
</html>

插槽slot

将共性抽取到组件中,将不同暴露为插槽

预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容

是搜索框,还是文字,还是菜单。由调用者自己来决定

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <cpn><button>按钮</button></cpn>
    <cpn><span>啦啦啦</span></cpn>
    <cpn><i>星辰大海</i></cpn>

    <cpn></cpn>
    <cpn></cpn>

    <cpn>
      <i>我是i标签</i>
      <div>我是div元素</div>
      <p>我是p标签</p>
    </cpn>

  </div>

  <template id="cpn">
    <div>
      <h2>----START----</h2>
      <p>我是组件,啦啦啦</p>
      <slot><button>按钮</button></slot>  <!--插槽的默认值-->
    </div>
  </template>
<script src="vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world!"
    },
    components: {
      cpn: {
        template: '#cpn'
      }
    }
  })
</script>
</body>
</html>

具名插槽:有名字的插槽

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <cpn></cpn>
    <cpn><button slot="center">我被替换了</button></cpn>
  </div>

  <template id="cpn">
    <div>
      <slot name="left"><span>左边</span></slot>
      <slot name="center"><span>中间</span></slot>
      <slot name="right"><span>右边</span></slot>
      <slot></slot>
    </div>
  </template>
<script src="vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world!"
    },
    components: {
      cpn: {
        template: '#cpn'
      }
    }
  })
</script>
</body>
</html>

编译作用域

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <cpn v-show="isShow"></cpn> <!--当前模板的作用域是Vue实例,所以isShow会在Vue实例中找-->
  </div>

  <template id="cpn">
    <div>
      <h2>我是子组件</h2>
      <p>我是内容</p>
      <button v-show="isShow">按钮</button> <!--当前模板作用域在cpn组件-->
    </div>
  </template>
<script src="vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world!",
      isShow: true
    },
    components: {
      cpn: {
        template: '#cpn',
        data(){
          return {
            ifShow: false
          }
        }
      }
    }
  })
</script>
</body>
</html>

作用域插槽

父组件替换插槽的标签,但是内容由子组件来提供

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <!--当前在父组件中使用子组件,获取子组件中的pLanguages-->
    <cpn>
      <!--1.通过<template slot-scope="slot">获取到slot属性-->
      <template slot-scope="slot">
        <!--通过slot.data就可以获取到刚才我们传入的data了-->
        <!-- <span v-for="item in slot.data">{{item}} - </span> -->
        <span>{{slot.data.join(' - ')}}</span>
      </template>
    </cpn>

    <cpn>
      <template slot-scope="slot">
        <!-- <span v-for="item in slot.data">{{item}} - </span> -->
        <span>{{slot.data.join(' * ')}}</span>
      </template>
    </cpn>
  </div>

  <template id="cpn">
    <div>
      <slot :data="pLanguages"> <!--data只是插槽的名字-->
          <ul>
            <li v-for="item of pLanguages">{{item}} * </li>
          </ul>
      </slot>
    </div>
  </template>
<script src="vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world!",
    },
    components: {
      cpn: {
        template: '#cpn',
        data(){
          return {
            pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++', 'Java']
          }
        }
      }
    }
  })
</script>
</body>
</html>
posted @ 2021-05-01 00:59  我是一言  阅读(205)  评论(0编辑  收藏  举报