Skip to content

Instantly share code, notes, and snippets.

@andrew--r
Forked from mpj/templating_problems.MD
Last active October 24, 2019 17:55
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrew--r/1328f11e37112c37128929ac4ea5d554 to your computer and use it in GitHub Desktop.
Save andrew--r/1328f11e37112c37128929ac4ea5d554 to your computer and use it in GitHub Desktop.
Критика шаблонизаторов

Оригинальная заметка Матиаса Питера Йохансона, переведена с разрешения автора.


Размышления о языках шаблонов

Меня часто спрашивают, что я думаю о Vue.

Не буду оценивать Vue, так как я недостаточно хорошо знаком с ней, но я очень хорошо знаком с шаблонизаторами. Собственная система шаблонов Vue вместо JSX во многих статьях преподносится как причина, по которой вы должны выбрать Vue. Из-за этого я на стену лезу от негодования, потому что негативные стороны этого подхода даже не удостаиваются упоминания или обсуждения. JSX существует по достаточно веским причинам. Для меня JSX — большой шаг к упрощению и улучшению шаблонов.

Языки шаблонов часто продаются за счёт поверхностной элегантности. Недавно я наткнулся на очередную статью, восхваляющую Vue (и особенно её шаблоны). В ней был следующий пример JSX:

render() {
  let { items } = this.props
  
  let children
  if (items.length > 0) {
    children = (
      <ul>
        {items.map(item =>
          <li key={item.id}>{item.name}</li>)}
      </ul>
    )
  } else {
    children = <p>No items found.</p>
  }
  return (
    <div className="list-container">
      {children}
    </div>
  )
}

В статье этот пример осуждается за многословность, а затем приводится его реализация на Vue:

<template>
  <div class="list-container">
    <ul v-if="items.length">
      <li v-for="item in items">
        {{item.name}}
      </li>
    </ul>
    <p v-else>No items found.</p>
  </div>
</template>

Это выглядит как лукавство со стороны автора, потому что первый пример действительно написан слишком многословно. В более лаконичном виде он выглядел бы так:

let ListContainer = ({ items }) => {
  <div className="list-container">
    {items.length === 0
      ? <p>No items found</p>
      : <ul>
        {items.map(item => 
          <li key={item.id}>{item.name}</li>
        )}
      </ul>
    }
  </div> 
}

Я не знаю, специально ли автор вводит читателей в заблуждение, или он просто взял пример из какого-то руководства по React. Буду считать, что он взял пример из какой-то статьи. Если это действительно так, есть вероятность, что пример JSX был специально написан многословно для большей наглядности и очевидности происходящего: при изучении новых инструментов вам не нужна краткость, вам нужно простое и понятное объяснение того, как инструмент работает, без лишней магии.

Пример с Vue, однако, не особо даёт понять, КАК на самом деле он, чёрт возьми, РАБОТАЕТ. Я знаю, как работает Array.map, но что такое v-for и что за синтаксис используется внутри этого атрибута? Да, довольно легко понять ЧТО конкретно этот шаблон делает, потому что он читается почти как обычная речь на английском. Но когда вы на практике столкнётесь с подобными языками шаблонов, ваша продуктивность ощутимо упадёт, потому что вам придётся тратить время на изучение (и часто расширение) языка. Да, это небольшой язык, но это всё ещё язык, который придётся изучить. В случае с JSX вещей для изучения гораздо меньше, и в нём можно применять уже существующие знания и инструменты JavaScript.

Предположим для примера, что я хочу отфильтровать все элементы, которые неактивны. JSX — это просто JavaScript, поэтому я спокойно использую filter:

let ListContainer = ({ items }) => {
  <div className="list-container">
    {items.length === 0
      ? <p>No items found</p>
      : <ul>
        {items
          .filter(item => item.active) // <-- ДОБАВЛЕННАЯ СТРОКА
          .map(item => 
            <li key={item.id}>{item.name}</li>
          )
        }
      </ul>
    }
  </div> 
}

Как это сделать в шаблоне Vue? Честно, без понятия. Я хотел привести здесь пример, но спустя десять минут гуглинга я всё ещё не мог найти, как это делается. Конечно, можно найти, как это делается, но суть в том, что у меня УЖЕ ЕСТЬ Array.filter! И проблема возникла ТОЛЬКО из-за того, что мы изобрели новый язык вместо использования уже существующего.

PHP-сообщество поняло это 10 лет назад, когда большинство вменяемых PHP-программистов перестали использовать Smarty, поняв, что PHP — сам по себе замечательный язык шаблонов.

Нет никакой разницы и в случае с JavaScript — он замечательно подходит на роль языка шаблонов, так что не изобретайте ещё один узкоспециализированный язык.

@bloadvenro
Copy link

bloadvenro commented May 23, 2017

С точки зрения фундаментальной логики, если взглянуть на VUE шаблонизацию (+ API всяких фильтров), то она по принципу действия не отличается от React ничем: передаем данные, используем свои или встроенные хелперы (aka фильтры), а также условия и циклы. VUE шаблоны может и получаются немного более разгруженными за счет маникакального вынесения логики в директивы и хелперы, а также отсутствия некрасивых скобок и тернарных операторов, но вы посчитайте, сколько символов в виде директив и всяких pile-like палочек для фильтров (с порой неочевидной логикой) нужно будет взамен писать. Все это не дает мне ощущения весомого преимущества (имхо). Мы лишь приобретаем дополнительный синтаксический слой для указания того, что мы хотим сделать в наших шаблонах.

Разобраться в JSX/VUE шаблонах - дело часа, может трех. Не вижу особого смысла тут рассуждать, ибо выбираешь ты не отдельно шаблонизатор, а целую экосистему инструентов, и, как правило, возникают проблемы посерьезнее синтаксиса шаблонов.

Я писал JSX шаблоны, и мне все там казалось удобным: прозрачный доступ к любым атрибутам, хелперам и библиотекам, поток данных легко воспринимается, расфасовка самих данных происходит в презентационных компонентах, поэтому все чистенько. При простом взгляде возникало ощущение полного контроля.

P.S. Зачем контейнеру логика фильтрации внутри, да еще и столь специфичная по .active значению? Что мешает еще короче записать?

file: ListContainer.jsx
-----------------------------

export default ({ items = [] }) => (
  <div className="list-container">
    {items.length
      ? <ul>{li(items)}</ul> // Уже отфильтрованные айтемы, li - переиспользуемый обертыватель.
      : <p>No items found</p>
    }
  </div> 
)

Согласитель, мало отличается от (вообще логику фильтрации тоже не мешало бы убрать)

<div class="list-container">
  <ul v-if="items.length">
    <li v-for="item in items" v-if="item.active"> // кстати, что если кто-то решит вставить более сложное условие?
      {{item.name}}
    </li>
  </ul>
  <p v-else>No items found.</p>
</div>

ListContainer можно в принципе сделать абстрактным контекстным компонентом в рамках приложения, если вынести из него всю специфичность.

@Piterden
Copy link

// кстати, что если кто-то решит вставить более сложное условие? @bloadvenro

<template>
  <div class="list-container">
    <ul v-if="items.length">
      <li 
        v-for="(item, key) in items"
        v-if="moreComplicatedCondition"
        :key="key"
      >
        {{ item.name }}
      </li>
    </ul>
    <p v-else>No items found.</p>
  </div>
</template>

<script>
export default {
  computed: {
    moreComplicatedCondition () {
      return this.whatEverYouWant
    },
  },
}
</script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment