vue组件开发

7.vue组件 ***

7.1 什么是组件

组件 (Component)是 Vue.js 最强大的功能之一,组件是一个自定义元素或称为一个模块,包括所需的模板(HTML)、逻辑(JavaScript)和样式(CSS)。

组件化开发的特点:

  • 标准

  • 分治

  • 重用

  • 组合

组件也是有 全局(component) 与 局部(components) 之分。

组件的属性可以写为: template data methods watch computed filters directives(自定义指令)

组件特点:***

  1. data 是函数 函数里返回一个 对象,因为需要保持局部作用域
  2. 组件必须只有一个根节点
  3. 组件名 尽量不要用 驼峰, 如果用了 驼峰, 那么在 html 需要更改为 短横的写法

7.2 组件的注册及其使用

在使用组件时需要注意以下几点:

  • 构造 Vue 实例时传入的各种选项大多数都可以在组件里使用,只有一个例外:data必须是函数,同时这个函数要求返回一个对象
data: function(){ 
  return { 
    msg: '你好世界' 
  } 
}

组件中的data为什么是函数

1.使用函数,在每次复用组件时,就可以产生一个新的data,给每个组件实例创建一个新的空间,
2.若是使用data对象形式,所有复用组件共用一个data,不是很好

  • 组件模板 template

    • 必须是单个根元素
    <!-- 单个根元素 -->
    <div>
      <ul>
        <li></li> 
      </ul> 
      <ul>
        <li></li> 
      </ul> 
    </div> 
    <!-- 不符合单个根元素的情况 --> 
    <p></p> 
    <p></p>
    
    • 支持模板字符串形式
  • 组件名称命名方式

    • 短横线方式(推荐)
      • my-component
    • 大驼峰方式(只能在其他组件模板字符串中使用,不能在HTML模板中直接使用)
      • MyComponent

大驼峰式组件名不能在HTML模板中直接使用,如果需要在HTML模板中使用,需要将其进行特定规则转化:

  • 首字母从大写转为小写

  • 后续每遇到大写字母都要转化成小写并且在转化后的小写字母前加 -

例如, WoDeZuJian 这个大驼峰组件名在HTML中使用的时候需要写成 wo-de-zu-jian

7.2.1 全局组件

全局组件:意味着可以在多个Vue实例下使用
全局组件注册形式如下:

// 声明全局组件 
Vue.component(componentName,{ 
  data: '组件数据', 
  template: '组件模版内容' 
})

上述示例中, component() 的第一个参数是 组件名 (实则可以看作是HTML标签名称),第二个参数是一个对象形式的选项,里面存放组件的声明信息。全局组件注册后,任何Vue实例都可以使用。

例如,有以下代码:

// 声明一个全局的HelloWorld组件 
Vue.component('HelloWorld', { 
  data: function(){ 
    return { 
      msg: 'HelloWorld' 
    } 
  },
  template: '<div>{{msg}}</div>' 
});

实例:

<body>
    组件的最大的优势:可以复用
    <div id="app">
        <!--组件在引用时需要写双标记 ,单标签不管引用多少次只执行一次-->
        <btn-counter></btn-counter>
        <btn-counter></btn-counter>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 创建新的全局组件 
        // 全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的Vue根实例,也包括其组件树中的所有子组件的模板中。
        //注册组件(传递两个参数,1、标签名 2、组件结构器)
        Vue.component("btn-counter", {
            data: function () { //组件中的数据,组件的data必须是一个函数
                return {
                    count: 0
                }
            },
            //组件的模板
            template: `<button @click="count++">点击了{{count}}次</button>`
        })

        var vm = new Vue({
            el: "#app",
            data: {
                msg: "hello"
            }
        })
		//注意当点击按钮时,每个组件都会各自独立维护它的 count。
		//因为你每用一次组件,就会有一个它的新实例被创建。
    </script>
</body>

7.2.2 局部组件

局部组件定义后只能在当前注册它的Vue实例中使用,其是通过某个 Vue 实例/组件的实例选项components 注册。

例如,有以下代码:

var Child = { 
  template: '<div>A custom component!</div>' 
}
new Vue({ 
  components: { 
    // <my-component> 将只在父组件模板中可用 
    'my-component': Child 
  } 
})
<body>
  <div id="app">
    <component-demo></component-demo>
  </div>
</body>
<script src="../js/vue.js"></script>
<script>
  new Vue({
    el: '#app',
    components: {
      'component-demo': {
        template: '<h1>局部注册组件{{ num }}</h1>',
        data () {
          return {
            num: 500
          }
        },
        computed: {},
        watch: {},
        mounted () {}
      }
    }
  })
</script>

7.2.3 组件的使用 ***

在HTML模板中,组件以一个自定义标签的形式存在,起到占位符的功能。通过Vue.js的声明式渲染后,占位符将会被替换为实际的内容,下面是一个最简单的模块示例:

<div id="app"> 
  <my-component></my-component>
</div>

电话号码簿组件实例,截图:

在这里插入图片描述

例如:

<div id="app">
		<!--3.将组件渲染到页面上-->
		注意,如果组件定义使用的是驼峰式写法,
		直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
		<my-header></my-header>
		<my-content :phone="phoneList"></my-content>
		<my-footer></my-footer>

	</div>
	<script type="text/javascript">
		//注意:在传参时尽量不要写大写,如果写了大写,
		//那么在js中使用 驼峰式写法,在 html中必须更改为短横
		//1.定义组件
		//头
		var myHeader = {
			template: `<h2 class="myHeader">我的电话号码簿</h2>`
		}

		//列表孙子,属于myContent的子节点
		var myItem = {
			props: ['i'],
			template: `<li >
							<span>{{i.id}}</span> 
							<span>{{i.name}}</span>
							<span>{{i.phone}}</span>
							<span><img src="../img/phone.jpg"/></span>
						</li>`
		}

		//内容
		var myContent = {
			props: ['phone'],
			created: function () {
				console.log(this.phone);

			},
			template: `<div class="myContent">
					<ul>
						<my-item v-for="(item,index) in phone" :i="item"></my-item>
					</ul>
				</div>`,
			//在循环渲染时,需要传一条记录到子组件,也就是传item,这样子更方便
			components: {
				myItem
			}
		}

		//尾部
		var myFooter = {
			template: `<div class="myFooter">版权:© 归web21班所有</div>`
		}

		/**************************************************/
		var vm = new Vue({
			el: "#app",
			data: {
				phoneList: [//电话号码数据
					{ "id": 1, "name": "斯琴高娃", "phone": "13412340987" },
					{ "id": 2, "name": "高晓松", "phone": "13512340987" },
					{ "id": 3, "name": "迪丽热巴", "phone": "13612341234" },
					{ "id": 4, "name": "韩红", "phone": "13312340987" },
					{ "id": 5, "name": "梁朝伟", "phone": "15812340987" },
				]
			},
			//2.引入组件
			components: {
				myHeader,
				myContent,
				myFooter
			}
		})
	</script>

7.3 组件间传值**

如前面介绍组件时所说,组件有 分治 的特点,每个组件之间具有一定的独立性,但是在实际工作中使用组件的时候有互相之间传递数据的需求,此时就得考虑如何进行 组件间传值 的问题了。

7.3.1 父->子传值props ***

简单父传子:

<div id="app">
		<!--组件的调用,传的参数的名称 ,和子组件中 props所需要接收的名字要一致
			传参,如果传递的是 常量不需要冒号   ;如果 是 变量的话就需要冒号-->
		<ball value="足球" :test="num1"></ball>
		<ball value="篮球" :test="num2"></ball>
		<ball value="乒乓球" :test="num3"></ball>
	</div>
	<script type="text/javascript" src="../js/vue.js"></script>
	<script type="text/javascript">
		//这个代码会报错
		//因为组件只允许有一个根节点
		var ball = {
			//父组件传参给子组件时,使用 props接收
			props: ['value', 'test'],//接收的父组件的传参
			template: `<div>我喜欢:{{value}}</div><div>我进了 {{test}}个球</div>`
		};
		var vm = new Vue({
			el: "#app",
			data: {
				num1: 10,
				num2: 20,
				num3: 50
			},
			components: {
				ball
			}
		})
	</script>
  • 父组件以属性的形式绑定值到子组件身上

  • 子组件通过使用属性props接收

    • props是单向绑定的(只读属性):当父组件的属性变化时,将传导给子组件,但是反过来不会

    • props属性支持两种常见的写法形式

      • 数组

        • 优点:书写简单

        • 缺点:不能设置默认值、数据类型

      • 对象

        • 优点:可以设置数据默认值与数据类型
  • 缺点:写法复杂

单向数据流实例:

<div id="app">
		<child :msg="message"></child>
	</div>
	<script type="text/javascript">
		//组件
		var child = {
			props: ["msg"],//接收从父组件传过来的值
			data: function () {
				return {
					basemsg: this.msg //将从父组件接收的值赋值给 这个组件的变量
				}
			},

			template: `<div>我是子组件<br/>
				从父组件传过来的数据是:{{basemsg}}
				<button @click ="change">修改msg的值</button>
				</div>`,
			methods: {
				change: function () {
					console.log('子组件中的函数执行了');
					//this.msg = " new value";
					//上一句会报错,原因是,vue的组件是单项数据流,
					//只能从父组件传参数给子组件,父组件的值发生变化会影响子组件
					//不应该在一个子组件内部改变 prop
					this.basemsg = "newvalue";//修改了子组件自己的data数据

				}
			}
		}

		/****************************************/
		var vm = new Vue({
			el: "#app",
			data: {
				message: "hello"
			},
			components: {
				child
			}
		})
	</script>

props数组、对象的写法:

<div id="app">
		<prop-com :propb="n1" :propc="n2" :propd="n1" :prope="n4" :propf="n5"></prop-com>
		<prop-com :propb="n2" :propc="n2" :propf="n5"></prop-com>
	</div>
	<script type="text/javascript">
		//传的参数必须是 props里允许的数据类型
		var propCom = {
			//props:["propa"],
			/*
			props:{
					'title':String,
					'likes': Number,
					'ispublished': Boolean,
					'commentids': Array,
					'author': Object,
				},*/
			props: {
				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
					}
				}
			},
			template: `<div>我是一个新的组件
				<ul>
				<li>{{propb}}</li>
				<li>{{propc}}</li>
				<li>{{propd}}</li>
				<li>{{prope}}</li>
				<li>{{propf}}</li>
				</ul>
				</div>`
		}
		/*************************************************/
		var vm = new Vue({
			el: "#app",
			data: {
				n1: 123,
				n2: 'hello',
				n3: true,
				n4: { "aaaa": "8888" },
				n5: 'success'
			},
			components: {
				propCom
			}
		})
	</script>

单向数据流的处理:

<div id="app">
		<child :msg="message"></child>
	</div>
	<script type="text/javascript">
		//组件
		var child = {
			props: ["msg"],//接收从父组件传过来的值
			data: function () {
				return {
					basemsg: this.msg //将从父组件接收的值赋值给 这个组件的变量
				}
			},
			template: `<div>我是子组件<br/>
				从父组件传过来的数据是:{{basemsg}}
				<button @click ="change">修改msg的值</button>
				</div>`,
			methods: {
				change: function () {
					console.log('子组件中的函数执行了');
					//this.msg = " new value";
					//上一句会报错,原因是,vue的组件是单项数据流,
					//只能从父组件传参数给子组件,父组件的值发生变化会影响子组件
					//不应该在一个子组件内部改变 prop
					this.basemsg = "newvalue";//修改了子组件自己的data数据
				}
			}
		}
		/****************************************/
		var vm = new Vue({
			el: "#app",
			data: {
				message: "hello"
			},
			components: {
				child
			}
		})
	</script>

使用computed:

<div id="app">
			<child :msg = "message"></child>
		</div>
		<script type="text/javascript">
			//组件的单项数据流的特点:父组件的值发生变化会影响子组件,
			//而子组件的值发生变化,不会影响到父组件,只影响自己的输出
			var child = {
				props:["msg"],//接收从父组件传过来的值
				computed:{
					changemsg:function(){
						return this.msg.toUpperCase();
					}
				},
				template:`<div>我是子组件<br/>从父组件传过来的数据是:{{changemsg}}</div>`,	
			}
			/****************************************/
			var vm = new Vue({
				el:"#app",
				data:{
					message:"hello"
				},
				components:{
					child
				}
			})
		</script>

7.3.2 子->父传值$emit() ***

  • 子组件模版内容中用 $emit() 定义 自定义事件 , $emit() 方法有2个参数

    • 第一个参数为自定义的事件名称

    • 第二个参数为需要传递的数据(可选)

  • 父组件模板内容中的子组件占位标签上用v-on(或@)绑定子组件定义的自定义事件名,监听子组件的事件,实现通信

子传父实例:

	<div id="app">
		<mybtn @myclick="clickFun"></mybtn>
		<hr />
		<div class="parent">
			父组件的值message:{{message}}
		</div>
	</div>
	<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
	<script type="text/javascript">
		/*
		1.在子组件的函数里去主动触发一个事件,事件名称自己定义  ,并且在主动触发时会传递一个参数  this.$emit("myclick","newvalue");
		2.在html中 调用 该组件时,事件名称需与主动触发的事件名称一致
		<mybtn @myclick = "clickFun"></mybtn>
		3.父组件的clickFun 事件,可以接收从子组件传过来的值
		*/
		//1.定义组件
		var mybtn = {
			template: `<div class="child" @click="clickbtn">子组件点击-改变父组件的值</div>`,
			methods: {
				clickbtn: function () {
					console.log("子组件的函数被触发了");
					this.$emit("myclick", "newvalue");//$emit 主动触发
				}
			}
		}
		/******************************************************/
		var vm = new Vue({
			el: "#app",
			data: { message: '我是父组件的数据' },
			components: { mybtn },
			methods: {
				clickFun: function (value) {
					console.log("父组件的函数触发了");
					console.log("从子组件传过来的值", value);
					this.message = value;
				}
			}
		})
	</script>

自定义组件上使用 v-model:

<div id="app">
		<input type="text" v-model="message" />{{message}}
		<child v-model="msg"></child>
		<hr>
		父组件:{{msg}}
	</div>
	<script type="text/javascript" src="../js/vue.js"></script>
	<script type="text/javascript">
		//直接在组件上写 v-model是不起作用
		var child = {
			props: ['value'],
			template: `<div>
				我是子组件
				<input type="text" v-bind:value="value" @input="$emit('input',$event.target.value)"/>
				</div>`
		}
		/*************************************/
		var vm = new Vue({
			el: "#app",
			data: {
				msg: "hi vue",
				message: 'hello'
			},
			components: {
				child
			}
		})
	</script>

习题

要求:1.点击父组件按钮显示子组件,点击子组件隐藏本身,

​ 2.使用.native 给子组件绑定原生事件

<div id="app">
		<div>
			<input type="button" value="我是父组件按钮,点击显示子组件" @click="changeType" />
		</div>
		<hr />
		<!-- .native 可以触发自定义组件上的原生事件 -->
		<child v-show="isshow" @childshow="isShowFun" @mouseover.native="show"></child>
	</div>
	<script type="text/javascript" src="../js/vue.js"></script>
	<script type="text/javascript">
		//需求:点击父组件的按钮,显示子组件;点击子组件的印象按钮,将子组件隐藏
		var child = {
			template: `<div class="child">
				我是子组件,我在蓝色的海洋里
				<button @click="childFun">点击按钮,隐藏子组件</button>
				</div>`,
			methods: {
				childFun: function () {
					//需要将 值传递给父节点
					this.$emit("childshow", false);
				}
			}
		}
		/****************************************************/
		var vm = new Vue({
			el: '#app',
			data: {
				isshow: false
			},
			components: {
				child
			},
			methods: {
				changeType: function () {
					this.isshow = true;
				},
				isShowFun: function (type) {
					console.log("父节点接收的数据:", type);
					this.isshow = type;
				},
				show: function () {
					console.log("自定义组件上的原生事件触发了");
				}
			}
		})
	</script>

.sync

	<div id="app">
		<div>
			<input type="button" value="我是父组件按钮,点击显示子组件" @click="changeType" />
		</div>
		<hr />
		<!-- <child v-show="isshow" @childshow="isShowFun"></child> -->
		<!-- <child v-show="isshow" @up-is-show="isShowFun"></child> -->
		<!-- <child v-show="isshow" @update:is-show="isShowFun"></child> -->
		<!-- <child v-show="isshow" @update:is-show="function(type){isshow=type;}"></child> -->
		<!-- <child v-show="isshow" @update:is-show="type=>isshow=type"></child> -->
		<child v-show="isshow" :is-show.sync="isshow"></child>
		<!--.sync 就是  @update:is-show="type=>isshow=type" 的语法糖-->
	</div>
	<script type="text/javascript">
		//需求:点击父组件的按钮,显示子组件;点击子组件的印象按钮,将子组件隐藏
		var child = {
			template: `<div class="child">
				我是子组件,我在蓝色的海洋里
				<button @click="childFun">点击按钮,隐藏子组件</button>
				</div>`,
			methods: {
				childFun: function () {
					//需要将 值传递给父节点
					// this.$emit("up-is-show",false);
					this.$emit("update:is-show", false);
				}
			}
		}
		/****************************************************/
		var vm = new Vue({
			el: '#app',
			data: {
				isshow: false
			},
			components: {
				child
			},
			methods: {
				changeType: function () {
					this.isshow = true;
				},
				// isShowFun:function(type){
				// 	console.log("父节点接收的数据:",type);
				// 	this.isshow = type;
				// }
			}
		})

	</script>

