چگونه می توان تجربه کاربر را با UI خوش بینانه و SWR بهبود بخشید
آیا تا به حال متوجه شده اید که برخی از برنامه ها چگونه می توانند ذهن شما را بخوانند؟ شما روی یک دکمه کلیک میکنید، و قبل از اینکه حتی بتوانید پلک بزنید، کار تمام میشود - بدون صفحههای بارگیری، بدون انتظار. مثل جادو است، درست است؟ خوب، اجازه دهید یک راز کوچک به شما بگویم: این قدرت رابط کاربری Optimistic UI است.
در این مقاله، ما به رابط کاربری خوشبینانه میپردازیم و نحوه عملکرد آن را تحلیل میکنیم و تجربه وب شما را مانند کره نرم نگه میدارد. ما با هم یک برنامه کار ساده میسازیم که نشان میدهد چگونه رابط کاربری خوشبینانه میتواند به تبدیل وظایف دنیوی به تعاملات سریعی کمک کند که باعث میشود کاربران احساس خوشحالی کنند.
پیش نیازها
مبانی JavaScript و React
مبانی برنامه نویسی Async و Axios
دانش کتابخانه های واکشی هوک گرا نیز مفید خواهد بود
آنچه را پوشش خواهیم داد:
چرا رابط کاربری خوشبینانه مهم است؟
سایر مزایای رابط کاربری خوشبینانه
معرفی SWR: Stale-While-Revalidate
نحوه ایجاد رابط کاربری Task App
- UI معمولی CRUD
– رابط کاربری خوشبینانه CRUD
UI خوش بینانه چیست؟
در هسته خود، Optimistic UI این است که برنامه شما را سریع و پاسخگو نگه دارد، حتی زمانی که چیزهای زیادی در پشت صحنه اتفاق می افتد. این مانند داشتن یک ابرقدرت است که به برنامه شما اجازه می دهد آینده را پیش بینی کند - خوب، به نوعی.
هنگامی که اقدامی را در برنامه خود انجام می دهید - چه اضافه کردن یک مورد جدید به یک فهرست یا به روز رسانی یک نمایه - رابط کاربری خوش بینانه بدون منتظر ماندن برای تأیید از طرف سرور، فوراً انجام می شود. این خوشبین نهایی است، همیشه با این فرض که همه چیز به خوبی پیش خواهد رفت.
چرا رابط کاربری خوشبینانه مهم است؟
پس چرا باید به رابط کاربری خوشبینانه اهمیت دهید؟ ساده: زیرا این سس مخفی است که برنامه های خوب را به برنامه های عالی تبدیل می کند.
در مورد آن فکر کنید: وقتی روی یک دکمه کلیک می کنید، انتظار دارید چیزی اتفاق بیفتد - و انتظار دارید که سریع اتفاق بیفتد. اینجاست که UI خوش بینانه می درخشد. رابط کاربری Optimistic با ارائه بازخورد فوری به کاربران و ایجاد احساس سریع در برنامه شما، تجربه کلی کاربر را بهبود می بخشد.
دیگر نیازی به خیره شدن به بارگیری صفحهها یا این که فکر کنید آیا کلیک شما واقعاً کاری انجام داده است، وجود ندارد - با Optimistic UI، هر اقدامی آسان و مؤثر به نظر میرسد.
سایر مزایای رابط کاربری خوشبینانه
تأخیر درک شده کاهش یافته : رابط کاربری خوشبینانه با نمایش تغییرات بلافاصله بدون انتظار برای تأیید سرور، تأخیر درک شده را کاهش میدهد. این تصوری از زمان پاسخگویی سریعتر ایجاد میکند، حتی اگر ارتباط سرور بیشتر طول بکشد.
پاسخگویی بهبود یافته : رابط کاربری خوشبینانه به کاربران اجازه میدهد تا بدون وقفهای از بارگیری اسپینرها یا صفحههای انتظار، به طور مداوم با برنامه تعامل داشته باشند. این جریان بدون وقفه، پاسخگویی کلی برنامه را افزایش می دهد.
پشتیبانی از تعاملات پیچیده : رابط کاربری خوشبینانه به تعاملات پیچیده، مانند کشیدن و رها کردن، فرآیندهای چند مرحلهای و همکاری در زمان واقعی کمک میکند تا احساس روان و شهودی داشته باشند. این انعطاف پذیری امکاناتی را برای ویژگی ها و عملکردهای نوآورانه در برنامه باز می کند.
افزایش تعامل کاربر : پاسخگویی و تعامل ارائه شده توسط Optimistic UI می تواند منجر به افزایش تعامل و حفظ کاربر شود. احتمال بازگشت کاربران به برنامه ای که تجربه ای روان و لذت بخش را ارائه می دهد، بیشتر است.
معرفی SWR: Stale-While-Revalidate
قبل از اینکه به پیاده سازی بپردازیم، اجازه دهید لحظه ای در مورد SWR صحبت کنیم. SWR یک کتابخانه React Hook سبک وزن برای واکشی داده است. SWR مخفف Stale-While-Revalidate است و هنگام واکشی داده ها در برنامه های React شما تعادل کاملی بین عملکرد و تازگی ایجاد می کند.
SWR همچنین بهطور خودکار دادهها را در پسزمینه تأیید میکند در حالی که همچنان دادههای قدیمی را از حافظه پنهان ارائه میکند. این بدان معناست که برنامه شما سریع و پاسخگو باقی می ماند، حتی زمانی که داده های تازه را از سرور دریافت می کند.
اما این همه ماجرا نیست – SWR همچنین از آپشن های کلیدی مانند ذخیرهسازی، صفحهبندی و مدیریت خطا پشتیبانی میکند و آن را به ابزاری قدرتمند در زرادخانه شما برای ساخت برنامههای وب سریع و قابل اعتماد و همچنین پیادهسازی Optimistic UI تبدیل میکند.
نحوه تنظیم محیط
من یک مخزن GitHub با فایل های شروع کننده برای سرعت بخشیدن به کار آماده کرده ام. به سادگی این مخزن را شبیه سازی کنید و وابستگی ها را نصب کنید.
کد شروع شامل اجزای اساسی JSX مورد نیاز و همچنین برخی از توابع پایه Axios برای انجام عملیات CRUD می باشد. پس از نصب تمام بسته های لازم با npm i
، ترمینال خود را باز کنید و نقطه پایانی محلی خود را با استفاده از json-server راه اندازی کنید.
npx json-server data/db.json -p 3500
برای مشاهده تمام داده های موجود، به آن مسیر بروید:
نحوه ایجاد رابط کاربری Task App
در این بخش ابتدا برنامه های CRUD را بدون Optimistic UI و سپس با Optimistic UI پیاده سازی می کنیم تا تفاوت بین آنها را نشان دهیم.
UI معمولی CRUD
با رفتن به مؤلفه TaskContainer
خود شروع کنید، سپس از قلاب useSWR
برای فراخوانی تابع واکشی خود استفاده کنید.
const { isLoading, error, data: tasks, mutate, } = useSWR(cacheKey, fetchTasks, { onSuccess: (data) => data.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)), });
SWR از یک قلاب و الگوی واکشی داده مشابه برای کتابخانه های دیگر مانند React Query (TanStack Query) و Redux Toolkit Query استفاده می کند. این الگوی واکشی قلاب اغلب یک حالت بارگذاری، یک حالت خطا، دادههای واکشی شده شما (در صورت وجود) و یک تابع جهش را برمیگرداند (اما بعداً در مورد آن بیشتر توضیح خواهیم داد).
توجه : cacheKey
یک کلید منحصر به فرد است که برای اطلاع دادن به SWR در زمان و مکان برای فراخوانی مجدد عملکرد شما استفاده می شود. تابع onSuccess
روشی است که برای راه اندازی یک عمل دیگر در صورت موفقیت آمیز بودن واکشی استفاده می شود - در این مورد، مرتب سازی داده ها به ترتیب نزولی.
با برگشت داده های خود، اکنون می توانید نشانه گذاری JSX را ایجاد کنید.
return ( <div className="flex flex-col gap-8 p-4"> <div className="p-4 shadow-lg "> <div className="flex flex-col gap-4 "> {tasks && tasks.map((task, index) => { return ( <div key={task.id} className="flex gap-4 items-center py-2 px-6 rounded-md bg-[#74a0a6]"> <div> <label htmlFor={`task-${task.id}`} key={task.id} className={`flex gap-4 text-[14px] items-center font-bold list-none p-4 rounded bg-[#88adb3] cursor-pointer hover:bg-[#609299]`}> <div className="inline-flex items-center"> <label className="relative flex items-center p-3 rounded-full cursor-pointer" htmlFor="checkbox"> <input type="checkbox" name={`task-${task.id}`} id={`task-${task.id}`} className="before:content[''] peer relative h-5 w-5 cursor-pointer appearance-none rounded-md border border-[#edebd9] transition-all before:absolute before:top-2/4 before:left-2/4 before:block before:h-12 before:w-12 before:-translate-y-2/4 before:-translate-x-2/4 before:rounded-full before:bg-blue-gray-500 before:opacity-0 before:transition-opacity checked:border-lines checked:bg-[#545240] checked:before:bg-[#edebd9] hover:before:opacity-10 before:checked:hover:before:opacity-10 " checked={task.completed} /> <span className="absolute transition-opacity opacity-0 pointer-events-none text-stone-100 top-2/4 left-2/4 -translate-y-2/4 -translate-x-2/4 peer-checked:opacity-100"> <svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor" stroke="currentColor" strokeWidth="1"> <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd"></path> </svg> </span> </label> </div> </label> </div> <div> <h2 className="text-xl font-bold text-[#161515] "> {task.title} </h2> <p className="text-sm font-semibold text-[#42403f] "> {task.description} </p> <div className="flex gap-2 mt-2 text-xs font-bold"> <div className="flex items-center "> <img src={userImages[index]} alt="" className="w-10 h-10 rounded-full " /> <span> {task.assignedTo}</span> </div> </div> </div> <div className="p-2 ml-auto rounded-full cursor-pointer hover:bg-red-300" > <FaTrash color="#545240" /> </div> </div> ); })} </div> </div> </div> );
پس از آن، به مؤلفه Taskform
خود بروید و یک فرم UI برای ایجاد وظایف جدید ایجاد کنید.
import { addSingleTask } from "./services/api"; import toast from "react-hot-toast"; import { useState } from "react"; export default function Taskform() { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [assignedTo, setAssignedTo] = useState(""); return ( <div className="bg-[#74a0a6] p-4 rounded-md"> <form className="flex flex-col w-full gap-2 "> <label htmlFor="title"> <p className="font-bold ">Title</p> <input type="text" className="w-full font-medium focus:outline-[#74a0a6] focus-within:outline-[#74a0a6] p-1 bg-transparent border rounded-md" value={title} onChange={(e) => setTitle(e.target.value)} /> </label> <label htmlFor="description"> <p className="font-bold ">Description</p> <input type="text" className="w-full font-medium focus:outline-[#74a0a6] focus-within:outline-[#74a0a6] p-1 bg-transparent border rounded-md" value={description} onChange={(e) => setDescription(e.target.value)} /> </label> <label htmlFor="assignedTo"> <p className="font-bold ">Assigned To</p> <input type="text" className="w-full font-medium focus:outline-[#74a0a6] focus-within:outline-[#74a0a6] p-1 bg-transparent border rounded-md" value={assignedTo} onChange={(e) => setAssignedTo(e.target.value)} /> </label> <button className="p-2 mt-3 border text-white rounded-md w-max hover:bg-white hover:text-[#74a0a6]"> Add </button> </form> </div> ); }
پس از آن، آن را به مؤلفه TaskContainer
خود وارد کنید.
return ( <div className="flex flex-col gap-8 p-4"> <Taskform /> <div className="p-4 shadow-lg "> <div className="flex flex-col gap-4 "> {tasks && tasks.map((task, index) => {
برای گفت ن یک کار جدید، یک تابع handler در Taskform
ایجاد کنید، سپس تابع POST
خود را از فایل API خود وارد کنید.
const addTaskMutation = async (e) => { e.preventDefault(); const createdAt = new Date().toISOString(); // Get current timestamp as a string try { await addSingleTask({ title, description, assignedTo, completed: false, createdAt, }); toast.success("Task added succesfully."); setTitle(""); setDescription(""); setAssignedTo(""); } catch (err) { toast.error("Failed to add the new task."); } };
در نهایت، تابع mutate
را پس از فراخوانی تابع POST
خود فراخوانی کنید تا SWR بتواند داده های فعلی شما را باطل کند و درخواست جدیدی ارائه دهد. میتوانید این تابع جهشیافته را از قلاب useSWR
در TaskContainer
دریافت کنید، سپس آن را از طریق props به فرم ارسال کنید.
const { isLoading, error, data: tasks, mutate, } = useSWR(cacheKey, fetchTasks, { onSuccess: (data) => data.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)), }); return ( <div className="flex flex-col gap-8 p-4"> <Taskform mutate={mutate} />
سپس آن را در TaskForm
فراخوانی کنید.
import { addSingleTask } from "./services/api"; import toast from "react-hot-toast"; import { useState } from "react"; export default function Taskform({ mutate }) { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [assignedTo, setAssignedTo] = useState(""); const addTaskMutation = async (e) => { e.preventDefault(); const createdAt = new Date().toISOString(); try { await addSingleTask({ title, description, assignedTo, completed: false, createdAt, }); mutate(); toast.success("Task added succesfully."); setTitle(""); setDescription(""); setAssignedTo(""); } catch (err) { toast.error("Failed to add the new task."); } }; return ( <div className="bg-[#74a0a6] p-4 rounded-md"> <form className="flex flex-col w-full gap-2 " onSubmit={(e) => addTaskMutation(e)}> <label htmlFor="title"> <p className="font-bold ">Title</p> <input type="text" className="w-full font-medium focus:outline-[#74a0a6] focus-within:outline-[#74a0a6] p-1 bg-transparent border rounded-md" value={title} onChange={(e) => setTitle(e.target.value)} /> </label> <label htmlFor="description"> <p className="font-bold ">Description</p> <input type="text" className="w-full font-medium focus:outline-[#74a0a6] focus-within:outline-[#74a0a6] p-1 bg-transparent border rounded-md" value={description} onChange={(e) => setDescription(e.target.value)} /> </label> <label htmlFor="assignedTo"> <p className="font-bold ">Assigned To</p> <input type="text" className="w-full font-medium focus:outline-[#74a0a6] focus-within:outline-[#74a0a6] p-1 bg-transparent border rounded-md" value={assignedTo} onChange={(e) => setAssignedTo(e.target.value)} /> </label> <button className="p-2 mt-3 border text-white rounded-md w-max hover:bg-white hover:text-[#74a0a6]"> Add </button> </form> </div> ); }
اکنون کامپوننت خود را آزمایش کنید نتیجه زیر را به همراه دارد:
همانطور که می بینید، فهرست پس از ارسال هر فرم به روز می شود. اما این هنوز نیاز ما به رابط کاربری خوش بینانه را نشان نمی دهد. احتمالاً به این فکر می کنید که اگر عملیات به این سرعت انجام شد، چرا با Optimistic UI خود را به زحمت بیندازید؟
خوب، برای شروع، هیچ برنامه واقعی هرگز نمی تواند سرعت سرور JSON محلی شما را شکست دهد، زیرا داده ها به راحتی در دسترس شما هستند و کاربران اغلب اتصالات شبکه ناپایدار دارند.
بیایید سرعت واکشی را کاهش دهیم تا درخواست داده در دنیای واقعی را بهتر نشان دهیم. این یک سناریوی دنیای واقعی را بهتر شبیهسازی میکند زیرا کاربران اغلب از مکانهای مختلف میآیند که سرعت اینترنت متفاوتی دارند.
با ایجاد یک تابع تاخیر که قبل از هر یک از فراخوانی تابع شما اجرا می شود، شروع کنید.
import axios from "axios"; const tasksApi = axios.create({ baseURL: "http://localhost:3500", }); export const tasksUrlEndpoint = "/tasks"; const delay = () => new Promise((res) => setTimeout(() => res(), 1200)); export const fetchTasks = async () => { await delay(); const response = await tasksApi.get(tasksUrlEndpoint); return response.data; }; export const addSingleTask = async ({ title, description, completed, assignedTo, createdAt, }) => { await delay(); const response = await tasksApi.post(tasksUrlEndpoint, { title, description, completed, assignedTo, createdAt, }); return response.data; }; export const updateSingleTask = async (task) => { await delay(); const response = await tasksApi.patch(`${tasksUrlEndpoint}/${task.id}`, task); return response.data; }; export const deleteSingleTask = async ({ id }) => { await delay(); return await tasksApi.delete(`${tasksUrlEndpoint}/${id}`, id); };
سپس دوباره عملیات ایجاد خود را امتحان کنید.
همانطور که ممکن است متوجه شده باشید، عملیات ایجاد تنها پس از اتمام عملکرد عملکرد تاخیر (به مدت 1.2 ثانیه) اجرا می شود که باعث ایجاد یک طلسم کوتاهی از عدم فعالیت روی صفحه می شود.
روش معمول برای رسیدگی به این دورهها بین بارگذاری، معمولاً یک چرخش بارگیری یا نشانگر است که به شما او میگوید برخی از فعالیتهای پسزمینه در حال اجرا است. اما این اغلب جریان شما را هنگام کار در برنامه مختل می کند و کاملاً رک و پوست کنده ناامید کننده است.
همین اثر ثابت را می توان در عملیات به روز رسانی مشاهده کرد، جایی که کاربران باید منتظر تایید سرور برای دیدن داده های تازه باشند.
const updateTaskMutation = async (updatedTask) => { try { await updateSingleTask(updatedTask); mutate(); toast.success("Successfully updated task"); } catch (err) { toast.error("Failed to update the task."); } }; return ( <div className="flex flex-col gap-8 p-4"> <Taskform mutate={mutate} /> <div className="p-4 shadow-lg "> <div className="flex flex-col gap-4 "> {tasks && tasks.map((task, index) => { return ( <div key={task.id} className="flex gap-4 items-center py-2 px-6 rounded-md bg-[#74a0a6]"> <div> <label htmlFor={`task-${task.id}`} key={task.id} className={`flex gap-4 text-[14px] items-center font-bold list-none p-4 rounded bg-[#88adb3] cursor-pointer hover:bg-[#609299]`}> <div className="inline-flex items-center"> <label className="relative flex items-center p-3 rounded-full cursor-pointer" htmlFor="checkbox"> <input type="checkbox" name={`task-${task.id}`} id={`task-${task.id}`} className="before:content[''] peer relative h-5 w-5 cursor-pointer appearance-none rounded-md border border-[#edebd9] transition-all before:absolute before:top-2/4 before:left-2/4 before:block before:h-12 before:w-12 before:-translate-y-2/4 before:-translate-x-2/4 before:rounded-full before:bg-blue-gray-500 before:opacity-0 before:transition-opacity checked:border-lines checked:bg-[#545240] checked:before:bg-[#edebd9] hover:before:opacity-10 before:checked:hover:before:opacity-10 " checked={task.completed === true} onChange={() => updateTaskMutation({ ...task, completed: !task.completed, }) } /> <span className="absolute transition-opacity opacity-0 pointer-events-none text-stone-100 top-2/4 left-2/4 -translate-y-2/4 -translate-x-2/4 peer-checked:opacity-100"> <svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor" stroke="currentColor" strokeWidth="1"> <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd"></path> </svg> </span> </label> </div> </label> </div> <div> <h2 className="text-xl font-bold text-[#161515] "> {task.title} </h2> <p className="text-sm font-semibold text-[#42403f] "> {task.description} </p> <div className="flex gap-2 mt-2 text-xs font-bold"> <div className="flex items-center "> <img src={userImages[index]} alt="" className="w-10 h-10 rounded-full " /> <span> {task.assignedTo}</span> </div> </div> </div> <div className="p-2 ml-auto rounded-full cursor-pointer hover:bg-red-300" > <FaTrash color="#545240" /> </div> </div> ); })} </div> </div> </div> ); }
و در عملیات حذف، که همچنین منتظر انطباق سرور برای آبرسانی مجدد صفحه است.
const deleteTaskMutation = async ({ id }) => { try { await deleteSingleTask({ id }); mutate(); toast.success("Successfully deleted task"); } catch (err) { toast.error("Failed to delete the task."); } }; return ( <div className="flex flex-col gap-8 p-4"> <Taskform mutate={mutate} /> <div className="p-4 shadow-lg "> <div className="flex flex-col gap-4 "> {tasks && tasks.map((task, index) => { return ( <div key={task.id} className="flex gap-4 items-center py-2 px-6 rounded-md bg-[#74a0a6]"> <div> <label htmlFor={`task-${task.id}`} key={task.id} className={`flex gap-4 text-[14px] items-center font-bold list-none p-4 rounded bg-[#88adb3] cursor-pointer hover:bg-[#609299]`}> <div className="inline-flex items-center"> <label className="relative flex items-center p-3 rounded-full cursor-pointer" htmlFor="checkbox"> <input type="checkbox" name={`task-${task.id}`} id={`task-${task.id}`} className="before:content[''] peer relative h-5 w-5 cursor-pointer appearance-none rounded-md border border-[#edebd9] transition-all before:absolute before:top-2/4 before:left-2/4 before:block before:h-12 before:w-12 before:-translate-y-2/4 before:-translate-x-2/4 before:rounded-full before:bg-blue-gray-500 before:opacity-0 before:transition-opacity checked:border-lines checked:bg-[#545240] checked:before:bg-[#edebd9] hover:before:opacity-10 before:checked:hover:before:opacity-10 " checked={task.completed === true} onChange={() => updateTaskMutation({ ...task, completed: !task.completed, }) } /> <span className="absolute transition-opacity opacity-0 pointer-events-none text-stone-100 top-2/4 left-2/4 -translate-y-2/4 -translate-x-2/4 peer-checked:opacity-100"> <svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor" stroke="currentColor" strokeWidth="1"> <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd"></path> </svg> </span> </label> </div> </label> </div> <div> <h2 className="text-xl font-bold text-[#161515] "> {task.title} </h2> <p className="text-sm font-semibold text-[#42403f] "> {task.description} </p> <div className="flex gap-2 mt-2 text-xs font-bold"> <div className="flex items-center "> <img src={userImages[index]} alt="" className="w-10 h-10 rounded-full " /> <span> {task.assignedTo}</span> </div> </div> </div> <div className="p-2 ml-auto rounded-full cursor-pointer hover:bg-red-300" onClick={() => deleteTaskMutation({ id: task.id })}> <FaTrash color="#545240" /> </div> </div> ); })} </div> </div> </div> ); }
این چند ثانیه عدم فعالیت یا بارگذاری میتواند بر میزان رضایت کاربران از برنامه شما تأثیر بگذارد، به همین دلیل است که ما از رابط کاربری خوشبینانه برای رفع آن استفاده میکنیم.
رابط کاربری خوشبینانه CRUD
روش عملی این کار به این صورت است که، وقتی یک عمل را انجام میدهید، در حالی که عملیات async در پسزمینه اجرا میشود، بلافاصله به حالت رابط کاربری (کش) اضافه میشود.
بیشتر بخوانید
اگر عملیات موفقیت آمیز باشد، هیچ چیز در رابط کاربری تغییر نمی کند و همه چیز مانند اولین تلاش عمل می کند. اما در صورت عدم موفقیت، حالت UI به حالت قبلی خود باز می گردد و یک خطا از طریق نان تست شما نمایش داده می شود.
یک رویکرد UI خوشبینانه تجربه کاربری بسیار بهتری را نسبت به بارگذاری پیامها یا اسپینرهای سنتی ارائه میدهد. هنگامی که پس از کلیک کردن روی یک دکمه پاسخ فوری می بینید، برنامه سریعتر و پاسخگوتر احساس می شود و شما را درگیر و راضی نگه می دارد. میتوانید بدون انتظار برای تأیید سرور، به تعامل با برنامه یکپارچه ادامه دهید و تجربه را روانتر و شهودیتر کنید.
این بازخورد فوری زمان انتظار درک شده شما را کاهش می دهد و رابط را از نظر بصری پایدار نگه می دارد و از سوسو زدن های آزاردهنده یا تغییرات ناگهانی جلوگیری می کند. بهعلاوه، زمانی که برنامه احساس میکند این پاسخگو است، احتمالاً به استفاده از آن ادامه میدهید و تجربه مثبتی خواهید داشت.
از طرف دیگر، بارگیری پیامها یا اسپینرها میتواند جریان شما را مختل کند و باعث کند نرمافزار کندتر احساس شود و به طور بالقوه شما را ناامید کند.
هنوز هم کمی شبیه به حرف های بیهوده به نظر می رسد، نه؟ خوب، بیایید در حین حرکت یاد بگیریم!
در فایل swrAPI
خود، یک تابع جهش دیگر ایجاد کنید. این تابع دارای دو پارامتر است: وظیفه جدیدی که می خواهید اضافه کنید و فهرست کارهای موجود.
export const addTaskMutation = async (newTask, tasks) => { };
سپس از تابع create
موجود شما برای ایجاد یک کار جدید استفاده می کند. پس از این، نتیجه را ذخیره می کنید و آن نتیجه را در یک آرایه جدید همراه با وظایف موجود برمی گردانید.
export const addTaskMutation = async (newTask, tasks) => { const addedTask = await addSingleTask(newTask); return [...tasks, addedTask].sort( (a, b) => new Date(b.createdAt) - new Date(a.createdAt) ); };
همانطور که حدس میزنید، این تابع مانند تابع create
قبلی که نوشتیم عمل میکند، اما این چیزی است که بعداً دنبال میکنیم.
در مرحله بعد، یک تابع options
ایجاد کنید که مسئول برخورد با عملیات همگام به عنوان یک عملیات همزمان است و بلافاصله پاسخ می دهد.
این تابع همچنین پارامترهایی مانند:
optimisticData
: که داده های جدیدی است که می خواهید بلافاصله نمایش دهید.
rollbackOnError
: که در صورت عدم موفقیت درخواست، حالت را به حالت قبلی تنظیم می کند.
populateCache
: که بلافاصله این داده های خوش بینانه را در حالت رابط کاربری ما تنظیم می کند.
revalidate
: که به ما امکان می دهد واکشی دیگری را پس از اجرای این تابع فعال یا غیرفعال کنیم.
export const addTaskOptions = (newTask, tasks) => { return { optimisticData: [...tasks, newTask].sort( (a, b) => new Date(b.createdAt) - new Date(a.createdAt) ), rollbackOnError: true, populateCache: true, revalidate: false, }; };
برای استفاده از این روش UI خوش بینانه با عملیات create
، هر دو تابع را به TaskForm
خود وارد کنید. هر دو تابع باید در تابع mutate
پیچیده شوند، زیرا هر دو در تلاش برای جهش دادن داده ها هستند.
import { addTaskMutation as addSingleTask, addTaskOptions, } from "./services/swrAPI"; import toast from "react-hot-toast"; import { useState } from "react"; export default function Taskform({ mutate, tasks }) { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [assignedTo, setAssignedTo] = useState(""); const addTaskMutation = async (e) => { e.preventDefault(); const createdAt = new Date().toISOString(); try { await mutate( addSingleTask( { title, description, assignedTo, completed: false, createdAt, }, tasks ), addTaskOptions( { title, description, assignedTo, completed: false, createdAt, }, tasks ) ); toast.success("Task added succesfully."); } catch (err) { toast.error("Failed to add the new task."); } };
توجه : آرایه وظایف از طریق props به TaskForm
منتقل می شود تا این عملکرد کار کند.
برای مشاهده مواردی که ممکن است خطا وجود داشته باشد، با اضافه کردن یک شرط تصادفی، به عملکردهای خود شانس موفقیت یا شکست 50/50 بدهید.
export const addSingleTask = async ({ title, description, completed, assignedTo, createdAt, }) => { await delay(); if (Math.random() < 0.5) throw new Error("Failed to add new task"); const response = await tasksApi.post(tasksUrlEndpoint, { title, description, completed, assignedTo, createdAt, }); return response.data; }; export const updateSingleTask = async (task) => { await delay(); if (Math.random() < 0.5) throw new Error("Failed to update task"); const response = await tasksApi.patch(`${tasksUrlEndpoint}/${task.id}`, task); return response.data; }; export const deleteSingleTask = async ({ id }) => { await delay(); if (Math.random() < 0.5) throw new Error("Failed to update task"); return await tasksApi.delete(`${tasksUrlEndpoint}/${id}`, id); };
اکنون با آزمایش نقطه پایانی create
نتیجه زیر به دست می آید:
و voilà! برنامه شما رسماً خوشبین است. تلاش می کند تا بلافاصله کار جدید را به فهرست اضافه کند، حتی اگر شکست بخورد و در صورت بروز خطا، به آرامی به عقب برمی گردد.
این به طور مشابه برای عملیات به روز رسانی کار می کند - با عملکرد update
به روز شده شروع می شود:
export const updateTaskMutation = async (updatedTask, tasks) => { const updatedTaskResponse = await updateSingleTask(updatedTask); return tasks.map((task) => task.id === updatedTask.id ? updatedTaskResponse : task ); };
سپس options
مربوطه آن عمل می کند:
export const updateTaskOptions = (updatedTask, tasks) => { return { optimisticData: tasks.map((task) => task.id === updatedTask.id ? updatedTask : task ), rollbackOnError: true, populateCache: true, revalidate: false, }; };
برای آزمایش این موضوع، تابع جدید updateSingleTask
و updateOptions
را در TaskConatiner
خود وارد کنید و تابع handler را به روز کنید.
const updateTaskMutation = async (updatedTask) => { try { await mutate( updateSingleTask(updatedTask, tasks), updateTaskOptions(updatedTask, tasks) ); toast.success("Successfully updated task"); } catch (err) { toast.error("Failed to update the task."); } };
و در نهایت برای عمل حذف:
// Function for deleting a task export const deleteTaskMutation = async (taskToDelete, tasks) => { await deleteSingleTask(taskToDelete); return tasks.filter((task) => task.id !== taskToDelete.id); }; // Options for deleting a task export const deleteTaskOptions = (taskToDelete, tasks) => { return { optimisticData: tasks.filter((task) => task.id !== taskToDelete.id), rollbackOnError: true, populateCache: true, revalidate: false, }; };
که می تواند در کنترل کننده حذف TaskContainer
مانند زیر استفاده شود:
const deleteTaskMutation = async ({ id }) => { try { await mutate( deleteSingleTask({ id }, tasks), deleteTaskOptions({ id }, tasks) ); toast.success("Successfully deleted task"); } catch (err) { toast.error("Failed to delete the task."); } };
معایب رابط کاربری خوشبینانه
اکنون باید فکر کنید، اگر رابط کاربری خوشبینانه بسیار عالی است، چرا از آن در همه جا استفاده نکنید؟
خوب، مانند همه چیز، آن عمل بدون اعتدال به آشوب تبدیل می شود. در اینجا دلایلی وجود دارد که چرا باید از رابط کاربری خوشبینانه در حد اعتدال استفاده کنید.
بهروزرسانیهای بیش از حد : رابط کاربری خوشبینانه ممکن است با بهروزرسانیها کمی تحت تأثیر قرار گیرد، بهخصوص اگر برنامه شما سریعتر از اتصال اینترنت شما حرکت کند. بهروزرسانیهای زیاد میتوانند سرعت کار را کاهش دهند، پس ایجاد تعادل ضروری است.
افشای منطق سمت سرور : در حالی که بارگذاری تمام هوشمندی ها در برنامه شما (مانند تولید شناسه های منحصر به فرد یا تحلیل اینکه آیا نام کاربری قبلاً گرفته شده است) وسوسه انگیز است، به یاد داشته باشید که سرور شما نیز نقش مهمی ایفا می کند. اجازه دادن به قسمت جلویی برنامه شما می تواند به خطرات امنیتی و کدهای نامرتب منجر شود، پس مراقب باشید که منطق خود را کجا قرار می دهید.
مدیریت اشتباهات: در حالی که رابط کاربری خوشبینانه معمولاً انتظار حرکت آرام را دارد، زندگی راهی برای پرتاب توپهای منحنی دارد. از وقفه ناگهانی اینترنت گرفته تا یک استراحت غیرمنتظره قهوه توسط سرور، ایرادات ممکن است برای مدیریت به خوبی سردرد باشد.
اجتناب از تغییرات سریع : تصور کنید یک کالا را به سبد خرید خود اضافه کنید، و سپس تصمیم به حذف آن بگیرید قبل از اینکه درخواست " گفت ن" حتی به سرور برسد. مثل این است که نظر خود را در پیشخوان تسویه حساب تغییر دهید - کمی گیج کننده، درست است؟ تغییرات سریعی مانند این میتواند برنامه شما را ناآرام کند، پس بهتر است با احتیاط ادامه دهید.
موارد استفاده ایدهآل برای رابط کاربری خوشبینانه
در حالی که رابط کاربری خوشبینانه ممکن است جام مقدس مدیریت دولتی نباشد که انتظار کشف آن را داشتید، موارد استفاده خوبی دارد مانند:
برنامههای پیامرسانی فوری : تقریباً همه پلتفرمهای پیامرسانی فوری در حال حاضر از این الگو استفاده میکنند. پیامهای شما فوراً در پنجره چت ظاهر میشوند، حتی قبل از اینکه توسط سرور تأیید شوند. این یک تجربه چت یکپارچه و پاسخگو ایجاد می کند و مکالمه را بدون زحمت جریان می دهد.
ابزارهای ویرایش مشارکتی : چه در حال کار بر روی یک سند با همکاران یا همکاری در یک پروژه با هم تیمیها باشید، UI Optimistic تضمین میکند که تغییرات در زمان واقعی منعکس میشوند. همانطور که تایپ میکنید، ویرایش میکنید یا بهروزرسانی میکنید، تغییرات شما فوراً برای دیگران قابل مشاهده است و همکاری و بهرهوری را تقویت میکند.
فیدهای رسانه های اجتماعی : در فید رسانه های اجتماعی خود حرکت کنید، پست ها، لایک ها و نظرات را خواهید دید که مانند جادو ظاهر می شوند. رابط کاربری خوشبینانه تضمین میکند که تعاملات، مانند لایک کردن یک پست یا گذاشتن نظر، فوراً منعکس میشوند و تجربه مرور جذابتری را ارائه میدهند.
وبسایتهای تجارت الکترونیکی : گفت ن اقلام به سبد خرید، بهروزرسانی تعداد و خروج از فروشگاه باید به نظر یک نسیم باشد. رابط کاربری خوشبینانه با بهروزرسانی فوری سبد خرید و نمایش بازخورد، مانند در دسترس بودن کالا یا تغییرات قیمت، بدون تأخیر، روند خرید را سرعت میبخشد.
برای راحتی، در اینجا منابعی وجود دارد که ممکن است به آنها نیاز داشته باشید:
من می خواهم دیو گری را تصدیق کنم. این ویدیوی یوتیوب او بود که الهام بخش این مقاله بود.
نتیجه
همانطور که ما به UI خوش بینانه پایان می دهیم، واضح است که این تکنیک می تواند تجربه کاربری را تغییر دهد. این عجله پیام شما ظاهر می شود و یا سبد خرید شما به روز رسانی در زمان واقعی است.
رابط کاربری خوشبینانه در مورد سرعت و همچنین این است که باعث میشود کاربران احساس کنند - متصل، توانمند و خوشحال هستند. پس ، دفعه بعد که کلیک میکنید و باز شدن جادو را میبینید، به یاد داشته باشید: این فقط یک کد نیست... این نبض خوشحالی کاربر است (نه یک تبلیغ کوکاکولا 😂). این جادو را در برنامه های خود زنده نگه دارید!
کد نویسی مبارک و روزی خوش بینانه داشته باشید!
مقالات من را دوست دارید؟
با خیال راحت برای من قهوه بخرید تا مغزم را درگیر نگه دارم و مقالات بیشتری از این قبیل ارائه دهید.
اطلاعات تماس
می خواهید به من وصل شوید یا با من تماس بگیرید؟ در صورت تمایل به من در مورد موارد زیر ضربه بزنید:
توییتر / X: @jajadavid8
لینکدین: دیوید جاجا
ایمیل: Jajadavidjid@gmail.com
ارسال نظر