Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug:子组件传参如果是个函数返回的对象,在父组件回调函数中对ref/reactive声明的变量赋值,如果该变量也在模版中使用,则会导致子组件所有接收的props每次都被重置 #10910

Closed
qq575792372 opened this issue May 11, 2024 · 2 comments

Comments

@qq575792372
Copy link

qq575792372 commented May 11, 2024

Vue version

3.4.27

Link to minimal reproduction

https://stackblitz.com/edit/vitejs-vite-71s1ge?file=src%2FApp.vue

Steps to reproduce

提供两个组件:
index.vue 入口组件

<template>
  <CustomTree v-bind="getAttrs1()" v-on="getEvents" />
  currentData:{{ currentData }}
</template>

<script setup>
  import CustomTree from "./CustomTree.vue";
  import { ref, computed } from "vue";

  // 这里只要是个普通对象,不管里面是普通对象还是ref声明的,用v-bind="getAttr"之后,子组件的属性在点击回调之后不会被重复触发
  const getAttrs = {
    treeData: [
      {
        id: "01",
        name: "根节点",
        pid: "",
        children: [
          { id: "0101", name: "节点-1", pid: "01" },
          { id: "0102", name: "节点-2", pid: "01" },
          {
            id: "0103",
            name: "节点-3",
            pid: "01",
          },
          { id: "0104", name: "节点-4", pid: "01" },
        ],
      },
    ],
    defaultExpandAll: true,
  };

  // 问题:这里是函数返回的对象,不管是不是箭头函数,还是computed用function返回的对象,只要是函数都不行,用v-bind="getAttr1()"之后,子组件的属性在点击回调后就是会被重复触发,不明原因
  const getAttrs1 = () => ({
    treeData: [
      {
        id: "01",
        name: "根节点",
        pid: "",
        children: [
          { id: "0101", name: "节点-1", pid: "01" },
          { id: "0102", name: "节点-2", pid: "01" },
          {
            id: "0103",
            name: "节点-3",
            pid: "01",
          },
          { id: "0104", name: "节点-4", pid: "01" },
        ],
      },
    ],
    defaultExpandAll: true,
  });
  const getAttrs2 = computed(() => {
    return () => {
      return {
        treeData: [
          {
            id: "01",
            name: "根节点",
            pid: "",
            children: [
              { id: "0101", name: "节点-1", pid: "01" },
              { id: "0102", name: "节点-2", pid: "01" },
              {
                id: "0103",
                name: "节点-3",
                pid: "01",
              },
              { id: "0104", name: "节点-4", pid: "01" },
            ],
          },
        ],
        defaultExpandAll: true,
      };
    };
  });

  const currentData = ref(null);
  const getEvents = {
    nodeClick: (data) => {
      console.log("父组件-nodeClick", data);
      currentData.value = "只要这里对ref/reactive声明的变量重新赋值,并且在模版页面中用了该属性,就会导致子组件所有属性被重复触发的问题'";
    },
  };
</script>

CustomTree.vue 子组件

<template>
  <div>
    <div
      style="border: solid 1px red; margin: 10px"
      v-for="item in treeData[0].children"
      :key="item.id"
      @click="handleNodeClick(item)"
    >
      {{ item.name }}
    </div>
  </div>
</template>

<script setup>
  import { ref, watch } from "vue";

  const props = defineProps({
    treeData: { type: Array, default: () => [] },
    defaultExpandAll: { type: Boolean, default: null },
  });

  watch(
    () => props,
    (val) => {
      console.log("子组件-监听到props数据被改变了", val);
    },
    {
      immediate: true,
      deep: true,
    }
  );

  const emit = defineEmits(["node-click"]);
  const handleNodeClick = (node) => {
    // 调用父组件回调
    emit("node-click", node);
  };
</script>

<style scoped lang="less"></style>

问题描述:

子组件传参如果是个函数返回的对象,在父组件回调函数中对ref/reactive声明的变量赋值,如果该变量也在模版中使用,则会导致子组件所有接收的props每次都被重置。

问题版本:

目前用了好几个版本都一样,包括最新的3.4.27,应该都存在这个问题。

问题触发前置条件:

1.给子组件传参的是个函数返回对象形式。
2.父组件的回调中是只对ref/reactive声明的响应式对象赋值才触发,普通对象不会出现问题。
3.以上两个满足后,还需要在页面中使用该属性才触发问题,只是赋值不使用不会出现问题。

使用场景:

大概如下面的伪代码,父组件中会给插槽中传值,子组件中接收父组件的这些值又在其内部进行处理,之所以这样写,也是因为属性是可以传function的,比如element-plus中的el-uploadon-change/on-success等方法也都是function的形式传参。

<parent>
  <template #default="{slotProps}">
     <child v-bind="child_props(slotProps)" v-on="child_events"/>
  </template>
</parent>

// 子组件的属性
const child_props = (slotProps) => ({
  customChange:(slotProps,value)=>{
    // 这里是处理的函数,可以处理插槽传递过来的值
  },
  disabled: false,
})

// 子组件的事件
const child_events = {
  input:(value)=>{
  }
}

What is expected?

在父组件的回调中正常对ref声明的变量赋值后,子组件中的参数不会被重复触发。

What is actually happening?

image
打开控制台上,会发现每次点击回调后,子组件的属性都会被重复触发

System Info

和浏览器无关

Any additional comments?

@jh-leong
Copy link
Contributor

jh-leong commented May 11, 2024

In your example, getAttrs1 returns a new object each time it runs, and v-bind gets compiled into _mergeProps($setup.getAttrs1(), { ... }). This means that whenever the parent component updates, it gets a new object after _mergeProps runs again, causing the props to change.

For the situation you mentioned, you can return a pre-declared object within the function. Refer to playground

@LinusBorg
Copy link
Member

This is all expected behavior

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants