vue3 入门遇到问题

子组件怎么监听数据修改,然后修改状态。但是父组件数据修改后子组件没有重新渲染?
这问题不知道怎么描述,不过案例很简单,bug也很明确。就是不知道怎么写T_T。

demo: https://codesandbox.io/s/happ...

step1
step2

父组件 home.vue

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <ul v-for="(v, k) in datas" :key="k">
      <HelloWorld
        @change-data="changeData"
        @remove-data="removeData"
        v-for="(data, i) in v"
        :key="i"
        :data="data"
        :idx="i"
        :type="k"
      />
    </ul>
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
import { onMounted } from 'vue'

import hooks from '@/hooks'

export default {
  name: 'Home',
  components: {
    HelloWorld
  },
  setup() {
    const { datas, fetchDatas, changeData, removeData } = hooks()

    onMounted(() => fetchDatas())

    return { datas, changeData, removeData }
  }
}
</script>

<style scoped>
ul {
  width: 350px;
  margin: 0 auto;
}
</style>

hooks.js

import { reactive, ref } from 'vue'

const objs = {
  a: [{ name: '1' }, { name: '2' }, { name: '3' }],
  b: [{ name: '4' }, { name: '5' }, { name: '6' }]
}

export default function hooks() {
  const datas = ref({})
  function fetchDatas() {
    setTimeout(() => {
      datas.value = objs
    }, 300)
  }

  // const datas = reactive(objs)

  function changeData({ data }) {
    console.log('home', { ...data })
  }

  function removeData({ type, idx }) {
    console.log('home', type, idx)
    datas.value[type].splice(idx, 1)
  }

  return {
    datas,
    fetchDatas,
    changeData,
    removeData
  }
}

子组件 HelloWorld.vue

<template>
  <li class="item">
    <div class="tip">change1: {{ !!data.changed }}</div>
    <div class="tip">change2: {{ !!changed }}</div>
    <div class="flex">
      <div>
        <input type="text" v-model="data.name" />
      </div>
      <div class="flex control">
        <div @click="removeData">删除</div>
        <!-- <div @click="changeData" v-if="data.changed">提交修改</div> -->
        <div @click="changeData" v-if="changed">提交修改</div>
      </div>
    </div>
  </li>
</template>

<script>
import { watch, ref, watchEffect, toRefs, reactive } from 'vue'

export default {
  name: 'HelloWorld',
  props: {
    data: Object,
    type: String,
    idx: Number
  },
  setup(props, { emit }) {
    const changed = ref(false)
    // const data = reactive({ data: {} })

    watchEffect(() => {
      console.log('cmpt.watchEffect', props.data)
      // data.data = props
      // changed.value = false
    })

    watch(props, curr => {
      console.log('cmpt.watch.idx', { ...curr })
      props.data.changed = true
      changed.value = true
    })

    function removeData() {
      emit('remove-data', props)
    }

    function changeData() {
      emit('change-data', props)
    }

    // const { data } = toRefs(props)

    // console.log('toRefs', toRefs(data.data))

    return { changed, changeData, removeData }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.item {
  margin: 5px 0;
  text-align: left;
  list-style: none;
  .tip {
    color: #777;
    font-size: 13px;
  }
}
.flex {
  display: flex;
  > div {
    margin-right: 10px;
  }
}
.control {
  > div {
    border-radius: 4px;
    background-color: #eee;
    font-size: 14px;
    padding: 4px 10px;
    cursor: pointer;
  }
}
</style>
阅读 2.5k
2 个回答

首先传递给子组件的props是不建议更改的,所以需要在子组件中重新保存一份props的副本,如下,template中也是使用的副本
HelloWorld.vue

<template>
  <li class="item">
    <div class="tip">change1: {{ !!model.changed }}</div>
    <div class="tip">change2: {{ !!changed }}</div>
    <div class="flex">
      <div>
        <input type="text" v-model="model.name" />
      </div>
      <div class="flex control">
        <div @click="removeData">删除</div>
        <div @click="changeData" v-if="changed">提交修改</div>
        <!-- <div @click="changeData" v-if="changed">提交修改</div> -->
      </div>
    </div>
  </li>
</template>

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

export default {
  name: "HelloWorld",
  props: {
    data: Object,
    type: String,
    idx: Number,
  },
  setup(props, { emit }) {
    // 使用model来保存data的副本,所有修改不再基于props.data,而是基于model
    const model = ref({ ...props.data });
    const changed = ref(false);
    // 同步props.data到model
    watchEffect(() => {
      console.log("props change", props.data);
      model.value = { ...props.data };
    });
    // 根据内部model更改来控制提交显示
    watch(
      model,
      () => {
        changed.value = true;
      },
      { deep: true }
    );

    const changeData = () => {
      emit("change-data", { ...props, data: model.value });
    };

    const removeData = () => {
      emit("remove-data", { ...props, data: model.value });
    };

    return { model, changed, changeData, removeData };
  },
};
</script>

App.vue中,key用的是name,最好有一个唯一的id, 使用index的话,会导致删除数据后,后续数据发生变化

    v-for="(data, i) in v"
-   :key="i"
+   :key="data.name"
    :data="data"

hooks.js

  function changeData({ type, idx, data }) {
    console.log("hooks:changeData", { ...data }, type, idx);
    datas.value[type][idx] = { ...data, changed: true };
  }

整体思想就是子组件更改的都是自身内部的数据,不点击提交和删除的话,数据就不会修改,修改数据都是在源头处理的,这里就是在hooks.js里面,然后子组件观测到父组件传过来的props变化后,也就是通过watchEffect, 将其同步到内部的副本上

不知道问题理解的对不对,希望能帮助到你

这个问题我可以这样理解么? 就是父组件的数据发生变化了,但是子组件没有渲染对吗?
如果是这样的话,可以看一下这个 => https://cn.vuejs.org/v2/api/#...

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