
На написание этой статьи меня вдохновил YouTube-канал Fireship, записывающий отличные видео о веб-разработке, крайне рекомендую их посмотреть, если вам интересна эта тема.
Вот видео с канала, в котором в 10 фреймворках создают todo-приложение:
Я решил, что не хочу тратить на это кучу времени, и в основном использовал статью как оправдание для изучения нескольких новых фреймворков, поэтому пять раз собрал одно и то же приложение. Я планирую создать простое приложение для добавления заметок, в котором пользователи могут писать текст и сохранять его как отдельные заметки. Некоторые из этих фреймворков я уже использовал для создания подобных приложений, а в других не делал ничего похожего, или даже не использовал их вообще, поэто��у это будет чуть сложнее.
Создаём приложение
jQuery
Чтобы упростить создание приложения без фреймворка, я буду использовать jQuery. Начал я с создания базовой структуры файлов и открытия
index.html. Если вам любопытно, структура файлов выглядит так:
По сути, у меня есть таблица стилей в SCSS, которую я скомпилирую в CSS, и на этом пока всё. Разметка html выглядит пока так, но в дальнейшем я её расширю:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="./css/styles.css" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <title>Notes App</title> </head> <body> <div class="container"> <header> <h1>Notes App</h1> </header> <main> <div class="note"> <form> <input required type="text" id="note-title" placeholder="Note Title" /> <textarea id="note-body" placeholder="Note Body"></textarea> <input type="submit" id="note-submit" title="Add Note" /> </form> </div> </main> </div> </body> </html>
Таблица стилей выглядит так:
body { height: 100%; width: 100%; margin: 0; } .container { width: 100%; height: auto; margin: 0; display: flex; flex-direction: column; header { display: flex; align-items: center; width: 100%; height: 56px; background-color: #4e78b8; color: white; h1 { margin-left: 6px; } } main { margin: 10px; display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-gap: 1rem; align-items: center; .note { display: flex; flex-direction: column; padding: 10px; background-color: #a15fbb; border-radius: 5px; form { display: flex; flex-direction: column; textarea { resize: none; } } } } }
Далее я компилирую код при помощи
sass scss/styles.scss:css/styles.css, после чего мы готовы писать JavaScript. По сути, нам достаточно добавить в DOM новый div с парой дочерних элементов для отправки формы и сохранение в локальное хранилище. Вот что у меня в результате получилось:let notes = []; $(document).ready(function () { if (localStorage.getItem("notes")) notes = JSON.parse(localStorage.getItem("notes")); setNotes(); }); $("#note-submit").click(function (e) { let noteTitle = $("#note-title").val(); let noteDesc = $("#note-body").val(); let note = { title: noteTitle, desc: noteDesc } notes.push(note); console.log(notes); localStorage.setItem("notes", JSON.stringify(notes)); setNotes(); }); function setNotes() { notes.forEach((note) => { $("main").prepend(` <div class="note"> <h4>${note.title}</h4> <span>${note.desc}</span> </div> `); }); }
Вероятно, это не самый лучший код, но мне он показался логичным, к тому же я решил, что для этого проекта и не нужен идеальный код. Как бы то ни было, это было гораздо проще, чем я ожидал из предыдущего опыта, и мне это действительно понравилось. Вероятно, в других приложениях будет отличаться порядок заметок, потому что я не мог заморачиваться тем, чтобы они всегда добавлялись до формы, но после других заметок.
Angular
Забавен контраст между тем, сколько можно сделать с помощью Angular, и тем, что делаем мы. Вопрек�� впечатлению, которое могло сложиться по предыдущим постам, мне очень нравится Angular, просто я недолюбливают его недостаток модульности по сравнению с чем-то наподобие React. Впрочем, ладно, давайте сгенерируем проект:
$ ng new angular
И это буквально всё, что нужно для начала. Разве CLI Angular не великолепен? Для базовой структуры приложения я напишу, по сути, тот же код:
<div class="container"> <header> <h1>Notes App</h1> </header> <main> <div class="note" *ngFor="let note of [0, 1, 2, 3]"> <h4>Note Title</h4> <span>Note Body</span> </div> <div class="note"> <form> <input required type="text" #noteTitle placeholder="Note Title" ngModel /> <textarea #noteBody placeholder="Note Body" ngModel></textarea> <input type="submit" #noteSubmit title="Add Note" /> </form> </div> </main> </div>
Это может показаться вам спорным, но я собираюсь выполнять всю логику приложения в самом компоненте приложения, без дочерних компонентов. В целом это немного упростит работу, хотя на самом деле это необязательно. Давайте используем практически те же стили, что и раньше:
.container { width: 100%; height: auto; margin: 0; display: flex; flex-direction: column; header { display: flex; align-items: center; width: 100%; height: 56px; background-color: #4e78b8; color: white; h1 { margin-left: 6px; } } main { margin: 10px; display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-gap: 1rem; align-items: center; .note { display: flex; flex-direction: column; padding: 10px; background-color: #a15fbb; border-radius: 5px; form { display: flex; flex-direction: column; textarea { resize: none; } } } } }
Теперь мы просто можем написать код, похожий на тот, что писали раньше:
import { Component } from '@angular/core'; type Note = { title: string; desc: string; } @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { notes: Array<Note> = []; title!: string; body?: string; constructor() { const data = localStorage.getItem("notes"); if (data) this.notes = JSON.parse(data); } submitForm() { let note: Note = { title: this.title, desc: this.body || "" } this.notes.push(note); localStorage.setItem("notes", JSON.stringify(this.notes)); } }
Сделав это, мы можем вернуться к шаблону и исправить логику заметок:
<div class="container"> <header> <h1>Notes App</h1> </header> <main> <div class="note" *ngFor="let note of notes"> <h4>{{note.title}}</h4> <span>{{note.desc}}</span> </div> <div class="note"> <form #addNoteForm="ngForm"> <input required type="text" placeholder="Note Title" [(ngModel)]="title" name="Title" /> <textarea placeholder="Note Body" [(ngModel)]="body" name="Body"></textarea> <input type="submit" #noteSubmit title="Add Note" (click)="submitForm()" /> </form> </div> </main> </div>
Вот и всё, ребята!
React
Думаю, здесь всё будет сложнее необходимого из-за природы React. React спроектирован так, чтобы быть более модульным и лёгким, чем другие фреймворки, но в каком-то смысле из-за своей структуры для мелких приложений он сложнее. Я начал генерацию своего react-приложения с собственного шаблона
sammy-libraries:$ yarn create react-app react-app --template sammy-libraries
У меня время от времени возникает баг: node sass (который я использую обычно, потому что dart sass, по моему опыту, имеет более медленное время компиляции для React) отказывается компилировать мой код sass, поэтому я просто удалил node_modules и yarn.lock, а затем снова запустил
yarn, и всё заработало идеально. Вот что я сделал: я начал с создания index.scss, аналогичного styles.scss из первого приложения, а затем в своём компоненте App воссоздал базовую структуру приложения:import React, { useEffect, useState } from "react"; import NotesList from "components/NotesList"; import { NoteType } from "components/Note"; //import "scss/App.scss"; function App() { const [notesList, setNotesList] = useState<NoteType[]>([]); const [noteTitle, setNoteTitle] = useState<string>(""); const [noteDesc, setNoteDesc] = useState<string>(""); useEffect(() => { const data = localStorage.getItem("notes"); if (data) { setNotesList(JSON.parse(data)); } }, []); useEffect(() => { localStorage.setItem("notes", JSON.stringify(notesList)); }, [notesList]) const addNote = (event: React.FormEvent<HTMLFormElement>) => { let note: NoteType = { title: noteTitle, desc: noteDesc, }; setNotesList([...notesList, note]); event.preventDefault(); }; const changeTitle = (event: React.ChangeEvent<HTMLInputElement>) => { setNoteTitle(event.currentTarget.value); }; const changeDesc = (event: React.ChangeEvent<HTMLTextAreaElement>) => { setNoteDesc(event.currentTarget.value); }; return ( <div className="container"> <header> <h1>Notes App</h1> </header> <NotesList addNote={addNote} changeTitle={changeTitle} changeDesc={changeDesc} notes={notesList} /> </div> ); } export default App;
Пока проект ничего не делает, поэтому давайте добавим другие компоненты. Я создал три компонента в отдельных папках компонентов и заполнил их соответствующим образом.
NotesList.tsx:import React from "react"; import AddNote from "components/AddNote"; import Note, { NoteType } from "components/Note"; type NotesListProps = { notes: NoteType[]; addNote: (event: React.FormEvent<HTMLFormElement>) => void; changeTitle: (event: React.ChangeEvent<HTMLInputElement>) => void; changeDesc: (event: React.ChangeEvent<HTMLTextAreaElement>) => void; }; function NotesList({ notes, addNote, changeTitle, changeDesc }: NotesListProps) { return ( <main> {notes.map((note) => { return ( <Note note={{ title: note.title, desc: note.desc, }} /> ); })} <AddNote addNote={addNote} changeTitle={changeTitle} changeDesc={changeDesc} /> </main> ); } export default NotesList;
Note.tsx:import React from "react"; export type NoteType = { title: string; desc: string; } interface NoteProps { note: NoteType; } function Note(props: NoteProps) { return ( <div className="note"> <h4>{props.note.title}</h4> <span>{props.note.desc}</span> </div> ); } export default Note;
И
AddNote.tsx:import React from "react"; interface AddNoteProps { changeTitle: (event: React.ChangeEvent<HTMLInputElement>) => void; changeDesc: (event: React.ChangeEvent<HTMLTextAreaElement>) => void; addNote: (event: React.FormEvent<HTMLFormElement>) => void; } function AddNote(props: AddNoteProps) { return( <div className="note"> <form onSubmit={props.addNote}> <input type="text" placeholder="Note Title" onChange={props.changeTitle} /> <textarea placeholder="Note Body" onChange={props.changeDesc}></textarea> <input type="submit" value="Add Note" /> </form> </div> ); } export default AddNote;
Не самое сложное из того, что я делал, но определённо кажется более сложным, чем работа с jQuery или Angular, по крайней мере, мне. Я очень люблю React и считаю его любимым фреймворком, просто не знаю, что любить в нём, работая над подобными проектами. Если бы мне пришлось выбирать, то я бы сказать, что самый чистый из всех — это Angular, а JQuery кажется самым логичным (по крайней мере, для этого проекта). React — самый неуклюжий, с ним удобно работать, но он не кажется особо подходящим.
Vue
С этим фреймворком я работал один раз, и кому-то это может показаться оскорблением, но я действительно не видел смысла в возне с ним. Я могу использовать и Angular, и React. Мне кажется, что они покрывают почти все мои потребности (а оставшееся решают библиотеки), поэтому Vue никогда не казался мне полезным. Что ж, давайте создадим свой проект на Vue.
$ vue ui
По сути, я использовал всё по умолчанию, но с TypeScript и SCSS (в основном с dart sass, чтобы не ломать зависимости), потому что мне нравится применять их в своих проектах. Единственная реальная причина того, что я не использовал TypeScript в первом проекте, заключалась в том, что мне не хотелось возиться с обеспечением совместной работы jQuery и TS, но если вам интересно, то это возможно.
Как я подошёл к созданию приложения? Сначала я удалил почти всё, что было сгенерировано автоматически, и заменил код App вот на это:
<template> <div class="container"> <header> <h1>Notes App</h1> </header> <main> <Note v-for="(note, index) in notes" :key="index" :title="note.title" :body="note.body" /> <div class="note"> <form @submit="submitForm()"> <input type="text" placeholder="Note Title" v-model="title" /> <textarea placeholder="Note Body" v-model="body"></textarea> <input type="submit" value="Add Note" /> </form> </div> </main> </div> </template> <script lang="ts"> import { Component, Vue } from "vue-property-decorator"; import Note from "./components/Note.vue"; type NoteType = { title: string; body: string; }; @Component({ components: { Note, }, }) export default class App extends Vue { notes: Array<NoteType> = []; title!: string; body?: string; constructor() { super(); const data = localStorage.getItem("notes"); if (data) this.notes = JSON.parse(data); } submitForm(): void { let note: NoteType = { title: this.title, body: this.body || "", }; this.notes.push(note); localStorage.setItem("notes", JSON.stringify(this.notes)); } } </script> <style lang="scss"> body { height: 100%; width: 100%; margin: 0; } .container { width: 100%; height: auto; margin: 0; display: flex; flex-direction: column; header { display: flex; align-items: center; width: 100%; height: 56px; background-color: #4e78b8; color: white; h1 { margin-left: 6px; } } main { margin: 10px; display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-gap: 1rem; align-items: center; .note { display: flex; flex-direction: column; padding: 10px; background-color: #a15fbb; border-radius: 5px; form { display: flex; flex-direction: column; textarea { resize: none; } } } } } </style>
А компонент Note выглядел вот так:
<template> <div class="note"> <h4>{{ this.title }}</h4> <span>{{ this.body }}</span> </div> </template> <script lang="ts"> import { Component, Prop, Vue } from "vue-property-decorator"; @Component({ components: {}, }) export default class App extends Vue { @Prop() title!: string; @Prop() body?: string; } </script>
И на этом всё.
Svelte
Я хотел изучить этот фреймворк, но и не думал касаться его, пока не решил написать эту статью. По сути, я начинал, не зная совершенно ничего, за исключением того, что Svelte очень любят веб-разработчики, но после этого опыта я, наверно, продолжу создавать проекты со Svelte. Вероятно, пока я в нём очень плох, но со временем ситуация может измениться.
Потратив десять минут на поиск несуществующего CLI для Svelte yarn create-*, я решил настроить проект с бойлерплейтом фреймворка. Я преобразовал проект в TypeScript, потому что я фанат языков со строгой типизацией, а потом приступил к работе. Занявшись стилями, я пошёл на принцип и отказался от SCSS. Под этим я подразумеваю, что не мог возиться с настройкой SCSS, какой бы лёгкой она ни была, поэтому просто скомпилировал его вручную. Вот с таким компонентом я начал работу:
<script lang="ts"> import Note from "./components/Note.svelte"; type NoteType = { title: string; body: string; }; let notes: Array<NoteType> = []; const data = localStorage.getItem("notes"); if (data) notes = JSON.parse(data); let title: string = ""; let body: string = ""; function onSubmit() { let note: NoteType = { title: title, body: body }; notes.push(note); localStorage.setItem("notes", JSON.stringify(notes)); } </script> <div class="container"> <header> <h1>Notes App</h1> </header> <main> {#each notes as note} <Note title={note.title} body={note.body} /> {/each} <div class="note"> <form on:submit={onSubmit}> <input type="text" placeholder="Note Title" bind:value={title} /> <textarea placeholder="Note Body" bind:value={body}></textarea> <input type="submit" value="Add Note" /> </form> </div> </main> </div> <style> body { height: 100%; width: 100%; margin: 0; } .container { width: 100%; height: auto; margin: 0; display: flex; flex-direction: column; } .container header { display: flex; align-items: center; width: 100%; height: 56px; background-color: #4e78b8; color: white; } .container header h1 { margin-left: 6px; } .container main { margin: 10px; display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-gap: 1rem; align-items: center; } .container main .note { display: flex; flex-direction: column; padding: 10px; background-color: #a15fbb; border-radius: 5px; } .container main .note form { display: flex; flex-direction: column; } .container main .note form textarea { resize: none; } </style>
А вот компонент Note:
<script lang="ts"> export var title: string; export var body: string; </script> <div class="note"> <h4>{title}</h4> <span>{body}</span> </div>
Я обнаружил проблему, решения которой не знаю, и на данном этапе не хочу её решать: стили работают, только если вставить их в
bundle.css, который затем сбрасывается при обновлении кода и ресурсов. Это не будет проблемой в полностью собранном приложении, но очень раздражает при тестировании. Не думаю, что буду решать эту проблему в ближайшее время, но, вероятно, со временем устраню её.Заключение
Выше я говорил, что буду пробовать создавать при помощи Svelte и другие приложения, но не знаю, насколько решительно я готов выполнять это обещание. Хотя мне понравились многие аспекты Svelte, в нём слишком много препятствий, чтобы использовать его чаще. Учитывая проект, над которым я работал, React, по моему мнению, подвергся несправедливой критике. Angular по-прежнему кажется мне самым чистым, Vue — самым интересным, а jQuery — самым лучшим, что меня сильно удивило. Думаю, если бы мне пришлось выбирать фреймворк для будущих проектов, то это определённо бы зависело от проекта, но сам я вполне могу представить, что использую все пять, даже с учётом сложностей Svelte. Тем не менее, вероятно, основную свою работу я буду выполнять в Angular и React, а jQuery и Vue будут следующими по приоритету. Наверно, я дам Svelte ещё один шанс, но не думаю, что буду создавать в нём особо много проектов, вне зависимости от того, была ли справедлива критика в этом проекте. В любом случае, мне кажется, что любой из этих фреймворков станет отличным выбором для множества способов применения. Теперь я вижу, почему людям нравится Vue, но не могу сказать, что моё мнение очень сильно изменилось.
Код
Весь код выложен на github
— — — —
Дата-центр ITSOFT – размещение и аренда серверов и стоек в двух ЦОДах в Москве; colocation GPU-ферм и ASIC-майнеров, аренда GPU-серверов. Лицензии связи, SSL-сертификаты. Администрирование серверов и поддержка сайтов. UPTIME за последние годы составляет 100%.