۸ روش برای بهبود اپلیکیشن ری اکت — راهنمای کاربردی
برخی اوقات در زمان ساخت اپلیکیشنهای راکت ممکن است برخی فرصتها برای بهبود اپلیکیشن خود را از دست بدهیم، زیرا زمانی که اپلیکیشن سریع کار میکند، به اشتباه تصور میکنیم که نقصی ندارد. در چنین موقعیتهایی ممکن است تصور کنیم که چون خروجی پروژه برای ما نرمال به نظر میرسد، کاربران نیز چنین وضعیتی درباره آن خواهند داشت. زمانی که ذهن این طرز فکر را پیش بگیرد، ممکن است موضوعاتی که میتوان کد را برای دریافت نتیجه بهتر بهبود بخشید را نادیده بگیرد. در این راهنما با 8 روش برای بهبود اپلیکیشن ری اکت آشنا خواهیم شد.
1. موجودیتها را حفظ کنید
نخستین روشی که برای ارتقای کیفیت اپلیکیشن ریاکت وجود دارد، توجه به «موجودیتها» (Identities) است. باید به خاطر داشته باشید که میتوانید متغیرها و تابعها را درون React.useMemo قرار دهید تا به آنها امکان خاطرسپاری خودشان را بدهید. بدین ترتیب ریاکت آنها را برای رندرهای آتی حفظ میکند.
در غیر این صورت اگر آنها را به حافظه نسپارید، ارجاعشان در رندرهای آتی از بین میرود. بدین ترتیب هزینه سربار مضاعفی در نتیجه عملیات غیر ضروری ایجاد میشود. برای نمونه فرض کنید میخواهیم یک قلاب سفارشی بسازیم که فهرستی از urls به عنوان آرگومان میگیرد به طوری که میتواند آنها را در یک آرایه از promise-ها تجمیع کند که با Promise.all به صورت resolved درمیآیند.
نتایج درون حالت درج میشوند و به محض پایان، به کامپوننت App ارسال میشوند. این Promise-ها روی آرایه urls نگاشت میشوند که شامل چهار URL مختلف است که باید واکشی شوند:
1import React from 'react'
2import axios from 'axios'
3
4export const useApp = ({ urls }) => {
5 const [results, setResults] = React.useState(null)
6
7 const promises = urls.map(axios.get)
8
9 React.useEffect(() => {
10 Promise.all(promises).then(setResults)
11 }, [])
12
13 return { results }
14}
15
16const App = () => {
17 const urls = [
18 'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=a',
19 'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=u',
20 'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=y',
21 'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=z',
22 ]
23
24 useApp({ urls })
25
26 return null
27}
28
29export default App
وظیفه ما این است که دادهها را از لینکها واکشی کنیم به این ترتیب به صورت ایدهآل تنها چهار درخواست باید ارسال شوند. اما اگر نگاهی به زبانه Network درون مرورگر کروم بیندازیم میبینیم که در عمل هشت درخواست فرستاده میشوند. دلیل این وضعیت آن است که آرگومان urls موجودیت خود را مانند قبل حفظ نمیکند، زیرا زمانی که App رندر مجدد میشود، هر بار یک وهله جدید از آرایه میسازد و از این رو ریاکت با آن مانند یک مقدار تغییریافته رفتار میکند.
برنامههای رایانهای گاهی اوقات فکر میکنند که هوشمندی بیشتری از ما دارند و چنین رفتاری در پیش میگیرند. برای رفع این مشکل میتوانیم از React.useMemo استفاده کنیم، به طوری که تا وقتی آرایه شامل url-ها تغییری نیافته است، آرایه promise-ها خودش را در هر بار رندر، مجدداً محاسبه نمیکند. بدین ترتیب کد خود را با بهکارگیری این مفهوم به صورت زیر بازسازی میکنیم:
1const useApp = ({ urls }) => {
2 const [results, setResults] = React.useState(null)
3
4 const promises = urls.map((url) => axios.get(url))
5
6 React.useEffect(() => {
7 Promise.all(promises).then(setResults)
8 }, [])
9
10 return { results }
11}
12
13const App = () => {
14 const urls = React.useMemo(() => {
15 return [
16 'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=a',
17 'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=u',
18 'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=y',
19 ]
20 }, [])
21
22 const { results } = useApp({ urls })
23
24 return null
25}
اکنون اگر کد فوق را اجرا کنیم، متوجه میشویم که همچنان هشت درخواست ارسال میشوند. دلیل این وضعیت آن است که گرچه ارائه urls را حفظ کردهایم، اما همچنان باید متغیرهای promises را نیز درون قلاب ذخیره کنیم، زیرا آنها نیز هر زمان که قلاب اجرا میشوند وهلهای از خود میسازند:
1const promises = React.useMemo(() => {
2 return urls.map((url) => axios.get(url))
3}, [urls])
به این ترتیب کد باید اکنون در هر بار اجرا تنها چهار درخواست ارسال کند:
2. ادغام props در فرزندان
گاهی اوقات ممکن است با موقعیتی مواجه شویم که باید یک prop قبل از رندر با فرزندان ادغام شود. ریاکت امکان دیدن props-های هر عنصر خودش و عناصر دیگر را فراهم ساخته است و برای نمونه key آن را عرضه میکند. میتوان عنصر فرزند را درون یک کامپوننت جدید قرار دارد و props-های جدید را از آنجا تزریق کرد و یا میتوان prop-های جدید را با استفاده از متد ادغام کرد.
برای نمونه فرض کنید یک کامپوننت App داریم که از یک قلاب useModal استفاده میکند و برخی ابزارهای کارآمد برای مدیریت مدلها به وسیله ارائه کنترلهایی مانند open, close و opened در اختیار ما قرار میدهد. میخواهیم این prop را به کامپوننت VisibilityControl ارسال کنیم، زیرا باید کارکردهای بیشتری را پیش از ارسال دادههای modal به فرزندان ارائه کنیم:
1import React from 'react'
2
3const UserContext = React.createContext({
4 user: {
5 firstName: 'Kelly',
6 email: 'frogLover123@gmail.com',
7 },
8 activated: true,
9})
10
11const VisibilityControl = ({ children, opened, close }) => {
12 const ctx = React.useContext(UserContext)
13 return React.cloneElement(children, {
14 opened: ctx.activated ? opened : false,
15 onClick: close,
16 })
17}
18
19export const useModal = ({ urls } = {}) => {
20 const [opened, setOpened] = React.useState(false)
21 const open = () => setOpened(true)
22 const close = () => setOpened(false)
23
24 return {
25 opened,
26 open,
27 close,
28 }
29}
30
31const App = ({ children }) => {
32 const modal = useModal()
33
34 return (
35 <div>
36 <button type="button" onClick={modal.opened ? modal.close : modal.open}>
37 {modal.opened ? 'Close' : 'Open'} the Modal
38 </button>
39 <VisibilityControl {...modal}>{children}</VisibilityControl>
40 </div>
41 )
42}
43
44const Window = ({ opened }) => {
45 if (!opened) return null
46 return (
47 <div style={{ border: '1px solid teal', padding: 12 }}>
48 <h2>I am a window</h2>
49 </div>
50 )
51}
52
53export default () => (
54 <App>
55 <Window />
56 </App>
57)
VisibilityControl مطمئن میشود که پیش از صادر کردن اجازه استفاده نرمال فرزندان از opened، مقدار activated به صورت true در آمده است. اگر این وضعیت در مسیر امن استفاده شود، VisibilityControl کارکرد جلوگیری از کاربران غیرفعال شده از دسترسی به محتوای امن را فراهم میسازد.
3. ترکیب ردیوسرها برای ساخت یک ردیوسر بزرگ
این بهینهسازی در مواردی به کار میآید که لازم باشد دو یا چند Reducer در اپلیکیشن با هم ترکیب شوند تا یک ردیوسر بزرگتر تشکیل دهند. این رویکرد مشابه طرز کار combineReducers در React-Redux است. فرض کنید میخواهیم یک اپلیکیشن میکروسرویس بزرگ بسازیم و از ابتدا در نظر گرفتهایم هر بخش در اپلیکیشن مسئول «چارچوب/حالت» (context/state) خودش باشد.
اما در ادامه به این نتیجه میرسیم که حالتهای مختلف باید در یک حالت بزرگتر ادغام شوند تا بتوانیم همه آنها را در محیط یکسانی مدیریت کنیم. بدین ترتیب فایلهای authReducer.js, ownersReducer.js و frogsReducer.js را داریم که باید با هم ترکیب شوند:
- فایل authReducer.js
1const authReducer = (state, action) => {
2 switch (action.type) {
3 case 'set-authenticated':
4 return { ...state, authenticated: action.authenticated }
5 default:
6 return state
7 }
8}
9
10export default authReducer
- فایل ownersReducer.js
1const ownersReducer = (state, action) => {
2 switch (action.type) {
3 case 'add-owner':
4 return {
5 ...state,
6 profiles: [...state.profiles, action.owner],
7 }
8 case 'add-owner-id':
9 return { ...state, ids: [...state.ids, action.id] }
10 default:
11 return state
12 }
13}
14
15export default ownersReducer
- فایل frogsReducer.js
1const frogsReducer = (state, action) => {
2 switch (action.type) {
3 case 'add-frog':
4 return {
5 ...state,
6 profiles: [...state.profiles, action.frog],
7 }
8 case 'add-frog-id':
9 return { ...state, ids: [...state.ids, action.id] }
10 default:
11 return state
12 }
13}
14
15export default frogsReducer
آنها را در فایل اصلی خود ایمپورت میکنیم و ساختار حالت را به صورت زیر تعریف مینماییم:
- فایل App.js
1import React from 'react'
2import authReducer from './authReducer'
3import ownersReducer from './ownersReducer'
4import frogsReducer from './frogsReducer'
5
6const initialState = {
7 auth: {
8 authenticated: false,
9 },
10 owners: {
11 profiles: [],
12 ids: [],
13 },
14 frogs: {
15 profiles: [],
16 ids: [],
17 },
18}
19
20function rootReducer(state, action) {
21 return {
22 auth: authReducer(state.auth, action),
23 owners: ownersReducer(state.owners, action),
24 frogs: frogsReducer(state.frogs, action),
25 }
26}
27
28const useApp = () => {
29 const [state, dispatch] = React.useReducer(rootReducer, initialState)
30
31 const addFrog = (frog) => {
32 dispatch({ type: 'add-frog', frog })
33 dispatch({ type: 'add-frog-id', id: frog.id })
34 }
35
36 const addOwner = (owner) => {
37 dispatch({ type: 'add-owner', owner })
38 dispatch({ type: 'add-owner-id', id: owner.id })
39 }
40
41 React.useEffect(() => {
42 console.log(state)
43 }, [state])
44
45 return {
46 ...state,
47 addFrog,
48 addOwner,
49 }
50}
51
52const App = () => {
53 const { addFrog, addOwner } = useApp()
54
55 const onAddFrog = () => {
56 addFrog({
57 name: 'giant_frog123',
58 id: 'jakn39eaz01',
59 })
60 }
61
62 const onAddOwner = () => {
63 addOwner({
64 name: 'bob_the_frog_lover',
65 id: 'oaopskd2103z',
66 })
67 }
68
69 return (
70 <>
71 <div>
72 <button type="button" onClick={onAddFrog}>
73 add frog
74 </button>
75 <button type="button" onClick={onAddOwner}>
76 add owner
77 </button>
78 </div>
79 </>
80 )
81}
82export default () => <App />
سپس میتوانیم با قلابها به صورت معمول کار کنیم، dispatch را فراخوانی کنیم، type تطبیق یافته را ارسال کنیم و آرگومانها را به ردیوسر اختصاصی یافته بفرستیم. مهمترین بخش که باید توجه کنیم، rootReducer است:
1function rootReducer(state, action) {
2 return {
3 auth: authReducer(state.auth, action),
4 owners: ownersReducer(state.owners, action),
5 frogs: frogsReducer(state.frogs, action),
6 }
7}
4. Sentry برای گزارش خطا
زمانی که از Sentry در اپلیکیشنهای ریاکت استفاده میکنیم، مزیت زیادی تولید میکند. داشتن گزارشهای تفصیلی در مورد خطاهای ارسالی به یک مکان مرکزی که به صورت یکباره تحلیل شوند، ابزاری بسیار مهم محسوب میشود.
ابتدا Sentry را با دستور زیر نصب کنید:
npm install @sentry/browser
سپس آن را برای اپلیکیشن ریاکت تنظیم کنید و وارد وبسایت Sentry.io شوید. پس از آن که حساب کاربری خود را ساختید، میتوانید گزارشهای خطا را در داشبورد پروژهتان مشاهده و تحلیل کنید.
این گزارشها کاملاً دقیق و تفصیلی هستند، بنابراین میتوانید از حجم بالای اطلاعاتی که به رفع خطاها کمک میکند مانند دانستن دستگاه کاربر، نوع مرورگر، URL که در آن خطا رخ داده است، نشانی IP کاربر، ردگیری پشته خطا، مدیریت شدن یا نشدن خطا، نام تابع، سورس کد، فهرست مفیدی از مسیرها که رد اقدامات شبکه منجر به خطا را نشان میدهد، هدرها و موارد زیاد دیگر استفاده کنید.
در تصویر زیر نمایی از داشبورد آن را ملاحظه میکنید:
چندین عضو تیم میتوانند روی موارد مختلف توضیح بنویسند تا یک محیط مناسب برای کار تیمی ایجاد کنند.
5. از axios روی window.fetch استفاده کنید
اگر اهمیتی به کاربران مرورگر اینترنت اکسپلورر نمیدهید، میتوانید از window.fetch در اپلیکیشن ریاکت استفاده نکنید، زیرا هیچ کدام از مرورگرها غیر از اینترنت اکسپلورر از window.fetch استفاده نمیکنند، مگر این که polyfill عرضه کنید.
Axios (+) برای پشتیبانی از اینترنت اکسپلورر عالی است، اما کارکردهای سنتی که عرضه میکند نیز قابل توجه است. مثلاً با استفاده از آن میتوان درخواستها را در میانه راه لغو کرد. این در عمل روی هر وب اپلیکیشن اعمال میشود و خاص ریاکت نیست.
دلیل این که در این فهرست به این مورد اشاره می کیم آن است که امروزه به طور معمول window.fetch در اپلیکیشنهای ریاکت مورد استفاده قرار میگیرد. از آنجا که اپلیکیشنهای ریاکت از مراحل transpiling/compiling عبور میکنند، بسته به ابزارهایی که پیکربندی میشوند، ممکن است تصور شود که window.fetch نیز transpile میشود.
6. در زمان کار با گرههای DOM از ارجاع Callback به جای ارجاع شیء استفاده کنید
با این که React.useRef در زمینه الصاق و کنترل ارجاعها به یک گره DOM جدید محسوب میشود، اما همواره بهترین گزینه نیست. برخی اوقات میخواهیم کنترل بیشتری روی گره DOM داشته باشیم تا بتوانیم کارکرد اضافهای عرضه کنیم.
برای نمونه مستندات ریاکت (+) موقعیتی را توصیف میکنند که باید از یک ارجاع callback برای مطمئن شدن از این واقعیت استفاده کنیم که وقتی تغییراتی در مقدار ref کنونی ایجاد میشود، یک کامپوننت خارجی همچنان میتواند از بهروزرسانیها مطلع شود. این مزیتی است که ارجاعهای Callback نسبت به useRef دارند.
Material-ui از این مفهوم قدرتمند استفاده میکند تا کارکردهای اضافی را از طریق ماژولهای کامپوننت عرضه کند. مهمترین بخش در این مورد آن است که این رفتار به صورت طبیعی موجب پاکسازی میشود.
7. قلاب useWhyDidYouUpdate
این یک قلاب سفارشی است و تغییراتی که در زمان رندر مجدد کامپوننتها رخ میدهد، عرضه میکند. برخی اوقات زمانی که یک memoizer مانند کامپوننت مرتبه بالای React.memo کافی نیست، میتوانید از این قلاب سفارشی برای یافتن prop-هایی که باید به حافظه سپرده شوند بهره بگیرید.
1import React from 'react'
2
3function useWhyDidYouUpdate(name, props) {
4 // Get a mutable ref object where we can store props ...
5 // ... for comparison next time this hook runs.
6 const previousProps = useRef()
7
8 useEffect(() => {
9 if (previousProps.current) {
10 // Get all keys from previous and current props
11 const allKeys = Object.keys({ ...previousProps.current, ...props })
12 // Use this object to keep track of changed props
13 const changesObj = {}
14 // Iterate through keys
15 allKeys.forEach((key) => {
16 // If previous is different from current
17 if (previousProps.current[key] !== props[key]) {
18 // Add to changesObj
19 changesObj[key] = {
20 from: previousProps.current[key],
21 to: props[key],
22 }
23 }
24 })
25
26 // If changesObj not empty then output to console
27 if (Object.keys(changesObj).length) {
28 console.log('[why-did-you-update]', name, changesObj)
29 }
30 }
31
32 // Finally update previousProps with current props for next hook call
33 previousProps.current = props
34 })
35}
36
37export default useWhyDidYouUpdate
شیوه استفاده از آن نیز چنین است:
1const MyComponent = (props) => {
2 useWhyDidYouUpdate('MyComponent', props)
3
4 return <Dashboard {...props} />
5}
8. تابعهای مرتبه بالا
یکی از بزرگترین مزایای تابعهای مرتبه بالا این است که وقتی به صورت صحیحی استفاده شوند، زمان زیادی از شما و تیمتان صرفهجویی میکنند. برای نمونه میتوان از React-Toastify (+) برای نمایش نوتیفیکیشنها بهره گرفت. از آن میتوان در هر جا استفاده کرد. به علاوه امکان اجرای سریع تصمیمهای UX را فراهم میسازد.
اگر نیاز به مدیریت یک خطا باشد، کافی است یک نوتیفیکیشن Toast نمایش دهید. با این حال شاید متوجه شوید که وقتی اپلیکیشن بزرگتر میشود، و سطح پیچیدگی بالاتر میرود، نوتیفیکیشنهای Toast بیش از حد زیاد میشوند.
این موضوع به خودی خود اشکالی ندارد، اما روشی برای جلوگیری از تکرار وجود ندارد. این بدان معنی است که برخی نوتیفیکیشنهای Toast چندین بار روی صفحه ظاهر میشوند و حتی برخی دقیقاً مانند موارد قبل خود هستند.
بنابراین در نهایت باید از یک API کمک گرفت که این کتابخانه برای کمک به حذف نوتیفیکیشنهای فعال از طریق id با استفاده از ()toast.dismiss ارائه کرده است. برای توضیح بیشتر این بخش بهتر است ابتدا یک فایل را نشان دهیم که Toast-ها را از آنجا ایمپورت میکنیم:
1import React from 'react'
2import { GoCheck, GoAlert } from 'react-icons/go'
3import { FaInfoCircle } from 'react-icons/fa'
4import { MdPriorityHigh } from 'react-icons/md'
5import { toast } from 'react-toastify'
6
7/*
8 Calling these toasts most likely happens in the UI 100% of the time.
9 So it is safe to render components/elements as toasts.
10*/
11
12// Keeping all the toast ids used throughout the app here so we can easily manage/update over time
13// This used to show only one toast at a time so the user doesn't get spammed with toast popups
14export const toastIds = {
15 // APP
16 internetOnline: 'internet-online',
17 internetOffline: 'internet-offline',
18 retryInternet: 'internet-retry',
19}
20
21// Note: this toast && is a conditional escape hatch for unit testing to avoid an error.
22const getDefaultOptions = (options) => ({
23 position: toast && toast.POSITION.BOTTOM_RIGHT,
24 ...options,
25})
26
27const Toast = ({ children, success, error, info, warning }) => {
28 let componentChildren
29 // Sometimes we are having an "object is not valid as a react child" error and children magically becomes an API error response, so we must use this fallback string
30 if (!React.isValidElement(children) && typeof children !== 'string') {
31 componentChildren = 'An error occurred'
32 } else {
33 componentChildren = children
34 }
35 let Icon = GoAlert
36 if (success) Icon = GoCheck
37 if (error) Icon = GoAlert
38 if (info) Icon = FaInfoCircle
39 if (warning) Icon = MdPriorityHigh
40 return (
41 <div style={{ paddingLeft: 10, display: 'flex', alignItems: 'center' }}>
42 <div style={{ width: 30, height: 30 }}>
43 <Icon style={{ color: '#fff', width: 30, height: 30 }} />
44 </div>
45 <div style={{ padding: 8, display: 'flex', alignItems: 'center' }}>
46
47 <span style={{ color: '#fff' }}>{componentChildren}</span>
48 </div>
49 </div>
50 )
51}
52
53export const success = (msg, opts) => {
54 return toast.success(<Toast success>{msg}</Toast>, {
55 className: 'toast-success',
56 ...getDefaultOptions(),
57 ...opts,
58 })
59}
60
61export const error = (msg, opts) => {
62 return toast.error(<Toast error>{msg}</Toast>, {
63 className: 'toast-error',
64 ...getDefaultOptions(),
65 ...opts,
66 })
67}
68
69export const info = (msg, opts) => {
70 return toast.info(<Toast info>{msg}</Toast>, {
71 className: 'toast-info',
72 ...getDefaultOptions(),
73 ...opts,
74 })
75}
76
77export const warn = (msg, opts) => {
78 return toast.warn(<Toast warning>{msg}</Toast>, {
79 className: 'toast-warn',
80 ...getDefaultOptions(),
81 ...opts,
82 })
83}
84
85export const neutral = (msg, opts) => {
86 return toast(<Toast warning>{msg}</Toast>, {
87 className: 'toast-default',
88 ...getDefaultOptions(),
89 ...opts,
90 })
91}
میدانیم که این کد چندان خوب نیست، اما در ادامه آن را اصلاح میکنیم. این کدی است که در کامپوننت مجزا داریم تا بررسی کنیم آیا Toast قبلی پیشتر روی صفحه بوده است یا نه و اگر چنین باشد، تلاش میکنیم تا آن را حذف کنیم و Toast جدید را نمایش دهیم.
1import { toast } from 'react-toastify'
2import {
3 info as toastInfo,
4 success as toastSuccess,
5 toastIds,
6} from 'util/toast'
7import App from './App'
8
9const Root = () => {
10 const onOnline = () => {
11 if (toast.isActive(toastIds.internetOffline)) {
12 toast.dismiss(toastIds.internetOffline)
13 }
14
15 if (toast.isActive(toastIds.retryInternet)) {
16 toast.dismiss(toastIds.retryInternet)
17 }
18 if (!toast.isActive(toastIds.internetOnline)) {
19 toastSuccess('You are now reconnected to the internet.', {
20 position: 'bottom-center',
21 toastId: toastIds.internetOnline,
22 })
23 }
24 }
25
26 const onOffline = () => {
27 if (!toast.isActive(toastIds.internetOffline)) {
28 toastInfo('You are disconnected from the internet right now.', {
29 position: 'bottom-center',
30 autoClose: false,
31 toastId: toastIds.internetOffline,
32 })
33 }
34 }
35
36 useInternet({ onOnline, onOffline })
37
38 return <App />
39}
این کد به خوبی کار میکند اما toast-های دیگری در سراسر اپلیکیشن داریم که باید به همین روش اصلاح شوند. بدن ترتیب باید به هر فایل که یک نوتیفیکیشن toast نمایش میدهد برویم و موارد تکراری را حذف کنیم.
زمانی که در این عصر صحبت از سر زدن به تکتک فایلها و اصلاح آنها میشود باید متوجه شویم که یک جای کار میلنگد. بنابراین فایل util/toast.js را بررسی میکنیم و آن را طوری بازنویسی میکنیم که مشکل ما را حل کند. فایل پس از اصلاح به صورت زیر درمیآید:
- فایل src/util/toast.js
1import React, { isValidElement } from 'react'
2import isString from 'lodash/isString'
3import isFunction from 'lodash/isFunction'
4import { GoCheck, GoAlert } from 'react-icons/go'
5import { FaInfoCircle } from 'react-icons/fa'
6import { MdPriorityHigh } from 'react-icons/md'
7import { toast } from 'react-toastify'
8
9/*
10 Calling these toasts most likely happens in the UI 100% of the time.
11 So it is safe to render components/elements as toasts.
12*/
13
14// Keeping all the toast ids used throughout the app here so we can easily manage/update over time
15// This used to show only one toast at a time so the user doesn't get spammed with toast popups
16export const toastIds = {
17 // APP
18 internetOnline: 'internet-online',
19 internetOffline: 'internet-offline',
20 retryInternet: 'internet-retry',
21}
22
23// Note: this toast && is a conditional escape hatch for unit testing to avoid an error.
24const getDefaultOptions = (options) => ({
25 position: toast && toast.POSITION.BOTTOM_RIGHT,
26 ...options,
27})
28
29const Toast = ({ children, success, error, info, warning }) => {
30 let componentChildren
31 // Sometimes we are having an "object is not valid as a react child" error and children magically becomes an API error response, so we must use this fallback string
32 if (!isValidElement(children) && !isString(children)) {
33 componentChildren = 'An error occurred'
34 } else {
35 componentChildren = children
36 }
37 let Icon = GoAlert
38 if (success) Icon = GoCheck
39 if (error) Icon = GoAlert
40 if (info) Icon = FaInfoCircle
41 if (warning) Icon = MdPriorityHigh
42 return (
43 <div style={{ paddingLeft: 10, display: 'flex', alignItems: 'center' }}>
44 <div style={{ width: 30, height: 30 }}>
45 <Icon style={{ color: '#fff', width: 30, height: 30 }} />
46 </div>
47 <div style={{ padding: 8, display: 'flex', alignItems: 'center' }}>
48
49 <span style={{ color: '#fff' }}>{componentChildren}</span>
50 </div>
51 </div>
52 )
53}
54
55const toaster = (function() {
56 // Attempt to remove a duplicate toast if it is on the screen
57 const ensurePreviousToastIsRemoved = (toastId) => {
58 if (toastId) {
59 if (toast.isActive(toastId)) {
60 toast.dismiss(toastId)
61 }
62 }
63 }
64 // Try to get the toast id if provided from options
65 const attemptGetToastId = (msg, opts) => {
66 let toastId
67 if (opts && isString(opts.toastId)) {
68 toastId = opts.toastId
69 } else if (isString(msg)) {
70 // We'll just make the string the id by default if its a string
71 toastId = msg
72 }
73 return toastId
74 }
75 const handleToast = (type) => (msg, opts) => {
76 const toastFn = toast[type]
77 if (isFunction(toastFn)) {
78 const toastProps = {}
79 let className = ''
80 const additionalOptions = {}
81 const toastId = attemptGetToastId(msg, opts)
82 if (toastId) additionalOptions.toastId = toastId
83 // Makes sure that the previous toast is removed by using the id, if its still on the screen
84 ensurePreviousToastIsRemoved(toastId)
85 // Apply the type of toast and its props
86 switch (type) {
87 case 'success':
88 toastProps.success = true
89 className = 'toast-success'
90 break
91 case 'error':
92 toastProps.error = true
93 className = 'toast-error'
94 break
95 case 'info':
96 toastProps.info = true
97 className = 'toast-info'
98 break
99 case 'warn':
100 toastProps.warning = true
101 className - 'toast-warn'
102 break
103 case 'neutral':
104 toastProps.warning = true
105 className - 'toast-default'
106 break
107 default:
108 className = 'toast-default'
109 break
110 }
111 toastFn(<Toast {...toastProps}>{msg}</Toast>, {
112 className,
113 ...getDefaultOptions(),
114 ...opts,
115 ...additionalOptions,
116 })
117 }
118 }
119 return {
120 success: handleToast('success'),
121 error: handleToast('error'),
122 info: handleToast('info'),
123 warn: handleToast('warn'),
124 neutral: handleToast('neutral'),
125 }
126})()
127
128export const success = toaster.success
129export const error = toaster.error
130export const info = toaster.info
131export const warn = toaster.warn
132export const neutral = toaster.neutral
به این ترتیب به جای این که به تکتک فایلها برویم، سادهترین راهحل این بود که یک تابع مرتبه بالا بسازیم. با این کار میتوانیم نقشها را معکوس کنیم به طوری که به جای جستجو به دنبال فایلها، toast-ها به تابع مرتبه بالا هدایت شوند. به این صورت کدهای موجود در فایلها ویرایش یا تغییر نمییابند. کارکرد آنها همچنان به طور معمول است و امکان حذف این Toast-های تکراری را بدون این که کد غیر ضروری بنویسیم پیدا میکنیم. این امر موجب صرفهجویی در زمان میشود. به این ترتیب به پایان این مقاله میرسیم. امیدواریم از مطالعه این راهنمای کاربردی در مورد ترفندهای بهبود اپلیکیشنهای ریاکت لذت برده باشید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- ۱۰ نکته و ترفند برای بهبود برنامه نویسی React — راهنمای کاربردی
- هشت ترفند مفید برای توسعه اپلیکیشن های React — راهنمای کاربردی
- طراحی احراز هویت مقدماتی با React — به زبان ساده
==