React?Vue??Svelte???なんもわかんないので全部ちょっとずつやってみた〜〜〜！！！


あ、どうもみなさん、Qiitaでははじめまして。S高3期生の**中村栞**と申します。普段は[note](https://note.com/32ma_me)に生息しています、よろしくお願いします。
## まえがき
ここ最近、**Webサイトを作る**機会がたびたび発生します。例えば**キャンパスで行うイベントのサイト**だったり、[自分のWebサイト](https://32ma.me)だったり、あとは**キャンパスで使うシステムのフロントエンド**を作ることもあったりします。
そんなことをやりながら、わたしはふとこう思いました。

「**モダンな技術を学べば、ユーザー体験がよくなってつよつよになれるんじゃね？**」

実際のところ**必ずしもそうとはいえない**かもしれませんが、まあとりあえずこのような考えが浮かんできました。しかしわたしは初学者、世の中にいろいろなものが散乱しすぎて**何が何やらよくわかりません**。そこで、**とりあえず有名どころを試してみよう**ということになりました。
## 今回使うもの
さてみなさん、**モダンなフロントエンドフレームワーク**といえば何が思いつきますか？ご覧のみなさんはわたしより**遥かに学がありまくりな方々**だと思いますので、色々な回答をいただけることでしょう。わたしは何もわからないので、とりあえず周りに聞いたりしながら**3つ**の候補を選びました。
### React
[![公式サイト react.dev](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617744/2586c6fa-883e-a905-4e3b-965fa2b23963.png)](https://react.dev)
まずはやっぱりこれでしょう、**王道中の王道**、React選手です。**開発主導はMeta社、実績は数知れず、エコシステムは盤石**。扱えれば**即役に立つ**といえるでしょう。
ただ、有識者の方々におすすめのフレームワークを聞いたとき、知名度にもかかわらず**なぜか候補に挙がらなかった**んですよね。ちょっと難解なのかも。
また、Web記事を漁ると**大規模開発向き**という意見が散見されました。初心者向きのものではないということでしょうか…？
### Vue.js
[![公式サイト vuejs.org](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617744/e1d983f1-0ced-85ac-557c-d783d75c4e21.png)](https://vuejs.org)
次に浮かんだのがVue.js。実はわたしが**最初に選んだフレームワーク**はこれです。過去にとあるプロジェクトをやらせていただいた時に、**Webサイトを担当された方がNuxtを使っていた**というのが決め手になりました。
チュートリアルをやってみての印象は**すげぇ簡単**というところです。**2日で終わっちゃいました**。基本は**シンプルにまとまっているな**というふうに思います。そんな感じ。
### Svelte
[![公式サイト svelte.dev](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617744/d5580949-5840-bc8b-2aae-64337080835e.png)](https://svelte.dev)
最後、ちょくちょく名前を聞いたのがSvelteです。有識者の方々が**揃っておすすめしていた**印象です。が、**サイバネティクス！？SvelteKit！？なにそれよくわからん**ということでちょっと避けていたんですよね。まだ知名度もそこまで高くないかな？知り合いが使っているのを見たことはあります。
## やること
さて、今回見ていくのは**Webサイトの書きやすさ**です。(アプリのフロントエンドとしてのやりやすさはいったんおいておきます)
自分のWebサイトにある以下の部分を実装していきます。
[![今回のお題](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617744/f6f2c5f5-b104-3f3f-e77c-862b2dfee0b2.png)](https://32ma.me/#skills)
ごく普通のリストですね。要件は次の通り。
- 個々のアイテムを`skillsItem`という名前のファイルに**コンポーネント化**する
- スクリプト内で**オブジェクトの入った配列を定義**し、それを利用して`skillsItem`コンポーネントを並べる ~~←実は本物はこれをやってないのは内緒~~

これらの書き方でサイトの作りやすさがなんとなく見えてくるかなと思ったので、このような方法をとりました。なお、左側のアイコンは**Iconifyから**取得しています。
## 環境
では、お題が整ったところで次に実行環境を整えていきましょう。
### 共通
- MacBook Air Late 2020 (M1, 16GB)
- macOS Sonoma 14.1.1
- Node.js 18.18.2
- Vite 4.5.0
- TypeScript 5.3.2
- `npm init vite` でプロジェクトを作成
- yarn 1.22.21
- 事前に[汎用CSS](https://32ma.me/general.css)をなんとかして適用済み
### React
- React 18.2.0
- TypeScript + SWC
- `styled-components@6.1.1`
- `@iconify/react@4.1.1`
### Vue.js
- Vue.js 3.3.8
- `@iconify/vue@4.1.1`
### Svelte
- Svelte 4.2.7
- `@iconify/svelte@3.1.4`
↑Svelteだけバージョン低めなのが気になる
## 書く
それでは準備が全て完了しましたので、お題と同じようなコードを各フレームワークで書いていきましょう。
### React
```tsx:components/skillsItem.tsx
import { Icon } from '@iconify/react';
import styled from "styled-components";

export default function skillsItem(p:{icon:string,category:string,title:string}){
    return(
    <StyledDiv>
        <Icon icon={p.icon}/>
        <div className="desc">
            <p>{p.category}</p>
            <p className="title">{p.title}</p>
        </div>
    </StyledDiv>
    );
}

const StyledDiv=styled.div`
    grid-template-columns:6rem calc(100% - 6rem);
    display:grid;
    grid-auto-flow:column;
    width:min(500px,calc(90vw - .5rem));
    margin-block:.5vh;
    margin-inline:.5rem;
    padding-block:1vh;
    background:rgb(var(--bg));
    border-radius:10px;
    justify-content:center;
    box-shadow:0 0 8px 3px rgba(var(--fg),.5);
    .iconify{
        margin:auto;
        width:3rem;
        height:auto;
    }
    .desc{
        margin-block:auto;
        margin-left:.5rem;
        position:relative
    }
    .desc::before{
        content:"";
        display:inline-block;
        background:rgb(var(--fg));
        width:2px;
        height:4vh;
        position:absolute;
        top:calc(50% - 2vh + 0.12rem);
        left:-.6rem;
    }
    p{
        margin:0;
    }
    .title{
        font-weight:600;
        font-size:1.5rem;
    }
`
```
```tsx:App.tsx
import SkillsItem from "./components/skillsItem.tsx";
import styled from "styled-components";
const skills=[
  {
    icon:"simple-icons:adobeillustrator",
    category:"デザイン",
    title:"Illustrator"
  },
  {
    icon:"simple-icons:adobexd",
    category:"デザイン",
    title:"Adobe XD"
  },
  {
    icon:"simple-icons:adobeaftereffects",
    category:"映像制作",
    title:"Adobe After Effects"
  },
  {
    icon:"simple-icons:googleappsscript",
    category:"プログラミング",
    title:"Google Apps Script"
  },
  {
    icon:"simple-icons:javascript",
    category:"プログラミング",
    title:"JavaScript"
  },
  {
    icon:"simple-icons:python",
    category:"プログラミング",
    title:"Python"
  },
  {
    icon:"simple-icons:vuedotjs",
    category:"Web制作",
    title:"Vue.js"
  }
];

export default function(){
    return (
      <Main>
        <h2>Skills</h2>
        <div className="list">
          {skills.map(key=>(
            <SkillsItem icon={key.icon} category={key.category} title={key.title}/>
          ))}
        </div>
      </Main>
  )
}

const Main=styled.main`
  .list{
    margin-top:2rem;
    display:grid;
    justify-items:center;
    grid-auto-rows:none;
    grid-gap:15px;
  }
  h2{
    font-size:2.5rem;
    text-align:center;
    font-weight:500;
    margin-top:3rem;
  }
`
```
なるほどなるほど。(コード配置は若干VueやSvelteのそれに寄せています)
まず感じたのは「**冗長だなぁ〜**」ということですね。JSX(TSX)は**JavaScript/TypeScriptの拡張**なので、どうしてもプログラムとして書かないといけません。~~あとコンポーネント名の先頭が大文字なのは解せない~~
ただ、**規模が大きくなるとこのほうが作りやすいんだろうなぁ**とも思いました。わたしのサイトみたいなシンプルなものだと若干冗長かもしれませんが、それこそ**𝕏みたいなクソデカプロジェクト**に使うならこうあったほうがいいような気もしますね。
### Vue.js
```vue:components/skillsItem.vue
<script setup>
    const props=defineProps({icon:string,category:string,title:string});
    import {Icon} from '@iconify/vue';
</script>
<template>
    <div class="item">
        <Icon :icon="icon"/>
        <div class="desc">
            <p>{{category}}</p>
            <p class="title">{{title}}</p>
        </div>
    </div>
</template>
<style scoped>
.iconify{
    margin:auto;
    width:3rem;
    height:auto;
}
.item{
    grid-template-columns:6rem calc(100% - 6rem);
    display:grid;
    grid-auto-flow:column;
    width:min(500px,calc(90vw - .5rem));
    margin-block:.5vh;
    margin-inline:.5rem;
    padding-block:1vh;
    background:rgb(var(--bg));
    border-radius:10px;
    justify-content:center;
    box-shadow:0 0 8px 3px rgba(var(--fg),.5);
}
.desc{
    margin-block:auto;
    margin-left:.5rem;
    position:relative
}
.desc::before{
    content:"";
    display:inline-block;
    background:rgb(var(--fg));
    width:2px;
    height:4vh;
    position:absolute;
    top:calc(50% - 2vh + 0.12rem);
    left:-.6rem;
}
p{
    margin:0;
}
.title{
    font-weight:600;
    font-size:1.5rem;
}
</style>
```
```vue:App.vue
<script setup>
  import skillsItem from "./components/skillsItem.vue";
  const skills=[
    {
      icon:"simple-icons:adobeillustrator",
      category:"デザイン",
      title:"Illustrator"
    },
    {
      icon:"simple-icons:adobexd",
      category:"デザイン",
      title:"Adobe XD"
    },
    {
      icon:"simple-icons:adobeaftereffects",
      category:"映像制作",
      title:"Adobe After Effects"
    },
    {
      icon:"simple-icons:googleappsscript",
      category:"プログラミング",
      title:"Google Apps Script"
    },
    {
      icon:"simple-icons:javascript",
      category:"プログラミング",
      title:"JavaScript"
    },
    {
      icon:"simple-icons:python",
      category:"プログラミング",
      title:"Python"
    },
    {
      icon:"simple-icons:vuedotjs",
      category:"Web制作",
      title:"Vue.js"
    }
  ];
</script>

<template>
  <main>
    <h2>Skills</h2>
    <div class="list">
        <skillsItem v-for="key in skills" :icon="key.icon" :category="key.category" :title="key.title"/>
    </div>
  </main>
</template>

<style scoped>
h2{
  font-size:2.5rem;
  text-align:center;
  font-weight:500;
  margin-top:3rem;
}
.list{
  margin-top:2rem;
  display:grid;
  justify-items:center;
  grid-auto-rows:none;
  grid-gap:15px;
}
</style>
```
ふむふむなるほど〜。~~これほぼサイトの元コードからコピペしてきたんですけどね~~
こうしてReactと並べてみると**かなり直感的**になりましたね。JavaScript、HTML、CSSに**きっちり境界線が敷かれています**。
Vueの勝手なイメージとして、**大体attributeでどうにかする**っていうのがあるんですけど、どうでしょう。`v-bind`も`v-for`も`v-if`も全部attributeですし。
### Svelte
(シンタックスハイライトに対応していなかったのでHTML扱いです)
```html:lib/skillsItem.svelte
<script lang="ts">
    export let icon:string,category:string,title:string;
    import Icon from '@iconify/svelte';
</script>
<div class="item">
    <Icon icon={icon}/>
    <div class="desc">
        <p>{category}</p>
        <p class="title">{title}</p>
    </div>
</div>
<style>
:global(.iconify){
    margin:auto;
    width:3rem;
    height:auto;
}
.item{
    grid-template-columns:6rem calc(100% - 6rem);
    display:grid;
    grid-auto-flow:column;
    width:min(500px,calc(90vw - .5rem));
    margin-block:.5vh;
    margin-inline:.5rem;
    padding-block:1vh;
    background:rgb(var(--bg));
    border-radius:10px;
    justify-content:center;
    box-shadow:0 0 8px 3px rgba(var(--fg),.5);
}
.desc{
    margin-block:auto;
    margin-left:.5rem;
    position:relative
}
.desc::before{
    content:"";
    display:inline-block;
    background:rgb(var(--fg));
    width:2px;
    height:4vh;
    position:absolute;
    top:calc(50% - 2vh + 0.12rem);
    left:-.6rem;
}
p{
    margin:0;
}
.title{
    font-weight:600;
    font-size:1.5rem;
}
</style>
```
```html:App.svelte
<script>
  import SkillsItem from "./lib/skillsItem.svelte";
  const skills=[
    {
      icon:"simple-icons:adobeillustrator",
      category:"デザイン",
      title:"Illustrator"
    },
    {
      icon:"simple-icons:adobexd",
      category:"デザイン",
      title:"Adobe XD"
    },
    {
      icon:"simple-icons:adobeaftereffects",
      category:"映像制作",
      title:"Adobe After Effects"
    },
    {
      icon:"simple-icons:googleappsscript",
      category:"プログラミング",
      title:"Google Apps Script"
    },
    {
      icon:"simple-icons:javascript",
      category:"プログラミング",
      title:"JavaScript"
    },
    {
      icon:"simple-icons:python",
      category:"プログラミング",
      title:"Python"
    },
    {
      icon:"simple-icons:vuedotjs",
      category:"Web制作",
      title:"Vue.js"
    }
  ];
</script>
<main>
  <h2>Skills</h2>
  <div class="list">
      {#each skills as key}
      <SkillsItem {...key}/>
      {/each}
  </div>
</main>
<style>
h2{
  font-size:2.5rem;
  text-align:center;
  font-weight:500;
  margin-top:3rem;
}
.list{
  margin-top:2rem;
  display:grid;
  justify-items:center;
  grid-auto-rows:none;
  grid-gap:15px;
}
</style>
```
**…え？**

**……え？？？**

**脳内設計通りにコードを組めるんだが？？？？？？？？？**

……はい。というわけで、Svelteは初めて触ったとき**マジの感動**を覚えました。これが**サイバネティクスで強化されたweb アプリ**ですか。最高かよ。
基本的に**VueのSFCと似たつくり**なのですが、全てをattributeに注ぎ込んでいたVueに対し、こちらは**Logic blocks**(論理ブロック？)というのを使うことで**より直感的**に書くことができます。
あと、Vueはattributeに変数を置きたい際に`v-bind`を使っていたのに対し、こちらはattributeでも波括弧`{}`で囲むので、**HTMLをハックしてる感**がより出て個人的に好きです。~~でもコンポーネント名の先頭が大文字なのは解せない~~
あ、`<style>`で**子コンポーネントのテンプレートにスタイルを適用できない**のはちょっと面倒ですね。`:global`を使ってあげる必要がある。
## まとめ
では今回の検証結果をまとめてみましょう。
- Reactは**非常にプログラマチック**で、シンプルなWebサイトには冗長かも。しかしながら**大規模になると火を吹きそう**
- Vue.jsはReactと比べ**いくらか直感的**だが、**とっつきにくい独自仕様**も少しある。ただ**規模に限らずある程度活躍してくれそう**
- Svelteは**作者エスパーか**ってぐらい**わたしの心をよくわかってる**。すごい。ただあまり触れてないので**大規模での実力は未知数**
- みんなコンポーネント名の先頭を大文字にしろって言ってきます
↑これ、調べたらReactでは**組み込みのHTMLコンポーネントと区別する**ためにそうしているらしいですね。そういうお作法だと思っておきます。

## おわりに
まぁでも、結局なんだかんだ言って**よくわかってないのは変わらない**ので、今一番やってるVueを中心にいろいろ勉強していこうかなと思います。ではまた〜

> 追記 2023/12/15
> ReactとかVueでコンポーネントを呼び出している部分ですが、
> ```tsx
> <SkillsItem {...props}/>
> ```
> ```vue
> <skillsItem v-for="key in skills" v-bind="key"/>
> ```
> これでいいらしいです。便利ですね！~~なんでSvelteの時点でReactのこれに気付かなかったんだろう~~