Javscript

JavaScript’te Memento Tasarım Modelinin Gücü – JSManifest

Programlamadaki Memento Modeli, bir nesnenin durumunu geri yüklemek için bir yola ihtiyaç duyduğumuz durumlarda kullanışlıdır.

Bir JavaScript geliştiricisi olarak, özellikle modern web uygulamalarında birçok durumda bu konseptle çalışıyoruz.

Web’de bir süredir geliştirme yapıyorsanız, terimi duymuş olabilirsiniz. hidrasyon.

Hidrasyonun ne olduğunu bilmiyorsanız, istemci tarafının JSON, JavaScript, HTML vb. herhangi bir programlama dilinde depolanan statik içeriği alıp tarayıcıların yapabileceği koda dönüştürdüğü web geliştirmede bir tekniktir. çalışma zamanı sırasında çalıştırın. Bu aşamada JavaScript çalıştırılır ve DOM sayfada çalışmaya başladığında olay dinleyicileri eklemek gibi şeyler yapabilir.

Hatıra deseni benzer. Bu yazıda, çalışma zamanı için Memento modelini uygulayacağız ve statik olarak hiçbir şey saklamayacağız.

ile çalıştıysanız JSON.parse ve JSON.stringify Şans eseri daha önce bir hatırayı yanlışlıkla uygulamış olabilirsiniz.

Genellikle Memento modelinin tam akışını uygulayan üç nesne vardır:

  1. yaratıcı
  2. hatıra
  3. kapıcı

bu yaratıcı memento olarak kendisinin yaratılmasını ve saklanmasını tetikleyen arayüzü tanımlar.

bu hatıra Sorumludan geçirilen ve alınan, Oluşturucunun dahili durum temsilidir.

bu kapıcı tek bir işi var: mağaza veya kaydetmek daha sonra kullanılacak hatıra. Sakladığı hatırayı alabilir ama hiçbir şeyi mutasyona uğratmaz.

Memento Tasarım Modelini Uygulamak

Şimdi modeli tanımladığımıza göre, kodda bu uygulamada ustalaşmak için uygulayacağız.

DOM öğesi olarak etkileşimli bir e-posta giriş alanı oluşturacağız. Giriş alanımıza bir akıllı davranış ekleyeceğiz, böylece kullanıcımız, eklemeleri gerektiğinin hemen farkına varacak. @ göndermeden önce sembolü.

Bunu, giriş alanları şöyle görünecek bir hata durumundayken bileceklerdir:

Bu, üzerinde çalışacağımız html işaretlemesidir:

<!DOCTYPE html>
<html>
  <head>
    <title>Memento</title>
    <meta charset="UTF-8" />
  </head>
  <body style="margin:50px;text-align:center;background:linear-gradient(
    76.3deg,
    rgba(44, 62, 78, 1) 12.6%,
    rgba(69, 103, 131, 1) 82.8%
  );height:250px;overflow:hidden;">
    <input type="email" id="emailInput" style="padding:12px;border-radius:4px;font-size:16px;" placeholder="Enter your email"></input>
    <script src="src/index.js"></script>
  </body>
</html>

Bu bizi bu arayüzle başlatacak:

memento-state-pattern-overview2.png

Şimdi yapacağımız ilk şey, aşağıdakiler için birkaç sabit değişken tanımlamaktır. hata hata stillerine değer atamak için kodumuz boyunca kullanacağımızı belirtin. Bu, kodlarımızı birden çok kez yeniden kullanacağımızdan, kodumuzu yazarken herhangi bir yazım hatası yapmamamızı sağlamak içindir:

const ERROR_COLOR = 'tomato'
const ERROR_BORDER_COLOR = 'red'
const ERROR_SHADOW = `0px 0px 25px rgba(230, 0, 0, 0.35)`
const CIRCLE_BORDER = '50%'
const ROUNDED_BORDER = '4px'

Bunun kalıpla hiçbir ilgisi yok, ancak bu gönderiden ekstra ipuçları alabilmeniz için bazı en iyi uygulamalarda rastgele kaymanın benim için iyi bir alışkanlık olduğunu düşünüyorum, neden olmasın? 😉

Şimdi arasında geçiş yapan bir yardımcı fonksiyon oluşturacağız. hata durumu ve normal durum çünkü bunu birden çok kez kullanacağız:

const toggleElementStatus = (el, status) => {
  if (status === 'error') {
    return Object.assign(el.style, {
      borderColor: ERROR_BORDER_COLOR,
      color: ERROR_COLOR,
      boxShadow: ERROR_SHADOW,
      outline: 'red',
    })
  }
  return Object.assign(el.style, {
    borderColor: 'black',
    color: 'black',
    boxShadow: '',
    outline: '',
  })
}

İki stil ön ayarı arasında geçiş yaparken kenarlık yarıçapını değiştirmek için bir yardımcıya da girebilirim. Bu, kodumuzu sanki gerçek bir uygulamaymış gibi daha “doğal” hissettirmek içindir, böylece doğrudan bu gönderideki renkler ve hatıra arasındaki ilişkiye odaklanmayız. Bazen, üzerinden geçmekte olduğumuz gerçek koda karşı rastgele kod perspektifini gördüğümüzde daha iyi öğrendiğimizi düşünüyorum:

const toggleBorderRadius = (el, preset) => {
  el.style.borderRadius =
    preset === 'rounded'
      ? ROUNDED_BORDER
      : preset === 'circle'
      ? CIRCLE_BORDER
      : '0px'
}

Bir sonraki yapacağımız şey, yaratıcı.

Unutmayın, yaratıcı, kendisinin yaratılmasını ve saklanmasını tetikleyen arayüzü memento olarak tanımlar.

function createOriginator({ serialize, deserialize }) {
  return {
    serialize,
    deserialize,
  }
}

Aslında, bizim için yaratıcıyı üreten basit bir fabrika yarattık.

İşte gerçek yaratıcı:

const originator = createOriginator({
  serialize(...nodes) {
    const state = []

    nodes.forEach(
      
      (node) => {
        const item = {
          id: node.id || '',
        }

        item.tagName = node.tagName.toLowerCase()

        if (item.tagName === 'input') {
          item.isError =
            node.style.borderColor === ERROR_BORDER_COLOR &&
            node.style.color === ERROR_COLOR
          item.value = node.value
        }

        item.isRounded = node.style.borderRadius === ROUNDED_BORDER
        item.isCircle = node.style.borderRadius === CIRCLE_BORDER

        state.push(item)
      },
    )

    return state
  },
  deserialize(...state) {
    const providedNode = state[state.length - 1]

    if (providedNode) state.pop()

    const nodes = []

    state.forEach((item) => {
      const node = providedNode || document.createElement(item.tagName)

      if (item.tagName === 'input') {
        if (item.isError) {
          toggleElementStatus(node, 'error')
        }
        if (item.isRounded) {
          toggleBorderRadius(node, 'rounded')
        } else if (item.isCircle) {
          toggleBorderRadius(node, 'circle')
        }
        node.value = item.value || ''
        if (item.placeholder) node.placeholder = item.placeholder
        if (item.id) node.id = item.id
      }

      nodes.push(node)
    })

    return nodes
  },
})

Yaratıcıda, serialize method bir DOM düğümünü alır ve yerel depolamada bir dize olarak saklayabilmemiz için bize DOM düğümünün bir durum temsilini döndürür. Yerel depolama yalnızca dizeleri kabul ettiğinden bu gereklidir.

Şu anda JavaScript’te bu kalıbın zirvesiyiz. Serileştirme, bu kalıbın bizim için önemli olmasının tek nedenidir, aksi takdirde DOM düğümlerini doğrudan yerel depolamaya kaydedebilir ve buna bir gün diyebilirdik.

bizim içinde serialize yöntemi dolaylı olarak temsili belirlememize yardımcı olan birkaç kural tanımladık.

İşte bahsettiğim satırlar:

if (item.tagName === 'input') {
  item.isError =
    node.style.borderColor === ERROR_BORDER_COLOR &&
    node.style.color === ERROR_COLOR
  item.value = node.value
}

item.isRounded = node.style.borderRadius === ROUNDED_BORDER
item.isCircle = node.style.borderRadius === CIRCLE_BORDER

Girdi öğelerinin hatıralarını saklarken, onu bu şekilde mi yoksa bu şekilde mi uygulayacağımız konusunda bir seçeneğimiz var:

if (item.tagName === 'input') {
  item.style.borderColor = node.style.borderColor
  item.style.color = node.style.color
  item.value = node.value
}

item.style.borderRadius = node.style.borderRadius

Bu konudaki tavsiyeme uyun: Kodunuzdan faydalı bir anlam yaratmak iyi bir uygulamadır. özellikle tasarım deseni uygulamalarınızda. Kodunuzda anlamı başlattığınızda, kodunuzun diğer alanlarında faydalı olabilecek daha yüksek seviyeli soyutlamaları düşünmenize yardımcı olur.

kullanma item.isError Hata stilleri ön ayarını temsil etmek, rastgele stiller atamak yerine projemiz zaman içinde daha karmaşık hale geldikçe yeniden kullanabileceğimiz ilginç yeniden kullanılabilir hatıralar yapmak için daha geniş fırsatlar sunar.

Örneğin, önemli bir alan boş bırakıldığında formların gönderilmemesi yaygın bir durumdur. Form, kendisini göndermekten alıkoyması gereken bir tür duruma geçmelidir.

