代码之家  ›  专栏  ›  技术社区  ›  DirkReuter

从子组件更新潮间带的useForm

  •  0
  • DirkReuter  · 技术社区  · 2 年前

    在我的应用程序中,我有几个 FilterMultiSelector 组件(在这个例子中,我简化了它),并试图在子组件向父组件发出更改时找到更新表单的方法。

    这是我的父组件:

    <script setup>
    import {useForm, usePage} from '@inertiajs/vue3'
    import FilterMultiSelector from '@/Composables/FilterMultiSelector.vue'
    
    const page = usePage()
    
    defineProps({
        assets: {
            type: Array,
        },
    })
    
    const form = useForm({
        name: '',
        assets: [],
    })
    const submit = () => {
        form.post(route('store'), {
            onFinish: () => {
                form.name = ''
                form.assets = []
            },
        })
    }
    
    const handleSelectedAssetsUpdate = (objects) => {
        console.log(objects)
        form.assets = [...objects]
    }
    </script>
    
    <template>
        
      <div class="my-2">
          <FilterMultiSelector
              :objects="assets"
              name="Assets"
              :filterAttributes="['label', 'property_name']"
              @update:selectedObjects="handleSelectedAssetsUpdate"
              :selected-objects="form.assets">
              <template #displayFilter="{object}">
                  <div class="flex justify-between text-xs">
                      <span class="font-bold">{{ object.label }}</span>
                      <span class="">{{ object.property_name }}</span>
                  </div>
              </template>
              <template #displaySelection="{object}">
                  <div class="flex justify-between gap-2">
                      <span class="font-bold">{{ object.label }}</span>
                      <span>{{ object.property_name }}</span>
                  </div>
              </template>
          </FilterMultiSelector>
      </div>
    
    </template>
    

    这就是 FilterMultiSelctor 组件,其中基本上 remove 方法需要注意:

    <script setup>
    import {computed, ref} from 'vue'
    import useDetectOutsideClick from '@/Composables/useDetectOutsideClick.js'
    import InputLabel from '@/Components/InputLabel.vue'
    import TextInput from '@/Components/TextInput.vue'
    import {XMarkIcon} from '@heroicons/vue/24/outline'
    
    let {name, objects, filterAttributes, selectedObjects} = defineProps({
        name: {
            type: String,
        },
        objects: {
            type: Array,
        },
        filterAttributes: {
            type: Array,
            default: () => [],
        },
        selectedObjects: {
            type: Array,
            default: () => [],
        },
    })
    const emit = defineEmits(['update:selectedObjects'])
    
    const componentRef = ref()
    const inputRef = ref()
    const searchString = ref('')
    
    const filteredObjects = computed(() => {
        return objects.filter((object) => {
            const searchLower = searchString.value.toLowerCase()
    
            return filterAttributes.length > 0
                ? filterAttributes.some((key) => {
                      const value = object[key]
                      return typeof value === 'string' && value.toLowerCase().includes(searchLower)
                  }) && !selectedObjects.includes(object)
                : Object.keys(object).some((key) => {
                      const value = object[key]
                      return typeof value === 'string' && value.toLowerCase().includes(searchLower)
                  }) && !selectedObjects.includes(object)
        })
    })
    
    const add = (object) => {
        if (!selectedObjects.includes(object)) {
            selectedObjects.push(object)
            emit('update:selectedObjects', selectedObjects)
        }
        searchString.value = ''
        inputRef.value.focus()
    }
    
    const remove = (object) => {
        const newSelectedObjects = selectedObjects.filter((t) => t.id !== object.id)
        emit('update:selectedObjects', [...newSelectedObjects])
    }
    
    useDetectOutsideClick(componentRef, () => {
        searchString.value = ''
    })
    </script>
    
    <template>
        <div class="relative">
            <InputLabel
                for="object"
                :value="name" />
            <TextInput
                ref="inputRef"
                id="object"
                type="text"
                class="mt-1 block w-full"
                v-model="searchString"
                placeholder="Search object ..."
                auotofocus />
    
            <div
                v-if="filteredObjects.length && searchString"
                class="absolute left-[.5%] top-[60px] z-10 w-[99%] rounded border bg-white p-4 shadow"
                ref="componentRef">
                <div
                    v-for="object in filteredObjects.slice(0, 10)"
                    :key="object.id"
                    @click="add(object)"
                    class="rounded px-2 py-1 text-sm hover:cursor-pointer hover:bg-gray-100">
                    <slot
                        name="displayFilter"
                        :object="object" />
                </div>
                <div
                    v-if="filteredObjects.length > 10"
                    class="pl-2 text-xs tracking-widest">
                    ...
                </div>
            </div>
    
            <div
                v-if="selectedObjects.length"
                class="mt-2 flex flex-wrap gap-2">
                <div
                    v-for="object in selectedObjects"
                    :key="object.id"
                    class="inline flex max-w-full items-center whitespace-nowrap rounded bg-primary-500 px-2 py-1 text-xs text-white hover:cursor-pointer hover:bg-primary-400"
                    @click="remove(object)">
                    <slot
                        name="displaySelection"
                        :object="object" />
                    <XMarkIcon class="ml-1 h-4 w-4" />
                </div>
            </div>
        </div>
    </template>
    

    的数据结构 assets 是:

    assets:Array
       0: Reactive
          id: 1
          label:"Foo"
          property_name:"Bar"
       1 ...
    

    当我现在添加三个资产:Foo、Bar、Lorem并移除Lorem时,我得到Foo和Bar。但现在,如果我去掉巴,我得到了福和洛雷姆。如果我现在把福撤掉,我会得到巴和洛雷姆。 因此,在我看不到的某个地方存在反应性问题。

    0 回复  |  直到 2 年前
        1
  •  0
  •   Shaya Ulman    2 年前

    代码的问题在于中道具的销毁 FilterMultiSelctor 导致其失去反应性的组分:

    let { name, objects, filterAttributes, selectedObjects } = defineProps({ ... });
    

    请参阅 docs 了解更多信息。

    保持反应性的最简单方法是定义道具对象并参考其属性:

    const props = defineProps({ ... });
    props.objects;
    

    (请注意,在模板中,您仍然可以直接引用道具)。

    如果你真的想破坏道具,你可以使用 toRef 为了避免失去反应性:

    import { toRefs } from 'vue';
    
    const props = defineProps({ ... });
    const { objects } = toRefs(props);
    

    This 这篇文章对这个问题有一个完整的概述。