۸ روش برای بهبود اپلیکیشن ری اکت — راهنمای کاربردی

۵ بازدید
آخرین به‌روزرسانی: ۲۶ اسفند ۱۳۹۸
زمان مطالعه: ۱۳ دقیقه

برخی اوقات در زمان ساخت اپلیکیشن‌های راکت ممکن است برخی فرصت‌ها برای بهبود اپلیکیشن خود را از دست بدهیم، زیرا زمانی که اپلیکیشن سریع کار می‌کند، به اشتباه تصور می‌کنیم که نقصی ندارد. در چنین موقعیت‌هایی ممکن است تصور کنیم که چون خروجی پروژه برای ما نرمال به نظر می‌رسد، کاربران نیز چنین وضعیتی درباره آن خواهند داشت. زمانی که ذهن این طرز فکر را پیش بگیرد، ممکن است موضوعاتی که می‌توان کد را برای دریافت نتیجه بهتر بهبود بخشید را نادیده بگیرد. در این راهنما با 8 روش برای بهبود اپلیکیشن ری اکت آشنا خواهیم شد.

1. موجودیت‌ها را حفظ کنید

نخستین روشی که برای ارتقای کیفیت اپلیکیشن ری‌اکت وجود دارد، توجه به «موجودیت‌ها» (Identities) است. باید به خاطر داشته باشید که می‌توانید متغیرها و تابع‌ها را درون React.useMemo قرار دهید تا به آن‌ها امکان خاطرسپاری خودشان را بدهید. بدین ترتیب ری‌اکت آن‌ها را برای رندرهای آتی حفظ می‌کند.

در غیر این صورت اگر آن‌ها را به حافظه نسپارید، ارجاعشان در رندرهای آتی از بین می‌رود. بدین ترتیب هزینه سربار مضاعفی در نتیجه عملیات غیر ضروری ایجاد می‌شود. برای نمونه فرض کنید می‌خواهیم یک قلاب سفارشی بسازیم که فهرستی از urls به عنوان آرگومان می‌گیرد به طوری که می‌تواند آن‌ها را در یک آرایه از promise-ها تجمیع کند که با Promise.all به صورت resolved درمی‌آیند.

نتایج درون حالت درج می‌شوند و به محض پایان، به کامپوننت App ارسال می‌شوند. این Promise-ها روی آرایه urls نگاشت می‌شوند که شامل چهار URL مختلف است که باید واکشی شوند:

import React from 'react'
import axios from 'axios'

export const useApp = ({ urls }) => {
  const [results, setResults] = React.useState(null)

  const promises = urls.map(axios.get)

  React.useEffect(() => {
    Promise.all(promises).then(setResults)
  }, [])

  return { results }
}

const App = () => {
  const urls = [
    'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=a',
    'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=u',
    'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=y',
    'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=z',
  ]

  useApp({ urls })

  return null
}

export default App

وظیفه ما این است که داده‌ها را از لینک‌ها واکشی کنیم به این ترتیب به صورت ایده‌آل تنها چهار درخواست باید ارسال شوند. اما اگر نگاهی به زبانه Network درون مرورگر کروم بیندازیم می‌بینیم که در عمل هشت درخواست فرستاده می‌شوند. دلیل این وضعیت آن است که آرگومان urls موجودیت خود را مانند قبل حفظ نمی‌کند، زیرا زمانی که App رندر مجدد می‌شود، هر بار یک وهله جدید از آرایه می‌سازد و از این رو ری‌اکت با آن مانند یک مقدار تغییریافته رفتار می‌کند.

بهبود اپلیکیشن ری اکت

برنامه‌های رایانه‌ای گاهی اوقات فکر می‌کنند که هوشمندی بیشتری از ما دارند و چنین رفتاری در پیش می‌گیرند. برای رفع این مشکل می‌توانیم از React.useMemo استفاده کنیم، به طوری که تا وقتی آرایه شامل url-ها تغییری نیافته است، آرایه promise-ها خودش را در هر بار رندر، مجدداً محاسبه نمی‌کند. بدین ترتیب کد خود را با به‌کارگیری این مفهوم به صورت زیر بازسازی می‌کنیم:

