ปรับปรุงชั้นบริการของคุณในโปรเจ็กต์ชุดเครื่องมือ Redux

วิธีสร้างแอป React ด้วย RTK-QUERY

คู่มือสำหรับผู้เริ่มต้นในการเขียนแบบสอบถามด้วย rtk-query

เป็นไปไม่ได้เลยที่จะเป็นนักพัฒนาส่วนหน้าที่ทำงานร่วมกับ React และไม่เคยได้ยินเกี่ยวกับ Redux มาก่อนในชีวิตของคุณ บริษัทส่วนใหญ่ใช้ Redux ในโค้ดเบสของตน และการรู้แนวคิดพื้นฐานของ Redux ได้กลายเป็นหนึ่งในแก่นแท้ของการเป็น Frontender มีการพูดคุยกันมากมายเกี่ยวกับการใช้หรือไม่ใช้กับผลิตภัณฑ์ของคุณ ความจริงก็คือคุณไม่สามารถหาเครื่องมือที่สมบูรณ์แบบสำหรับทุกโครงการได้ คุณควรตัดสินใจว่าเครื่องมือใดเหมาะสมที่สุดสำหรับวัตถุประสงค์และผลิตภัณฑ์ของคุณ แม้ว่าเครื่องมือแต่ละอย่างจะมีข้อดีและข้อเสียอยู่บ้าง แต่สิ่งที่สำคัญที่สุดคือการแลกเปลี่ยนระหว่างข้อดีและข้อเสียของเครื่องมือสำหรับคุณ

ข้อความค้นหา RTK เป็นเครื่องมือดึงข้อมูลและแคชที่มีประสิทธิภาพ ได้รับการออกแบบมาเพื่อลดความซับซ้อนของกรณีทั่วไปสำหรับการโหลดข้อมูลในเว็บแอปพลิเคชัน โดยไม่จำเป็นต้องเขียนตรรกะในการดึงข้อมูลและแคชด้วยตนเอง

หากคุณใช้ redux โดยเฉพาะ redux-toolkit คุณรู้อยู่แล้วว่า rtk-query มาพร้อมกับการพึ่งพาในตัว มีเครื่องมือที่คล้ายกันมากมาย เช่น react-query แต่เมื่อคุณใช้ redux-toolkit และมี rtk-query อยู่ในบันเดิลของคุณอยู่แล้ว ทำไมไม่ลองใช้มันดูล่ะ? ประโยชน์บางประการของ trk-query คือ:

  • มันเข้ากันได้อย่างสมบูรณ์กับ redux
  • เขียนด้วย TypeScript
  • ปรับปรุงชั้นบริการของคุณให้สามารถนำมาใช้ซ้ำได้มากขึ้น และทำให้โครงการของคุณมี hooks ที่สร้างขึ้นอัตโนมัติขึ้นไปอีกระดับ
  • ติดตามและสังเกตได้ผ่าน redux-devtools
  • มันมีพฤติกรรมการแคชที่ดีจริงๆ

ในบทความนี้ ฉันจะช่วยคุณสร้างแอปพลิเคชัน React ของบล็อกง่ายๆ ด้วย rtk-query และ typescript เพื่อให้คุณคุ้นเคยกับเครื่องมือที่ยอดเยี่ยมนี้มากขึ้น แอปพลิเคชั่นนี้ประกอบด้วยมุมมองเพื่อแสดงรายการโพสต์และแบบฟอร์มง่ายๆ ในการเพิ่มโพสต์

ข้อกำหนดเบื้องต้น

  1. คุณควรรู้ ตอบสนอง
  2. คุณควรรู้แนวคิด redux และ redux-toolkit และต้องการใช้แนวคิดเหล่านี้ในโครงการของคุณ
  3. การมีประสบการณ์กับ TypeScript ถือเป็นข้อดี แต่คุณสามารถลบส่วน TS ออกจากโค้ดได้ และมันจะทำงานกับ JavaScript ให้คุณได้ :)

คุณสามารถค้นหาพื้นที่เก็บข้อมูลและรหัสทั้งหมดที่ฉันพูดถึงในบทความนี้ได้ "ที่นี่" สามารถดูโปรเจ็กต์สุดท้ายเวอร์ชันแสดงสดได้ "ที่นี่" ฉันยังข้ามคำอธิบายสไตล์หรือส่วน JSX ง่ายๆ เนื่องจากหัวข้อหลักที่นี่คือ rtk-query

มันทำงานอย่างไร?

อย่างที่ฉันได้กล่าวไว้ก่อนหน้านี้ rtk-query เป็นเครื่องมือดึงข้อมูลที่มีฟีเจอร์มากมายเพื่อทำให้โปรเจ็กต์ของคุณสะอาดขึ้นและนำกลับมาใช้ใหม่ได้มากขึ้น บริการของคุณสามารถสร้างในรูปแบบของปลายทางและการสืบค้นได้โดยใช้ createApi ซึ่งคล้ายกับ createReducer โดยสิ้นเชิง มันสร้าง hooks สำหรับการสืบค้นของคุณและคุณสามารถใช้มันได้อย่างง่ายดายในส่วนประกอบของคุณ คุณไม่จำเป็นต้องมีสถานะการโหลด การจัดการข้อผิดพลาด ฟังก์ชันการดึงข้อมูล การดึงข้อมูลล่วงหน้าด้วย useEffect และตัวแยกวิเคราะห์ข้อมูลอีกต่อไป เนื่องจาก rtk-query จะดูแลเรื่องนั้นให้กับคุณ

จริงๆ แล้ว การสืบค้นเป็นส่วนเล็กๆ ของบริการของคุณ ซึ่งอาจเป็น GET หรือ POST เพื่อรับหรือส่งข้อมูลไปยังเซิร์ฟเวอร์ได้ การสืบค้นทั้งหมดที่เกี่ยวข้องกับเอนทิตีจะรวมอยู่ในจุดสิ้นสุดเดียวที่สร้างโดย createApi ท้ายที่สุดแล้ว ชั้นบริการของคุณคือการรวมกันของตำแหน่งข้อมูลต่างๆ รวมถึงการสืบค้นของตัวเอง

rtk-query ใช้ fetch API จาก JavaScript แต่คุณสามารถกำหนดค่าให้ใช้ "axios" หรือ graphql ได้ นอกจากนี้ยังล้อมคำค้นหาของคุณด้วย "immer" ดังนั้นการเปลี่ยนแปลงข้อมูลจึงไม่ใช่เรื่องกังวลอีกต่อไป

การสร้างรากฐาน

คุณสามารถเลือกเครื่องมือ ใดก็ได้ และการตั้งค่าที่คุณต้องการสำหรับโปรเจ็กต์นี้ ฉันจะใช้ Vite แต่ไม่สำคัญว่าคุณจะชอบ CRA มากกว่าหรือต้องการกำหนดค่าสำเร็จรูปของคุณเอง หลังจากสร้างโปรเจ็กต์ React แล้ว เราควรติดตั้ง redux-toolkit + axios และกำหนดค่า:

npm install @reduxjs/toolkit react-redux axios

ขั้นแรก เราควรสร้างฟังก์ชันการสืบค้นพื้นฐานเพื่อกำหนดค่า rtk-query เพื่อใช้ axios:

// src/services/api.ts

const axiosBaseQuery =
  (): BaseQueryFn<AxiosRequestConfig, unknown, AxiosError> =>
  async ({ url, method, data, params }) => {
    try {
      Axios.defaults.baseURL = "https://jsonplaceholder.typicode.com/";
      const result = await Axios({
        url,
        method,
        data,
        params,
      });
      return { data: result.data };
    } catch (axiosError) {
      const error = axiosError as AxiosError;
      return {
        error,
      };
    }
  };

BaseQueryFn เป็นคำมั่นสัญญาที่ควรแก้ไขคำขอของเราด้วยข้อมูลบางส่วนหรือปฏิเสธด้วยวัตถุที่มีข้อผิดพลาด ที่นี่ เราได้รับการกำหนดค่าคำขอแบบง่ายของ axios จากการสืบค้นของเรา และดำเนินการดึงข้อมูล API ด้วย axios จากนั้นเราจะส่งคืนข้อมูลหากทุกอย่างเรียบร้อยดี มิฉะนั้น เราจะปฏิเสธด้วยวัตถุที่มีข้อผิดพลาด ตรวจสอบให้แน่ใจว่าได้ส่งคืนข้อมูลหรือข้อผิดพลาดในออบเจ็กต์ ไม่เช่นนั้น rtk-query จะไม่เข้าใจ

เราจะใช้ API ฟรี JSONPlaceholder ในโปรเจ็กต์นี้

ประการที่สอง เราควรบอก rtk-query ให้ใช้ฟังก์ชันการสืบค้นแบบกำหนดเองของเราแทน JS fetch API สามารถทำได้โดยส่งฟังก์ชันของเราไปที่ createApi

// src/services/api.ts

export const apiService = createApi({
  baseQuery: axiosBaseQuery(),
  endpoints: () => ({}),
});

ตอนนี้เราสามารถเพิ่มการสืบค้นของเราไปยังจุดสิ้นสุดได้ แต่อย่างที่คุณเห็น เราได้ส่งคืนวัตถุเปล่า เหตุผลก็คือในโลกแห่งความเป็นจริง เราไม่ต้องการให้บริการทั้งหมดของเราอยู่ในไฟล์ที่ยุ่งยาวเพียงไฟล์เดียว เราต้องการแยกออกเป็นโมดูลเล็กๆ หลายโมดูลเพื่อให้มีโค้ดเบสที่สะอาดยิ่งขึ้น ดังนั้นเราจึงส่งคืนออบเจ็กต์ว่างและเพียงแค่ ฉีด จุดสิ้นสุดของเราเข้าไปในออบเจ็กต์นี้ในภายหลัง

สุดท้าย เราต้องเพิ่ม apiService ไปยัง Redux Store และเพิ่ม Store ลงในแอปของเรา:

// src/store/store.ts