Bir formun hatırasını kaydedecek olsaydık, bu durumu geri yüklediğimizde kullanıcının “devre dışı” durumuna geri yüklendiğinden emin olmamız gerekir:

const originator = createOriginator({
  serialize(...nodes) {
    const state = []

    nodes.forEach(
      
      (node) => {
        const item = {
          id: node.id || '',
        }

        item.tagName = node.tagName.toLowerCase()

        if (item.tagName === 'input') {
          item.isError =
            node.style.borderColor === ERROR_BORDER_COLOR &&
            node.style.color === ERROR_COLOR
          item.value = node.value
        }

        item.isRounded = node.style.borderRadius === ROUNDED_BORDER
        item.isCircle = node.style.borderRadius === CIRCLE_BORDER

        if (node.textContent) item.textContent = node.textContent

        state.push(item)
      },
    )

    return state
  },
  deserialize(state) {
    const nodes = []

    if (!Array.isArray(state)) state = [state]

    state.forEach((item) => {
      const node = document.createElement(item.tagName)

      if (item.style) {
        Object.entries(item.style).forEach(([key, value]) => {
          node.style[key] = value
        })
      }

      if (item.isRounded) {
        toggleBorderRadius(node, 'rounded')
      } else if (item.isCircle) {
        toggleBorderRadius(node, 'circle')
      }

      if (item.spacing) {
        node.style.padding = item.spacing
      }

      if (item.id) node.id = item.id

      if (item.tagName === 'input') {
        if (item.isError) {
          toggleElementStatus(node, 'error')
        }
        node.value = item.value || ''
        if (item.placeholder) node.placeholder = item.placeholder
      } else if (item.tagName === 'label') {
        if (item.isError) {
          node.style.color = ERROR_COLOR
        }
      } else if (item.tagName === 'select') {
        if (item.options) {
          item.options.forEach((obj) => {
            node.appendChild(...originator.deserialize(obj, node))
          })
        }
      }

      if (item.textContent) node.textContent = item.textContent

      nodes.push(node)
    })

    return nodes
  },
})

const caretaker = createCaretaker()

function restore(state, container, { onRendered } = {}) {
  let statusSubscribers = []
  let status = ''

  const setStatus = (value, options) => {
    status = value
    statusSubscribers.forEach((fn) => fn(status, options))
  }

  const renderMemento = (memento, container) => {
    return originator.deserialize(memento).map((el) => {
      container.appendChild(el)

      if (memento.isError && status !== 'error') {
        setStatus('error')
      }

      if (memento.children) {
        memento.children.forEach((mem) => {
          renderMemento(mem, el).forEach((childEl) => el.appendChild(childEl))
        })
      }

      return el
    })
  }

  const render = (props, container) => {
    const withStatusObserver = (fn) => {
      statusSubscribers.push((updatedStatus) => {
        if (updatedStatus === 'error') {
          
        }
      })

      return (...args) => {
        const elements = fn(...args)
        return elements
      }
    }

    const renderWithObserver = withStatusObserver(renderMemento)

    const elements = renderWithObserver(props, container)
    statusSubscribers.length = 0
    return elements
  }

  const elements = render(state, container)

  if (onRendered) onRendered(status, elements)

  return {
    status,
    elements,
  }
}

const container = document.getElementById('root')

const { status, elements: renderedElements } = restore(mementoJson, container, {
  onRendered: (status, elements) => {
    if (status === 'error') {
      const submitBtn = container.querySelector('#submit-btn')
      submitBtn.disabled = true
      submitBtn.textContent = 'You have errors'
      toggleElementStatus(submitBtn, 'error')
    }
  },
})

Öğeleri doğrudan döndürmek yerine, döndürülen şeyin de hatırayı oluşturmanın mevcut durumu.

Buna daha yüksek bir perspektiften baktığımızda şu gerçeğin avantajını yaşıyoruz: isError bir form gibi bir şeyi temsil edebilir ve genel bakış sağlayabilir. Gerekli küçük bir alan eksikse veya bir değer doğru girilmemişse form gönderilmemelidir.

Bu durumda, formun interaktif olmamalı devre dışı bırakarak gönder düğmesi kullanıcıya göstermeden hemen önce:

memento-pattern-with-error-state-disabled- button.png

Eğer fark etmediyseniz, bizim restore orijinalimizi sarar deserialize bizim yöntemimizden yaratıcı.

Şimdi sahip olduğumuz şey, derin çocukları destekleyen daha yüksek düzeyde soyutlanmış bir hatıra. ve en render durumu (isError) tüm hatıramızın.

Çö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!

İlgili Makaleler

Bir cevap yazın

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

Başa dön tuşu