JavaScript’te Diğer İşlevleri Döndüren İşlevlerin Gücü – JSManifest

JavaScript'te Diğer İşlevleri Döndüren İşlevlerin Gücü – JSManifest

JavaScript, doğası gereği son derece esnek olduğu için yaygın olarak bilinir. Bu gönderi, işlevlerle çalışarak bundan yararlanmanın bazı örneklerini gösterecektir.

Fonksiyonlar herhangi bir yerde iletilebildiğinden, onları fonksiyonların argümanlarına geçirebiliriz.

Genel olarak programlama ile ilgili herhangi bir şeyle ilgili ilk uygulamalı deneyimim JavaScript’te kod yazmaya başlamaktı ve pratikte kafamı karıştıran bir kavram, işlevleri diğer işlevlere geçirmekti. Tüm profesyonellerin yaptığı bu “gelişmiş” şeylerden bazılarını yapmaya çalıştım ama sonunda şöyle bir şeyle karşılaştım:

function getDate(callback) {
  return callback(new Date())
}

function start(callback) {
  return getDate(callback)
}

start(function (date) {
  console.log(`Todays date: ${date}`)
})

Bu kesinlikle gülünçtü ve hatta sadece bunu yapıp aynı davranışı geri alabilecekken, gerçek dünyada işlevleri neden diğer işlevlere geçirdiğimizi anlamayı daha da zorlaştırdı:

const date = new Date()
console.log(`Todays date: ${date}`)

Ama bu neden daha karmaşık durumlar için yeterince iyi değil? Özel yaratmanın amacı nedir? getDate(callback) serin hissetmenin yanı sıra işlev ve ekstra iş yapmak zorunda mı?

Daha sonra bu kullanım durumları hakkında daha fazla soru sormaya başladım ve bir topluluk panosunda iyi bir kullanım örneği vermem istendi, ancak kimse açıklamak ve bir örnek vermek istemedi.

Şu andan itibaren geriye dönüp baktığımda, sorunun zihnimin nasıl düşüneceğimi bilmediğini fark ettim. programlı olarak henüz. Zihninizi orijinal hayatınızdan bilgisayar dilinde programlamaya kaydırmak biraz zaman alır.

JavaScript’te yüksek dereceli işlevlerin ne zaman yararlı olduğunu anlamaya çalışmanın sıkıntılarını anladığım için, iyi bir kullanım örneğini adım adım açıklamak için bu makaleyi yazmaya karar verdim. herkesin yazabileceği çok temel bir işlevden başlayarakve oradan ek faydalar sağlayan karmaşık bir uygulamaya doğru yol alıyoruz.

Amaçlı işlev

İlk önce bizim için bir hedefe ulaşması amaçlanan bir fonksiyonla başlayacağız.

Bir nesneyi alıp stilleri istediğimiz şekilde güncelleyen yeni bir nesne döndüren bir fonksiyona ne dersiniz?

Bu nesneyle çalışalım (bunu bir bileşen):

const component = {
  type: 'label',
  style: {
    height: 250,
    fontSize: 14,
    fontWeight: 'bold',
    textAlign: 'center',
  },
}

Fonksiyonumuzu korumak istiyoruz height daha az değil 300 ve uygula border düğme bileşenlerine (bileşenler type: 'button') ve bize geri verin.

Bu şöyle görünebilir:

function start(component) {
  
  if (component.style.height < 300) {
    component.style['height'] = 300
  }
  if (component.type === 'button') {
    
    component.style['border'] = '1px dashed teal'
  }
  if (component.type === 'input') {
    if (component.inputType === 'email') {
      
      component.style.textTransform = 'uppercase'
    }
  }
  return component
}

const component = {
  type: 'div',
  style: {
    height: 250,
    fontSize: 14,
    fontWeight: 'bold',
    textAlign: 'center',
  },
}

const result = start(component)
console.log(result)

Sonuç:

{
  "type": "div",
  "style": {
    "height": 300,
    "fontSize": 14,
    "fontWeight": "bold",
    "textAlign": "center"
  }
}

Diyelim ki, her bileşeni kendi içine yerleştirerek daha fazla bileşene sahip olabileceği fikrini ortaya koyalım. children Emlak. Bu, bunu iç bileşenleri de ele almamız gerektiği anlamına gelir.

Yani, böyle bir bileşen verildiğinde:

{
  "type": "div",
  "style": {
    "height": 300,
    "fontSize": 14,
    "fontWeight": "bold",
    "textAlign": "center"
  },
  "children": [
    {
      "type": "input",
      "inputType": "email",
      "placeholder": "Enter your email",
      "style": {
        "border": "1px solid magenta",
        "textTransform": "uppercase"
      }
    }
  ]
}

Bizim fonksiyonumuz belli ki işi halletme yeteneğine sahip değil, henüz:

function start(component) {
  
  if (component.style.height < 300) {
    component.style['height'] = 300
  }
  if (component.type === 'button') {
    
    component.style['border'] = '1px dashed teal'
  }
  if (component.type === 'input') {
    if (component.inputType === 'email') {
      
      component.style.textTransform = 'uppercase'
    }
  }
  return component
}

Yakın zamanda bileşenlere çocuk kavramını eklediğimizden, artık nihai sonucu çözmek için en az iki farklı şey olduğunu biliyoruz. Soyutlama hakkında düşünmeye başlamak için iyi bir zaman. Kod parçalarını yeniden kullanılabilir işlevlere soyutlamak, kodunuzu daha okunabilir ve sürdürülebilir kılar çünkü bir şeyin uygulama ayrıntılarında bazı sorunların hatalarını ayıklamak gibi sorunlu durumları önler.

Küçük parçaları bir şeyden soyutladığımızda, bu parçaları daha sonra nasıl bir araya getireceğimizi düşünmeye başlamak da iyi bir fikirdir, buna kompozisyon diyebiliriz.

Soyutlama ve Kompozisyon

Bilmek ne soyutlamak, ne yaptığımızı düşünmek nihai hedef idi:

"A function that will take an object and return a new object that updated the styles on it the way we want it to"

Esasen bu işlevin tüm amacı, bir değeri beklediğimiz temsilde olacak şekilde dönüştürmektir. Orijinal işlevimizin bir bileşenin stillerini dönüştürmek olduğunu unutmayın, ancak sonra ayrıca Bu bileşenlere eklenen bileşenler, kendi içinde bileşenleri de içerebilir. children özelliği, bu yüzden bu iki parçayı soyutlayarak başlayabiliriz çünkü büyük olasılıkla değere benzer şeyler yapması gereken daha fazla işlev yapmamız gereken daha fazla durum olacaktır. Bu öğretici uğruna, bu soyutlanmış işlevlere şu şekilde başvurabilirsiniz: çözücüler:

function resolveStyles(component) {
  
  if (component.style.height < 300) {
    component.style['height'] = 300
  }
  if (component.type === 'button') {
    
    component.style['border'] = '1px dashed teal'
  }
  if (component.type === 'input') {
    if (component.inputType === 'email') {
      
      component.style.textTransform = 'uppercase'
    }
  }
  return component
}

function resolveChildren(component) {
  if (Array.isArray(component.children)) {
    component.children = component.children.map((child) => {
      
      return resolveStyles(child)
    })
  }
  return component
}

function start(component, resolvers = []) {
  return resolvers.reduce((acc, resolve) => {
    return resolve(acc)
  }, component)
}

const component = {
  type: 'div',
  style: {
    height: 250,
    fontSize: 14,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  children: [
    {
      type: 'input',
      inputType: 'email',
      placeholder: 'Enter your email',
      style: {
        border: '1px solid magenta',
      },
    },
  ],
}

const result = start(component, [resolveStyles, resolveChildren])
console.log(result)

Sonuç:

{
  "type": "div",
  "style": {
    "height": 300,
    "fontSize": 14,
    "fontWeight": "bold",
    "textAlign": "center"
  },
  "children": [
    {
      "type": "input",
      "inputType": "email",
      "placeholder": "Enter your email",
      "style": {
        "border": "1px solid magenta",
        "textTransform": "uppercase"
      }
    }
  ]
}

Son değişiklikler

Şimdi, bu kodun nasıl feci hatalara neden olabileceğinden bahsedelim – uygulamanızı çökertecek hatalar.

Çözümleyicilere yakından bakarsak ve nihai sonucu hesaplamak için nasıl kullanıldıklarına bakarsak, iki nedenden dolayı kolayca bozulabileceğini ve uygulamamızın çökmesine neden olabileceğini söyleyebiliriz:

  1. mutasyona uğrar – Ya bilinmeyen bir hata meydana gelirse ve değere yanlışlıkla tanımsız değerler atayarak değeri yanlış mutasyona uğratırsa? Değer de değişir fonksiyonun dışında çünkü mutasyona uğradı (nasıl olduğunu anlayın Referanslar iş).

eğer çıkarırsak return component itibaren resolveStyleshemen bir ile karşı karşıyayız TypeError çünkü bu, sonraki çözümleyici işlevi için gelen değer olur:

TypeError: Cannot read property 'children' of undefined
  1. Çözümleyiciler önceki sonuçları geçersiz kılar – Bu iyi bir uygulama değildir ve soyutlamanın amacını bozar. Bizim resolveStyles değerlerini hesaplayabilir, ancak önemli değil resolveChildren işlev tamamen yeni bir değer döndürür.

şeyleri değişmez tutmak

Bu işlevleri yaparak amacımıza güvenle ilerleyebiliriz. değişmez ve aynı değer verilirse her zaman aynı sonucu döndürmelerini sağlayın.

Yeni değişiklikleri birleştirme

bizim içinde resolveStyles işlevi, orijinal değerle birlikte birleştireceğimiz değiştirilmiş değerleri içeren yeni bir değer (nesne) döndürebiliriz. Bu şekilde, çözümleyicilerin birbirlerini geçersiz kılmamalarını ve geri dönmelerini sağlayabiliriz. undefined daha sonra diğer kodlar için hiçbir etkisi olmaz:

function resolveStyles(component) {
  let result = {}

  
  if (component.style.height < 300) {
    result['height'] = 300
  }
  if (component.type === 'button') {
    
    result['border'] = '1px dashed teal'
  }
  if (component.type === 'input') {
    if (component.inputType === 'email') {
      
      result['textTransform'] = 'uppercase'
    }
  }
  return result
}

function resolveChildren(component) {
  if (Array.isArray(component.children)) {
    return {
      children: component.children.map((child) => {
        return resolveStyles(child)
      }),
    }
  }
}

function start(component, resolvers = []) {
  return resolvers.reduce((acc, resolve) => {
    return resolve(acc)
  }, component)
}

Bir proje büyüdüğünde

10 olsaydık stil çözümleyiciler ve üzerinde çalışan yalnızca 1 çözümleyici çocuklarbakımı zorlaşabilir, böylece birleştikleri kısımda onları ayırabiliriz:

function callResolvers(component, resolvers) {
  let result

  for (let index = 0; index < resolvers.length; index++) {
    const resolver = resolvers[index]
    const resolved = resolver(component)
    if (resolved) {
      result = { ...result, ...resolved }
    }
  }

  return result
}


function start(component, resolvers = []) {
  let baseResolvers
  let styleResolvers

  
  if (Array.isArray(resolvers.base)) baseResolvers = resolvers.base
  else baseResolvers = [resolvers.base]
  
  if (Array.isArray(resolvers.styles)) styleResolvers = resolvers.styles
  else styleResolvers = [resolvers.styles]

  return {
    ...component,
    ...callResolvers(component, baseResolvers),
    style: {
      ...component.style,
      ...callResolvers(component, styleResolvers)),
    },
  }
}

Bu çözümleyicileri çağıran kod, onu yeniden kullanabilmemiz ve yinelemeyi azaltabilmemiz için kendi işlevine ayrılmıştır.

Ya sonucunu hesaplamak için daha fazla bağlama ihtiyaç duyan bir çözümleyicimiz varsa?

Örneğin, bir resolveTimestampInjection enjekte eden çözümleyici işlevi time bazı seçenekler parametresi kullanıldığında, sarmalayıcıda bir yere iletildi mi?

Ek bağlam gerektiren işlevler

Çözümleyicilere ek bağlam elde etme yeteneği vermek güzel olurdu ve sadece component argüman olarak değer. Bu yeteneği, çözücü fonksiyonlarımızın ikinci parametresini kullanarak sağlayabiliriz, ancak bu parametrelerin bileşen bazında daha düşük seviyeli soyutlamalar için kaydedilmesi gerektiğini düşünüyorum.

Çözümleyiciler bir işlev döndürme ve bunun yerine ihtiyaç duydukları bağlamı döndürülen işlevin bağımsız değişkenlerinden alma yeteneğine sahip olsaydı?

Şuna benzeyen bir şey:

function resolveTimestampInjection(component) {
  return function ({ displayTimestamp }) {
    if (displayTimestamp === true) {
      return {
        time: new Date(currentDate).toLocaleTimeString(),
      }
    }
  }
}

Orijinal kodun davranışını değiştirmeden bu işlevi etkinleştirebilirsek iyi olur:

function callResolvers(component, resolvers) {
  let result

  for (let index = 0; index < resolvers.length; index++) {
    const resolver = resolvers[index]
    const resolved = resolver(component)
    if (resolved) {
      result = { ...result, ...resolved }
    }
  }

  return result
}


function start(component, resolvers = []) {
  let baseResolvers
  let styleResolvers

  
  if (Array.isArray(resolvers.base)) baseResolvers = resolvers.base
  else baseResolvers = [resolvers.base]
  
  if (Array.isArray(resolvers.styles)) styleResolvers = resolvers.styles
  else styleResolvers = [resolvers.styles]

  return {
    ...component,
    ...callResolvers(component, baseResolvers),
    style: {
      ...component.style,
      ...callResolvers(component, styleResolvers)),
    },
  }
}

