SOLIDJS:以优雅的方式消费REST API
#javascript #typescript #api #solidjs

在这篇简短的文章中,我将向您展示一种优雅的方式(对我来说)消费REST API。

语境

当我们消耗REST API端点时,通常是相同的。
REST API严格对HTTP动词和终点的命名。

通常是获取项目和post / delete / patch分别创建 /删除 /编辑项目的请求。< / p>

本文仅处理以下http动词:get / post / delete / patch。您可以为您的案件大量调整代码。

我们想要什么

本文的目的是创建一个钩子,以便于轻松消耗REST API。

我们想要这样的东西(Quotes rest API管理示例):

const { items, add, remove, edit } = useQuotes();

remove而不是delete,因为这是JavaScript中的保留键

此挂钩处理REST API调用更新本地数据!
fetch 自动完成。

为了做到这一点,我们使用createResource APIContext API

为了促进此挂钩的创建,我们创建了一个“钩子出版物”,以轻松创建许多类型的端点。

我们的“挂钩构造器”如何使用?

简单:

usecitations.tsx

import { createRESTApiHook } from "./createRESTApiHook";

export type Quote = {
  id: string;
  text: string;
  title: string;
  author?: string;
  tags: string[];
  numberOfVotes: number;
};

const { Provider, useRESTApi } = createRESTApiHook<Quote>();

export {
  Provider as QuotesProvider,
  useRESTApi as useQuotes
};

使用useQuotes

  const { items, add, remove, edit, refetch } = useQuotes();

因为我们使用上下文

app.tsx

<QuotesProvider baseURL="http://localhost:8080/quotes">
  // Components using API
</QuotesProvider>

仅此而已!

我们的“钩子制造商”是如何创建的?

简单:

createrestapihook.tsx

import {
  createContext,
  createResource,
  Resource,
  JSXElement,
  useContext,
} from "solid-js";

interface ProviderProps {
  baseURL: string;
  children: JSXElement;
}


export function createRESTApiHook<T>() {
  interface ContextValue {
    items: Resource<T[]>;
    add: (item: Omit<T, "id">) => Promise<boolean>;
    edit: (itemId: string, item: Partial<T>) => Promise<boolean>;
    remove: (itemId: string) => Promise<boolean>;
    refetch: () => void;
  }

  const context = createContext<ContextValue>();


  function Provider(props: ProviderProps) {
    const [items, { mutate, refetch }] =
      createResource<T[]>(fetchItems);

    async function fetchItems() {
      const res = await fetch(props.baseURL);
      return res.json();
    }

    async function add(item: Omit<T, "id">) {
      try {
        const res = await fetch(props.baseURL, {
          method: "POST",
          body: JSON.stringify(item),
          headers: {
            "Content-Type": "application/json",
          },
        });
        const addedItem = await res.json();
        mutate((prev) => (prev ? [...prev, addedItem] : [addedItem]));
        return true;
      } catch (err) {
        console.error(err);
        return false;
      }
    }

    async function edit(itemId: string, item: Partial<T>) {
      try {
        const res = await fetch(`${props.baseURL}/${itemId}`, {
          method: "PATCH",
          body: JSON.stringify(item),
          headers: {
            "Content-Type": "application/json",
          },
        });
        const updatedItem = await res.json();
        mutate((prev) =>
          prev?.map((elt: any) => (elt.id === itemId ? updatedItem : elt))
        );
        return true;
      } catch (err) {
        console.error(err);
        return false;
      }
    }

    async function remove(itemId: string) {
      try {
        await fetch(`${props.baseURL}/${itemId}`, {
          method: "DELETE",
        });
        mutate((prev) => prev?.filter((elt: any) => elt.id !== itemId));
        return true;
      } catch (err) {
        console.error(err);
        return false;
      }
    }

    const value: ContextValue = {
      items,
      add,
      edit,
      remove,
      refetch,
    };

    return <context.Provider value={value}>{props.children}</context.Provider>;
  }

  function useRESTApi() {
    const ctx = useContext(context);

    if (!ctx) {
      throw new Error("useRESTApi must be used within a RestAPIProvider");
    }

    return ctx;
  }

  return {
    Provider,
    useRESTApi
  }
}

结论

您已经可以在应用程序中将此“挂钩概要”放在。

当然,可以改进或适应此功能。
例如,如果您的项目的ID字段不是id,则可以将其他东西放置或通过道具适应。

感谢您的阅读!