export const store = configureStore({
  reducer: {
    [apiService.reducerPath]: apiService.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(apiService.middleware),
});
// src/main.tsx 

<Provider store={store}>
    <App />
 </Provider>

ทำไม? apiService ยังมีตัวลดส่วน Redux ที่สร้างขึ้นอัตโนมัติและมิดเดิลแวร์แบบกำหนดเองที่จัดการอายุการใช้งานการสมัคร จำเป็นต้องเพิ่มทั้งสองสิ่งนี้ในร้านค้า Redux

สร้างบริการแรกของเรา

เราเริ่มต้นด้วยปลายทาง GET เพื่อดึงรายการโพสต์จากเซิร์ฟเวอร์ เราสามารถใช้ injectEndpoint API จาก rtk-query เพื่อสร้างตำแหน่งข้อมูลและเพิ่มลงในชั้นบริการพื้นฐานของเรา

// src/services/posts.ts

// Interface of JSONPlaceholder posts
export interface Post {
  id: number;
  userId: number;
  title: string;
  body: string;
}

export const postService = apiService.injectEndpoints({
  endpoints: (build) => ({
    // query<ResultType, QueryArg>
    getPosts: build.query<Post[], null>({
      query: () => ({ method: "GET", url: "posts" }),
    }),
  }),
});

// Auto-generated hooks
export const { useGetPostsQuery } = postService;

endpoints เป็นฟังก์ชันที่มีอาร์กิวเมนต์บังคับหนึ่งอาร์กิวเมนต์ — build — ซึ่งส่งคืนออบเจ็กต์ของการสืบค้นที่แตกต่างกัน build เป็นโปรแกรมอรรถประโยชน์ที่ช่วยให้คุณสร้างจุดสิ้นสุดของคุณโดยใช้ตัวสร้างที่แตกต่างกัน build.query ควรใช้สำหรับจุดสิ้นสุด GET และ build.mutation ควรใช้กับปลายทางอื่นๆ เช่น POST และ PATCH มันคล้ายกับที่เรามีใน Graphql นิดหน่อย ตัวสร้างเหล่านี้ได้รับวัตถุที่มีหนึ่งอาร์กิวเมนต์บังคับซึ่งก็คือ query การสืบค้น หรือพูดง่ายๆ ก็คือฟังก์ชันที่คุณใช้เพื่อดึงข้อมูล API และควรส่งคืนการกำหนดค่าคำขอ axios ของคุณ เช่น วิธีการ พารามิเตอร์ และ url

จากนั้น rtk-query จะสร้าง hooks เพื่อให้คุณดึงข้อความค้นหาเหล่านี้ได้อย่างง่ายดาย คุณสามารถส่งการตั้งค่าเพิ่มเติมไปยังตัวสร้างแบบสอบถามของคุณ เช่น transformResponse ซึ่งเราไม่ครอบคลุม เนื่องจากอยู่นอกขอบเขตของบทความนี้

มาใช้บริการครั้งแรกของเรากัน

ถึงเวลาใช้บริการของเราแล้ว rtk-query สร้าง hook useGetPostsQuery ให้เรา เบ็ดนี้ส่งคืนวัตถุที่ประกอบด้วยค่าที่มีประโยชน์บางอย่างที่จะใช้ เช่นเดียวกับสถานะการโหลด ข้อผิดพลาด ข้อมูลที่ส่งคืน และฟังก์ชันการดึงข้อมูล

  • สถานะการโหลด: มันจะเป็น จริง เมื่อเราดำเนินการค้นหานี้ในองค์ประกอบของเรา
  • ออบเจ็กต์ข้อผิดพลาด: หากการค้นหาของเราล้มเหลว ออบเจ็กต์ข้อผิดพลาดจะถูกส่งกลับ มิฉะนั้นจะเป็น null
  • ข้อมูล:ข้อมูลที่ส่งคืนของเรา null จนกว่าการสืบค้นของเราจะดำเนินการ สำเร็จ
  • ฟังก์ชันการดึงข้อมูล:เราสามารถใช้ฟังก์ชันนี้เพื่อเรียกใช้แบบสอบถามอีกครั้งเมื่อใดก็ตามที่เราต้องการ เช่น ในกรณีที่เราต้องการให้มีปุ่ม “ลองอีกครั้ง” หรือปุ่มรีเฟรช
// src/components/PostList.tsx

const PostList: React.FC<unknown> = () => {
  const { data: posts, isLoading, error, refetch } = useGetPostsQuery(null);

  if (isLoading) {
    return <div>Loading posts...</div>;
  }

  if (error) {
    return <ErrorBanner error={error as AxiosError} refetch={refetch} />;
  }

  return (
    <div>
      <h2>Posts</h2>
      <div>
        {posts?.map((post) => (
          <div className="postItem">
            <h4>{post.title}</h4>
            <p>{post.body}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export default PostList;

ฮุค useQuery จะดึงข้อมูล API ของเราบนคอมโพเนนต์เมานท์เป็นครั้งแรก เราสามารถควบคุมมันได้ด้วยตนเองโดยใช้ useLazyQuery แทน อย่างที่คุณเห็น ส่วนประกอบของเราสะอาดขึ้นมากแล้ว และเราไม่ได้ใช้ useEffect หรือ useState ใด ๆ ในองค์ประกอบของเรา

มาสร้างฟอร์มของเรากันเถอะ

หากต้องการก้าวต่อไป เราจะกล่าวถึงคำถามเกี่ยวกับการเปลี่ยนแปลงหนึ่งรายการด้วย เราต้องการสร้างโพสต์โดยใช้แบบฟอร์มที่ได้รับชื่อและเนื้อหาจากผู้ใช้ เริ่มจากแบบสอบถามกันก่อน:

// src/services/posts.ts

export interface CreatePostDto {
  title: string;
  body: string;
}

export const postService = apiService.injectEndpoints({
  endpoints: (build) => ({
    // query<ResultType, QueryArg>
    getPosts: build.query<Post[], null>({
      query: () => ({ method: "GET", url: "posts" }),
    }),
    // We use mutation for POST endpoints
    createPost: build.mutation<Post, CreatePostDto>({
      query: (data) => ({
        method: "POST",
        url: "posts",
        // We pass static userId to simplify this part
        data: { userId: 1, ...data },
      }),
    }),
  }),
});

// Auto-generated hooks
export const { useGetPostsQuery, useCreatePostMutation } = postService;

เมื่อเปรียบเทียบกับตำแหน่งข้อมูล GET เรามีความแตกต่าง 2 ประการดังนี้:

  1. เนื่องจากเราต้องการเพิ่มข้อมูลด้วยวิธี POST เราจึงใช้ build.mutation แทน build.query
  2. เราระบุอินเทอร์เฟซใหม่สำหรับอาร์กิวเมนต์ของจุดสิ้นสุดของเราและส่งข้อมูลที่เกี่ยวข้องไปยังคำขอ axios ของเรา

ตอนนี้เราควรใช้บริการใหม่นี้ในองค์ประกอบแบบฟอร์มของเรา:

// src/components/PostForm.tsx

const PostForm: React.FC<unknown> = () => {
  const [title, setTitle] = useState<string>("");
  const [body, setBody] = useState<string>("");

  const [createPost, { isLoading }] = useCreatePostMutation();

  const submitForm = async (e: FormEvent) => {
    e.preventDefault();
    try {
      await createPost({ title, body });
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <form onSubmit={submitForm}>
      <input
        name="title"
        type="text"
        placeholder="Title"
        value={title}
        onChange={(e) => setTitle(e.currentTarget.value)}
      />
      <textarea
        name="body"
        placeholder="Body..."
        rows={5}
        value={body}
        onChange={(e) => setBody(e.currentTarget.value)}
      />
      <input
        type="submit"
        value={isLoading ? "Wait..." : "Submit"}
        disabled={isLoading}
      />
    </form>
  );
};

export default PostForm;

useMutation hook ส่งกลับสิ่งอันดับ รายการแรกของอาร์เรย์นี้คือ fetch API ซึ่งคุณสามารถเรียกใช้ด้วยอาร์กิวเมนต์ที่ระบุได้ รายการที่สองคือออบเจ็กต์ที่ประกอบด้วยค่าที่สำคัญบางอย่าง เช่น สถานะการโหลด นอกจากนี้ยังส่งคืนออบเจ็กต์ข้อผิดพลาดที่เราสามารถใช้เพื่อแสดงข้อผิดพลาดในกรณีที่การส่งข้อมูลล้มเหลว

ห่อมันขึ้นมา

เราได้ครอบคลุมแนวคิดพื้นฐานที่สำคัญที่สุดของ rtk-query แล้ว เราได้เห็นแล้วว่าเราจะสร้างบริการและจุดสิ้นสุดของเราในรูปแบบโมดูลาร์ได้อย่างไร และวิธีใช้ hooks ที่สร้างขึ้นอัตโนมัติที่ยอดเยี่ยมในส่วนประกอบของเรา หลังจากนั้น ฉันจะเผยแพร่บทความติดตามผลที่อธิบายคุณลักษณะขั้นสูงและแนวคิดของ rtk-query เช่น การแคช การดึงการกำหนดค่า การแปลงข้อมูล ฯลฯ

โค้ดเบสเต็มรูปแบบของทุกสิ่งที่เรากล่าวถึงได้รับการเผยแพร่ใน "พื้นที่เก็บข้อมูล" นี้ และปรับใช้บน "หน้า" นี้

หากคุณมีข้อเสนอแนะหรือข้อเสนอแนะ อย่าลังเลที่จะถามฉันทาง "อีเมลส่วนตัว" ของฉัน หรือเพิ่มความคิดเห็น และหากคุณชอบบทความนี้ อย่าลืมปรบมือ!

เนื้อหาเพิ่มเติมได้ที่ PlainEnglish.io.

ลงทะเบียนเพื่อรับ จดหมายข่าวรายสัปดาห์ฟรี ของเรา ติดตามเราบน Twitter, LinkedIn, YouTube และ Discord .

สนใจที่จะขยายขนาดการเริ่มต้นซอฟต์แวร์ของคุณหรือไม่ ลองดูที่ วงจร