const component = {
  type: 'div',
  style: {
    height: 250,
    fontSize: 14,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  children: [
    {
      type: 'input',
      inputType: 'email',
      placeholder: 'Enter your email',
      style: {
        border: '1px solid magenta',
      },
    },
  ],
}

const result = start(component, {
  resolvers: {
    base: [resolveTimestampInjection, resolveChildren],
    styles: [resolveStyles],
  },
})

Burası, üst düzey işlevler oluşturmanın gücünün parlamaya başladığı yerdir ve iyi haber şu ki, bunların uygulanması kolay!

Soyutlamaları uzaklaştırmak

Bu işlevi etkinleştirmek için bir adım daha yükseğe çık Çözümleyicileri bir içine sararak soyutlamada üst düzey fonksiyon bağlamı alt düzey çözümleyici işlevlerine enjekte etmekten sorumludur.

function makeInjectContext(context) {
  return function (callback) {
    return function (...args) {
      let result = callback(...args)
      if (typeof result === 'function') {
        
        result = result(context)
      }
      return result
    }
  }
}

Artık çözümleyici olarak kaydettiğimiz herhangi bir işlevden bir işlev döndürebiliriz. ve yine de uygulamamızın davranışını aynı şekilde koruyunşöyle:

const getBaseStyles = () => ({ baseStyles: { color: '#333' } })
const baseStyles = getBaseStyles()

const injectContext = makeInjectContext({
  baseStyles,
})

function resolveTimestampInjection(component) {
  return function ({ displayTimestamp }) {
    if (displayTimestamp === true) {
      return {
        time: new Date(currentDate).toLocaleTimeString(),
      }
    }
  }
}

Son örneği göstermeden önce, makeInjectContext daha yüksek dereceden işlev ve ne yaptığını gözden geçirin:

İlk önce tüm çözümleyici işlevlere iletilmesini istediğiniz bir nesneyi alır ve argüman olarak geri çağırma işlevini alan bir işlev döndürür. Bu geri arama parametresi daha sonra orijinal çözümleyici işlevlerinden biri olacaktır. Bunu yapmamızın nedeni, dediğimiz şeyi yapıyor olmamızdır. sarma. Geri aramayı bir dış işlevle sardık, böylece orijinal işlevimizin davranışını korurken ek işlevler enjekte edebiliriz. içerideki geri aramayı burada aramamızı sağlamak. Eğer geri arama sonucunun dönüş türü bir işlevdirgeri aramanın bağlama ihtiyacı olduğunu varsayacağız, bu yüzden geri aramanın sonucunu bir kez daha ara–ve bağlamdan geçtiğimiz yer burasıdır.

Bu geri aramayı çağırdığımızda (arayan tarafından sağlanan bir işlev) ve sarmalayıcı işlevi içinde bazı hesaplamalar yapın, sarmalayıcıdan gelen değerlerimiz var ve arayandan. Bu, nihai hedefimiz için iyi bir kullanım örneğidir, çünkü her bir çözümleyici işlevine bir değeri veya önceki bir çözümleyici işlevinden gelen sonucu geçersiz kılma yeteneğini etkinleştirmek yerine sonuçları birleştirmek istedik.! Farklı sorunları çözmek için başka gelişmiş kullanım örneklerinin olmasının hiçbir önemi yoktur ve bu, doğru durum için doğru stratejiyi kullanmamız gereken bir durumu sergilemek için iyi bir örnektir – çünkü benim gibiyseniz, muhtemelen denemişsinizdir. her açık fırsat gördüğünüzde çok sayıda gelişmiş kullanım senaryosu uygulamak – bu kötü bir uygulamadır çünkü bazı gelişmiş modeller diğerlerinden daha iyidir duruma bağlı olarak!

ve şimdi bizim start işlevi için ayarlanması gerekir makeInjectContext daha yüksek dereceli fonksiyon:

const getBaseStyles = () => ({ baseStyles: { color: '#333' } })

function start(component, { resolvers = {}, displayTimestamp }) {
  const baseStyles = getBaseStyles()
  
  const context = { baseStyles, displayTimestamp }
  
  const enhancedResolve = makeInjectContext(context)

  let baseResolvers
  let styleResolvers

  
  if (Array.isArray(resolvers.base)) baseResolvers = resolvers.base
  else baseResolvers = [resolvers.base]
  
  if (Array.isArray(resolvers.styles)) styleResolvers = resolvers.styles
  else styleResolvers = [resolvers.styles]

  return {
    ...component,
    ...callResolvers(component, baseResolvers.map(enhancedResolve)),
    style: {
      ...component.style,
      ...callResolvers(component, styleResolvers.map(enhancedResolve)),
    },
  }
}

const component = {
  type: 'div',
  style: {
    height: 250,
    fontSize: 14,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  children: [
    {
      type: 'input',
      inputType: 'email',
      placeholder: 'Enter your email',
      style: {
        border: '1px solid magenta',
      },
    },
  ],
}

const result = start(component, {
  resolvers: {
    base: [resolveTimestampInjection, resolveChildren],
    styles: [resolveStyles],
  },
})

Ve yine de beklenen sonuçlarla bir nesneyi geri alıyoruz!

{
  "type": "div",
  "style": {
    "height": 300,
    "fontSize": 14,
    "fontWeight": "bold",
    "textAlign": "center"
  },
  "children": [
    {
      "type": "input",
      "inputType": "email",
      "placeholder": "Enter your email",
      "style": {
        "border": "1px solid magenta"
      },
      "textTransform": "uppercase"
    }
  ],
  "time": "2:06:16 PM"
}

Bir cevap yazın

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