React’te Büyük Bileşenleri Basitleştirmenin Gücü – JSManifest

React'te Büyük Bileşenleri Basitleştirmenin Gücü – JSManifest

Büyük bileşenlere sahip olmak her zaman kötü bir şey değildirancak bileşenleri daha da basitleştirebileceğiniz fırsatlardan yararlanmak iyi bir uygulamadır. özellikle ek faydalar sağladığında.

Büyük bir bileşeniniz olduğunda, Yapabilmek dezavantajlı hale gelir çünkü bir bileşen büyüdükçe bakımı ve zaman içinde okunması daha zor hale gelir.

Aşağıdaki bu bileşene bakalım ve basitleştirmenin neden daha iyi olacağının nedenlerini görelim.

(Bu, bir üretim uygulamasından gelen koddur, yani bu aslında bir gerçek dünya örnek)

Bileşen SidebarSection aşağıda bazı sahne malzemeleri alır props.ids dizeler olarak bir öğe kimlikleri dizisidir ve props.items her bir öğenin özelliklerini kullanarak kenar çubuğu öğelerini eşleyen bir nesnedir. id anahtar olarak. Kenar çubuğu öğelerini oluşturmak için bu sahne öğelerini kullanır:

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import List from '@material-ui/core/List'
import Divider from '@material-ui/core/Divider'
import ListSubheader from '@material-ui/core/ListSubheader'
import { EDIT_NOTEBOOK, DELETE_NOTEBOOK } from 'features/toplevel'
import { selectSelected } from 'features/sidebar'
import SidebarContext from './SidebarContext'
import SidebarItem from './SidebarItem'

function SidebarSection({ id: sectionId, ids, items, depth, expanded }) {
  const ctx = React.useContext(SidebarContext)
  const selectedId = useSelector(selectSelected)

  if (!ctx) return null

  return (
    <List dense={depth > 0} disablePadding>
      {ids.map((id: string, itemIndex: number) => {
        const key = `SidebarSection_${id}_item${itemIndex}`
        const item = items[id]

        switch (item.type) {
          case 'divider':
            return <Divider key={key} style={{ padding: 0, margin: 0 }} />
          case 'label':
            return (
              <ListSubheader
                key={key}
                style={{
                  transform: expanded ? undefined : 'scale(0.55)',
                  textOverflow: 'ellipsis',
                  overflow: 'hidden',
                  userSelect: 'none',
                }}
                disableGutters={!expanded}
              >
                {item.label}
              </ListSubheader>
            )
          case 'notebook': {
            
            const onHoverAction = (action: any) => {
              if (action.Icon) {
                const notebook = item.data
                if (notebook) {
                  action.onClick = ctx.createHoverControlsActionOnClick({
                    context:
                      action.name === 'edit'
                        ? EDIT_NOTEBOOK
                        : action.name === 'delete'
                        ? DELETE_NOTEBOOK
                        : '',
                    data:
                      action.name === 'edit'
                        ? item
                        : action.name === 'delete'
                        ? {
                            id: notebook.id,
                            title: notebook.info.title,
                            description: notebook.info.description,
                            isEncrypt: notebook.isEncrypt,
                            created_at: notebook.created_at,
                            modified_at: notebook.modified_at,
                          }
                        : null,
                  })
                }
              }
            }

            return (
              <SidebarItem
                key={key}
                sectionId={sectionId}
                depth={depth}
                item={ctx.unserializeItem(item, { onHoverAction })}
                isSelected={item.id === selectedId}
                {...ctx}
              />
            )
          }
          case 'static':
            return (
              <SidebarItem
                key={key}
                sectionId={sectionId}
                depth={depth}
                item={ctx.unserializeItem(item)}
                isSelected={item.id === selectedId}
                {...ctx}
              />
            )
          default:
            return null
        }
      })}
    </List>
  )
}

Bileşen aslında görünmüyor o kötü, ancak bileşeni her düzenlediğimizde düşünürseniz, anlamamız gerekir her çünkü bir şeyi değiştirmenin bileşenin diğer kısımlarını bozup bozamayacağını bilmiyoruz.

