英文:
Why isn't reactive the data with props in this situation?
问题
我在`Desks.vue`组件中有两个组件,`Rooms.vue`和`Desks.vue`,除了`App.vue`组件。
当我在`Rooms.vue`组件中点击一个滑动元素时,会打开`Desks.vue`组件并将`room`对象传递到`App.vue`,然后传递到`Desks.vue`作为属性。在`Desks.vue`中,属性`room`是响应式的,所以当我点击另一个滑动元素时,`prop.room`会发生变化。然而,在`reserveData`中,`room`属性没有变化。
你能告诉我为什么在`reserveData`对象中`room`属性不是响应式的吗?
英文:
I have two componets beside the App.vue component, the Rooms.vue and the Desks.vue.
When I click on the on one of the swiper element in the Rooms.vue component, then opens the Desks.vue component and emits the room object to App.vue, and from there is passed down into the Desks.vue as prop. In the Desks.vue the prop room is reactive, so when I click on another swiper element, the prop.room is changed. However, in the reserveData the room property doesn't change.
Can you tell me why isn't reactive the room prop in the reserveData object?
App.vue
const room = ref(null)
const openRoom = (rm) => {
  room.value = rm
  console.log(room.value)
}
const closeRoom = () => {
  room.value = null
}
</script>
<template>
  <Header title="Friendbook"/>
  <Rooms @openRoom="openRoom" :room="room"/>
  <transition>
    <Desks v-if="room" :room="room" @closeRoom="closeRoom"/>
  </transition>
</template>
Rooms.vue
const emit = defineEmits(['openRoom'])
const props = defineProps({room: Object})
const roomStore = useRoomStore()
const onSwiper = (swiper) => {
  // console.log(swiper)
};
const onSlideChange = () => {
  // console.log('slide change');
};
const activeRoomIdx = ref(null)
const clickOnRoom = (room, idx) => {
  activeRoomIdx.value = idx;
  emit('openRoom', room);
}
watch(() => props.room, (newVal, oldVal) => {
  if (!newVal) {
    activeRoomIdx.value = null
  }
})
</script>
<template>
  <div class="container">
    <h3>Rooms</h3>
    <div class="rooms-container">
      <span class="swiper-button prev"></span>
      <swiper
          :modules="swiperOptions.modules"
          :space-between="12"
          @swiper="onSwiper"
          @slideChange="onSlideChange"
          :navigation="swiperOptions.navigation"
          :breakpoints="swiperOptions.breakpoints"
      >
        <swiper-slide v-for="(room, idx) in roomStore.rooms">
          <div class="room-container" :class="{active: activeRoomIdx === idx}" @click="clickOnRoom(room, idx)">
            {{ room.name }}
          </div>
        </swiper-slide>
      </swiper>
      <span class="swiper-button next"></span>
    </div>
  </div>