const useApp = ({ urls }) => {
  const [results, setResults] = React.useState(null)

  const promises = urls.map((url) => axios.get(url))

  React.useEffect(() => {
    Promise.all(promises).then(setResults)
  }, [])

  return { results }
}

const App = () => {
  const urls = React.useMemo(() => {
    return [
      'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=a',
      'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=u',
      'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?terms=y',
    ]
  }, [])

  const { results } = useApp({ urls })

  return null
}

اکنون اگر کد فوق را اجرا کنیم، متوجه می‌شویم که همچنان هشت درخواست ارسال می‌شوند. دلیل این وضعیت آن است که گرچه ارائه urls را حفظ کرده‌ایم، اما همچنان باید متغیرهای promises را نیز درون قلاب ذخیره کنیم، زیرا آن‌ها نیز هر زمان که قلاب اجرا می‌شوند وهله‌ای از خود می‌سازند:

const promises = React.useMemo(() => {
  return urls.map((url) => axios.get(url))
}, [urls])

به این ترتیب کد باید اکنون در هر بار اجرا تنها چهار درخواست ارسال کند:

بهبود اپلیکیشن ری اکت

2. ادغام props در فرزندان

گاهی اوقات ممکن است با موقعیتی مواجه شویم که باید یک prop قبل از رندر با فرزندان ادغام شود. ری‌اکت امکان دیدن props-های هر عنصر خودش و عناصر دیگر را فراهم ساخته است و برای نمونه key آن را عرضه می‌کند. می‌توان عنصر فرزند را درون یک کامپوننت جدید قرار دارد و props-های جدید را از آنجا تزریق کرد و یا می‌توان prop-های جدید را با استفاده از متد ادغام کرد.

برای نمونه فرض کنید یک کامپوننت App داریم که از یک قلاب useModal استفاده می‌کند و برخی ابزارهای کارآمد برای مدیریت مدل‌ها به وسیله ارائه کنترل‌هایی مانند open, close و opened در اختیار ما قرار می‌دهد. می‌خواهیم این prop را به کامپوننت VisibilityControl ارسال کنیم، زیرا باید کارکردهای بیشتری را پیش از ارسال داده‌های modal به فرزندان ارائه کنیم:

import React from 'react'

const UserContext = React.createContext({
  user: {
    firstName: 'Kelly',
    email: 'frogLover123@gmail.com',
  },
  activated: true,
})

const VisibilityControl = ({ children, opened, close }) => {
  const ctx = React.useContext(UserContext)
  return React.cloneElement(children, {
    opened: ctx.activated ? opened : false,
    onClick: close,
  })
}

export const useModal = ({ urls } = {}) => {
  const [opened, setOpened] = React.useState(false)
  const open = () => setOpened(true)
  const close = () => setOpened(false)

  return {
    opened,
    open,
    close,
  }
}

const App = ({ children }) => {
  const modal = useModal()

  return (
    <div>
      <button type="button" onClick={modal.opened ? modal.close : modal.open}>
        {modal.opened ? 'Close' : 'Open'} the Modal
      </button>
      <VisibilityControl {...modal}>{children}</VisibilityControl>
    </div>
  )
}

const Window = ({ opened }) => {
  if (!opened) return null
  return (
    <div style={{ border: '1px solid teal', padding: 12 }}>
      <h2>I am a window</h2>
    </div>
  )
}

export default () => (
  <App>
    <Window />
  </App>
)

VisibilityControl مطمئن می‌شود که پیش از صادر کردن اجازه استفاده نرمال فرزندان از opened، مقدار activated به صورت true در آمده است. اگر این وضعیت در مسیر امن استفاده شود، VisibilityControl کارکرد جلوگیری از کاربران غیرفعال شده از دسترسی به محتوای امن را فراهم می‌سازد.

3. ترکیب ردیوسرها برای ساخت یک ردیوسر بزرگ