Bir örnek onHoverAction anahtar durumunda oluşturulan işlev. bileşenimizi gereksiz yere şişiriyor ve uygulanmasına bağlı olarak SidebarItem neden olma potansiyeline sahiptir sonsuz döngü çünkü bileşen her yeniden oluşturulduğunda buna başvuru yeniden oluşturuluyor.

Ayrıca tüm bu bileşeni birim testlerine karşı biraz daha hassas hale getiriyor çünkü SidebarSection uygulama detaylarından sorumlu olacak bileşen onHoverAction. Birim testlerimizde, uygulama detaylarının farkında olmalıyız. onHoverAction olduğunu test ettiğimizde SidebarSection bileşenin doğru şekilde davranması pek mantıklı değil (bu, sözdizimi hataları gibi şeylere dikkat etmek anlamına gelir, çünkü işlevin içindeki bir yazım hatası, SidebarSection ve bileşeni kötü bir iş çıkardığı için suçlarız)

Bunu basitçe dışarı çıkararak basitleştirebiliriz, böylece artık suçu bileşene yüklemek zorunda kalmayacağız:

function onHoverAction(item, createOnClick) {
  return (action) => {
    if (action.Icon) {
      const notebook = item.data
      if (notebook) {
        action.onClick = ctx.createHoverControlsActionOnClick({
          context:
            action.name === 'edit'
              ? EDIT_NOTEBOOK
              : action.name === 'delete'
              ? DELETE_NOTEBOOK
              : '',
          data:
            action.name === 'edit'
              ? item
              : action.name === 'delete'
              ? {
                  id: notebook.id,
                  title: notebook.info.title,
                  description: notebook.info.description,
                  isEncrypt: notebook.isEncrypt,
                  created_at: notebook.created_at,
                  modified_at: notebook.modified_at,
                }
              : null,
        })
      }
    }
  }
}

function SidebarSection({ id: sectionId, ids, items, depth, expanded }) {
  const ctx = React.useContext(SidebarContext)
  const selectedId = useSelector(selectSelected)

  if (!ctx) return null

  return (
    <List dense={depth > 0} disablePadding>
      {ids.map((id: string, itemIndex: number) => {
        const key = `SidebarSection_${id}_item${itemIndex}`
        const item = items[id]

        switch (item.type) {
          case 'divider':
            return <Divider key={key} style={{ padding: 0, margin: 0 }} />
          case 'label':
            return (
              <ListSubheader
                key={key}
                style={{
                  transform: expanded ? undefined : 'scale(0.55)',
                  textOverflow: 'ellipsis',
                  overflow: 'hidden',
                  userSelect: 'none',
                }}
                disableGutters={!expanded}
              >
                {item.label}
              </ListSubheader>
            )
          case 'notebook': {
            return (
              <SidebarItem
                key={key}
                sectionId={sectionId}
                depth={depth}
                item={ctx.unserializeItem(item, {
                  onHoverAction: onHoverAction(
                    item,
                    ctx.createHoverControlsActionOnClick,
                  ),
                })}
                isSelected={item.id === selectedId}
                {...ctx}
              />
            )
          }
          case 'static':
            return (
              <SidebarItem
                key={key}
                sectionId={sectionId}
                depth={depth}
                item={ctx.unserializeItem(item)}
                isSelected={item.id === selectedId}
                {...ctx}
              />
            )
          default:
            return null
        }
      })}
    </List>
  )
}

Tek yaptığımız, işlevi başka bir yere taşımaktı ve bu, bize neredeyse hiç ekstra çaba harcamadan büyük faydalar sağlıyor:

  1. İşleve yapılan başvuru aynı kalacaktır.
  2. bu SidebarSection artık uygulama konusunda endişelenmesine gerek olmadığı için huzurlu bir hayat yaşayabilir onHoverAction doğru şekilde. Tek yapması gereken argümanları iletmek. onHoverAction bekler.
  3. Artık birim testi yapabiliriz onHoverAction ayrı olarak, çünkü dışa aktarma olarak mevcuttur. Bunun beklenen şekilde çalışıp çalışmadığını görmek ister misiniz? Basitçe içe aktarın, üç parametreyi iletin ve ne döndürdüğünü görün.
  4. SidebarSection okunması ve bakımı daha kolay hale gelir.