</template>
Desks.vue
const props = defineProps({
  room: Object,
})
const {room} = toRefs(props)
defineEmits(['closeRoom'])
const employeeStore = useEmployeeStore()
const roomStore = useRoomStore()
const reserveData = reactive({
  employee: false,
  date: false,
  room: room.value.name,
  chosenDesk: false
})
const attrs = ref([])
const onDateChange = (range) => {
  console.log(range)
  console.log('hello')
}
const range = ref({start: new Date(new Date().getTime() -86400000), end: new Date(new Date().getTime() -86400000)})
const error = ref(false)
const makeDate = (date) => {
  let day = date.getUTCDate();
  let month = date.getUTCMonth() + 1;
  let year = date.getUTCFullYear();
  if (day < 10) {
    day = `0${day}`
  }
  if (month < 10) {
    month = `0${month}`
  }
  return `${day}-${month}-${year}`
}
const choseDesk = (newDesk) => {
  reserveData.chosenDesk = newDesk.name
  const desks = roomStore.rooms.find(room => room.name === props.room.name).desks
  const currDeskReserves = desks.find(desk => desk.name === newDesk.name).reserved
  attrs.value = currDeskReserves.map(cdr => {
    return {
      dot: 'red',
      popover: {
        label: cdr.person,
        visibility: 'hover',
        hideIndicator: false,
      },
      ...cdr
    }
  })
}
watch(() => range.value, (newVal, oldVal) => {
  reserveData.date = newVal
})
</script>
<template>
  <div class="container">
    <h3>{{ room.name }} Desks</h3>
    <div class="container-wrapper">
      <div class="desks-container">
        <div class="desk" :class="{active: reserveData.chosenDesk === desk.name}" @click="choseDesk(desk)"
             v-for="desk in room.desks">
          {{ desk.name }}
        </div>
      </div>
      <div class="inputs-container">
        <select name="employees" id="employees" @change="(e) => reserveData.employee = e.target.value">
          <option value="" selected disabled hidden>Für:</option>
          <option v-for="employee in employeeStore.employees" :key="employee" :value="employee">{{ employee }}</option>
        </select>
        <div class="from-to-container">
          <label for=""><span>Von:</span><input :value="makeDate(range.start)" type="text" id="from" disabled></label>
          <label for=""><span>Bis:</span><input :value="makeDate(range.end)" type="text" id="to" disabled></label>
        </div>
        <div class="buttons-container">
          <div v-if="error" class="error-message">Some days are reserved</div>
          <div class="buttons">
            <input class="small" type="button" value="Buchen" @click="roomStore.addNewReserve(reserveData)">
            <input v-if="error" class="small secondary" type="button" value="Abbrechen">
          </div>
        </div>
      </div>
      <div class="calendar-container">
        <VDatePicker
            @input="onDateChange"
            v-model.range="range"
            locale="de"
            trim-weeks
            :min-date="new Date()"
            color="indigo"
            :first-day-of-week="2"
            :attributes="attrs"
        />
      </div>
      <div class="close-button" @click="$emit('closeRoom')"></div>
    </div>
  </div>
</template>
答案1
得分: 3
When you initialize reserveData, you set the room property to the name of your current room ref:
const reserveData = reactive({
  room: room.value.name,
  ...
})
This is a value, not the reactive object. So the room prop is now a string, and it will not change when your room ref changes.
A simple way to fix it is to put the ref into reserveData and extract the room name from there, i.e. reserveData.room.name. Note that in Vue 3.3, you have to use toRef() with a function if you use it on props:
const room = toRef(() => props.room) // in Vue 3.3
const reserveData = reactive({
  room: room,
  ...
})
A better solution is probably to use a watcher that updates reserveData when the room changes:
watch(room, () => reserveData.room = room.value.name)
英文:
When you initialize reserveData, you set the room property to the name of your current room ref:
const reserveData = reactive({
  room: room.value.name,
  ...
})
This is a value, not the reactive object. So the room prop is now a string, and it will not change when your room ref changes.
A simple way to fix it is to put the ref into reserverData and extract the room name from there, i.e. reserveData.room.name. Note that in Vue 3.3, you have to use toRef() with a function if you use it on props:
const room = toRef(() => props.room) // in Vue 3.3
const reserveData = reactive({
  room: room,
  ...
})
A better solution is probably to use a watcher that updates reserveData when the room changes:
watch(room, () => reserveData.room = room.value.name)
答案2
得分: 1
你需要将reserveData对象定义为计算属性。
reserveData对象中的room属性不具有响应性,因为它仅在组件渲染时定义一次。如果你希望保持其中一个实际的值从一个prop中获取,那么reserveData对象必须定义为计算属性:
const reserveData = computed(() => ({
  employee: false,
  date: false,
  room: room.value.name,
  chosenDesk: false
}))
另外,你也可以不将reserveData定义为计算属性,而是创建一个watch函数来跟踪room prop的变化,并在reserveData对象中设置新的room值。
英文:
You have to make your reserveData object as a computed value.
The room property of reserveData object is not reactive because it's defined only once when component was rendered. If you want to keep there an actual value from a prop, the reserveData object must be computed
const reserveData = computed(() => ({
  employee: false,
  date: false,
  room: room.value.name,
  chosenDesk: false
}))
Also, instead of making reserveData computed, you can make a watch function that will track the prop room change and set a new value for room in the reserveData object.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论