简述在Vue脚手架中,组件以及父子组件(非父子组件)之间的传值
1、组件的定义
- 组成:
- template:包裹HTML模板片段(反映了数据与最终呈现给用户视图之间的映射关系)
- 只支持单个template标签;
- 支持lang配置多种模板语法;
- script:配置Vue和载入其他组件或者依赖库
- 只支持单个script标签;
- 支持通过import方式载入其他.vue后缀的组件文件;
- style:设置样式
- 支持多个style标签;
- 支持scoped属性,css只应用于当前组件的元素中;
- 支持lang配置多种预编译语法;
- template:包裹HTML模板片段(反映了数据与最终呈现给用户视图之间的映射关系)
- 局部组件:这里是一个三级地址组件
- 首先看一下目录:在./src/components/下面新建立一个文件夹,此时命名为chinaAddress,在里面建立入口文件index.js,将你写好的组件通过import导入
-
- 在index.js里面进行全局注册组件;install是一个默认的方法
import chinaAddress from './src/main'; /* istanbul ignore next */ chinaAddress.install = function(Vue) { Vue.component(chinaAddress.name, chinaAddress); }; export default chinaAddress;
src里面的main.vue代码如下:
<template> <div class="area"> <el-select v-model="provinceCode" :placeholder="$t('message.common.province')" class="select" clearable @clear="clear"> <el-option v-for="item in provinceList" :key="item.value" :label="item.name" :value="item.value"> </el-option> </el-select> <div> {{$t('message.common.provinceOrDirectly')}} </div> <el-select v-model="cityCode" :placeholder="$t('message.common.city')" class="select"> <el-option v-for="item in cityList" :key="item.value" :label="item.name" :value="item.value"> </el-option> </el-select> <div> {{$t('message.common.city')}} </div> <el-select v-model="countryCode" :placeholder="$t('message.common.country')" class="select"> <el-option v-for="item in countryList" :key="item.value" :label="item.name" :value="item.value"> </el-option> </el-select> <div> {{$t('message.common.districtOrCountry')}} </div> </div> </template> <script> import chinaAddressData from './chinaAddressData' export default { name: 'chinaAddress', data() { return { provinceCode: '', //省code cityCode: '', //市code countryCode: '', //县code provinceList: [], //省表数据 cityList: [], //具体省下面市列表数据 countryList: [], //具体市下面县列表数据 addressCode: [], //组件内部省市县code数组 } }, props: { value: { type: Array, default () { return [] } }, }, computed: { }, components: { }, watch: { value(newVal, oldVal) { if (newVal.length) { const [provinceCode, cityCode, countryCode] = newVal this.provinceCode = provinceCode ? provinceCode : '' this.cityCode = cityCode ? cityCode : '' this.countryCode = countryCode ? countryCode : '' } }, provinceCode(newVal, oldVal) { if (newVal) { this.cityList = chinaAddressData.filter(val => val.parent === newVal) this.cityCode = this.cityList[0].value } this.emitValueChange() }, cityCode(newVal, oldVal) { if (newVal) { this.countryList = chinaAddressData.filter(val => val.parent === newVal) this.countryCode = this.countryList[0].value } this.emitValueChange() }, countryCode(newVal, oldVal) { this.emitValueChange() } }, mounted() { }, created() { this.provinceList = chinaAddressData.filter(val => !val.parent) }, methods: { emitValueChange() { this.$nextTick(() => { this.addressCode = [].concat(this.provinceCode, this.cityCode, this.countryCode).filter(val => val) this.$emit('input', this.addressCode) const addressInfo = { provinceCode: '', provinceName: '', cityCode: '', cityName: '', countryCode: '', countryName: '', } if (this.addressCode.length) { const [provinceCode, cityCode, countryCode] = this.addressCode addressInfo.provinceCode = provinceCode addressInfo.cityCode = cityCode addressInfo.countryCode = countryCode addressInfo.provinceName = chinaAddressData.find(val => val.value === provinceCode)['name'] addressInfo.cityName = chinaAddressData.find(val => val.value === cityCode)['name'] addressInfo.countryName = chinaAddressData.find(val => val.value === countryCode)['name'] } this.$emit('change', addressInfo) }) }, clear() { this.provinceCode = '' this.cityCode = '' this.countryCode = '' this.emitValueChange() } }, } </script> <style lang="scss" scoped> .area { width: 100%; display: flex; .select { width: 26%; } } </style>
js文件为:
- 在main.js里面进行全局注册:(没有这一步,这是全局组件)
import chinaAddress from 'url'; Vue.use(chinaAddress)
- 在组件里面使用:
<china-address v-model="addressCodeList" @change="addressChange"></china-address>
- 在index.js里面进行全局注册组件;install是一个默认的方法
-
- 局部组件的步骤如下:
- 定义:
var com = { //局部组件的定义,当名字为驼峰式命名myCom 写成<my-com></my-com> template: "<div>局部组件com{{msg}}</div>", data() {//每个组件的实例都有独立的数据 return { msg: "hello", n: this.v }; }, methods: {} };
- 注册:(在父组件中注册)
components: { Header, com },
- 定义:
- 局部组件的步骤如下:
- 全局组件:
- 定义:
Vue.component("组件的名字",{ template:"模板的内容"(换行时用模板字符串``)//配置项 })
- 定义:
2、数据传递方式(组件通信)
- props传值($parent以及$children)这两种方式只能用于父子组件之间的通信
- 父子组件之间的传值
- 父组件 -----> 子组件(参考资料https://www.cnblogs.com/Sky-Ice/p/9267192.html)
- 子组件不能修改父组件传递过来的值,因为数据时单向的(参考资料https://www.cnblogs.com/Sky-Ice/p/10456533.html)
在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。 Prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得
难以理解。 解决办法: 原理:将要更改的值,传递给父组件,在父组件中更改,再传递给子组件 步骤: 先将值传递给子组件,子组件 props 接收并使用,然后通过 $emit 广播一个事件给父组件,并将值一并传递,父组件 @子组件广播过来的事件,
并定义一个方法,在该方法中,改变传递过来的值,父组件又会将值传递给子组件,这样就形成了一个闭环,问题得以解决 - 传值:
- 在父组件上用v-bind绑定要传递的属性,若没有v-bind,则属性的值始终是 字符串 v-bind:v="msg"
<template> <div id="example" style="margin-bottom:200px"> 我是首页 <hr />这里是分界线 <Header v-bind:v="msg"></Header> </div> </template> <script> import Header from "./header"; export default { name: "example", data() { return { msg: "我是你爸爸" }; }, components: { Header,//子组件 }, methods: {}, mounted() {} }; </script>
- 在子组件上用props属性进行接收:
- 对象方式:(验证)
props:{ v : { type : String,//null表示可以为任意类型,Number为数值型,Object为一个对象, default : 'hello',//不传之时指定默认值 required : true,//不传值则产生错误
twoWay : true,//如果绑定类型不对将发出警告 } }, - 数组方式:props : ["v"]
<template> <div id="header"> <p>我是父组件传递过来的数据:{{v}}</p> </div> </template> <script> export default { props:["v"], name: "thesisHeader", data() { return { search: "", //搜索框内容 }; }, methods: {}, };
- 对象方式:(验证)
- 在父组件上用v-bind绑定要传递的属性,若没有v-bind,则属性的值始终是 字符串 v-bind:v="msg"
- 传方法:
- 在使用子组件时,根据传值的方式将方法进行绑定,:run="run";
- 在子组件中,依旧使用props进行接收,this.run()运行;
- 子组件还可以用这种方式调用父组件的 this.$parent.方法/属性
- 传父组件本身:
- 在父组件中,使用子组件时,动态绑定this,即:home="this";
- 子组件接收props:["home"];
- 子组件使用:this.home.父组件的属性或者方法
- 子组件不能修改父组件传递过来的值,因为数据时单向的(参考资料https://www.cnblogs.com/Sky-Ice/p/10456533.html)
- 子组件 -----> 父组件(参考资料https://www.cnblogs.com/Sky-Ice/p/9289922.html)
- 步骤:
- 依旧是父组件Home
<template> <div> <div id="example" style="width:200px;height:200px;background:#ff0;font-size:25px;line-height:200px;text-align:center;margin:20px" >我是父组件</div> <Header></Header> </div> </template> <script> import Header from "./header"; export default { name: "example", data() { return { msg: "我是你爸爸" }; }, components: { Header //子组件 }, methods: {}, mounted() {} }; </script>
<template> <div id="header"> <div style="width:200px;height:200px;background:pink;font-size:25px;padding-top:100px;text-align:center;margin:20px" >我是子组件</div> <button @click="give">给父组件传值</button> </div> </template> <script> export default { name: "thesisHeader", data() { return { msg: "爸爸,我是您的宝贝女儿啊!" }; }, methods: { give(){ } }, mounted() {} };
- 子组件发送数据
give() { this.$emit("receive", { msg: this.msg });//$emit用来触发自定义事件,第一个参数是事件的名称,第二个参数是传递给监听事件的数据 }
- 父组件在使用子组件的地方进行监听
<Header @receive="getData"></Header>
- 接收数据,并进行处理
getData(data){ console.log(data)//{msg: "爸爸,我是您的宝贝女儿啊!"} }
- 依旧是父组件Home
- 同理:传方法与自身都可以,像父组件给子组件的形式 当然,父组件还可以这么调用 this.$refs.名字.属性/方法
//传方法
myMethod() { alert("我是属于子组件的方法"); }, give() { this.$emit("receive", { msg: this.msg, myMethod: this.myMethod }); }
结果:{msg: "爸爸,我是您的宝贝女儿啊!", myMethod: ƒ} data.myMethod()执行//传组件本身
give() { this.$emit("receive", { header: this }); }
//父组件的接收处理方法getData(data){console.log(data.header)//子组件本身console.log(data.header.msg)//子组件属性data.header.myMethod()//子组件方法}
- 步骤:
- 父组件 -----> 子组件(参考资料https://www.cnblogs.com/Sky-Ice/p/9267192.html)
- 父子组件通信的练习:
- 父组件
<template> <div> <div id="example" style="width:200px;height:200px;background:#ff0;font-size:25px;line-height:200px;text-align:center;margin:20px" >我是父组件</div> <Header @receive="getData" :message="msg"></Header> </div> </template> <script> import Header from "./header"; export default { name: "example", data() { return { msg: "我是你爸爸" }; }, components: { Header //子组件 }, methods: { getData(data){ this.msg = data.changeMessage; } }, mounted() {} };
- 子组件
<template> <div id="header"> <div style="width:200px;height:200px;background:pink;font-size:25px;padding-top:100px;text-align:center;margin:20px" >我是子组件 <p>爸爸说:{{message}}</p> </div> <button @click="give">给父组件传值</button> </div> </template> <script> export default { props:["message"], name: "thesisHeader", data() { return { msg: "爸爸,我是您的宝贝女儿啊!" }; }, methods: { myMethod() { alert("我是属于子组件的方法"); }, give() { this.$emit("receive", {changeMessage : "照顾好自己"}); } }, mounted() {} }; </script>
- 父组件
- 父子组件之间的传值
- 非父子组件之间的通信
- provide/inject
provide/ inject 是vue2.2.0新增的api, 简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。这里不论子组件嵌套有多深,
只要调用了inject
那么就可以注入provide
中的数据,而不局限于只能从当前父组件的props属性中回去数据 - ref/refs
- eventBus( bus是空的vue的实例,把它放在根组件的data里面,并且bus上面有两个方法$emit,$on)
eventBus 又称为事件总线,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。
- 缺点:当项目较大,就容易造成难以维护的灾难;
- 使用步骤
- 在父组件中,存在两个兄弟组件,父组件A的代码为:
<template> <div class="parent"> <son></son> <grand-son></grand-son> </div> </template> <script> import son from "./son"; import grandSon from "./grandson"; export default { name: "parent", data() { return {}; }, components: { son, grandSon }, methods: {} }; </script> <style scoped> </style>
- 在子组件son中通过$emit发送事件
<template> <div class="son"> <p @click="handler">我要告诉我的弟弟,我多大了</p> </div> </template> <script> import eventBus from "../../eventBus/bus"; export default { name: "son", data() { return { age: "18" }; }, methods: { handler() { eventBus.$emit("handlerData", { data: this.age }); } } };
- 在另一个子组件中通过$on来监听接收到的消息
<template> <div class="grandson">我的哥哥今年{{age}}岁了</div> </template> <script> import eventBus from "../../eventBus/bus"; export default { name: "", data() { return { age: "" }; }, // inject: ["age"], methods: {}, mounted() { eventBus.$on("handlerData", data => { this.age = data.data; }); } }; </script> <style scoped> </style>
- 如果想移除事件,通过
eventBus.$off('事件名称',{})
- 在父组件中,存在两个兄弟组件,父组件A的代码为:
- vuex
- provide/inject
可以参考本博客写的,如果使用vuex:https://www.cnblogs.com/wxh0929/p/11984417.html
-
- localStorage与sessionStorage
- 实现方式
window.localStorage.setItem(key,keyValue); window.localStorage.getItem(key)
- 优点:代码实现简单,通信比较容易;缺点:数据和状态比较混乱,不太容易进行维护;
- 注意:
注意用JSON.parse() / JSON.stringify() 做数据格式转换 localStorage / sessionStorage可以结合vuex, 实现数据的持久保存,同时使用vuex解决 数据和状态混乱问题.
- 实现方式
- localStorage与sessionStorage
- 组件通信
- slot分发内容
3、混合
作用:实现代码抽离复用 eg:当创建了多个vue对象,都要用到一个方法,不应该将这个方法复制到每个对象的methods里面,应该采用'mixin' <script> var myMixin = { methods : {//公共方法 test(){console.log(test)} } } var vm1 = new Vue({ el : "#box", mixins : [myMixin], template:{} }) var vm2 = new Vue({...})//box2 </script>
注意:在混合的过程当中,如果出现同名的钩子函数,两者都会被调用,而混合的钩子函数在自己的钩子函数前面执行;
如果出现同名的methods、components等值为对象的选项时,则自己组件的选项优先(只执行自己组件内的);
4、动态组件(参考资料https://www.cnblogs.com/xiaohuochai/p/7395694.html)
- 定义:多个组件使用同一个挂载点,然后动态的在它们之间切换;我们可以使用保留的<component>元素,动态的绑定到它的is属性;
<template> <div id="home"> <el-radio-group v-model="checkId" @change="change"> <el-radio label="Header">头部</el-radio> <el-radio label="Footer">底部</el-radio> </el-radio-group> <div class="checkComponent"> <component :is="checkId"></component> </div> </div> </template> </div> </template> <script> import Header from "./header"; import Footer from "./footer"; export default { name: "example", data() { return { checkId: "Header" }; }, components: { Header, //子组件 Footer }, methods: { change(value) { this.checkId = value; } } }; </script>
- 特点
- 缓存:<keep-alive>包裹动态组件时,可以把切换出去的组件保留在内存中,而不是销毁,从而保留它的状态或者避免重复渲染DOM,提高性能;
<template> <div id="home"> <el-radio-group v-model="checkId" @change="change"> <el-radio label="Header">头部</el-radio> <el-radio label="Footer">底部</el-radio> </el-radio-group> <div class="checkComponent"> <keep-alive> <component :is="checkId"></component> </keep-alive> </div> </div> </template> </div> </template> <script> import Header from "./header"; import Footer from "./footer"; export default { name: "example", data() { return { checkId: "Header" }; }, components: { Header, //子组件 Footer }, methods: { change(value) { this.checkId = value; } } }; </script>
- include和exclude属性允许组件有条件的缓存
<keep-alive include="Header">//只缓存header footer不缓存,如果要缓存多个,用逗号分隔 <component :is="checkId"></component> </keep-alive>
- 字符串方式:include = "a,b"
- 数组方式:v-bind:include = [ "a",“b” ]
- 正则方式:v-bind:include = "/a | b /";
- activated和deactivated钩子函数
- 定义:使用
<keep-alive>
会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在activated
阶段获取数据,承担原来created钩子中获取数据的任务;
- 定义:使用
- 缓存:<keep-alive>包裹动态组件时,可以把切换出去的组件保留在内存中,而不是销毁,从而保留它的状态或者避免重复渲染DOM,提高性能;
- 参考资料:https://www.jianshu.com/p/0b0222954483 组件实际应用:实现组件的有条件缓冲
5、遇到的问题
北栀女孩儿