あ、どうもみなさん、Qiitaでははじめまして。S高3期生の中村栞と申します。普段はnoteに生息しています、よろしくお願いします。
まえがき
ここ最近、Webサイトを作る機会がたびたび発生します。例えばキャンパスで行うイベントのサイトだったり、自分のWebサイトだったり、あとはキャンパスで使うシステムのフロントエンドを作ることもあったりします。
そんなことをやりながら、わたしはふとこう思いました。
「モダンな技術を学べば、ユーザー体験がよくなってつよつよになれるんじゃね?」
実際のところ必ずしもそうとはいえないかもしれませんが、まあとりあえずこのような考えが浮かんできました。しかしわたしは初学者、世の中にいろいろなものが散乱しすぎて何が何やらよくわかりません。そこで、とりあえず有名どころを試してみようということになりました。
今回使うもの
さてみなさん、モダンなフロントエンドフレームワークといえば何が思いつきますか?ご覧のみなさんはわたしより遥かに学がありまくりな方々だと思いますので、色々な回答をいただけることでしょう。わたしは何もわからないので、とりあえず周りに聞いたりしながら3つの候補を選びました。
React
まずはやっぱりこれでしょう、王道中の王道、React選手です。開発主導はMeta社、実績は数知れず、エコシステムは盤石。扱えれば即役に立つといえるでしょう。
ただ、有識者の方々におすすめのフレームワークを聞いたとき、知名度にもかかわらずなぜか候補に挙がらなかったんですよね。ちょっと難解なのかも。
また、Web記事を漁ると大規模開発向きという意見が散見されました。初心者向きのものではないということでしょうか…?
Vue.js
次に浮かんだのがVue.js。実はわたしが最初に選んだフレームワークはこれです。過去にとあるプロジェクトをやらせていただいた時に、Webサイトを担当された方がNuxtを使っていたというのが決め手になりました。
チュートリアルをやってみての印象はすげぇ簡単というところです。2日で終わっちゃいました。基本はシンプルにまとまっているなというふうに思います。そんな感じ。
Svelte
最後、ちょくちょく名前を聞いたのがSvelteです。有識者の方々が揃っておすすめしていた印象です。が、サイバネティクス!?SvelteKit!?なにそれよくわからんということでちょっと避けていたんですよね。まだ知名度もそこまで高くないかな?知り合いが使っているのを見たことはあります。
やること
さて、今回見ていくのはWebサイトの書きやすさです。(アプリのフロントエンドとしてのやりやすさはいったんおいておきます)
自分のWebサイトにある以下の部分を実装していきます。
ごく普通のリストですね。要件は次の通り。
- 個々のアイテムを
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をなんとかして適用済み
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
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;
}
`
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
<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>
<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扱いです)
<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>
<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でコンポーネントを呼び出している部分ですが、<SkillsItem {...props}/>
<skillsItem v-for="key in skills" v-bind="key"/>
これでいいらしいです。便利ですね!
なんでSvelteの時点でReactのこれに気付かなかったんだろう