Nuxt3 使用 useFetch / $fetch 编辑页面,以及用于存储的类型化对象未加载

huangapple go评论76阅读模式
英文:

Nuxt3 edit page with useFetch / $fetch as well as a typed object for storage not loading

问题

I can help you translate the provided text into Chinese. Here's the translated content:

我对Nuxt3相当新。我正在尝试使用Nuxt3构建一个新的API应用程序。我曾经有“某种方式”工作的版本,但没有一个达到我的期望。理论上和代码中我想要涵盖的事情有:

在存储上有一个类型化的“容器”,我可以使用它来跟踪表单的当前状态。

# types/models.ts
export type Client = {
  id?: number; 
  name: string;
}

# ID是可选的,因为容器也可以用于尚没有ID的新记录

我在我的视图中导入了这个:

<script setup lang="ts">
  import { Client } from '~/types/models';

  const { auth } = useSupabaseAuthClient();
  const { data: { session } } = await auth.getSession()

  const route = useRoute();

  # 我无法成功地将类型分配给在视图中使用的这个。
  const client = ref<Client>({
    name: "a",
  });

  # 由于响应中的结果属性被包装在“client”中,如下所示使用
  interface TransformedData {
    client: Client
  }

  const { data: result } = await useFetch(`http://localhost:3000/clients/${route.params.id}`, {
    headers: {
      'Authorization': `Bearer ${session?.access_token}`
    },
    transform: (result) => (result as TransformedData).client,
  });

  # 我无法正确地将结果传递给client
  client.value = await (result.value as Client);


  const submitForm = async () => {
    const { data: result } = await useFetch(`http://localhost:3000/clients/${route.params.id}`, {
      method: "PATCH",
      body: {
        client: {
          name: client.value.name,
        }
      },
      headers: {
        'Authorization': `Bearer ${session?.access_token}`
      },
      transform: (result) => (result as TransformedData).client,
    });

    // client.name = (result.value as Client).name;
  }
</script>

<template>
  <form @submit.prevent="submitForm">
    <input v-model="client.name" type="text" name="name" id="name" />
    <button type="submit">保存</button>
  </form>
</template>

我基本上正在寻找的是最佳实践:

  • 在视图中拥有一个“存储”(我猜错了术语)
  • 使GET请求填充存储的数据
  • 使页面的表单和标题(等等)显示存储的数据
  • 使PATCH请求使用该“存储”将其发送到服务器并更新视图。

由于这是所有其他视图的基础,我宁愿在这里有一个有意义的良好实现,而不是像我之前尝试的“某种意外工作”。

我尝试过的事情

  • 将GET请求包装在const loadData中,并在onMounted上调用它,但没有产生任何结果,不起作用。
  • 使用useAsyncData和$fetch,出现了相同的问题。
  • 基本上在代码中更改所有位置,并尝试不同的语法和方法。
  • 阅读了我感觉像是互联网上的所有信息。

如果有人有最佳实践要分享,或者有解决这个问题的想法,那将非常感激!

非常感谢!

英文:

I am rather new to nuxt3. I am trying to build a new app api based with nuxt3. I had versions working "somehow" but non succeeded to my expectation. Things I want to have covered in theory plus code:

Have a typed "container" for storage I can use to track the current state of the form.

# types/models.ts
export type Client = {
id?: number; 
name: string;
}
# id is optional as the container can be used for new records also which does not have an ID yet.

I am importing this in my view:

&lt;script setup lang=&quot;ts&quot;&gt;
import { Client } from &#39;~/types/models&#39;
const { auth } = useSupabaseAuthClient();
const { data: { session } } = await auth.getSession()
const route = useRoute();
# I am not successfully able to assign the type to use this in the views.
const client = ref&lt;Client&gt;({
name: &quot;a&quot;,
});
# Due to the result attributes is wrapped in &quot;client&quot; in the response, used below
interface TransformedData {
client: Client
}
const { data:result } = await useFetch(`http://localhost:3000/clients/${route.params.id}`, {
headers: {
&#39;Authorization&#39;: `Bearer ${session?.access_token}`
},
transform: (result) =&gt; (result as TransformedData).client,
});
# I am not able to pass the result correctly to client.
client.value = await(result.value as Client);
const submitForm = async () =&gt; {
const { data:result } = await useFetch(`http://localhost:3000/clients/${route.params.id}`, {
method: &quot;PATCH&quot;,
body: {
client: {
name: client.value.name,
}
},
headers: {
&#39;Authorization&#39;: `Bearer ${session?.access_token}`
},
transform: (result) =&gt; (result as TransformedData).client,
});
// client.name = (result.value as Client).name;
}
&lt;/script&gt;
&lt;template&gt;
&lt;form @submit.prevent=&quot;submitForm&quot;&gt;
&lt;input v-model=&quot;client.name&quot; type=&quot;text&quot; name=&quot;name&quot; id=&quot;name&quot; /&gt;
&lt;button type=&quot;submit&quot;&gt;Save&lt;/button&gt;
&lt;/form&gt;
&lt;/template&gt;

What I am basically looking for is a best practive to do this:

  • Have a "store" in the view (I guess wrong wording)
  • Have the GET request filling the data of the store
  • Have the form and title (etc) of the page showing the store data
  • Have the PATCH request to use that "store" to send it to the server and update the view.

As this is the base for all the other views, I'd rather like to have a good implemenation here which makes sense instead of "somehow unexpectedly working" as I had in between.

Things I tried

  • Wrap the GET request into a const loadData and call it onMounted, but that did not yield any results, did not work.
  • use useAsyncData and $fetch, same issues.
  • Basically changing all the places in the code and none trying different syntax and approaches.
  • Read all the internet I feel like.

It would be greatly appreciated if someone has a best practice to share or has ideas on how to solve this!

Thanks loads!

答案1

得分: 1

这是我的设置在视图中:

&lt;script setup lang=&quot;ts&quot;&gt;
  import { useClientStore } from &quot;@/stores/client&quot;;
  import { useAuthStore } from &quot;@/stores/auth&quot;;

  const { auth } = useSupabaseAuthClient();
  const authStore = useAuthStore();
  const store = useClientStore();

  const route = useRoute();

  onMounted(() =&gt; {
    authStore.auth = auth;
    store.fetchRecord(route.params.id);
  });
&lt;/script&gt;

这是视图中相关的部分:

&lt;template&gt;
    &lt;button @click=&quot;store.fetchRecord(route.params.id)&quot;&gt;FETCH&lt;/button&gt;
    &lt;button @click=&quot;store.updateRecord&quot;&gt;UPDATE&lt;/button&gt;
    {{ store.name }}
&lt;/template&gt;

以及这是只包含获取方法以保持简短的存储(其他存储类似)

import { useAuthStore } from &quot;@/stores/auth&quot;;

export const useClientStore = defineStore(&#39;clients&#39;, {
  state: (): Client =&gt; ({
    id: null,
    name: &quot;&quot;
  }),

  getters: {
    record_url: (state) =&gt; `http://localhost:3000/clients/${state.id}`
  },

  actions: {
    async fetchRecord(id: any) {
      const { data: { session } } = await useAuthStore().auth.getSession();
      const token = `Bearer ${session?.access_token}`;

      const url = `http://localhost:3000/clients/${id}`;

      try {
        await useFetch(url, {
          headers: {
            &#39;Authorization&#39;: token
          },
          onResponse({ response }) {
            const client = response._data.client as Client;
            useClientStore().$patch((state) =&gt; {
              state.id = client.id;
              state.name = client.name;
              // more attribute translation here
            })
          },
        });
      } catch (error) {
        console.log(error)
      }
    },
  },
})

interface Client {
  id: number | null
  name: string
}

if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useClientStore, import.meta.hot))
}

除此之外,我将研究以下项目以获得更干净的解决方案:

  • 共享/访问身份验证令牌的最佳方式是否是这样
  • 更好地管理URL,以便可以使用存储构建到具有NuxtLink的项目的链接。以及是否有意义。
  • 再次检查一下对于最初加载数据并存储令牌的onMounted方法是否真的好。
  • 整体改进。

到目前为止,我在几天后就喜欢上了Nuxt。如果有人发现这个解决方案的潜在改进,请随时学习!再次感谢[Json Landbridge]。

英文:

I now have a state running I am ok with. This is a summary to help everyone. It is fine for my own project as it is just one solution, if I would go for a clients project here I'd try to move common logic from many of those stores into a composable / base store. Now I will just be duplicating the same code.

This is my setup in the view:

&lt;script setup lang=&quot;ts&quot;&gt;
import { useClientStore } from &quot;@/stores/client&quot;;
import { useAuthStore } from &quot;@/stores/auth&quot;;
const { auth } = useSupabaseAuthClient();
const authStore = useAuthStore();
const store = useClientStore();
const route = useRoute();
onMounted(() =&gt; {
authStore.auth = auth;
store.fetchRecord(route.params.id);
});
&lt;/script&gt;

This is the relevant part in the view:

&lt;template&gt;
&lt;button @click=&quot;store.fetchRecord(route.params.id)&quot;&gt;FETCH&lt;/button&gt;
&lt;button @click=&quot;store.updateRecord&quot;&gt;UPDATE&lt;/button&gt;
{{ store.name }}
&lt;/template&gt;

And this is the store with just the fetch method to keep it short (same thing for the others)

