vue组件

vue组件

什么是组件

  • 组件 (Component) 是 Vue.js 最强大的功能之一
  • 组件可以扩展 HTML 元素,封装可重用的代码

组件注册

全局注册

  • Vue.component(‘组件名称’, { }) 第1个参数是标签名称,第2个参数是一个选项对象
  • 全局组件注册后,任何vue实例都可以用

组件基础用法

<div id="example">
  <!-- 2、 组件使用 组件名称 是以HTML标签的形式使用  -->  
  <my-component></my-component>
</div>
<script>
    //   注册组件 
    // 1、 my-component 就是组件中自定义的标签名
	Vue.component('my-component', {
      template: '<div>A custom component!</div>'
    })

    // 创建根实例
    new Vue({
      el: '#example'
    })

</script>

组件注意事项

  • 组件参数的data值必须是函数同时这个函数要求返回一个对象
  • 组件模板必须是单个根元素
  • 组件模板的内容可以是模板字符串
 <div id="app">
     <!-- 
		4、  组件可以重复使用多次 
	      因为data中返回的是一个对象所以每个组件中的数据是私有的
		  即每个实例可以维护一份被返回对象的独立的拷贝   
	--> 
    <button-counter></button-counter>
    <button-counter></button-counter>
    <button-counter></button-counter>
      <!-- 8、必须使用短横线的方式使用组件 -->
     <hello-world></hello-world>
  </div>

<script type="text/javascript">
	//5  如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,
    // 7、但是在普通的标签模板中,必须使用短横线的方式使用组件
     Vue.component('HelloWorld', {
      data: function(){
        return {
          msg: 'HelloWorld'
        }
      },
      template: '<div>{{msg}}</div>'
    });
    
    
    
    Vue.component('button-counter', {
      // 1、组件参数的data值必须是函数 
      // 同时这个函数要求返回一个对象  
      data: function(){
        return {
          count: 0
        }
      },
      //  2、组件模板必须是单个根元素
      //  3、组件模板的内容可以是模板字符串  
      template: `
        <div>
          <button @click="handle">点击了{{count}}次</button>
          <button>测试123</button>
			#  6 在字符串模板中可以使用驼峰的方式使用组件	
		   <HelloWorld></HelloWorld>
        </div>
      `,
      methods: {
        handle: function(){
          this.count += 2;
        }
      }
    })
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>

局部注册

  • 只能在当前注册它的vue实例中使用
<div id="app">
      <my-component></my-component>
  </div>


<script>
    // 定义组件的模板
    var Child = {
      template: '<div>A custom component!</div>'
    }
    new Vue({
      //局部注册组件  
      components: {
        // <my-component> 将只在父模板可用  一定要在实例上注册了才能在html文件中使用
        'my-component': Child
      }
    })
 </script>

组件通信

父组件向子组件传值

  • 父组件发送的形式是以属性的形式绑定值到子组件身上。
  • 然后子组件用属性props接收
  • 在props中使用驼峰形式,模板中需要使用短横线的形式字符串形式的模板中没有这个限制
 <div id="app">
    <div>{{pmsg}}</div>
     <!--1、menu-item  在 APP中嵌套着 故 menu-item   为  子组件      -->
     <!-- 给子组件传入一个静态的值 -->
    <menu-item title='来自父组件的值'></menu-item>
    <!-- 2、 需要动态的数据的时候 需要属性绑定的形式设置 此时 ptitle  来自父组件data 中的数据 . 
		  传的值可以是数字、对象、数组等等
	-->
    <menu-item :title='ptitle' content='hello'></menu-item>
  </div>

  <script type="text/javascript">
    Vue.component('menu-item', {
      // 3、 子组件用属性props接收父组件传递过来的数据  
      props: ['title', 'content'],
      data: function() {
        return {
          msg: '子组件本身的数据'
        }
      },
      template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父组件中内容',
        ptitle: '动态绑定属性'
      }
    });
  </script>
  
1. 在⼦组件中声明props接收在⽗组件挂载的属性
2. 可以在⼦组件的template中任意使⽤
3. 在⽗组件绑定⾃定义的属性
  

子组件向父组件传值

  • 子组件用$emit()触发事件
  • $emit() 第一个参数为 自定义的事件名称 第二个参数为需要传递的数据
  • 父组件用v-on 监听子组件的事件
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        // 全局组件

        // 子往父传值

        // 在父组件中 子组件上绑定自定义事件
        // 在子组件中 触发原生的事件 在事件函数通过this.$emit触发自定义的事件
        Vue.component('Child', {
            template: `
                <div>
                    <h3>我是一个子组件</h3>   
                    <h4>{{childData}}</h4>
                    <input type="text" @input = 'handleInput'/>
                </div>
            `,
            props: ['childData'],
            methods:{
                handleInput(e){
                    const val = e.target.value;

                    this.$emit('inputHandler',val);
                }
            },
        })

        const App = {
            data() {
                return {
                    msg: '我是父组件传进来的值',
                    newVal:''
                }
            },
            methods:{
                input(newVal){
                    // console.log(newVal);
                    this.newVal = newVal;
                }
            },
            template: `
                <div>
                    <div class='father'>
                        数据:{{newVal}}
                    </div>
                    <Child :childData = 'msg' @inputHandler = 'input'></Child>
                </div>
            `,
            computed: {

            }
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>
    
    1. 在⽗组件中 ⼦组件上绑定⾃定义事件
    2. 在⼦组件中 触发原⽣的事件 在事件函数通过this.$emit触发⾃定义的事件
    

平⾏组件之间通信

  • 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
    • 提供事件中心 var hub = new Vue()
  • 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
  • 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
  • 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
 <div id="app">
    <div>父组件</div>
    <div>
      <button @click='handle'>销毁事件</button>
    </div>
    <test-tom></test-tom>
    <test-jerry></test-jerry>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      兄弟组件之间数据传递
    */
    //1、 提供事件中心
    var hub = new Vue();

    Vue.component('test-tom', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>TOM:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
          hub.$emit('jerry-event', 2);
        }
      },
      mounted: function() {
       // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on(方法名
        hub.$on('tom-event', (val) => {
          this.num += val;
        });
      }
    });
    Vue.component('test-jerry', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>JERRY:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
          hub.$emit('tom-event', 1);
        }
      },
      mounted: function() {
        // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on()方法名
        hub.$on('jerry-event', (val) => {
          this.num += val;
        });
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      },
      methods: {
        handle: function(){
          //4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据  
          hub.$off('tom-event');
          hub.$off('jerry-event');
        }
      }
    });
  </script>

其它组件通信⽅式

  • ⽗组件 provide来提供变量,然后再⼦组件中通过inject来注⼊变量.⽆论组件嵌套多深
<div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

</div>
<script>
        // **** 如何设计组件? *****
        // provide
        // inject
        // 父组件 provide来提供变量,然后再子组件中通过inject来注入变量.无论组件嵌套多深
        // 中央事件总线 bus
        Vue.component('B', {
            data() {
                return {
                    count: 0
                }
            },
            inject:['msg'],
            created(){
                console.log(this.msg);
                
            },
            template: `
                <div>
                    {{msg}}
                </div>
            `,
        })

        Vue.component('A', {
            data() {
                return {

                }
            },
            created(){
                // console.log(this.$parent.$parent);
                // console.log(this.$children);
                console.log(this);
                
                
            },
            template: `
                <div>
                     <B></B>
                </div>
            `
        })


        const App = {
            data() {
                return {
                    title:"老爹"
                }
            },
            provide(){
                return {
                    msg:"老爹的数据"
                }
            },
            template: `
                <div>
                    <A></A>
                </div>
            `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>

组件插槽

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

匿名插槽

  • ⼦组件定义 slot 插槽,但并未具名,因此也可以说是默认插槽。只要在⽗元素中插⼊的内容,默认加⼊到这个插槽中去
 <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        
        Vue.component('MBtn',{
            template:`
                <button>
                    <slot></slot>
                </button>
            `
        })

        const App = {
            data() {
                return {
                    title: "老爹"
                }
            },
           
            template: `
                <div>
                    <m-btn><a href="#">登录</a></m-btn>
                    <m-btn>注册</m-btn>
                </div>
            `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>

具名插槽

  • 具有名字的插槽
  • 使用 中的 “name” 属性绑定元素
  • 具名插槽可以出现在不同的地⽅,不限制出现的次数。只要匹配了name 那么这些内容就会被插⼊到这个 name 的插槽中去
 <div id="app">
    <base-layout>
       <!-- 2、 通过slot属性来指定, 这个slot的值必须和下面slot组件得name值对应上
				如果没有匹配到 则放到匿名的插槽中   --> 
      <p slot='header'>标题信息</p>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <p slot='footer'>底部信息信息</p>
    </base-layout>

    <base-layout>
      <!-- 注意点:template临时的包裹标签最终不会渲染到页面上     -->  
      <template slot='header'>
        <p>标题信息1</p>
        <p>标题信息2</p>
      </template>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <template slot='footer'>
        <p>底部信息信息1</p>
        <p>底部信息信息2</p>
      </template>
    </base-layout>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      具名插槽
    */
    Vue.component('base-layout', {
      template: `
        <div>
          <header>
			###	1、 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字
            <slot name='header'></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
			###  注意点: 
			###  具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序
            <slot name='footer'></slot>
          </footer>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>
</body>
</html>

作用域插槽

  • 父组件对子组件加工处理
  • 既可以复用子组件的slot,又可以使slot内容不一致
Vue.component('MyComp', {
 data(){
 	return {
 		data:{
 			username:'⼩⻢哥'
 			}
 		}
 	},
 template: `
 		<div>
 			<slot :data = 'data'></slot>
 			<slot :data = 'data' name='one'></slot>
 		</div>
 `
})
const App = {
 data() {
 	return {
 	}
 },
 template: `
 	<div>
 		<MyComp>
 		<!--默认的插槽 default可以省略-->
			 <template v-slot:default='user'>
 			{{user.data.username}}
 			 </template> 
 		</MyComp>
 		<MyComp>
 		<!--与具名插槽配合使⽤-->
 			<template v-slot:one='user'>
 			{{user.data.username}}
 			</template>
 		</MyComp>
	 </div>
`,}
new Vue({
 el: '#app',
 data: {
 },
 components: {
 App
  }
})

作⽤域插槽应⽤

先说⼀下我们假设的应⽤常⽤场景,我们已经开发了⼀个代办事项列表的组件,很多模块在⽤,现在要求在不影响已测试通过的模块功能

和展示的情况下,给已完成的代办项增加⼀个对勾效果也就是说,代办事项列表组件要满⾜⼀下⼏点

  1. 之前数据格式和引⽤接⼝不变,正常展示

  2. 新的功能模块增加对勾

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

    </div>
    <script src="./vue.js"></script>
    <script>
        // 已经开发了一个待办事项列表的组件,很多模块都在
        // A B
        // 1.之前数据格式和引用接口不变,正常显示
        // 2.新功能模块增加对勾
        const todoList = {
            data() {
                return {

                }
            },
            props: {
                todos: Array,
                defaultValue: []
            },
            template: `
        <ul>
            <li v-for='item in todos' :key='item.id'>
                <slot :itemValue = 'item'>
                   
                </slot>
                 {{item.title}}
               
            </li>
        </ul>
        `
        }

       

        const App = {
            data() {
                return {
                    todoList: [{
                            title: '大哥你好么',
                            isComplate: true,
                            id: 1
                        },
                        {
                            title: '小弟我还行',
                            isComplate: false,
                            id: 2
                        },
                        {
                            title: '你在干什么',
                            isComplate: false,
                            id: 3
                        },
                        {
                            title: '抽烟喝酒烫头',
                            isComplate: true,
                            id: 4
                        }
                    ]
                }
            },
            components: {
                todoList
            },
            template: `
            	  <todoList :todos='todoList'>
                     <template v-slot='data'>
                        <input type="checkbox" v-model='data.itemValue.isComplate' />
                    </template>
            	  </todoList>
        `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                App
            }

        })
    </script>

生命周期

  • 事物从出生到死亡的过程

  • 每个 Vue 实例在被创建时都要经过⼀系列的初始化过程。 例如:从开始创建、初始化数据、编译模板、挂载Dom、数据变化时更新

    DOM、卸载等⼀系列过程。 我们称 这⼀系列的过程 就是Vue的⽣命周期。 通俗说就是Vue实例从创建到销毁的过程,就是⽣命周期。 同时在这个过程中也会运⾏⼀些叫做⽣命周期钩⼦的函数,这给了⽤户在不同阶段添加⾃⼰的代码的机会,利⽤各个钩⼦来完成我们的业务代码。

常用的钩子函数

beforeCreate在实例初始化之后,数据观测和事件配置之前被调用 此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了
created在实例创建完成后被立即调用此时data 和 methods已经可以使用 但是页面还没有渲染出来,可以在此时发起ajax
beforeMount在挂载开始之前被调用 此时页面上还看不到真实数据 只是一个模板页面而已
mountedel被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上 在这个钩子函数里面我们可以使用一些第三方的插件
beforeUpdate数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的,在更新DOM之前 调⽤该钩⼦,应⽤:可以获取原始的DOM
updated由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。 页面上数据已经替换成最新的,应⽤:可以获取最新的DOM
beforeDestroy实例销毁之前调用
destroyed实例销毁后调用
activated被 keep-alive 缓存的组件激活时调用。当配合vue的内置组件 ⼀起使⽤的时候,才会调⽤下⾯此方法, 组件的作⽤它可以缓存当前组件
deactivated被 keep-alive 缓存的组件停用时调用。当配合vue的内置组件 ⼀起使⽤的时候,才会调⽤下⾯此方法, 组件的作⽤它可以缓存当前组件

组件进阶

动态组件

  • 有的时候,在不同组件之间进⾏动态切换是⾮常有⽤的,⽐如在⼀个多标签的界⾯⾥
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			.active {
				color: red;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
	</body>
	<script src="vue.js"></script>
	<script>
		const bus = new Vue();
		Vue.component('TabLi', {
			data() {
				return {}
			},
			methods: {
				clickHandler(title) {
					bus.$emit('handleChange', title);
				}
			},
			props: ['tabTitles'],
			template: `
		 <ul>
		 <li @click='clickHandler(title)' v-for='(title,i) in tabTitles' :key='i'>{{title}}</li>
		 </ul>
		`
		})
		const Home = {
			data() {
				return {
					isActive: false
				}
			},
			methods: {

				handleClick() {
					this.isActive = true;
				}
			},
			template: `<div @click='handleClick'
		:class='{active:isActive}'>Home Component</div>`
		}
		const Posts = {
			data() {
				return {}
			},
			template: `<div>Posts Component</div>`
		}
		const Archive = {
			data() {
				return {}
			},
			template: `<div>Archive Component</div>`
		}
		Vue.component('TabComp', {
			data() {
				return {
					title: 'Home'
				}
			},
			created() {
				bus.$on('handleChange', (title) => {
					this.title = title
				})
			},
			template: `
		
		 <keep-alive>
		  <componet :is='title'></componet>
		 </keep-alive>
		`,
			components: {
				Home,
				Posts,
				Archive
			}
		})
		const App = {
			data() {
				return {
					tabTitles: ['Home', 'Posts', 'Archive']
				}
			},
			template: `
		<div>
		<TabLi :tabTitles='tabTitles'></TabLi>
		<TabComp></TabComp>
		</div>
		`,
		}
		new Vue({
			el: '#app',
			data() {
				return {}
			},
			components: {
				App,
			}
		})
	</script>
</html>

在动态组件上使用keep-alive

  • 使⽤ is 特性来切换不同的组件,当在这些组件之间切换的时候,有时候会想保持这些组件的状态,以避免反复渲染导致的性能问题
<keep-alive>
 <componet :is='title'></componet>
</keep-alive>

异步组件

  • 在⼤型应⽤中,我们可能需要将应⽤分割成⼩⼀些的代码块,并且只在需要的时候才从服务器加载⼀个模块。为了简化,Vue 允许你以⼀个⼯⼚函数的⽅式定义你的组件,这个⼯⼚函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该⼯⼚函数,且会把结果缓存起来供未来重渲染。例如:
 <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

  </div>

const App = {
 data() {
 	return {
 		isShow:false
 	}
 },
 methods:{
 	asyncLoadTest(){
 		this.isShow = true;
 	}
 },
 template:`
 	<div>
 		<button @click='asyncLoadTest'>异步加载</button>
 		<test v-if='isShow'/>
 	</div>
`,
 components:{
 		//异步加载组件
 		test:()=>import('./Test.js')
 	}
}
new Vue({
 	el:'#app',
 	data(){
		 return {
 		}
 	},
 	components:{
 	App
 	}
})

获取DOM和⼦组件对象

尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript ⾥直接访问⼀个⼦组件。为了达到这个⽬的,你可以通过 ref 特性为这个⼦组件赋予⼀个 ID 引⽤。例如:

const Test = {
 	template: `<div class='test'>我是测试组件</div>`
}
const App = {
 data() {
 	return {
 	}
 },
 created() {
 	console.log(this.$refs.test); //undefined
 },
 mounted() {
 	// 如果是组件挂载了ref 获取是组件对象,如果是标签挂载了ref,则获取的是DOM元素
 	console.log(this.$refs.test);
 	console.log(this.$refs.btn);
 	// 加载⻚⾯ 让input⾃动获取焦点
 	this.$refs.input.focus();
 },
 components: {
 	Test
 },
 template: `
 	<div>
 		<button ref = 'btn'></button>
 		<input type="text" ref='input'>
 		<Test ref = 'test'></Test>
 	</div>
 ` }
new Vue({
	 el: '#app',
 	data: {
	 },
 	components: {
 	App
 	}
})

nextTick的⽤法

将回调延迟到下次 DOM 更新循环之后执⾏。在修改数据之后⽴即使⽤它,然后等待 DOM 更新

有些事情你可能想不到,vue在更新DOM时是异步执⾏的.只要侦听到数据变化,Vue将开启⼀个队列,并缓存在同⼀事件循环中发⽣的所有数据变更.如果同⼀个wather被多次触发,只会被推⼊到队列中⼀次.这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是⾮常重要的。然后,在下⼀个的事件循环“tick”中,Vue 刷新队列并执⾏实际(已去重的) 工作

<div id="app">
 	<h3>{{message}}</h3>
</div> <script src="./vue.js"></script> <script>
 const vm = new Vue({
 	el:'#app',
 	data:{
 		message:'123'
 	}
 })
 	vm.message = 'new Message';//更新数据
 	console.log(vm.$el.textContent); //123
 	Vue.nextTick(()=>{
 	console.log(vm.$el.textContent); //new Message
 })
</script>

当你设置 vm.message = ‘new Message’ ,该组件不会⽴即重新渲染.当刷新队列时,组件会在下⼀个事件循环’tick’中更新.多数情况我们不需要关⼼这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘⼿。虽然 Vue.js 通常⿎励开发⼈员使⽤“数据驱动”的⽅式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后⽴即使⽤ Vue.nextTick(callback) 。这样回调函数将在 DOM更新完成后被调⽤。

nextTick的应⽤

  • 有个需求:在⻚⾯拉取⼀个接⼝,这个接⼝返回⼀些数据,这些数据是这个⻚⾯的⼀个浮层组件要依赖的,然后我在接⼝⼀返回数据就展示了这个浮层组件,展示的同时,上报⼀些数据给后台(这些数据就是⽗组件从接⼝拿的),这个时候,神奇的事情发⽣了,虽然我拿到数据了,但是浮层展现的时候,这些数据还未更新到组件上去,上报失败
const Pop = {
 data() {
 	return {
 		isShow:false
 	}
 },
 template:`
 	<div v-show = 'isShow'>
 	{{name}}
 	</div>
 `,
 props:['name'],
 methods: {
 	show(){
 		this.isShow = true;
 		alert(this.name);
 		}
 	},
}
const App = {
data() {
 return {
 	name:''
 	}
 },
 created() {
 // 模拟异步请求的数据
 	setTimeout(() => {
 		this.name = '嘻嘻嘻',
 		this.$refs.pop.show();
 	}, 2000);
 },
 components:{Pop},
 	template: `<pop ref='pop' :name='name'></pop>`
}
const vm = new Vue({
 	el: '#app',
 	components: {
 	   App
 	}
})

完美解决:

created() {
 	// 模拟异步请求的数据
 	setTimeout(() => {
 		this.name = '嘻嘻嘻',
 		this.$nextTick(()=>{
 		this.$refs.pop.show();
 		})
 	}, 2000);
},

对象变更检测注意事项

由于JavaScript的限制,Vue不能检测对象属性的添加和删除对于已经创建的实例,Vue不允许动态添加根级别的响应式属性.但是,可以通过 Vue.set(object,key,value) ⽅法向嵌套独享添加响应式属性

<div id="app">
 	<h3>
 		{{user.name}}{{user.age}}
 		<button @click='handleAdd'>添加年龄</button>
 	</h3>
	</div> <script src="./vue.js"></script> <script>
 new Vue({
 	el:'#app',
 	data:{
 		user:{},
 	},
 	created() {
 		setTimeout(() => {
 			this.user = {
 				name:'张三'
  		}
 	}, 1250);
 },
 methods: {
 	handleAdd(){
 	console.log(this);
 	// ⽆响应式
 	// this.user.age = 20;
 	// 响应式的
 	this.$set(this.user,'age',20);
 		}
     },
 })
</script>
this.$set(this.user,'age',20);//它只是全局Vue.set的别名

如果想为已存在的对象赋值多个属性,可以使⽤ Object.assign()

// ⼀次性响应式的添加多个属性
this.user = Object.assign({}, this.user, {
 age: 20,
 phone: '113131313'
})

混⼊mixin偷懒

混⼊(mixin)提供了⼀种⾮常灵活的⽅式,来分发Vue组件中的可复⽤功能.⼀个混⼊对象可以包含任意组件选项。当组件使⽤混⼊对象时,所有混⼊对象的选项将被“混合”进⼊该组件本身的选项。

<div id="app">
 	{{msg}}  //嘻嘻嘻
</div> 
<script src="./vue.js"></script>
<script>
 const myMixin = {
 	data(){
 		return {
 		msg:'123'
 	}
 },
 created() {
 	this.sayHello()
 },
 methods: {
 	sayHello(){
 	console.log('hello mixin')
 		}	
 	},
 }
 new Vue({
 	el: '#app',
 	data(){
 		return {
 			msg:'嘻嘻嘻'
 		}
 	},
 	mixins: [myMixin]
 })

mixin应用

我们有⼀对不同的组件,他们的作⽤是切换⼀个状态布尔值,⼀个模态框和⼀个提示框.这些提示框和模态框除了在功能,没有其它共同点:它们看起来不⼀样,⽤法不⼀样,但是逻辑⼀样

<div id="app">
 	<App></App>
</div> <script src="./vue.js"></script> <script>
 // 全局混⼊ 要格外⼩⼼ 每次实例创建 都会调⽤
 Vue.mixin({
 	created(){
 		console.log('hello from mixin!!');
 	}
 })
 // 抽离
 const toggleShow = {
 	data() {
 		return {
 		   isShow: false
 	}
 },
 methods: {
 	toggleShow() {
 		this.isShow = !this.isShow
 		}
 	}
 }
 const Modal = {
 	template: `<div v-if='isShow'><h3>模态框组件</h3></div>`,
 data() {
 	return {
 	}
 },
 mixins:[toggleShow]
 }
 const ToolTip = {
 	data() {
 		return {
 	}
 },
 template: `<div v-if='isShow'><h3>提示组件</h3></div>`,
 mixins:[toggleShow]
 }
 const App = {
 	data() {
  		return {
 	}
 },
 template: `
     <div>
 		<button @click='handleModel'>模态框
		</button>
 		<button @click='handleToolTip'>提示框
		</button>
 		<Modal ref='modal'></Modal>
 		<ToolTip ref="toolTip"></ToolTip>
 	</div>
 `,
 components: {
 	Modal,
 	ToolTip
 },
 methods: {
 	handleModel() {
 		this.$refs.modal.toggleShow()
 	},
	 handleToolTip() {
 		this.$refs.toolTip.toggleShow()
 		}
 	},
 }
 new Vue({
 	el: '#app',
 	data: {},
 	components: {
 		App
 	},
 })

购物车案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
    .container {
    }
    .container .cart {
      width: 300px;
      margin: auto;
    }
    .container .title {
      background-color: lightblue;
      height: 40px;
      line-height: 40px;
      text-align: center;
      /*color: #fff;*/  
    }
    .container .total {
      background-color: #FFCE46;
      height: 50px;
      line-height: 50px;
      text-align: right;
    }
    .container .total button {
      margin: 0 10px;
      background-color: #DC4C40;
      height: 35px;
      width: 80px;
      border: 0;
    }
    .container .total span {
      color: red;
      font-weight: bold;
    }
    .container .item {
      height: 55px;
      line-height: 55px;
      position: relative;
      border-top: 1px solid #ADD8E6;
    }
    .container .item img {
      width: 45px;
      height: 45px;
      margin: 5px;
    }
    .container .item .name {
      position: absolute;
      width: 90px;
      top: 0;left: 55px;
      font-size: 16px;
    }

    .container .item .change {
      width: 100px;
      position: absolute;
      top: 0;
      right: 50px;
    }
    .container .item .change a {
      font-size: 20px;
      width: 30px;
      text-decoration:none;
      background-color: lightgray;
      vertical-align: middle;
    }
    .container .item .change .num {
      width: 40px;
      height: 25px;
    }
    .container .item .del {
      position: absolute;
      top: 0;
      right: 0px;
      width: 40px;
      text-align: center;
      font-size: 40px;
      cursor: pointer;
      color: red;
    }
    .container .item .del:hover {
      background-color: orange;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    
    var CartTitle = {
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
    var CartList = {
      props: ['list'],
      template: `
        <div>
          <div :key='item.id' v-for='item in list' class="item">
            <img :src="item.img"/>
            <div class="name">{{item.name}}</div>
            <div class="change">
              <a href="" @click.prevent='sub(item.id)'>-</a>
              <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
              <a href="" @click.prevent='add(item.id)'>+</a>
            </div>
            <div class="del" @click='del(item.id)'>×</div>
          </div>
        </div>
      `,
      methods: {
        changeNum: function(id, event){
          this.$emit('change-num', {
            id: id,
            type: 'change',
            num: event.target.value
          });
        },
        sub: function(id){
          this.$emit('change-num', {
            id: id,
            type: 'sub'
          });
        },
        add: function(id){
          this.$emit('change-num', {
            id: id,
            type: 'add'
          });
        },
        del: function(id){
          // 把id传递给父组件
          this.$emit('cart-del', id);
        }
      }
    }
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        total: function() {
          // 计算商品的总价
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart',{
      data: function() {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          },{
            id: 2,
            name: '机顶盒',
            price: 1000,
            num: 1,
            img: 'img/b.jpg'
          },{
            id: 3,
            name: '海尔冰箱',
            price: 1000,
            num: 1,
            img: 'img/c.jpg'
          },{
            id: 4,
            name: '小米手机',
            price: 1000,
            num: 1,
            img: 'img/d.jpg'
          },{
            id: 5,
            name: 'PPTV电视',
            price: 1000,
            num: 2,
            img: 'img/e.jpg'
          }]
        }
      },
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>
          <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      },
      methods: {
        changeNum: function(val) {
          // 分为三种情况:输入域变更、加号变更、减号变更
          if(val.type=='change') {
            // 根据子组件传递过来的数据,跟新list中对应的数据
            this.list.some(item=>{
              if(item.id == val.id) {
                item.num = val.num;
                if(val.num<0){
					item.num=0
				}
                // 终止遍历
                return true;
              }
            });
          }else if(val.type=='sub'){
            // 减一操作
            this.list.some(item=>{
              if(item.id == val.id) {
                item.num -= 1;
                if(item.num<0){
					item.num=0
				}
                // 终止遍历
                return true;
              }
            });
          }else if(val.type=='add'){
            // 加一操作
            this.list.some(item=>{
              if(item.id == val.id) {
                item.num += 1;
                // 终止遍历
                return true;
              }
            });
          }
        },
        delCart: function(id) {
          // 根据id删除list中对应的数据
          // 1、找到id所对应数据的索引
          var index = this.list.findIndex(item=>{
            return item.id == id;
          });
          // 2、根据索引删除对应数据
          this.list.splice(index, 1);
        }
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });

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

结束寄语

完结,撒花【鼓掌】

相信通过上面的知识点和案列,应该对vue组件有了一定的了解吧,接下来我们将会继续学习到vue-cli的知识喔…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值