组件传值

319 阅读3分钟

属性传值---父组件传子组件

父组件通过属性名:变量名给子组件传变量名的值: 子组件的props接收数据
在子组件模板中使用props中的属性名就可以使用接收数据

1.在子组件中自定义可以接收的属性:

<template> 
<div> <p>{{name}}</p> </div> 
</template>
<script>
export default{
  props:['name']//props:['name','age']可以传多个自定义属性
}
</script>
在子组件使用父组件传入的值就可以直接使用属性名

2.在父组件中引用子组件然后传值,就是在使用的子组件中绑定一个与子组件中自定义属性相同的属性名,等号后面就是要传入的值也可以不传值,使用子组件中详细描述规定的默认值

<template>
<app-status :name="name"></app-status>
</template>
<script>
//引入组件
import Status from "./status.vue"
export default{
  data(){
   return{
       name:"zcy"
     }
  },
  //注册组件
  components:{
    "app-status":Status
  } 
}
</script>

组件自定义事件传值----子组件传父组件

通过属性传值是单向的,子组件的data 数据需要交给父组件使用就要通过在子组件上定义自定义事件,在子组件中通过$emit 来触发事件;子组件的事件被触发并传参,事件处理函数可以接收到子组件的数据;事件绑定的事件处理函数在父节点上,故可在事件处理函数中用到子组件的数据值来修改父节点的数据。

//父组件中:
<my-search @myevent="handleEvent"></my-search>
//myevent是事子组件的自定义事件 
//handleEvent是绑定的父组件的方法


子组件中:
在任意业务中触发事件:this.$emit("myevent","要给父组件传的数据")

利用子组件使用 $emit('自定义事件名',需要传的参数) 派发事件,父组件可使用 @自定义事件名方式监听,当事件回调函数被触发时,形参就收到了子组件传来的值。

image.png

也是组件自定义事件但是省略父组件的事件回调函数绑定。

子组件中使用$emit派发事件update是固定写法,父组件监听事件时需要绑定.sync这个就相当于自己会更新父组件的数据就是子组件传来的实参,如此就可以省略父组件绑定组件自定义事件的回调函数,子组件传入的值就可以直接修改父组件的msg的数据。底层父组件还是绑定了事件回调函数,将接收的值设置给父组件的数据。

<template>
  <div id="app">
    <nav1 :ajaxinfo.sync='msg' :sonprop1='msg'></nav1>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
import nav1 from "@/components/Nav1.vue";

export default {
  data() {
    return {
      msg: "",
    };
  },
  components: {
    nav1,
  },
  async mounted() {
    let res=await this.$axios("/hyomin");
    this.msg=res.data;
  },
};
</script>

<style lang="scss">

</style>
<template>
    <div class="daohanglan">
        <span>首页</span>
        <span>校园招聘</span>
        <span>社会招聘</span>
        <span @click="getinfo" class="btn">关于我们</span>
        <div>父组件传来的值:{{sonprop1}}</div>
    </div>
</template>

<script>
export default {
    props:["sonprop1"],
    methods: {
        async getinfo(){
            let res=await this.$axios("/youngmini");
            this.$emit("update:ajaxinfo",res.data);
        }
    },
}
</script>

<style lang="scss" scoped="scoped">
.daohanglan {
    background-color: gold;
    span {
        margin-left: 10px;
        cursor: pointer;
    }
    .btn {
        &:hover{color:purple};
    }
    div {
        margin-top: 10px;
    }
}
</style>

image.png

利用v-model进行子组件传值给父组件,v-model指令的底层原理就是v-bind:value和v-on:input,所以在父组件中绑定v-model指令的数据就会通过value传给子组件,子组件调用$emit()派发事件触发input事件的回调函数,就会将传入的参数用于更新父组件中绑定了v-model指令的数据。

<template>
  <div id="app">
    <nav1 v-model='msg'></nav1>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
import nav1 from "@/components/Nav1.vue";

export default {
  data() {
    return {
      msg: "",
    };
  },
  components: {
    nav1,
  },
  async mounted() {
    let res=await this.$axios("/hyomin");
    this.msg=res.data;
  },
};
</script>

<style lang="scss">

</style>

<template>
    <div class="daohanglan">
        <span>首页</span>
        <span>校园招聘</span>
        <span>社会招聘</span>
        <span @click="getinfo" class="btn">关于我们</span>
        <div>父组件传来的值:{{value}}</div>
    </div>
</template>

<script>
export default {
    props:["value"],
    methods: {
        async getinfo(){
            let res=await this.$axios("/youngmini");
            this.$emit("input",res.data);
        }
    },
}
</script>

<style lang="scss" scoped="scoped">
.daohanglan {
    background-color: gold;
    span {
        margin-left: 10px;
        cursor: pointer;
    }
    .btn {
        &:hover{color:purple};
    }
    div {
        margin-top: 10px;
    }
}
</style>

image.png

多层组件传值,就是爷爷组件传值给孙子组件。$attrs$listeners ,可以实现跨级组件通信。$listeners官网解说就是事件传递,$attrs官网解说就是属性传递

one组件:<two v-bind:xx="100" v-on:twoEvent="fn"></two>
two组件中:<three v-bind="$attrs" v-on="$listeners"></three>
three组件:可以访问two的 属性和触发事件: {{this.$attrs.xx}} this.$emit("twoEvent",20)

two组件的$attrs就负责把one组件传来的属性传递给three组件,$listeners负责把three组件触发的事件和传递的参数在传递给one组件。

$attrs是一个对象代表了父组件传入的所有属性,子组件可以不用再注册属性名,直接使用$attrs.属性名$listeners也是一个对象代表了子组件传来的所有事件

<template>
  <div id="app">
    <p>{{ msg }}</p>
    <two :info='msg' @twoevent='fn'></two>
  </div>
</template>

<script>
import two from "@/components/two.vue";

export default {
  data() {
    return {
      msg: "ONE组件的数据",
    };
  },
  methods: {
    fn(arg){
      this.msg=arg;
    }
  },
  components: {
    two
  }
};
</script>

<style lang="scss">
  #app{
    width: 400px;
    height: 700px;
    background-color: aqua;
  }
</style>

<template>
  <div class="two">
    <p>{{$attrs.info}}</p>
    <three v-bind="$attrs" v-on="$listeners"></three>
  </div>
</template>

<script>
import three from "@/components/three.vue";
export default {
    components:{
        three
    }
}
</script>

<style lang="scss" scoped="scoped">
    .two{
        width: 300px;
        height: 500px;
        background-color: goldenrod;
    }
</style>
<template>
    <div class="three">
        <button @click="getinfo">修改值</button>
    </div>
</template>

<script>
export default {
    methods: {
        getinfo(){
            this.$attrs.info="three修改了ONE组件的数据"
            this.$emit("twoevent",this.$attrs.info);
        }
    },
}
</script>

<style lang="scss" scoped="scoped">
.three{
    background-color: purple;
    width: 100px;
    height: 100px;
}
</style>

image.png

image.png

访问组件节点(子组件通过$parent得到父组件对象,就可以操作父组件的数据)

$root: 访问根组件vm对象,所有的子组件都可以将这个实例作为一个全局仓库来访问或使用,现在有更好的技术vuex代替。
 
 $parent:访问父组件对象,直接操作父组件的data数据比如父组件有一个info的数据,就可以在子组件中用this.$parent.info得到数据或者操作数据。
 不需要再使用属性传值,但是容易出现渲染混乱之后只渲染一个的情况
 
 $children:访问子组件对象数组,不能保证顺序没有按照顺序加载因此不能按照下标去获取组件然后操作组件,也不是响应式的
          
 $refs:只会在组件渲染完成之后生效,并且它们不是响应式的。你应该避免在模板或计算属性中访问 $refs。
//在组件或者原生元素绑定ref属性(类似于标签的id属性):
 <myinput ref="myInput1"></myinput>
 <input ref="myInput2"></input>
 
 //在父组件中可以通过 this.$refs访问到子组件或者原生标签元素:
 methods: {
   focus: function () {
     this.$refs.myInput2.focus()
   }
 }
 
<template>
  <div id="app">
    <p>one组件</p>
    <button @click="fn">点击访问组件</button>
    <two></two>
  </div>
</template>

<script>
import two from "@/components/two.vue";

export default {
  methods: {
    fn(){
      console.log(this,this.$parent,this.children,this.$root,1111);
    }
  },
  components: {
    two
  }
};
</script>

<style lang="scss">
  #app{
    width: 400px;
    height: 700px;
    background-color: aqua;
  }
</style>
<template>
  <div class="two">
    <p>two组件</p>
    <button @click="getinfo">点击访问组件</button>
    <three></three>
  </div>
</template>

<script>
import three from "@/components/three.vue";
export default {
    methods: {
        getinfo(){
            console.log(this,this.$parent,this.children,this.$root,22222);
        }
    },
    components:{
        three
    }
}
</script>

<style lang="scss" scoped="scoped">
    .two{
        width: 300px;
        height: 500px;
        background-color: goldenrod;
    }
</style>
<template>
    <div class="three">
        <p>three组件</p>
        <button @click="getinfo">点击访问组件</button>
    </div>
</template>

<script>
export default {
    methods: {
        getinfo(){
            console.log(this,this.$parent,this.children,this.$root,3333);
        }
    },
}
</script>

<style lang="scss" scoped="scoped">
.three{
    background-color: purple;
    width: 200px;
    height: 300px;
}
</style>

通过$refs将纯DOM操作的功能迁移到Vue框架的项目中

只需要将原生DOM操作通过getByID等方式获取节点的改为通过$refs来获取节点,其他代码不需要改动,将DOM操作的代码放在mounted生命周期函数中才可以操作页面。

<template>
  <div id="app">
    <div class="box" ref="box">
      <!-- 小滑块 -->
      <div class="mask" ref="mask"></div>
    </div>
    <div class="imgbox">
      <!-- imgbox宽高和小滑块等比例缩放 -->
      <img src="./assets/img-41.jpg" ref="img" />
    </div>
  </div>
</template>

<script>
//一个任何元素求相对于文档中body的坐标,就是相对于页面的距离
Object.prototype.offset = function () {
  let parent1 = this.offsetParent; //获取上一级定位元素对象
  let x = this.offsetLeft;
  let y = this.offsetTop;
  while (parent1 != null) {
    x += parent1.offsetLeft;
    y += parent1.offsetTop;
    parent1 = parent1.offsetParent;
  }
  return { x, y };
};
export default {
  mounted() {
    let mask = this.$refs.mask;
    let box = this.$refs.box;
    let img = this.$refs.img;
    //鼠标移入divbox就不隐藏小滑块
    box.onmouseenter = function (event) {
      mask.style.display = "block";
      //让小滑块跟着鼠标移动,则小滑块需要设置定位
      box.onmousemove = function (event) {
        //求出鼠标指针在box盒子的偏移量,就是鼠标离box盒子左上角的坐标
        //求出鼠标离页面(body)的距离减去box盒子离页面(body)的距离
        //求鼠标在某个盒子的距离就用鼠标离页面(body)的距离减去某个盒子离页面(body)的距离
        let weizhi = box.offset();
        let x1 = weizhi.x;
        let y1 = weizhi.y;
        //鼠标到页面的距离
        let x2 = event.pageX;
        let y2 = event.pageY;
        //鼠标离box盒子左上角的坐标,减去小滑块的宽高一半就可以让鼠标位于小滑块的中心位置
        let x = x2 - x1 - mask.offsetHeight / 2;
        let y = y2 - y1 - mask.offsetWidth / 2;
        //不能让滑块超出box盒子,超出就定位在临界点
        if (y <= 0) {
          y = 0;
        }
        if (y >= box.offsetHeight - mask.offsetHeight) {
          y = box.offsetHeight - mask.offsetHeight;
        }
        //横轴同理
        if (x <= 0) {
          x = 0;
        }
        if (x >= box.offsetWidth - mask.offsetWidth) {
          x = box.offsetWidth - mask.offsetWidth;
        }
        //让小滑块中心跟着鼠标指针移动
        mask.style.top = y + "px";
        mask.style.left = x + "px";
        //小滑块先右移动,图片向左移动
        img.style.top = -y * (img.offsetHeight / box.offsetHeight) + "px";
        img.style.left = -x * (img.offsetWidth / box.offsetWidth) + "px";
      };
    };
    //鼠标移除divbox就隐藏小滑块
    box.onmouseleave = function (event) {
      mask.style.display = "none";
      mask.onmousemove = null;
    };
  },
};
</script>

<style lang="scss">
.box {
  width: 200px;
  height: 125px;
  background-image: url("./assets/img-41.jpg");
  background-size: 100%;
  background-repeat: no-repeat;
  position: relative;
}

.mask {
  width: 30px;
  height: 30px;
  background-color: rgba(255, 220, 0, 0.6);
  display: none;
  position: absolute;
}
.imgbox {
  width: 300px;
  height: 300px;
  position: relative;
  left: 300px;
  top: -150px;
  /* 超出视口的图片隐藏 */
  overflow: hidden;
}
.imgbox img {
  position: absolute;
  width: 1208px;
  height: 750px;
}
</style>