7.3.3 EventBus兄弟传参 **

EventBus又被称之为中央事件总线

在Vue中通过单独的 事件中心 来管理非 父子关系 组件(兄弟)间的通信:

公众号千千万,都得先关注公众号,一旦发送消息,就可以收到消息 - 专注交流一百年

在这里插入图片描述

核心步骤

  • 建立事件中心
const eventBus = new Vue()
  • 传递数据
eventBus.$emit('自定义事件名',传递的数据)
  • 接收数据
eventBus.$on('自定义事件名'[,callback])
  • 销毁事件中心
eventBus.$off('自定义事件名')

先建立事件中心 const bus = new Vue()

在需要接受数据的地方先监听自定义事件以及接受数据的回调函数bus.$on('my-event', (data) => {})

在需要传递数据的地方提交 自定义事件以及参数 bus.$emit('my-event', params)

<body>
  <div id="app">
    <mycontent></mycontent>
    <myfooter></myfooter>
  </div>
</body>
<template id="content">
  <div>
    {{ content }}
  </div>
</template>
<template id="footer">
  <ul>
    <li @click="goPage('home')">首页</li>
    <li @click="goPage('kind')">分类</li>
    <li @click="goPage('cart')">购物车</li>
    <li @click="goPage('user')">我的</li>
  </ul>
</template>

<script src="../js/vue.js"></script>
<script>
  // 微信公众号服务器 - 中央事件总线
  const bus = new Vue()
  const Content = {
    template: '#content',
    data () {
      return {
        content: ''
      }
    },
    mounted () {
      // 关注公众号
      bus.$on('my-event', (data) => { // home / kind/ cart / user
        this.content = data
      })
    }
  }
  const Footer = { 
    template: '#footer',
    methods: {
      goPage (type) { // home / kind/ cart / user
        // 发送消息
        bus.$emit('my-event', type)
      }
    },
  }

  new Vue({
    el: '#app',
    components: {
      mycontent: Content,
      myfooter: Footer
    }
  })
</script>

7.3.4 ref *

ref 属性被用来给元素或子组件注册引用信息,引用信息将会注册在父组件的 $refs 对象上。

如果在普通的 DOM 元素上使用 ref 属性,则引用指向的就是 DOM 元素;

如果 ref 属性用在子组件上,引用就指向子组件实例

  • ref 放在标签上,拿到的是原生节点。

  • ref 放在组件上 拿到的是组件实例

  • 原理:在父组件中通过 ref 属性(会被注册到父组件的 $refs 对象上)拿到组件/DOM对象,从而得到组件/DOM中的所有的信息,也包括值

<body>
  <div id="app">
    <div ref="oDiv">ref作用在标签上</div>
    <com ref="test"></com>
  </div>
</body>
<template id="com">
  <div>
    <h1>ref的使用</h1>
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  const Com = {
    template: '#com',
    data () {
      return {
        msg: 'hello msg'
      }
    },
    methods: {
      fn () {
        console.log('dsajhkdggdshjg')
      }
    }
  }
  new Vue({
    el: '#app',
    components: {
      com: Com
    },
    mounted () {
      console.log(this)
      // this.$refs.oDiv dom元素
      this.$refs.oDiv.style.color="red"
      // this.$refs.test 组件的实例
      console.log(this.$refs.test.msg)
      this.$refs.test.fn()
    }
  })
</script>

注意:

ref 属性这种获取子元素/组件的方式虽然写法简单,容易上手,但是其由于权限过于开放,不推荐使用,有安全问题。(不仅可以获取值,还可以获取其他所有的元素/组件的数据,甚至可以修改这些数据。)

7.3.5 $root

可以用来从一个子组件访问父组件的实例,

在绝大多数情况下,触达父级组件会使得你的应用更难调试和理解,尤其是当你变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。

<body>
    <div id="app">
        <com></com>
    </div>
</body>
<template id="com">
    <div>
        <h1>$root访问根实例</h1>
        <button @click="test">获取跟节点信息</button>
    </div>
</template>
<script src="../js/vue.js"></script>
<script>

    var com = {
        template: '#com',
        methods: {
            test: function () {
                console.log(this.$root.foo);
                this.$root.baz();
            }
        }
    }
    var app = new Vue({
        el: '#app',
        data: {
            foo: 1
        },
        computed: {
            bar: function () { /* ... */ }
        },
        methods: {
            baz: function () {
                console.log('根组件方法');
            }
        },
        components: {
            com
        }

    })
</script>

7.3.6 parent

可以通过$parent直接获取到父组件的实例

<body>
  <div id="app">
    <com></com>
  </div>
</body>
<template id="com">
  <div>
    <h1>parent的使用</h1>
    {{ $parent.msg }}
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  const Com = {
    template: '#com',
    mounted () {
      console.log(this.$parent)
    }
  }
  new Vue({
    el: '#app',
    components: {
      com: Com
    },
    data () {
      return {
        msg: 'hello msg'
      }
    },
    methods: {
      fn () {
        console.log('dsajhkdggdshjg')
      }
    }
  })
</script>

如果需要在组件的结构中访问父组件的属性和方法,不需要添加this,但是也可以添加

7.3.7 祖先组件传递后代组件

在这里插入图片描述

通过 provide + inject 完成传值 跨级传参

<body>
  <div id="app">
    <com></com>
  </div>
</body>
<template id="com">
  <div>
    <h1>provide + inject的使用</h1>
    {{ aaa }}
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  const Com = {
    template: '#com',
    inject: ['aaa']
  }
  new Vue({
    el: '#app',
    components: {
      com: Com
    },
    data: {
      msg: 'hello msg'
    },
    // provide: {
    //   aaa: '啦啦啦' // 不能直接访问 this.msg
    // }
    provide () { // 可以访问this.msg
      console.log(this)
      return {
        aaa: this.msg
      }
    }
  })
</script>

provideinject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。

传参方式总结,以下几种方式都可以用来组件间传参:

  • props 父传子 ***
  • $refs ***
  • $emit 子传父 ***
  • eventBus 兄弟传参 ***
  • provide 和 inject 跨级传参
  • $root $parent $children 父子
  • $attrs与 $listeners 父子 跨级 传参
  • vuex(这个略) *****
  • localStorage / sessionStorage 使用缓存传参
  • v-model 可以父子通信的 v-bind v-on
  • 作用域插槽 v-slot 子组件的数据传递给父组件使用

7. 3.8 $nextTick **

,可以将参数中的 回调函数里面的代码,推后到 dom都加载完之后执行,

使用nextTick可以获取到最新渲染到页面的节点信息

7.4 动态组件

通过分支条件判断实现选项卡切换

<body>
  <div id="app">
    <home v-if="type==='home'"></home>
    <kind v-else-if="type==='kind'"></kind>
    <cart v-else-if="type==='cart'"></cart>
    <user v-else></user>
    <ul>
      <li @click="goPage('home')">首页</li>
      <li @click="goPage('kind')">分类</li>
      <li @click="goPage('cart')">购物车</li>
      <li @click="goPage('user')">我的</li>
    </ul>
  </div>
</body>
<script src="../js/vue.js"></script>
<script>
  const Home = { template: '<div>首页</div>' }
  const Kind = { template: '<div>分类</div>' }
  const Cart = { template: '<div>购物车</div>' }
  const User = { template: '<div>我的</div>' }
  new Vue({
    el: '#app',
    data: {
      type: 'home'
    },
    components: {
      home: Home,
      kind: Kind,
      cart: Cart,
      user: User,
    },
    methods: {
      goPage (type) {
        this.type = type
      }
    }
  })
</script>

通过使用保留的 元素,动态地绑定到它的 is 特性,我们让多个组件可以使用同一个挂载点,并动态切换。

<body>
  <div id="app">
    <component :is="type"></component>
    <!-- type 对应的是 组件的名称 -->
    <ul>
      <li @click="type='home'">首页</li>
      <li @click="type='kind'">分类</li>
      <li @click="type='cart'">购物车</li>
      <li @click="type='user'">我的</li>
    </ul>
  </div>
</body>
<script src="../js/vue.js"></script>
<script>
  const Home = { template: '<div>首页</div>' }
  const Kind = { template: '<div>分类</div>' }
  const Cart = { template: '<div>购物车</div>' }
  const User = { template: '<div>我的</div>' }
  new Vue({
    el: '#app',
    data: {
      type: 'home'
    },
    components: {
      home: Home,
      kind: Kind,
      cart: Cart,
      user: User,
    }
  })
</script>

思考:如果每个组件中都有一个输入框,点击切换时输入不同的内容,然后再切换,查看效果

keep-alive

keep-alive的作用:

keep-alive 可以将已经切换出去的非活跃组件保留在内存中。如果把切换出去的组件保留在内存中,可以保留它的状态,避免重新渲染。

<body>
  <div id="app">
    <!-- keep-alive 可以将已经切换出去的非活跃组件保留在内存中。
      如果把切换出去的组件保留在内存中,可以保留它的状态,避免重新渲染。
      保留组件的状态,避免组件的重新渲染
    -->
    <keep-alive>
      <component :is="type"></component>
    </keep-alive>
    <!-- type 对应的是 组件的名称 -->
    <ul>
      <li @click="type='home'">首页</li>
      <li @click="type='kind'">分类</li>
      <li @click="type='cart'">购物车</li>
      <li @click="type='user'">我的</li>
    </ul>
  </div>
</body>
<script src="../js/vue.js"></script>
<script>
  const Home = { template: '<div>首页<input type="text" placeholder="首页"/></div>' }
  const Kind = { template: '<div>分类<input type="text" placeholder="分类"/></div>' }
  const Cart = { template: '<div>购物车<input type="text" placeholder="购物车"/></div>' }
  const User = { template: '<div>我的<input type="text" placeholder="我的"/></div>' }
  new Vue({
    el: '#app',
    data: {
      type: 'home'
    },
    components: {
      home: Home,
      kind: Kind,
      cart: Cart,
      user: User,
    }
  })
</script>

思考:使用keep-alive 看似保留了所有的状态,但是如果某一个组件不要保留状态呢

<body>
  <div id="app">
    <!-- 
      https://cn.vuejs.org/v2/api/#keep-alive
      keep-alive 如何保证 不是全部的 状态保存
      include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
      exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
      max - 数字。最多可以缓存多少组件实例。
    -->
    <!-- include默认就是组件的名称, 逗号后不要添加空格 -->
    <!-- <keep-alive include="home,kind,user"> -->
      <!-- 在定义组件时,可以添加name属性,include可以写name属性 -->
      <!-- <keep-alive include="homeCom,kindCom,userCom"> -->
      <!-- <keep-alive :include="['homeCom','kindCom','userCom']"> -->
        <keep-alive :include="/homeCom|kindCom|userCom/">

      <component :is="type"></component>
    </keep-alive>
    <!-- type 对应的是 组件的名称 -->
    <ul>
      <li @click="type='home'">首页</li>
      <li @click="type='kind'">分类</li>
      <li @click="type='cart'">购物车</li>
      <li @click="type='user'">我的</li>
    </ul>
  </div>
</body>
<script src="../js/vue.js"></script>
<script>
  const Home = { name: 'homeCom', template: '<div>首页<input type="text" placeholder="首页"/></div>' }
  const Kind = { name: 'kindCom', template: '<div>分类<input type="text" placeholder="分类"/></div>' }
  const Cart = { name: 'cartCom', template: '<div>购物车<input type="text" placeholder="购物车"/></div>' }
  const User = { name: 'userCom', template: '<div>我的<input type="text" placeholder="我的"/></div>' }
  new Vue({
    el: '#app',
    data: {
      type: 'home'
    },
    components: {
      home: Home,
      kind: Kind,
      cart: Cart,
      user: User,
    }
  })
</script>

使用keep-alive之后会触发什么钩子函数(手机应用程序)

<body>
  <div id="app">
    <!-- 缓存的组件会自动触发两个生命周期钩子函数
      activated     缓存的组件 被切换显示的时候 ---- 数据的请求
      deactivated   缓存的组件 被切换消失的时候
    -->
    <keep-alive :include="/homeCom|kindCom|userCom/">
      <component :is="type"></component>
    </keep-alive>
    <!-- type 对应的是 组件的名称 -->
    <ul>
      <li @click="type='home'">首页</li>
      <li @click="type='kind'">分类</li>
      <li @click="type='cart'">购物车</li>
      <li @click="type='user'">我的</li>
    </ul>
  </div>
</body>
<script src="../js/vue.js"></script>
<script>
  const Home = { activated(){
    // 数据的请求等
  },deactivated() {
    
  }, name: 'homeCom', template: '<div>首页<input type="text" placeholder="首页"/></div>' }
  const Kind = { activated(){
    // 数据的请求等
  },deactivated() {
    
  }, name: 'kindCom', template: '<div>分类<input type="text" placeholder="分类"/></div>' }
  const Cart = { name: 'cartCom', template: '<div>购物车<input type="text" placeholder="购物车"/></div>' }
  const User = { activated(){
    // 数据的请求等
  },deactivated() {
    
  }, name: 'userCom', template: '<div>我的<input type="text" placeholder="我的"/></div>' }
  new Vue({
    el: '#app',
    data: {
      type: 'home'
    },
    components: {
      home: Home,
      kind: Kind,
      cart: Cart,
      user: User,
    }
  })
</script>

案例:使用动态组件实现简易的步骤向导效果

在这里插入图片描述

<body>
  <div id="app"> 
    <button @click='change("step1")'>第一步</button> 
    <button @click='change("step2")'>第二步</button>
    <button @click='change("step3")'>第三步</button> 
    <keep-alive> 
      <component :is="name"></component> 
    </keep-alive> 
  </div> 
</body> 
<script src="../js/vue.js"></script> 
<script>
  var step1 = {
    template: '<div>这是第一步的操作</div>'
  }
  var step2 = {
    template: '<div>这是第二步的操作</div>'
  } 
  var step3 = {
    template: '<div>这是第三步的操作</div>'
  }
  var vm = new Vue({ 
    el: "#app", 
    data: { 
      name: "step2",
    },
    components: { 
      step1, 
      step2, 
      step3 
    },
    methods: { 
      change:function(name){ 
        this.name = name 
      } 
    } 
  }) 
</script>

7.5 组件插槽

组件的最大特性就是 重用 ,而用好插槽能大大提高组件的可重用能力。

**插槽的作用:**父组件向子组件传递内容。

在这里插入图片描述

通俗的来讲,插槽无非就是在 子组件 中挖个坑,坑里面放什么东西由 父组件 决定。

插槽类型有:

  • 单个(匿名)插槽

  • 具名插槽

  • 作用域插槽

7.5.1 匿名插槽

匿名插槽一般就是使用单个插槽

<div id="app">
		<zhuban>
			<span>cpu</span>
			<span>显卡</span>
			<span>声卡</span>
			<span>光驱</span>
		</zhuban>
	</div>
	<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
	<script type="text/javascript">
		//<slot></slot> 插槽
		//将调用组件的子级节点解析到 <slot></slot> 所对应的位置
		var zhuban = {
			template: `<div>
				主机箱,零件包括:
				<slot></slot>
				</div>`
		}
		/**********************************/
		var vm = new Vue({
			el: '#app',
			data: {},
			components: {
				zhuban
			}
		});
	</script>

注意:子组件的 slot 标签中允许书写内容,当父组件不往子组件传递内容时, slot 中的内容才会被展示出来。

7.5.2 具名插槽

slot 元素可以用一个特殊的特性 name 来进一步配置如何分发内容。多个插槽可以有不同的名字,具名插槽将匹配内容片段中有对应 slot 特性的元素。

上中下 形式网页布局示例代码

	<div id="app">
		<zhuban>
			<span slot="center">cpu</span>
			<span slot="io">显卡</span>
			<span slot="io">声卡</span>
			<span slot="io">光驱</span>
		</zhuban>
	</div>
	<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
	<script type="text/javascript">
		//<slot></slot> 插槽
		// 将调用组件的子级节点解析到 <slot></slot> 所对应的位置
		//具名插槽,就是给 插槽起一个名字,让代码解析时,按照名字解析到相应的位置
		var zhuban = {
			template: `<div>
				主机箱,零件包括:
				<ul>
					<li>io:<slot name="io"></slot></li>
					<li>大脑:<slot name="center"></slot></li>
				</ul>
				</div>`
		}
		/**********************************/
		var vm = new Vue({
			el: '#app',
			data: {},
			components: {
				zhuban
			}
		});
	</script>

具名插槽存在的意义就是为了解决在单个页面中同时使用多个插槽。

7.5.3 作用域插槽

**应用场景:**父组件对子组件的内容进行加工处理

作用域插槽是一种特殊类型的插槽,作用域插槽会绑定了一套数据,父组件可以拿这些数据来用,于是,情况就变成了这样:样式父组件说了算,但父组件中内容可以显示子组件插槽绑定的数据。

<body>
  <div id="app">
    <app-layout>
      <div slot-scope="props">
        <h1>父组件</h1>
        {{ props.text }}
      </div>
    </app-layout>
  </div>
</body>
<template id="layout">
  <div>
    <slot text="我是子组件中的内容"></slot>
  </div>
</template>
<script src="lib/vue.js"></script>
<script>
  const Com = {
    template: '#layout'
  }
  new Vue({
    el: '#app',
    components: {
      AppLayout: Com
    }
  })
</script>

插槽实例,模拟element-ui封装弹窗ui组件:

    <style>
        /* 6.需要给 模态框的子组件写一下样式 */
        .myDialog {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.4);
        }

        .dialogMain {
            background: #fff;
            margin: 100px auto;
            /* width: 400px; */
            height: 200px;
            border-radius: 5px;
            padding: 10px;
            display: flex;
            flex-direction: column;
        }

        .myheader {
            height: 50px;
            line-height: 50px;
            background: lightpink;
            font-size: 26px;
            display: flex;
            justify-content: space-between;
        }

        .myfooter {
            height: 50px;
            line-height: 50px;
            background: lightgreen;
        }

        .mycontent {
            flex: 1;
            background: lightskyblue;
        }

        .dialog-footer {
            text-align: right;
            float: right;
        }

        .dialog-footer button {
            width: 80px;
            height: 40px;
            line-height: 40px;
            font-size: 20px;
            border-radius: 5px;
            margin-top: 5px;
            margin-right: 10px;
        }
    </style>
<body>
    <div id='app'>
        <button @click="showDialog">点击打开模态框</button>
        <my-dialog v-show="dialogVisible" width="40%" title="我的模态框标题" :visible.sync="dialogVisible">
            <!--模态框的内容-->
            <span>这是一段模态框信息</span>
            <span slot="footer" class="dialog-footer">
                <button @click="dialogVisible = false">取 消</button>
                <button type="primary" @click="dialogVisible = false">确 定</button>
            </span>
        </my-dialog>
    </div>
    <script type="tempalte" id="mydialog">
        <div class="myDialog"  >
            <div class="dialogMain" :style="{width:width}">
                <div class="myheader">
                    <span class="h_l">{{title}}</span>
                    <span class="h_r" @click="hiddenDialog">×</span>
                </div>
                <div class="mycontent">
                    <!-- 7.添加匿名插槽 -->
                    <slot></slot>
                </div>
                <div class="myfooter">
                    <slot name="footer"></slot>
                </div>
            </div>
        </div>
    </script>

    <script src='./vue.js'></script>
    <script>
        //可以自己封装  ui组件 ,
        /*
        你之前有封装过ui组件么?
        有
        封装ui组件时都暴露了一些什么  接口?
        例如:封装模态框 ,需要  使用  传参 props 、  插槽  slot 、子组件修改父组件的状态值  .sync
        模态框的 宽度、模态框的标题、内容 、模态框的 确定  取消的按钮  ,模态框的关闭 等等  
        */

        //子组件
        var myDialog = {
            props: ['width', 'title'],
            template: `#mydialog`,
            methods: {
                hiddenDialog: function () {
                    this.$emit('update:visible', false);
                }
            }
        }

        var vm = new Vue({
            el: '#app',
            data: {
                dialogVisible: false
            },
            methods: {
                showDialog: function () {
                    this.dialogVisible = true;
                }
            },
            components: {
                myDialog
            }
        });
    </script>
</body>

7.7 插件

<body>
    <div id='app'>
        <myinput></myinput>
        <button @click="chajianfn">调用 插件中的函数</button>
    </div>
    <script src='./vue.js'></script>
    <script src="./chajian.js"></script>
    <script>

        //使用插件
        Vue.use(baseplugin);

        var vm = new Vue({
            el: '#app',
            data: {
            },
            mounted() {
                console.log(this.$axios);
                this.myfn();
            }
        });

        console.log(vm);
    </script>
</body>

7.8 过渡效果和动画效果

普通手写

<style>
        .box{
            width: 100px;
            height: 100px;
            background: hotpink;
        }
        .lee-enter-active, .lee-leave-active{
            transition: opacity 2s;
            opacity: 1;
        }
        .lee-enter, .lee-leave{
            opacity: 0;
        }
 </style>
 <body>
    <div id="app">
        <button @click='showType = !showType'>点击/显示/隐藏</button>
        <transition name='lee'>
            <div class="box" v-if='showType'></div>
        </transition>
    </div>
    <script src="../vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                showType: true
            }
        })
    </script>
</body>

使用animate.css

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
<body>
    <div id='app'>
        <div id="example-3">
            <button @click="show = !show">
                Toggle render
            </button>
            <transition name="custom-classes-transition" enter-active-class="animated wobble"
                leave-active-class="animated pulse">
                <p v-if="show">hello</p>
            </transition>
        </div>
    </div>
    <script src='./vue.js'></script>
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                show: true
            }
        });
    </script>
</body>

7.9

使用render 实现虚拟dom

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./js/vue.js"></script>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background: lightblue;
        }
    </style>
</head>

<body>


    <div id="app">
        <!-- <div id="content" class="box">
            <h1>vue虚拟dom</h1>
            <span></span>
            文本
        </div> -->
        <mydom></mydom>
    </div>
    <script>
        /*
        虚拟dom
        var dom = {
            id:"content",
            tagName:"div",
            children:[
                {
                    tagName:"h1",
                    innerText:"vue虚拟dom",
                    children:[
                        ...
                    ]
                },
                {
                    tagName:"span",
                     innerText:""
                },
                {
                    text:"文本"
                }
            ]
        }
        */

        //子组件
        Vue.component("mydom", {
            render: function (cE) {
                return cE('div', {
                    class: "box",
                    style: { color: "blue" },
                    attrs: {
                        id: "content"
                    }
                }, 'div的虚拟dom')
            }
        })


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

</html>

8. 使用 npm

8.1 先安装node

1.官网下载, https://nodejs.org/zh-cn/ , 下载长期维护版

win7 请安装 v10.***

在开始菜单 打开 cmd ,在cmd中 输入 :

  1. node -v

如果能显示 版本号,就证明 安装成功了

3.在 cmd输入 npm -v,如果能显示版本号,就证明 npm 可以使用了

4.切换目录

默认是C:

如果想要到 E:

需要 在cmd 输入 E:

切换目录

cd www

在 www下面 创建一个文件夹 nodetest

cd nodetest 进入这个文件夹

5.执行 命令 npm init 创建一个新项目

一路回车

6.npm install --save axios 安装 axios 的包

安装后在 nodetest这个文件夹下面会有个node_modules的包,我们叫做依赖包

简写方式: i 表示 install

-S 表示 --save

npm i -S swiper 安装了swiper的包

注意: --save 或者 -S 的意思是 将 安装的依赖的名字和版本号放到 package.json

"dependencies": {
    "axios": "^0.21.4",
    "swiper": "^7.0.6"
  }

7.指定版本号安装

npm i -S swiper@3.4.2

9. 项目结构

安装 vue create 项目名

node_modules 依赖项 安装的所有插件 注意:发布项目、上传git 都不需要 node_modules

public 静态文件 包括 项目的小图标 index.html 、json、

src 项目的主目录 (你的代码 都写在这里) ***

​ assets 静态文件 图片 、css

​ components 组件

​ App.vue 根组件

​ main.js 主入口文件

.gitignore 上传git服务器时需要忽略的文件

babel.config.js 解析 es6语法

package.json 项目的包配置文件 ,配置依赖项

README.md 项目的说明文档

yarn.lock 依赖包的锁定

vue.config.js 手动创建的 、可以扩展 webpack 配置项

dependencies 开发和生产都需要使用的依赖

devDependencies 开发环境使用的依赖

模板文件的 style 标签中的 scoped 属性 ,表示 以下的css样式只在 本组件起作用

使用 maoyan项目完成猫眼的组件的写法:

1.修改了 src/App.vue 删除了原有代码

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

<script>

export default {
  name: "App",
  components: {
    
  },
};
</script>

<style>

</style>

**.vue 以vue为扩展名的表示是一个vue文件

2.创建了 三个组件

src/components/myheader.vue

<template>
  <div class="header"></div>
</template>

<script>
export default {
  name: "myheader",
};
</script>

<style  scoped>
.header {
  width: 100%;
  height: 150px;
  background: lightblue;
}
</style>

src/components/myfooter.vue

<template>
  <div>尾部</div>
</template>

<script>
export default {
  name: "myfooter",
};
</script>

<style>
</style>

src/components/mycontent.vue

3.将 组件引入到 src/App.vue

<template>
  <div id="app">
    <myheader></myheader>
    <mycontent></mycontent>
    <myfooter></myfooter>
  </div>
</template>

<script>
//引入组件
import myheader from "./components/myheader.vue";
import mycontent from "./components/mycontent.vue";
import myfooter from "./components/myfooter.vue";
export default {
  name: "App",
  components: {
    myheader,
    mycontent,
    myfooter,
  },
};
</script>

<style>
* {
  padding: 0;
  margin: 0;
}
</style>

4.安装并引入 flexible.js

cnpm i -S lib-flexible

修改 src/main.js

//引入 vue
import Vue from 'vue'
//引入 App
import App from './App.vue'

//引入  flexible.js
+ import "lib-flexible";

//开发环境
Vue.config.productionTip = false

//创建 vue实例
new Vue({
  render: h => h(App),//渲染
}).$mount('#app')  //挂载  相当于  el:"#app"

5.安装并引入 axios

cnpm i -S axios

将 vue.config.js 拷贝过来,放到 根目录,重启服务器 npm run serve

修改 src/App.vue

<template>
  <div id="app">
    <myheader></myheader>
+-    <mycontent :reyingList="reyingList"></mycontent>
    <myfooter></myfooter>
  </div>
</template>

<script>
//引入组件
import myheader from "./components/myheader.vue";
import mycontent from "./components/mycontent.vue";
import myfooter from "./components/myfooter.vue";

//引入 axios
+ import axios from "axios";

export default {
  name: "App",
+  data: function () {
+    return {
+      reyingList: [],
+    };
+  },
  components: {
    myheader,
    mycontent,
    myfooter,
  },
      //下面的钩子函数中的代码都是新添加的
  mounted() {
    //获取热映列表的数据
    axios
      .get(
        "/ajax/movieOnInfoList?token=&optimus_uuid=91EF29D0D7D811EB8CF6A1D4671CD056A65811B291BC465A975913C18F908A7E&optimus_risk_level=71&optimus_code=10"
      )
      .then((res) => {
        console.log(res);
        this.reyingList = res.data.movieList;
      });
  },
};
</script>

<style>
* {
  padding: 0;
  margin: 0;
}
</style>

修改 了 src/components/mycontent.vue

<template>
  <div>
+    <ul>
+      <li v-for="item in reyingList" :key="item.id">{{ item.id }}</li>
+    </ul>
  </div>
</template>

<script>
export default {
+  props: ["reyingList"],
  name: "mycontent",
};
</script>

<style>
</style>

6.引入小图标 iconfont

首先将你作业中的 iconfont文件夹拷贝到 项目 src 目录下面

src/iconfont

修改 main.js

//引入  flexible.js
import "lib-flexible";

//引入 字体图标库   @表示的是  src目录
+ import "@/fonts/iconfont.css";

//开发环境
Vue.config.productionTip = false
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值