Aslında basitleştirmek için yapabileceğimiz tek şey bu değil. Bileşeni daha da basitleştirmek için başka bir fırsatımız var. Bu iki anahtar bloğunda yinelenen kod var:

case 'notebook':
  return (
    <SidebarItem
      key={key}
      sectionId={sectionId}
      depth={depth}
      item={ctx.unserializeItem(item, {
        onHoverAction: onHoverAction(
          action,
          item,
          ctx.createHoverControlsActionOnClick,
        ),
      })}
      isSelected={item.id === selectedId}
      {...ctx}
    />
  )
case 'static':
  return (
    <SidebarItem
      key={key}
      sectionId={sectionId}
      depth={depth}
      item={ctx.unserializeItem(item)}
      isSelected={item.id === selectedId}
      {...ctx}
    />
  )

Aslında olduğu gibi bırakmak pek sorun olmayabilir. Bununla birlikte, bu kodu okuyan herhangi bir geliştiricinin, o kadar farklı olmadıklarından %100 emin olmak için her bir pervaneyi satır satır okumak zorunda kalacağından eminim.

Sonuçta, ideal olarak, benzer görünen kodların ayrılmasının önemli nedenlerinin olduğuna inanmak isteriz, peki neden dünyada bunlar ayrıldı? Bizim durumumuzda gerçekten çok iyi bir sebep yoktu, bu yüzden gelecekteki geliştiricilerin bu bileşende hata ayıklamaya çalışırken bu garip senaryoya yakalanmamaları için bunu basitleştirmek iyi bir fikirdir.

Bunu basitçe yaparak basitleştirebiliriz:

case 'notebook':
case 'static':
  return (
    <SidebarItem
      key={key}
      sectionId={sectionId}
      depth={depth}
      item={ctx.unserializeItem(item, item.type === 'notebook' ? {
        onHoverAction: onHoverAction(
          action,
          item,
          ctx.createHoverControlsActionOnClick,
        ),
      } : undefined)}
      isSelected={item.id === selectedId}
      {...ctx}
    />
  )

Bunu basitçe yapmak birkaç önemli fayda sağladı:

  1. Yinelenen kodu ortadan kaldırdık.
  2. Kodun yalnızca bir “kopyasına” bakmamız gerektiğinden okuması artık daha kolay.
  3. Kendi kendini belgeleyen kod (temelde bize, türdeki öğelerin “not defteri” ve “statik” neredeyse tamamen aynıdır ve türe sahip öğelerin yanı sıra farklılıkları hakkında endişelenmenize gerek yoktur. 'notebook' tıklanabilir ve 'static' değil)

Aşırı düşünmekle geri tepmeyi basitleştirirken

Şimdi muhtemelen “basitleştirebileceğimiz” başka bir şey var. Switch kasalarımız biraz daha kısalmış olsa da, bakmak biraz çirkin görünüyor. bizimki böyle SidebarSection bileşen, uygulanan basitleştirme değişiklikleriyle şimdiki gibi görünüyor:

function SidebarSection({ id: sectionId, ids, items, depth, expanded }) {
  const ctx = React.useContext(SidebarContext)
  const selectedId = useSelector(selectSelected)

  if (!ctx) return null

  return (
    <List dense={depth > 0} disablePadding>
      {ids.map((id: string, itemIndex: number) => {
        const key = `SidebarSection_${id}_item${itemIndex}`
        const item = items[id]

        switch (item.type) {
          case 'divider':
            return <Divider key={key} style={{ padding: 0, margin: 0 }} />
          case 'label':
            return (
              <ListSubheader
                key={key}
                style={{
                  transform: expanded ? undefined : 'scale(0.55)',
                  textOverflow: 'ellipsis',
                  overflow: 'hidden',
                  userSelect: 'none',
                }}
                disableGutters={!expanded}
              >
                {item.label}
              </ListSubheader>
            )
          case 'notebook':
          case 'static':
            return (
              <SidebarItem
                key={key}
                sectionId={sectionId}
                depth={depth}
                item={ctx.unserializeItem(
                  item,
                  item.type === 'notebook'
                    ? {
                        onHoverAction: onHoverAction(
                          action,
                          item,
                          ctx.createHoverControlsActionOnClick,
                        ),
                      }
                    : undefined,
                )}
                isSelected={item.id === selectedId}
                {...ctx}
              />
            )

          default:
            return null
        }
      })}
    </List>
  )
}

