简体   繁体   English

如何为我创建和/或更新的每个资源创建可重用的表单组件(Vue 3、vue-router、Pinia)

[英]How can I create a reusable form component for each resource I create and/or update (Vue 3, vue-router, Pinia)

I have a Vue 3 app using Pinia stores that CRUD's data from my rest API. I've just started working with Vue 3 (from smaller vue 2 projects) and this is my first time using Pinia, so I'm still learning the intricacies of both.我有一个使用 Pinia 存储的 Vue 3 应用程序,它存储来自我的 rest API 的 CRUD 数据。我刚刚开始使用 Vue 3(来自较小的 vue 2 项目),这是我第一次使用 Pinia,所以我仍在学习其中的复杂性两者的。

One resource I manage from my api is called Applications , and I have a composable that manages API calls to retrive all apps, 1 app, or update the selected app.我通过我的 api 管理的一个资源称为Applications ,我有一个可组合项管理 API 调用以检索所有应用程序、1 个应用程序或更新所选应用程序。 Instead of creating a form component to UPDATE , and a form component to CREATE applications, I'd like to create a single form component that handles both.我不想创建一个用于UPDATE的表单组件和一个用于CREATE应用程序的表单组件,我想创建一个处理这两者的表单组件。 So far I can populate my form with an existing application using a route that contains an application_id , and I create a new application if no application_id is in my route.params .到目前为止,我可以使用包含application_id的路由使用现有应用程序填充我的表单,如果application_id不在我的route.params中,我会创建一个新应用程序。 I'm just not sure how to tell the form "Hey lets update this application instead of creating it.".我只是不确定如何告诉表单“嘿,让我们更新这个应用程序而不是创建它。”。 I thought of using v-if directives that each create a <button> (one to run update, one to run create method) depending on there is an application_id in my route.params , but that seems inefficient (it may be correct, I'm just lacking knowledge).我考虑过使用v-if指令,每个指令都创建一个<button> (一个运行更新,一个运行创建方法)取决于我的route.params中有一个application_id ,但这似乎效率低下(这可能是正确的,我只是缺乏知识)。 Here's my code:这是我的代码:

// ApplicationStore.js  (pinia store)
import { defineStore } from "pinia";
// Composable for axios API calls
import { getApplications, getApplicationByID, createApplication } from "@/composables/applications";

export const useApplicationStore = defineStore("application", {
  state: () => ({
    applications: [], //list of applications from database
    application: {},  //currently selected application for edit form
    loading: false,
    success: "Successfully Created",
    error: "",
  }),
  getters: {},
  actions: {
    async fetchApplications() {
      this.loading = true;
      this.applications = [];
      const { applications, error } = await getApplications();
      
      this.applications = applications;
      this.error = error;      
      this.loading = false;
    },
    async fetchApplicationByID(id) {
      this.loading = true;
      const { application, error } =  await getApplicationByID(id);
            
      this.application = application;
      this.error = error;
      this.loading = false;
    },
    async createNewApplication() {
      this.loading = true;
      const { application, results, error } = await createApplication(this.application);
      this.application = application;
      this.error = error;
      this.loading = false;

      if (results.status === 201) {
        // show this.success toast message
      }
    }
  }
});

Here is my ApplicationForm component.这是我的ApplicationForm组件。 It currently looks for route.param.id to see if an application is selected, if so it populates the form:它当前查找route.param.id以查看是否选择了应用程序,如果是,它会填充表单:

// ApplicationForm.vue
<template>
  <section class="columns">
    <div class="column">
      <div v-if="error" class="notification is-danger">{{ error }}</div>
      <div class="field">
        <label class="label">Name</label>
        <input v-model="application.name" class="input" type="text" />
      </div>
      <div class="field">
        <label class="label">Location</label>
        <input v-model="application.location" class="input" type="text" />
      </div>
      <div class="control">
        <button @click="createNewApplication" class="button">Save</button>
      </div>
    </div>
  </section>
</template>

<script setup>
  import { useRoute } from "vue-router";
  import { useApplicationStore } from "@/stores/ApplicationStore";
  import { storeToRefs } from "pinia";

  const route = useRoute();
  const { applications, application, error } = storeToRefs(useApplicationStore());
  const { createNewApplication } = useApplicationStore();
  
  //checking if there's an id parameter, if so it finds the application from the list in the store
  if (route.params.id) {
    application.value = applications.value.find(app => app.id === Number(route.params.id));
  } else {
    //form is blank
    application.value = {};
    error.value = "";
  }
</script>

Is there a preferred way to use this single form for both create and updates?是否有一种首选方法可以将这种单一形式用于创建和更新? I wonder if slots would be a good use case for this?我想知道slots是否是一个很好的用例? But then I think I'd still end up making multiple form components for each CRUD operation.但后来我想我最终还是会为每个 CRUD 操作制作多个表单组件。 Also, I considered using a v-if to render the buttons based on if an application is in the store or not, like this:另外,我考虑过使用v-if根据应用程序是否在商店中来呈现按钮,如下所示:

<button v-if="route.params.id" @click="updateApplication" class="button">Update</button>
<button v-else @click="createNewApplication" class="button">Save</button>

I can't help but feel there is a better way to handle this (it is something I'll utilize a lot in this and future projects).我忍不住觉得有更好的方法来处理这个问题(我将在这个项目和未来的项目中大量使用它)。 This is my first big vue/pinia app.这是我的第一个大型 vue/pinia 应用程序。 I'm loving the stack so far but these little things make me question whether or not I'm doing this efficiently.到目前为止,我很喜欢这个堆栈,但这些小事情让我怀疑我是否有效地做到了这一点。

If the form's UI is mainly expected to stay the same except for a few small differences (eg the button text), you could make the form emit a custom "submit" event and then handle that event from the parent component where you render the form (ie on the update page you have <ApplicationForm @submit="updateApplication"> and on the create page you have <ApplicationForm @submit="createNewApplication" /> :如果主要希望表单的 UI 除了一些小差异(例如按钮文本)之外保持不变,您可以使表单发出自定义“提交”事件,然后从呈现表单的父组件处理该事件(即在更新页面上你有<ApplicationForm @submit="updateApplication">并且在创建页面上你有<ApplicationForm @submit="createNewApplication" />

// ApplicationForm.vue
<template>
  <section class="columns">
    <div class="column">
      <div v-if="error" class="notification is-danger">{{ error }}</div>
      <div class="field">
        <label class="label">Name</label>
        <input v-model="application.name" class="input" type="text" />
      </div>
      <div class="field">
        <label class="label">Location</label>
        <input v-model="application.location" class="input" type="text" />
      </div>
      <div class="control">
        <button @click="$emit('submit')" class="button">{{ buttonText }}</button>
      </div>
    </div>
  </section>
</template>

As for the text, you can pass that as a prop (eg buttonText ) to the ApplicationForm component.至于文本,您可以将其作为道具(例如buttonText )传递给ApplicationForm组件。 If some sections of the form are more substantially different than just different text between the "Update" and "Create" form, that's when you'd use slots.如果表单的某些部分比“更新”和“创建”表单之间的不同文本有更大的不同,那就是您使用插槽的时候。

I wouldn't recommend making the <ApplicationForm /> component responsible for reading the route parameters;我不建议让<ApplicationForm />组件负责读取路由参数; that should generally be done only by the Vue component responsible for rendering the page (and then it should pass that data through props so that the component is as re-usable as possible)这通常应该只由负责呈现页面的 Vue 组件完成(然后它应该通过 props 传递该数据,以便组件尽可能可重用)

So your parent component could look something like this:所以你的父组件看起来像这样:

<ApplicationForm v-if="application" @submit="updateApplication" />
<ApplicationForm v-else @submit="createNewApplication" />

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM