در دهه گذشته، فناوری هایی مانند GraphQL نحوه ساخت برنامه های وب و نحوه ارتباط آنها با یکدیگر را تغییر داده اند. GraphQL مزایای خاصی را نسبت به API های REST ارائه می دهد – بیایید دریابیم که آنها چیست.
یکی از مزایای اصلی GraphQL این است که مشتری می تواند آنچه را که نیاز دارد از سرور درخواست کند و آن داده ها را دقیقاً و به طور قابل پیش بینی دریافت کند. بدون تلاش زیاد، میتوان دادههای تودرتو را بهجای افزودن چندین نقطه پایانی، فقط با افزودن ویژگیهای بیشتر به جستوجوهایمان به راحتی جمعآوری کرد. این از مسائلی مانند واکشی بیش از حد که می تواند بر عملکرد تأثیر بگذارد جلوگیری می کند.
معمولاً برای مدیریت GraphQL در سمت کلاینت، از Apollo Client استفاده می کنیم. این به توسعه دهندگان اجازه می دهد تا پرس و جوها/جهش ها را در برنامه ما تعریف، رسیدگی و در دسترس قرار دهند. همچنین می تواند به عنوان یک ابزار مدیریت حالت با برنامه سمت مشتری شما عمل کند.
در این مقاله، ما می خواهیم یاد بگیریم که چگونه با استفاده از GraphQL به روز رسانی های بلادرنگ در سمت کلاینت را مدیریت کنیم. ما یاد خواهیم گرفت که چگونه این کار را با ویژگیهای GraphQL مانند بهروزرسانی حافظه پنهان، اشتراکها و رابط کاربری خوشبینانه انجام دهیم. ما همچنین نحوه استفاده از آپولو را به عنوان ابزار مدیریت دولتی، احتمالاً جایگزین redux، بررسی خواهیم کرد. بعلاوه، نحوه ایجاد پرس و جوهای GraphQL قابل استفاده با Fragments و نحوه استفاده از دستورالعمل های Apollo برای نوشتن پرس و جوهای پیچیده تر را بررسی خواهیم کرد.
نصب و راه اندازی
قبل از شروع، اجازه دهید نصب و راه اندازی پروژه خود را مرور کنیم. بیایید مستقیماً وارد کد شویم. برای ایجاد یک برنامه React، مطمئن شوید که Node.js را روی رایانه خود نصب کرده اید. اگر قبلاً یک برنامه React ایجاد نکردهاید، میتوانید با تایپ کردن موارد زیر در ترمینال خود بررسی کنید که آیا Node.js را نصب کردهاید:
node -v
اگر نه، فقط به وب سایت Node.js بروید آخرین نسخه را دانلود کنید.
پس از انجام این کار، می توانیم با اجرای این دستور با برنامه React خود شروع کنیم:
npx create-react-app react-graphql
پس از اتمام، آپولو را با استفاده از:
nom i @apollo/client
سپس به پوشه پروژه خود در ترمینال می رویم:
cd react-graphql
یا بهتر از آن، می توانید ادامه دهید و مخزن را شبیه سازی کنید. مخزن شامل هر دو سمت کلاینت و سرور است، بنابراین ما وابستگی های دیگری نیز داریم که مورد نیاز است.
ما آن وابستگی ها را با اجرای:
npm install
درست قبل از شروع، این است مخزن حاوی کدی است که همه چیز را تحت به روز رسانی بلادرنگ در GraphQL، با استفاده از آپولو به عنوان ابزار مدیریت حالت، Fragments و دستورالعمل های Apollo نشان می دهد. همچنین، در اینجا این است مخزن حاوی کد نشان دهنده اشتراک در سمت مشتری است.
به روز رسانی بلادرنگ در GraphQL
توانایی ایجاد یک به روز رسانی در زمان واقعی در سمت مشتری به بهبود تجربه کاربری سایت کمک می کند و همه چیز را روان تر به نظر می رساند. فقط موقعیتی را تصور کنید که در آن کاربر با پر کردن یک فرم یک مورد جدید اضافه می کند و با اضافه شدن آن به لیست موارد در همان صفحه، فوراً به روز می شود. اگرچه، این بهروزرسانی بیدرنگ میتواند مستقیماً از طریق اشتراکها با سرور همگامسازی شود، یا ممکن است از طریق چیزهایی مانند Optimistic UI یا با استفاده از update
عملکرد بر روی useMutation
. پس بیایید به اجرای فنی برسیم. اینجاست مخزن حاوی کدی است که همه چیز را تحت به روز رسانی بلادرنگ در Graphql نشان می دهد، با استفاده از آپولو به عنوان ابزار مدیریت حالت، بخش ها و دستورالعمل های آپولو.
به روز رسانی کش به طور مستقیم با استفاده از update
عملکرد بر روی useMutation
useMutations
مستقیما از @apollo/client
کتابخانه، و به ما کمک می کند تا در داده های سرور خود جهش ایجاد کنیم.
معمولاً با استفاده از آپولو می توانیم جهش ایجاد کنیم useMutations
، اما فراتر از آن، کاری که ما انجام خواهیم داد استفاده از آن است update
تابعی برای به روز رسانی حافظه پنهان آپولو کلاینت ما به طور مستقیم از طریق useMutation
.
در این نمونه زیر، پرس و جوهایی را به سرور ارسال می کنیم تا لیستی از حیوانات خانگی مورد استفاده را دریافت کنیم useQuery
و با داشتن یک فرم برای افزودن حیوانات خانگی بیشتر به سرور خود، یک جهش ایجاد کنید useMutation
. مشکلی که ما خواهیم داشت این است که وقتی یک حیوان خانگی جدید به سرور اضافه می شود، بلافاصله به لیست حیوانات خانگی (در مرورگر) اضافه نمی شود، مگر اینکه صفحه به روز شود. این باعث می شود تجربه کاربری این بخش از برنامه احساس شکستگی کند، به خصوص که لیست حیوانات خانگی و فرم در یک صفحه هستند.
import React, { useState } from "react";
import gql from "graphql-tag";
import { useQuery, useMutation } from "@apollo/client";
import Loader from "../components/Loader";
import PetSection from "../components/PetSection";
//ALL_PETS uses gql from @apollo/client to allow us send nested queries
const ALL_PETS = gql`
query AllPets {
pets {
id
name
type
img
}
}
`;
// NEW_PET uses gql from @apollo/client to create mutations
const NEW_PET = gql`
mutation CreateAPet($newPet: NewPetInput!) {
addedPet(input: $newPet) {
id
name
type
img
}
}
`;
function Pets() {
const initialCount = 0;
const [count, setCount] = useState(initialCount);
const pets = useQuery(ALL_PETS);
const [createPet, newPet] = useMutation(NEW_PET);
const [name, setName] = useState("");
const type = `DOG`;
const onSubmit = (input) => {
createPet({
variables: { newPet: input },
});
};
// this function triggers the submit action by calling the onSubmit function above it
const submit = (e) => {
e.preventDefault();
onSubmit({ name, type });
};
//If the data is loading we display the <Loader/> component instead
if (pets.loading || newPet.loading) {
return <Loader />;
}
//loops through the pets data in order to get each pet and display them with props using the <PetSection> component
const petsList = pets.data.pets.map((pet) => (
<div className="col-xs-12 col-md-4 col" key={pet.id}>
<div className="box">
<PetSection pet={pet} />
</div>
</div>
));
return (
<div>
<form onSubmit={submit}>
<input
className="input"
type="text"
placeholder="pet name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<button type="submit" name="submit">
add pet
</button>
</form>
<div>
{petsList}
</div>
</div>
);
}
export default Pets;
استفاده كردن update
عملکرد در useMutation
هوک به ما اجازه می دهد تا با خواندن و نوشتن حافظه پنهان خود را مستقیماً به روز کنیم ALL_PETS
. بلافاصله دکمه ارسال را فشار می دهیم، داده ها با تغییر به لیست حیوانات خانگی در حافظه پنهان اضافه می شوند. ALL_PETS
. این به ما امکان می دهد حافظه پنهان سمت سرویس گیرنده خود را بلافاصله با داده های ثابت به روز کنیم.
import React, { useState } from "react";
import gql from "graphql-tag";
import { useQuery, useMutation } from "@apollo/client";
import Loader from "../components/Loader";
import PetSection from "../components/PetSection";
//ALL_PETS uses gql from @apollo/client to allow us send nested queries
const ALL_PETS = gql`
query AllPets {
pets {
id
name
type
img
}
}
`;
// NEW_PET uses gql from @apollo/client to create mutations
const NEW_PET = gql`
mutation CreateAPet($newPet: NewPetInput!) {
addedPet(input: $newPet) {
id
name
type
img
}
}
`;
function ThePets() {
const initialCount = 0;
const [count, setCount] = useState(initialCount);
const pets = useQuery(ALL_PETS);
//We then make use of useMutation and update() to update our ALL_PET
const [createPet, newPet] = useMutation(NEW_PET, {
update(cache, {data: {addedPet}}) {
const allPets = cache.readQuery({query: ALL_PETS})
cache.writeQuery({
query: ALL_PETS,
data: {pets: [addedPet, ...allPets.pets]}
})
}
});
const [name, setName] = useState("");
const type = `DOG`;
const onSubmit = (input) => {
createPet({
variables: { newPet: input },
});
};
//Handles the submission of Pets that eventually triggers createPet through onSumit
const submit = (e) => {
e.preventDefault();
onSubmit({ name, type });
};
//If the data is loading we display the <Loader/> component instead
if (pets.loading || newPet.loading) {
return <Loader />;
}
//loops through the pets data in order to get each pet and display them with props using the <PetSection> component
const petsList = pets.data.pets.map((pet) => (
<div className="col-xs-12 col-md-4 col" key={pet.id}>
<div className="box">
<PetSection pet={pet} />
</div>
</div>
));
return (
<div>
<form onSubmit={submit}>
<input
className="input"
type="text"
placeholder="pet name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<button type="submit" name="submit">
add pet
</button>
</form>
<div>
{petsList}
</div>
</div>
);
}
export default ThePets;
اشتراک در GraphQL
بر اساس عملکردها، اشتراک در GraphQL شبیه به پرس و جو است. تفاوت اصلی این است که در حالی که Queries فقط یک بار انجام می شود، اشتراک ها به سرور متصل می شوند و در صورت تغییر در آن اشتراک خاص، به طور خودکار به روز می شوند. اینجاست مخزن حاوی کد نشان دهنده اشتراک در سمت مشتری است.
ابتدا باید نصب کنیم:
npm install subscriptions-transport-ws
سپس به سمت خود می رویم index.js
برای وارد کردن و استفاده از آن.
import { WebSocketLink } from "@apollo/client/link/ws";
//setting up our web sockets using WebSocketLink
const link = new WebSocketLink({
uri: `ws://localhost:4000/`,
options: {
reconnect: true,
},
});
const client = new ApolloClient({
link,
uri: "http://localhost:4000",
cache: new InMemoryCache(),
});
توجه داشته باشید: uri
در بلوک کد مستقیماً در بالا برای نقطه پایانی ما است.
سپس وارد کامپوننت خود می شویم و به جای پرس و جو مانند آنچه در بالا داریم، به جای آن از این اشتراک استفاده می کنیم:
import { useMutation, useSubscription } from "@apollo/client";
//initiate our subscription on the client-side
const ALL_PETS = gql`
subscription AllPets {
pets {
id
name
type
img
}
}
`;
و به جای استفاده از useQuery
، با استفاده از داده های خود دسترسی پیدا می کنیم useSubscription
.
const getMessages = useSubscription(ALL_PETS);
رابط کاربری خوشبینانه
رابط کاربری خوشبینانه از این نظر که با سرور همگامسازی نمیشود، مانند یک اشتراک، کمی متفاوت است. هنگامی که ما یک جهش ایجاد می کنیم، به جای اینکه منتظر درخواست سرور دیگری باشیم، به طور خودکار از داده های وارد شده برای به روز رسانی لیست حیوانات خانگی بلافاصله استفاده می کند. سپس، هنگامی که داده های اصلی از سرور دریافت می شود، جایگزین پاسخ خوش بینانه خواهد شد. این نیز متفاوت از “به روز رسانی حافظه نهان به طور مستقیم با استفاده از update
عملکرد بر روی useMutation
“، حتی اگر در این فرآیند همچنان حافظه پنهان را به روز کنیم.
import React, { useState } from "react";
import gql from "graphql-tag";
import { useQuery, useMutation } from "@apollo/client";
import Loader from "./Loader";
import PetSection from "./PetSection";
//We use ALL_PET to send our nested queries to the server
const ALL_PETS = gql`
query AllPets {
pets {
id
name
type
img
}
}
`;
//We use NEW_PET to handle our mutations
const NEW_PET = gql`
mutation CreateAPet($newPet: NewPetInput!) {
addPet(input: $newPet) {
id
name
type
img
}
}
`;
function OptimisticPets() {
//We use useQuery to handle the ALL_PETS response and assign it to pets
const pets = useQuery(ALL_PETS);
//We use useMutation to handle mutations and updating ALL_PETS.
const [createPet, newPet] = useMutation(NEW_PET
, {
update(cache, {data: {addPet}}) {
const allPets = cache.readQuery({query: ALL_PETS})
cache.writeQuery({
query: ALL_PETS,
data: {pets: [addPet, ...allPets.pets]}
})
}
});;
const [name, setName] = useState("");
const type = `DOG`;
//Handles mutation and creates the optimistic response
const onSubmit = (input) => {
createPet({
variables: { newPet: input },
optimisticResponse: {
__typename: 'Mutation',
addPet: {
__typename: 'Pet',
id: Math.floor(Math.random() * 1000000) + '',
type: "CAT",
name: input.name,
img: 'https://via.placeholder.com/300',
}
}
});
};
//Here's our submit triggers the onSubmit function
const submit = (e) => {
e.preventDefault();
onSubmit({ name, type });
};
//returns the loading the component when the data is still loading
if (pets.loading ) {
return <Loader />;
}
//loops through the pets and displays them in the PetSection component
const petsList = pets.data.pets.map((pet) => (
<div className="col-xs-12 col-md-4 col" key={pet.id}>
<div className="box">
<PetSection pet={pet} />
</div>
</div>
));
return (
<div>
<form onSubmit={submit}>
<input
className="input"
type="text"
placeholder="pet name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<button type="submit" name="submit">
add pet
</button>
</form>
<div>
{petsList}
</div>
</div>
);
}
export default OptimisticPets;
وقتی کد بالا تماس می گیرد onSubmit
، حافظه پنهان Apollo Client یک را ذخیره می کند addPet
شی با مقادیر فیلد مشخص شده در optimisticResponse
. با این حال، حافظه پنهان اصلی را بازنویسی نمی کند pets(ALL_PETS)
با همان شناسه کش در عوض، یک نسخه مجزا و خوش بینانه از شی را ذخیره می کند. این تضمین می کند که داده های ذخیره شده ما دقیق باقی می ماند اگر ما optimisticResponse
غلط است.
آپولو کلاینت به تمام پرس و جوهای فعال که شامل موارد اصلاح شده است اطلاع می دهد pets(ALL_PETS)
. این پرسشها بهطور خودکار بهروزرسانی میشوند و مؤلفههای مرتبط با آنها برای نشان دادن دادههای خوشبینانه ما دوباره ارائه میشوند. این به هیچ درخواست شبکه ای نیاز ندارد، بنابراین فوراً به کاربر نمایش داده می شود.
در نهایت، سرور ما به جهش واقعی پاسخ می دهد تا درست را دریافت کند addPet
هدف – شی. سپس، حافظه پنهان آپولو کلاینت نسخه خوشبینانه ما را کنار می گذارد addPet
هدف – شی. همچنین نسخه کش شده را با مقادیر بازگشتی از سرور بازنویسی می کند.
آپولو کلاینت فوراً تمام درخواست های تحت تأثیر را مطلع می کند از نو. اجزای مربوطه دوباره ارائه می شوند، اما اگر پاسخ سرور مطابق با ما باشد optimisticResponse
، این کل فرآیند برای کاربر نامرئی است.
وقتی به ابزارهای مدیریت دولتی یا کتابخانههای مربوط به react فکر میکنیم، redux به ذهن میرسد. جالب است که آپولو همچنین می تواند به عنوان یک ابزار مدیریت برای ایالت محلی ما عمل کند. مشابه کاری که ما با API خود انجام داده ایم.
طرحواره ها و حل کننده های سمت مشتری
برای رسیدن به این هدف، باید طرحوارههایی را در سمت مشتری بنویسیم تا نوع دادهای را که میخواهیم و نحوه ساختار آن را تعریف کنیم. برای انجام این کار، ما ایجاد خواهیم کرد Client.js
که در آن طرحوارهها و حلکنندهها را تعریف میکنیم، پس از آن، آن را در پروژه خود با مشتری Apollo در دسترس جهانی قرار میدهیم.
برای این مثال، من آن را گسترش خواهم داد User
نوعی که از قبل برای افزودن وجود دارد height
به عنوان یک عدد صحیح حل کننده ها نیز برای پر کردن آن اضافه شده است height
فیلد در طرحواره ما
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-link'
import { HttpLink } from 'apollo-link-http'
import { setContext } from 'apollo-link-context'
import gql from 'graphql-tag'
//Extending the User type
const typeDefs = gql`
extend type User {
height: Int
}
`
//Declaring our height inside our resolvers within the client-side
const resolvers = {
User : {
height() {
return 35
}
}
}
const cache = new InMemoryCache()
const http = new HttpLink({
uri: 'http://localhost:4000/'
})
const link = ApolloLink.from([
http
])
const client = new ApolloClient({
link,
cache,
typeDefs,
resolvers
})
export default client
client.js
سپس می توانیم وارد کنیم client
به ما index.js
:
import client from "./client"
import {
ApolloProvider,
} from "@apollo/client";
//importing our client.js file into ApolloProvider
ReactDOM.render(
<ApolloProvider client={client}>
<Routing />
</ApolloProvider>,
document.getElementById("root")
);
index.js
در داخل کامپوننت، از آن دقیقاً به این صورت استفاده خواهد کرد. اضافه می کنیم @client
برای نشان دادن اینکه پرس و جو از سمت مشتری است و نباید سعی کند آن را از سرور بیرون بکشد.
const ALL_PETS = gql`
query AllPets {
pets {
id
name
type
img
owner {
id
height @client
}
}
}
`;
بنابراین ما دادهها را از سرور و کلاینت در یک پرس و جو میکشیم و از طریق جستجو در دسترس خواهد بود. useQuery
قلاب.
قطعات – ایجاد پرس و جوهای قابل استفاده مجدد
گاهی اوقات ممکن است نیاز داشته باشیم که یک پرس و جو را در اجزای مختلف انجام دهیم. بنابراین به جای اینکه چندین بار آن را کدگذاری کنیم، آن پرس و جو را به نوعی متغیر اختصاص می دهیم و به جای آن از آن متغیر استفاده می کنیم.
در کامپوننت ما فقط قطعه را به صورت تعریف می کنیم PetFields
بر Pet
(که نوع است). به این ترتیب ما فقط می توانیم از آن در هر دو استفاده کنیم query
و mutation
.
const DUPLICATE_FIELD = gql`
fragment PetFields on Pet {
id
name
type
img
}
`
const ALL_PETS = gql`
query AllPets {
pets {
...PetFields
}
}
${DUPLICATE_FIELD}
`;
const NEW_PET = gql`
mutation CreateAPet($newPet: NewPetInput!) {
addPet(input: $newPet) {
...PetFields
}
}
${DUPLICATE_FIELD}
`;
دستورالعمل های آپولو
هنگام ایجاد پرس و جو، ممکن است بخواهیم چند شرطی داشته باشیم که در صورت تحقق یا عدم تحقق یک شرط خاص، یک فیلد یا قطعه را حذف یا شامل شود. دستورالعمل های پیش فرض عبارتند از:
@skip
: نشان می دهد که در صورت انجام یک شرط، یک فیلد/قطعه باید نادیده گرفته شود.
const ALL_PETS = gql`
query AllPets($name: Boolean!){
pets {
id
name @skip: (if: $name)
type
img
}
}
`;
اینجا $name
یک بولی است که هنگام فراخوانی این کوئری به عنوان متغیر اضافه می شود. که سپس با استفاده می شود @skip
برای تعیین زمان نمایش فیلد name
. اگر درست باشد، رد میشود و اگر غلط باشد، آن فیلد را حل میکند.
@includes
همچنین به روشی مشابه کار کنید. اگر شرط باشد true
، آن فیلد حل شده و اضافه می شود و اگر باشد false
، حل نشده است.
ما همچنین داریم @deprecated
که می توان در آن استفاده کرد schemas
برای بازنشستگی زمینه ها، که در آن شما حتی می توانید دلایل را اضافه کنید.
ما همچنین داریم کتابخانه ها که به ما امکان می دهد دستورالعمل های بیشتری را اضافه کنیم، آنها می توانند هنگام ساختن چیزهای پیچیده با GraphQL مفید باشند.
نکات و ترفندها با استفاده از GraphQL Lodash در داخل پرسش های شما
GraphQL Lodash کتابخانه ای است که می تواند به ما کمک کند پرس و جو را به روشی کارآمدتر، بیشتر شبیه فرم پیشرفته دستورالعمل های آپولو.
این می تواند به شما کمک کند سرور خود را به گونه ای جستجو کنید که داده ها را منظم تر و فشرده تر برگرداند. به عنوان مثال، شما در حال پرس و جو هستید title
از films
مثل این:
films {
title
}
و آن را برمی گرداند title
از فیلم ها به عنوان اشیاء در یک آرایه.
"films": [
{
"title" : "Prremier English"
},
{
"title" : "There was a country"
},
{
"title" : "Fast and Furious"
}
{
"title" : "Beauty and the beast"
}
]
اما وقتی از lodash استفاده می کنیم map
دستورالعمل، چه زمانی میتوان از میان آرایه فیلمها حلقه زد تا یک آرایه واحد با همه عناوین بهعنوان فرزندان مستقیم داشته باشد. ما یک پرس و جو برای سرور خود ارسال می کنیم که به شکل زیر است:
films @_(map: "title") {
title
}
شما این پاسخ را دریافت خواهید کرد که ممکن است نسبتاً تمیزتر از پاسخ قبلی باشد.
"films": [
"Premier English",
"There was a country",
"Fast and Furious",
"Beauty and the beast"
]
یکی دیگر از مواردی که ثابت می کند مفید است است keyby
بخشنامه شما می توانید یک پرس و جو ساده مانند این ارسال کنید:
people {
name
age
gender
}
واکنش:
"people" : [
{
"name": "James Walker",
"age": "19",
"gender": "male"
},
{
"name": "Alexa Walker",
"age": "19",
"gender": "female"
},
]
استفاده کنیم @_keyup
دستورالعمل در درخواست ما:
people @_(keyBy: "name") {
name
age
gender
}
پاسخ دقیقاً شبیه این خواهد بود:
"people" : [
"James Walker" : {
"name": "James Walker",
"age": "19",
"gender": "male"
}
"Alexa Walker" : {
"name": "Alexa Walker",
"age": "19",
"gender": "female"
}
]
بنابراین در این مورد، هر پاسخ دارای یک کلید است، آن است name
از شخص
نتیجه
در این مقاله، ما موضوعات پیشرفته ای را برای دستیابی به به روز رسانی در زمان واقعی داده ها با استفاده از update()
عملکرد، اشتراک و رابط کاربری خوشبینانه. همه چیز برای بهبود تجربه کاربر.
ما همچنین به استفاده از GraphQL برای مدیریت وضعیت در سمت مشتری و ایجاد پرسوجوهای قابل تکرار با قطعات GrahQL اشاره کردیم. دومی به ما اجازه میدهد تا از همان پرسوجوها در اجزای مختلف در جایی که لازم است استفاده کنیم، بدون اینکه مجبور باشیم همه چیز را هر بار تکرار کنیم.
در پایان، دستورالعملهای Apollo و Grahql Lodash را مرور کردیم تا به ما کمک کند تا سرورهای خود را به روشی سریعتر و بهتر جستجو کنیم. شما همچنین می توانید بررسی کنید آموزش اسکات ماس اگر به دنبال پوشش Graphql و واکنش از ابتدا هستید.