Burada bulabileceğimiz bir sorun, her bir öğenin oluşturma bloğuna biraz fazla sorumluluk vererek, doğru bileşenleri doğru bileşenlere geçirmekten sorumlu hale getirmemizdir.

Bu şekilde düşünmek yerine şu şekilde yeniden yazmak daha iyi olabilir:

function getProps({ item, expanded, sectionId, selectedId, depth, ctx }) {
  switch (item.type) {
    case 'divider':
      return { style: { padding: 0, margin: 0 } }
    case 'label':
      return {
        style: {
          transform: expanded ? undefined : 'scale(0.55)',
          textOverflow: 'ellipsis',
          overflow: 'hidden',
          userSelect: 'none',
        },
        disableGutters: !expanded,
      }
    case 'notebook':
    case 'static':
      return {
        sectionId,
        depth,
        item: ctx.unserializeItem(
          item,
          item.type === 'notebook'
            ? {
                onHoverAction: onHoverAction(
                  item,
                  ctx.createHoverControlsActionOnClick,
                ),
              }
            : undefined,
        ),
        isSelected: item.id === selectedId,
        ...ctx,
      }
    default:
      return undefined
  }
}

function SidebarSection({ id: sectionId, ids, items, depth, expanded }) {
  const ctx = React.useContext(SidebarContext)
  const selectedId = useSelector(selectSelected)

  if (!ctx) return null

  return (
    <List dense={depth > 0} disablePadding>
      {ids.map((id: string, itemIndex: number) => {
        const key = `SidebarSection_${id}_item${itemIndex}`
        const item = items[id]

        let Component

        if (item.type === 'divider') {
          Component = Divider
        } else if (item.type === 'label') {
          Component = ListSubheader
        } else if (['notebook', 'static'].includes(item.type)) {
          Component = SidebarItem
        } else {
          return null
        }

        return (
          <Component
            key={key}
            {..getProps(
              item,
              expanded,
              sectionId,
              selectedId,
              depth,
              ctx
            })}
          />
        )
      })}
    </List>
  )
}

Şimdi daha da basitleştirdik SidebarSection sadece aramak için sorumluluk almak getProps ilgili aksesuarları sağlamak ve doğru olanı atamak Component dayalı item.type. Artık birim testi yapabiliriz getProps göre doğru aksesuarları iade ettiklerinden emin olmak için item.type.

Bu, tepki kodumuzu basitleştirmek için iyi bir girişim miydi? Getirilen avantajlara karşı ortaya çıkan dezavantajlara bakalım:

Faydalar:

  1. SidebarSection sorumluluklarını azalttı.
  2. SidebarSection daha küçük hale geldi.
  3. Hangi bileşenlerin hangi bileşene enjekte edildiğini açıkça görebiliriz.
  4. şimdi geçmek zorunda değiliz key={key} dört farklı zaman ve bunun yerine sadece <Component key={key}

Dezavantajları:

  1. SidebarSection küçülür, ancak dosya büyür.
  2. Bir “varlık” (her şey içerideydi SidebarSection) üç “varlık” haline geldi (şimdi SidebarSection, onHoverAction, getProps)
  3. Her şeyin üstesinden gelmek için yukarıdan aşağıya kaydırarak faremizi daha fazla vurgulama

Peki buna değer miydi?

Dürüst görüşüme göre, son kısmı yapmak çok uzun sürüyorsa, muhtemelen buna değmez. Hikayenin ahlaki, bu çok fazla çaba gerektirmeyen ancak sonuçta daha fazla fayda sağlayan kodu basitleştirmek kesinlikle buna değer.

Bu nedenle, makalemizde, bu yazıdaki ilk iki sadeleştirme girişimini destekliyorum, üçüncüsü konusunda biraz kararsızım.

Ancak, büyük bileşenleri basitleştirmenin gücünü tepki olarak gördük.

Çözüm

Ve bu yazının sonu burada bitiyor! Umarım bunu değerli bulmuşsunuzdur ve gelecekte daha fazlasını ararsınız!

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.