import { useAuthStore } from &quot;@/stores/auth&quot;;
export const useClientStore = defineStore(&#39;clients&#39;, {
state: (): Client =&gt; ({
id: null,
name: &quot;&quot;
}),
getters: {
record_url: (state) =&gt; `http://localhost:3000/clients/${state.id}`
},
actions: {
async fetchRecord(id: any) {
const { data: { session } } = await useAuthStore().auth.getSession();
const token = `Bearer ${session?.access_token}`;
const url = `http://localhost:3000/clients/${id}`;
try {
await useFetch(url, {
headers: {
&#39;Authorization&#39;: token
},
onResponse({ response }) {
const client = response._data.client as Client;
useClientStore().$patch((state) =&gt; {
state.id = client.id;
state.name = client.name;
// more attribute translation here
})
},
});
} catch (error) {
console.log(error)
}
},
},
})
interface Client {
id: number | null
name: string
}
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useClientStore, import.meta.hot))
}

Apart from this I will look into these items for a cleaner solution of this concept:

  • Is it the best way to share / access auth tokens
  • A better way to manage URLs so I can use the store to build links to items with NuxtLink. And if it makes sense at all.
  • Check once more if onMounted approach for initially loading my data and storing the token is actually good.
  • Overall improvements.

So far I like Nuxt after a few days. If anyone spots any potential improvements to this solution, happy to learn! Thanks again [Json Landbridge]

答案2

得分: 0

> 在视图中有一个"store"(我猜词汇不正确)

大多数开发者使用Pinia作为"状态管理"来集中存储数据。

> 使GET请求填充"store"的数据

等待请求的响应,然后将该响应设置到"store"中,然后将更新应用程序的其余部分。

> 使页面的表单和标题(等等)显示"store"的数据

同上,如果你正确地绑定了"store",那么它也会更新这部分。

> 使用PATCH请求来使用该"store"将其发送到服务器并更新视图。

我不太确定你在这里指的是什么,但我假设你想从"store"获取数据并将其发送到服务器?在Pinia中,使用"getters"很容易实现这一点。不过,这与更新视图是不同的,因为你不需要将数据发送到服务器来更新视图,一切都在前端发生。

编辑

使用useFetch的Pinia示例:

// 在组件内部
// 这是你创建的"store":
const store = useStore()
// API调用
await useFetch('/api/auth/login', {
  onResponse({ response }) {
    // 使用响应更新"store",请注意,您必须自己创建`updateData`函数
    store.updateData(response.data);
  },
})

> [评论]:从抽象角度来看,"store"甚至可能封装从API/远程存储中读取/更新的操作,但我没有看到。

当然,你可以在这里查看:https://blog.logrocket.com/consume-apis-vuex-pinia-axios/#consuming-apis-pinia-axios

这是在Pinia操作中使用Axios来调用API,然后将响应设置到同一"store"中的地方。

> [评论]:对于第二个问题:你能分享一个如何将数据传输到"store"的示例吗?

这取决于上下文,你有两个选择,使用useFetch和使用Axios在Pinia操作中

如果数据只在特定组件中使用,而不在其他地方使用。那么你可以使用useFetch,但对于其他情况,我绝对建议使用Axios在Pinia操作中的方法。

英文:

> Have a "store" in the view (I guess wrong wording)

What most developers use is Pinia as "state management" for centralized data storage.

> Have the GET request filling the data of the store

Await the response of the request, and then set that response in the store, which will then update the rest of the app.

> Have the form and title (etc) of the page showing the store data

Same as above, if you bind the store correctly then it will update this as well.

> Have the PATCH request to use that "store" to send it to the server and update the view.

I'm not sure what you mean here, but I assume you would like to get the data from the store and send it to the server? This is easily done with "getters" in Pinia. This is something different from updating the view though, as in you don't need to send data to the server to update the view, everything happens in the front-end.

EDIT

Pinia example with useFetch:

// Inside the component
// This is the store you created:
const store = useStore()
// The api call
await useFetch(&#39;/api/auth/login&#39;, {
  onResponse({ response }) {
    // Update the store with the response, note that `updateData` is a function you have to create yourself
    store.updateData(response.data);
  },
})

> [comment]: From an abstract point of view the store might even encapsulate reading / updating from an API / remote store, but I didn't see that.

Sure, you can take a look here: https://blog.logrocket.com/consume-apis-vuex-pinia-axios/#consuming-apis-pinia-axios

This is where you use Axios inside an Pinia action to call the api and then set the response in that same store.

> [comment]: For the second question: can you share an example how you would transfer the data into the store?

This depends on the context and you have 2 options here, the useFetch and the Axios inside the Pinia Actions.

If the data is only used in that specific component, and nowhere else. Then you can do useFetch, but for everything else I would definitely use the Axios inside the Pinia Actions approach.

huangapple
  • 本文由 发表于 2023年7月13日 14:43:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76676586.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定