این بهینه‌سازی در مواردی به کار می‌آید که لازم باشد دو یا چند Reducer در اپلیکیشن با هم ترکیب شوند تا یک ردیوسر بزرگ‌تر تشکیل دهند. این رویکرد مشابه طرز کار combineReducers در React-Redux است. فرض کنید می‌خواهیم یک اپلیکیشن میکروسرویس بزرگ بسازیم و از ابتدا در نظر گرفته‌ایم هر بخش در اپلیکیشن مسئول «چارچوب/حالت» (context/state) خودش باشد.

اما در ادامه به این نتیجه می‌رسیم که حالت‌های مختلف باید در یک حالت بزرگ‌تر ادغام شوند تا بتوانیم همه آن‌ها را در محیط یکسانی مدیریت کنیم. بدین ترتیب فایل‌های authReducer.js, ownersReducer.js و frogsReducer.js را داریم که باید با هم ترکیب شوند:

  • فایل authReducer.js
const authReducer = (state, action) => {
  switch (action.type) {
    case 'set-authenticated':
      return { ...state, authenticated: action.authenticated }
    default:
      return state
  }
}

export default authReducer
  • فایل ownersReducer.js
const ownersReducer = (state, action) => {
  switch (action.type) {
    case 'add-owner':
      return {
        ...state,
        profiles: [...state.profiles, action.owner],
      }
    case 'add-owner-id':
      return { ...state, ids: [...state.ids, action.id] }
    default:
      return state
  }
}

export default ownersReducer
  • فایل frogsReducer.js
const frogsReducer = (state, action) => {
  switch (action.type) {
    case 'add-frog':
      return {
        ...state,
        profiles: [...state.profiles, action.frog],
      }
    case 'add-frog-id':
      return { ...state, ids: [...state.ids, action.id] }
    default:
      return state
  }
}

export default frogsReducer

آن‌ها را در فایل اصلی خود ایمپورت می‌کنیم و ساختار حالت را به صورت زیر تعریف می‌نماییم:

  • فایل App.js
import React from 'react'
import authReducer from './authReducer'
import ownersReducer from './ownersReducer'
import frogsReducer from './frogsReducer'

const initialState = {
  auth: {
    authenticated: false,
  },
  owners: {
    profiles: [],
    ids: [],
  },
  frogs: {
    profiles: [],
    ids: [],
  },
}

function rootReducer(state, action) {
  return {
    auth: authReducer(state.auth, action),
    owners: ownersReducer(state.owners, action),
    frogs: frogsReducer(state.frogs, action),
  }
}

const useApp = () => {
  const [state, dispatch] = React.useReducer(rootReducer, initialState)

  const addFrog = (frog) => {
    dispatch({ type: 'add-frog', frog })
    dispatch({ type: 'add-frog-id', id: frog.id })
  }

  const addOwner = (owner) => {
    dispatch({ type: 'add-owner', owner })
    dispatch({ type: 'add-owner-id', id: owner.id })
  }

  React.useEffect(() => {
    console.log(state)
  }, [state])

  return {
    ...state,
    addFrog,
    addOwner,
  }
}

const App = () => {
  const { addFrog, addOwner } = useApp()

  const onAddFrog = () => {
    addFrog({
      name: 'giant_frog123',
      id: 'jakn39eaz01',
    })
  }

  const onAddOwner = () => {
    addOwner({
      name: 'bob_the_frog_lover',
      id: 'oaopskd2103z',
    })
  }

  return (
    <>
      <div>
        <button type="button" onClick={onAddFrog}>
          add frog
        </button>
        <button type="button" onClick={onAddOwner}>
          add owner
        </button>
      </div>
    </>
  )
}
export default () => <App />

سپس می‌توانیم با قلاب‌ها به صورت معمول کار کنیم، dispatch را فراخوانی کنیم، type تطبیق یافته را ارسال کنیم و آرگومان‌ها را به ردیوسر اختصاصی یافته بفرستیم. مهم‌ترین بخش که باید توجه کنیم، rootReducer است:

function rootReducer(state, action) {
  return {
    auth: authReducer(state.auth, action),
    owners: ownersReducer(state.owners, action),
    frogs: frogsReducer(state.frogs, action),
  }
}

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-هایی که باید به حافظه سپرده شوند بهره بگیرید.

import React from 'react'

function useWhyDidYouUpdate(name, props) {
  // Get a mutable ref object where we can store props ...
  // ... for comparison next time this hook runs.
  const previousProps = useRef()

  useEffect(() => {
    if (previousProps.current) {
      // Get all keys from previous and current props
      const allKeys = Object.keys({ ...previousProps.current, ...props })
      // Use this object to keep track of changed props
      const changesObj = {}
      // Iterate through keys
      allKeys.forEach((key) => {
        // If previous is different from current
        if (previousProps.current[key] !== props[key]) {
          // Add to changesObj
          changesObj[key] = {
            from: previousProps.current[key],
            to: props[key],
          }
        }
      })

      // If changesObj not empty then output to console
      if (Object.keys(changesObj).length) {
        console.log('[why-did-you-update]', name, changesObj)
      }
    }

    // Finally update previousProps with current props for next hook call
    previousProps.current = props
  })
}

export default useWhyDidYouUpdate

شیوه استفاده از آن نیز چنین است:

const MyComponent = (props) => {
  useWhyDidYouUpdate('MyComponent', props)

  return <Dashboard {...props} />
}

8. تابع‌های مرتبه بالا

یکی از بزرگترین مزایای تابع‌های مرتبه بالا این است که وقتی به صورت صحیحی استفاده شوند، زمان زیادی از شما و تیمتان صرفه‌جویی می‌کنند. برای نمونه می‌توان از React-Toastify (+) برای نمایش نوتیفیکیشن‌ها بهره گرفت. از آن می‌توان در هر جا استفاده کرد. به علاوه امکان اجرای سریع تصمیم‌های UX را فراهم می‌سازد.

اگر نیاز به مدیریت یک خطا باشد، کافی است یک نوتیفیکیشن Toast نمایش دهید. با این حال شاید متوجه شوید که وقتی اپلیکیشن بزرگ‌تر می‌شود، و سطح پیچیدگی بالاتر می‌رود، نوتیفیکیشن‌های Toast بیش از حد زیاد می‌شوند.

این موضوع به خودی خود اشکالی ندارد، اما روشی برای جلوگیری از تکرار وجود ندارد. این بدان معنی است که برخی نوتیفیکیشن‌های Toast چندین بار روی صفحه ظاهر می‌شوند و حتی برخی دقیقاً مانند موارد قبل خود هستند.

بنابراین در نهایت باید از یک API کمک گرفت که این کتابخانه برای کمک به حذف نوتیفیکیشن‌های فعال از طریق id با استفاده از ()toast.dismiss ارائه کرده است. برای توضیح بیشتر این بخش بهتر است ابتدا یک فایل را نشان دهیم که Toast-ها را از آنجا ایمپورت می‌کنیم:

import React from 'react'
import { GoCheck, GoAlert } from 'react-icons/go'
import { FaInfoCircle } from 'react-icons/fa'
import { MdPriorityHigh } from 'react-icons/md'
import { toast } from 'react-toastify'

/*
  Calling these toasts most likely happens in the UI 100% of the time.
  So it is safe to render components/elements as toasts.
*/

// Keeping all the toast ids used throughout the app here so we can easily manage/update over time
// This used to show only one toast at a time so the user doesn't get spammed with toast popups
export const toastIds = {
  // APP
  internetOnline: 'internet-online',
  internetOffline: 'internet-offline',
  retryInternet: 'internet-retry',
}

// Note: this toast && is a conditional escape hatch for unit testing to avoid an error.
const getDefaultOptions = (options) => ({
  position: toast && toast.POSITION.BOTTOM_RIGHT,
  ...options,
})

const Toast = ({ children, success, error, info, warning }) => {
  let componentChildren
  // 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
  if (!React.isValidElement(children) && typeof children !== 'string') {
    componentChildren = 'An error occurred'
  } else {
    componentChildren = children
  }
  let Icon = GoAlert
  if (success) Icon = GoCheck
  if (error) Icon = GoAlert
  if (info) Icon = FaInfoCircle
  if (warning) Icon = MdPriorityHigh
  return (
    <div style={{ paddingLeft: 10, display: 'flex', alignItems: 'center' }}>
      <div style={{ width: 30, height: 30 }}>
        <Icon style={{ color: '#fff', width: 30, height: 30 }} />
      </div>
      <div style={{ padding: 8, display: 'flex', alignItems: 'center' }}>
        &nbsp;&nbsp;
        <span style={{ color: '#fff' }}>{componentChildren}</span>
      </div>
    </div>
  )
}

export const success = (msg, opts) => {
  return toast.success(<Toast success>{msg}</Toast>, {
    className: 'toast-success',
    ...getDefaultOptions(),
    ...opts,
  })
}

export const error = (msg, opts) => {
  return toast.error(<Toast error>{msg}</Toast>, {
    className: 'toast-error',
    ...getDefaultOptions(),
    ...opts,
  })
}

export const info = (msg, opts) => {
  return toast.info(<Toast info>{msg}</Toast>, {
    className: 'toast-info',
    ...getDefaultOptions(),
    ...opts,
  })
}

export const warn = (msg, opts) => {
  return toast.warn(<Toast warning>{msg}</Toast>, {
    className: 'toast-warn',
    ...getDefaultOptions(),
    ...opts,
  })
}

export const neutral = (msg, opts) => {
  return toast(<Toast warning>{msg}</Toast>, {
    className: 'toast-default',
    ...getDefaultOptions(),
    ...opts,
  })
}

می‌دانیم که این کد چندان خوب نیست، اما در ادامه آن را اصلاح می‌کنیم. این کدی است که در کامپوننت مجزا داریم تا بررسی کنیم آیا Toast قبلی پیش‌تر روی صفحه بوده است یا نه و اگر چنین باشد، تلاش می‌کنیم تا آن را حذف کنیم و Toast جدید را نمایش دهیم.

import { toast } from 'react-toastify'
import {
  info as toastInfo,
  success as toastSuccess,
  toastIds,
} from 'util/toast'
import App from './App'

const Root = () => {
  const onOnline = () => {
    if (toast.isActive(toastIds.internetOffline)) {
      toast.dismiss(toastIds.internetOffline)
    }
    
    if (toast.isActive(toastIds.retryInternet)) {
      toast.dismiss(toastIds.retryInternet)
    }
    if (!toast.isActive(toastIds.internetOnline)) {
      toastSuccess('You are now reconnected to the internet.', {
        position: 'bottom-center',
        toastId: toastIds.internetOnline,
      })
    }
  }

  const onOffline = () => {
    if (!toast.isActive(toastIds.internetOffline)) {
      toastInfo('You are disconnected from the internet right now.', {
        position: 'bottom-center',
        autoClose: false,
        toastId: toastIds.internetOffline,
      })
    }
  }
  
  useInternet({ onOnline, onOffline })
  
  return <App />
}

این کد به خوبی کار می‌کند اما toast-های دیگری در سراسر اپلیکیشن داریم که باید به همین روش اصلاح شوند. بدن ترتیب باید به هر فایل که یک نوتیفیکیشن toast نمایش می‌دهد برویم و موارد تکراری را حذف کنیم.

زمانی که در این عصر صحبت از سر زدن به تک‌تک فایل‌ها و اصلاح آن‌ها می‌شود باید متوجه شویم که یک جای کار می‌لنگد. بنابراین فایل util/toast.js را بررسی می‌کنیم و آن را طوری بازنویسی می‌کنیم که مشکل ما را حل کند. فایل پس از اصلاح به صورت زیر درمی‌آید:

  • فایل src/util/toast.js
import React, { isValidElement } from 'react'
import isString from 'lodash/isString'
import isFunction from 'lodash/isFunction'
import { GoCheck, GoAlert } from 'react-icons/go'
import { FaInfoCircle } from 'react-icons/fa'
import { MdPriorityHigh } from 'react-icons/md'
import { toast } from 'react-toastify'

/*
  Calling these toasts most likely happens in the UI 100% of the time.
  So it is safe to render components/elements as toasts.
*/

// Keeping all the toast ids used throughout the app here so we can easily manage/update over time
// This used to show only one toast at a time so the user doesn't get spammed with toast popups
export const toastIds = {
  // APP
  internetOnline: 'internet-online',
  internetOffline: 'internet-offline',
  retryInternet: 'internet-retry',
}

// Note: this toast && is a conditional escape hatch for unit testing to avoid an error.
const getDefaultOptions = (options) => ({
  position: toast && toast.POSITION.BOTTOM_RIGHT,
  ...options,
})

const Toast = ({ children, success, error, info, warning }) => {
  let componentChildren
  // 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
  if (!isValidElement(children) && !isString(children)) {
    componentChildren = 'An error occurred'
  } else {
    componentChildren = children
  }
  let Icon = GoAlert
  if (success) Icon = GoCheck
  if (error) Icon = GoAlert
  if (info) Icon = FaInfoCircle
  if (warning) Icon = MdPriorityHigh
  return (
    <div style={{ paddingLeft: 10, display: 'flex', alignItems: 'center' }}>
      <div style={{ width: 30, height: 30 }}>
        <Icon style={{ color: '#fff', width: 30, height: 30 }} />
      </div>
      <div style={{ padding: 8, display: 'flex', alignItems: 'center' }}>
        &nbsp;&nbsp;
        <span style={{ color: '#fff' }}>{componentChildren}</span>
      </div>
    </div>
  )
}

const toaster = (function() {
  // Attempt to remove a duplicate toast if it is on the screen
  const ensurePreviousToastIsRemoved = (toastId) => {
    if (toastId) {
      if (toast.isActive(toastId)) {
        toast.dismiss(toastId)
      }
    }
  }
  // Try to get the toast id if provided from options
  const attemptGetToastId = (msg, opts) => {
    let toastId
    if (opts && isString(opts.toastId)) {
      toastId = opts.toastId
    } else if (isString(msg)) {
      // We'll just make the string the id by default if its a string
      toastId = msg
    }
    return toastId
  }
  const handleToast = (type) => (msg, opts) => {
    const toastFn = toast[type]
    if (isFunction(toastFn)) {
      const toastProps = {}
      let className = ''
      const additionalOptions = {}
      const toastId = attemptGetToastId(msg, opts)
      if (toastId) additionalOptions.toastId = toastId
      // Makes sure that the previous toast is removed by using the id, if its still on the screen
      ensurePreviousToastIsRemoved(toastId)
      // Apply the type of toast and its props
      switch (type) {
        case 'success':
          toastProps.success = true
          className = 'toast-success'
          break
        case 'error':
          toastProps.error = true
          className = 'toast-error'
          break
        case 'info':
          toastProps.info = true
          className = 'toast-info'
          break
        case 'warn':
          toastProps.warning = true
          className - 'toast-warn'
          break
        case 'neutral':
          toastProps.warning = true
          className - 'toast-default'
          break
        default:
          className = 'toast-default'
          break
      }
      toastFn(<Toast {...toastProps}>{msg}</Toast>, {
        className,
        ...getDefaultOptions(),
        ...opts,
        ...additionalOptions,
      })
    }
  }
  return {
    success: handleToast('success'),
    error: handleToast('error'),
    info: handleToast('info'),
    warn: handleToast('warn'),
    neutral: handleToast('neutral'),
  }
})()

export const success = toaster.success
export const error = toaster.error
export const info = toaster.info
export const warn = toaster.warn
export const neutral = toaster.neutral

به این ترتیب به جای این که به تک‌تک فایل‌ها برویم، ساده‌ترین راه‌حل این بود که یک تابع مرتبه بالا بسازیم. با این کار می‌توانیم نقش‌ها را معکوس کنیم به طوری که به جای جستجو به دنبال فایل‌ها، toast-ها به تابع مرتبه بالا هدایت شوند. به این صورت کدهای موجود در فایل‌ها ویرایش یا تغییر نمی‌یابند. کارکرد آن‌ها همچنان به طور معمول است و امکان حذف این Toast-های تکراری را بدون این که کد غیر ضروری بنویسیم پیدا می‌کنیم. این امر موجب صرفه‌جویی در زمان می‌شود. به این ترتیب به پایان این مقاله می‌رسیم. امیدواریم از مطالعه این راهنمای کاربردی در مورد ترفندهای بهبود اپلیکیشن‌های ری‌اکت لذت برده باشید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
better-programming

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *