587
646

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

バックエンドエンジニアのためのVue.js、React、Angular入門(2025改訂版)

Last updated at Posted at 2020-01-24

解説のバージョン(2025年3月時点)

  • Vue3.5
  • React19
  • Angular19

WEBシステム、WEBプログラム開発において昨今ではVue、React、AngularなどのJSフレームワークが主流となってきています。ただ、これらの活用は学習コストが高いといわれていました。その原因について、自分はフロントエンドありきで話が進みすぎていたからだと考えています。したがって、自分の投稿記事はバックエンド処理をメインにこなしてきたWEBシステムエンジニアに向けた、フォーム操作をメインに置いた半備忘録兼自分なりに解釈した解説となります。

ちなみに自分はサーバ構築からフロントエンド、バックエンドまでこなしているワンオペエンジニア(フリーランス、非正規雇用に非ず)です。それから自分は根っからの文系なので、公式チュートリアルの抽象的な説明にどうしても慣れないというのもあったので、かなり独自解釈が多いかも知れません。

  • コンポーネント、ルーティング、スタイル周り、TypeScriptに関する続編も公開しています(編集が困難になるほど容量圧迫してたので、それぞれで独立させています)。

続編で一番見られているSvelte版(Svelte5、runeにも対応しました)

solidJSも鋭意作成中

§1:Vue.jsとReact、そしてAngular

その前に、vue.jsとReact、そしてAngularとはどういったもので、どんな意図で開発されたものかを知っておく必要があります。そして、その理念を知っていたら、各フレームワークの理屈もわかりやすいからです。

ひとまず、共通のスローガンがあります。それはいずれも素早くデータの同期をとりたいために実現させている技術であることです。

※もともとはjQueryとの比較目的でしたが、昨今新たにjQueryを学習する機会は減っているので、2025年版では紹介対象外としています(jQueryも2024年になって遂に4が登場しましたが…)。

Vue.js

Vue.js(ヴュージェイエス、以下Vueと表記)はもともとGoogleが開発したデータ同期用ライブラリAngularJS開発者の一人、エヴァン・ヨーが、個人で開発を始めたJSフレームワークです。Vueは柔軟に開発できるというコンセプトで生み出されたプログレッシブ・フレームワークという概念を持っており、フォーム操作の円滑化のために開発されました。そのため、小規模の開発に向いた柔軟な利用も可能です。そのVueとは一言でどんな技術かというと

アプリケーション内のインラインテンプレートに対して、Vueディレクティブという魔法をかけ、多種多様な呪文(プロパティ)でデータを加工していく

こういうものです。VueディレクティブとはVueで定義されるv-hogeという各種コマンドのことで、データのインプットとアウトプットをリアルタイムで行うことができたりするリアクティブ(同期)操作のことです。そして、AngularJSはhtml側でngというプロパティやメソッドを使ってバックエンドの処理をしており、主にフォーム周りの制御をより円滑にするために開発されたものでした。したがって、かなり明解だった反面、開発が進むとかなりhtmlソースが汚されてしまうので、技術をある程度継承し、Angularというパッケージ単位のフレームワークを作ったわけです。しかし、このAngularJSの双方向バインディングと簡潔さを捨てるのはもったいないと、飛躍的に発展させたのがVue.jsでありVueディレクティブというもので、これはソースが汚れる原因となったhtml側でのバックエンド処理を、スクリプト側(コンポーネントを作成して管理)で処理させるようにしました。つまり

  • AngularJS: 双方向バインディング処理をhtml側で処理できたために、ソースが汚れてしまい、敬遠されてしまった。
  • Vue.js  : バインド(双方向バインディングとほぼ同定義)処理をスクリプト側に記載させるようにし、html側は基本、メソッドとプロパティだけ入出力させるようにした(つまり、JavaSrciptの基本に返った)

この理念を覚えておけば、今後の学習にも役立ちます。また、既存の書き方をoptions APIと呼びますが、Vue3よりcomposition API(コンポジションAPI)を用いた記法が可能となっています。またVue3.2からはボイラープレート(冗長な記述)を除去したsetup記法が主流となっており、公式はsetupを推奨しています(setupはCDNがないので、公式のplaygroundなどを活用するといいでしょう)。

また、昨今ではバックエンド用フレームワーク(LaravelやRailsなど)のフロントエンドツールとして選択されることも多くなっています(Laravelの場合はVueと提携)。

React

React(リアクト)はMeta社(当時はFacebook社)が自社ツール(Facebook、Instagram)のデータ管理のために開発したコンポーネント型のJSフレームワークで、その鍵となるのはJSXという記法です。そのJSXとはなにかというと、スクリプトの中に直接埋め込める、html言語とほぼそっくりそのままの処理用言語で、それを作った理由は後述するように完全なるデザイン部分とプログラム部分の切り分け(すなわち部品管理の徹底化)のためです。ですが、その独特の記法によって技術者や入門者に違和感を与え敬遠されてしまっていたのも事実で、そのために何度も記法が変更されてきており、昨今ではだいぶ見やすく、そして記述しやすくなっています。それでもReactの基本的な理念と動きは変わっておらず、一言でいうと

html内に魔法結社(JSXの製造拠点、つまりはコンポーネント)を作り、そこでリアクティブな部品(JSX)を錬金する

こういうものです。つまり、Vue.jsだといわゆるリアクティブな部品(Vueディレクティブ)自体はhtml上にあったのに対し、Reactの場合は、リアクティブな部品はhtmlになく、バックエンド上のコンポーネントで作成されることになります。そして、リアクティブな処理を行う際もそのバックエンド内だけで処理するので、部品もバックエンドにしか存在しないことで、開発を分担しやすく小~中規模、かつきめ細かいステート管理が必要なデータ処理に向いています。

  • React :部品の管理を徹底するために、リアクティブなデータ処理をhtml側で一切できないようにしている。部品の調達と管理はすべてJSXに則ったコンポーネント内で行う。

これが基本です。あと、コンポーネントの記法もいろいろあって、しかもしょっちゅう変更されていた(2025年現在は関数コンポーネントでの記述が主流なので、新たに学習する場合は関数コンポーネントだけにするといいでしょう。

Angular

Angular(アンギュラー)はGoogle社が開発したAngularJSでの失敗を教訓に、その失敗の原因となったソースの汚れを解消させたものです。そしてvue、Reactのようなライブラリとは違い、RubyのRailsやPHPのLaravel、PythonのDjangoのような、それ一つでパッケージとなっているフルスタックフレームワークです。また、TypeScriptがベースとなっています。

パッケージ自体がAngularという魔法世界(フレームワーク)であり、その中でバインディングという魔法の扉でデータをやりとりする

なので、そのパッケージ内では自由自在にAngularの技術を利用できます(VueやReactのように、どこからどこまでという範囲指定が不要)。ですが、前述したようにソースの汚れを反省して作ったフレームワークなので、いわゆるリアクティブな部品は各アーキテクチャ内で実行するようになっていて、バインディングというものでデータや処理のやりとりができるようになっています。なので、上記2つと比較するといろいろなメソッドでデータの同期を取るというよりは、Ajaxでやりとりしているようなものです。ただ、基本はAngularJS時代とそこまで変わっていないためにあまり難しくなく、どちらかというとVue.jsに(特にoptionsAPI時代のVue.js)近いですが、JavaScriptでの記述法がそのまま応用できるためVue.jsよりデータのやりとりの理屈は解りやすいので、フレームワーク開発に慣れているバックエンドエンジニアなら、上記2つより学習は楽でTypeScriptの癖さえ押さえておけば簡単にプログラムを作れます。また、大規模開発にも対応できます(ただ、普通に軽量システムに用いることも可能ですし、敬遠の原因の一つともいわれるRxJSも、ルーティング(続編の第6章参照)でデータを受け渡しするまでは覚える必要ありません。スペックが揃っているので、小規模~中規模システムには少しばかり性能を持て余していた感があったのですが、スタンドアロンコンポーネントとsignalを標準実装してから小規模なアプリケーションにも適応しています。

  • Augular:バインディング処理を行う部品はhtml上にあるが、処理は外部のアーキテクチャ内で行う。言語はTypeScriptを用いる。

※TypeScriptですが、JavaScriptと互換性を持っているため、そこまで難しく考えることはないと思います。

※本記事でAngularJSは採り上げません。よく勘違いしている記事を見かけますがAngularJSとAngularは全く別物で、互換性は全くない上にAngularJSは2021年12月を以てサポート終了しています。

§2:バックエンドのための基本文法

前述したように、これらのJSフレームワークの学習コストが肥大化してしまった原因は、フロントエンドありきで解説していることが多かったためでしょう。そのため、バックエンドエンジニアが基礎の基礎である文法もわからずに、そっちばかりに目が行ってしまっていることで混乱を招き、これらの技術に手を出す気力を与えず、比較的記述が簡単なjQuery依存から脱皮できなかったのではないかと考えています(自分もそうでした)。

したがって、自分はjQueryで操作してきたフォーム操作をVue、React、Angularではどう記述するのかを重点において解説してきました。

また、かつてはライブラリとして使用することを前提としていた部分もあったために、CDNでの記述もできましたが、Vueは非対応に、Reactも非推奨となっているので、VueはVueクライアント、ReactはReact Nativeで演習環境を作成しています(2025年現在においては、Vite対応なのでそれを利用して簡単に開発環境を構築できます)。

また現在では、それぞれ公式サイトにプレイグラウンドがあるので、そこで実践してみてはいかがでしょうか。

※アプリケーションの仕組みにおける詳細については各続編の演習5と6で詳しく紹介します。

演習1:フォーム操作:テキスト文字を表示させる

フォーム操作の基本の基本です。ですが、この基本だけでかなり記法の根本が理解できるのも事実です。ただ、単純に表示させるだけなのも面白くないので、キーの打鍵ごとに値を表示させるようにしましょう。

Vueで再現

Vueで記述すると以下のようになります。

lesson1.vue
<script setup>
import { ref } from 'vue'; //外部からrefをインポート
const mes = ref("") //refはプリミティブな変数を展開するために必要
console.log(mes.value) //値に展開する場合はvalueを付与すること
</script>

<template>
	<input type="text" v-model="mes">
	<p>入力された文字<span>{{ mes }}</span></p>
</template>

script setupは非常に記述がスリムかつ、シンプルになりました。

ref

プリミティブ(単一)なデータをリアクティブにするメソッドで、外部からインポートして使用します。注意点として、refで取得した値はスクリプト上で展開する場合、hoge.valueとする必要があります。この変数を同期させることをリアクティブと呼んでいます。

※refによってリアクティブになった変数は、ステータスも保持しています。

v-modelディレクティブ

refで指定した変数に対し、値を同期させるためのディレクティブで、これを用いればすぐに値を取得できます。

マスタッシュ

テンプレート上に値を展開する記号はマスタッシュといいます。{{ hoge }}と表記されます。注意点はrefを通した場合で、スクリプト上ではhoge.valueなのに対し、テンプレート上ではvalueの付与は不要です。

Reactで再現

次はReactで記述してみます。

jsx.lesson1.jsx
import { useState } from "react"; //ローカルの場合で、useStateを使用するための定義
function Lesson(){
	//処理部分
	const [mes,setMes] = useState(""); //バインディング処理
	//レンダー部分
	return(
		<div>
			<input onChange={(e)=>setMes(e.target.value)} />
			<p>入力された文字<span>{ mes }</span></p>
		</div>
	);
}
export default Lesson

JSX

Reactはテンプレートではなく、JSXを返します。また、このJSXは共通する以下の決まりがあります。

  • 単一の親要素しか生成してはいけない(中に子要素、孫要素があるのは問題ない)
    もし、複数のタグを使いたい場合は、<>という空タグでラッピングするのが昨今のセオリーです。
  • <input>タグのようにそれ一つで完結するタグは必ず<input />のようにスラッシュを入れること。これを入れないとJSXがどこまで適用したらいいのかわからずにエラーが起きます。これはコンポーネントの<App />などでも同様です。
  • JSXは必ず丸括弧(...)で囲って値を返すこと。これは次章のmapメソッドでも活用します。
  • プロパティに特殊な記述ルールがある
    たとえば、classプロパティはclassが予約語であるため、ReactではclassNameと記述する必要があります。

useState

Reactにおいてすべての基本となるフックです。このuseStateで設定した変数がリアクティブ(同期対象)になります。フックはReactを操作する上で極めて重要なので、演習3で詳しく紹介します。

const [hoge,setHoge] = useState(初期値)

●イベント

イベントはonHogeとキャメルケースで記述し、後述するオブジェクトリテラルでラッピングします。また、その関数は以下のように、式を展開しないと無限ループに陥ります。

			<input onChange={(e)=>setMes(e.target.value)} />

eは便宜上の引数で、e.target.valueとすることで、任意のフォームにおける値を取得することができます。この取得した値を、前述したuseStateフックのセッタに代入しています。

◆Angular

同じ動作をAngularでも再現してみます。Angularもスタンドアロンコンポーネントになってからは、単一コンポーネントを制御するのが楽になりました。

デコレータ

Angularはデコレータというもので、各部品に対してプロパティを設定します。コンポーネントの場合@Componentデコレータで、親コンポーネントの名称指定、外部ファイルのインポートやテンプレート、スタイルの設定を行います。また、デコレータを使用するには各種モジュールを、事前に外部からインポートしておく必要があります。

lesson.component.ts
import { Component } from '@angular/core'; //Componentデコレータをインポート
import {FormsModule } from '@angular/forms'; 

//デコレータ
@Component({
  selector: 'app-lesson',
  imports:[FormsModule], //インポートを忘れない
  template: `
	<input type="text" [(ngModel)]="mes">
	<p>入力された文字<span>{{ mes }}</span></p>  
  `,
})

//コンポーネントクラス
export class LessonComponent{
	mes = '' //テンプレートで使用する変数を事前に設定する
}

◆バインディング

[(ngModel)]は双方向バインディングといって、これ一つでVueのv-modelと同じように、フォームの値に対し同期制御ができます。ちなみに[hoge]はプロパティバインディングといって、値を受け取る側、(hoge)はイベントバインディングといって、値を受け渡す側となります([(hoge)]はその2つを合体させたものです)。

※また、この双方向バインディングはFormsModuleを使用するので、imports[FormsModule]と、@Componentsデコレータに対するオプション記述を忘れないようにしましょう。

◆テンプレート

基本はtemplateURLプロパティが設定され、外部ファイルから呼び出すようになっていますが、短いテンプレートの場合は、templateとプロパティを書き換え、内部に直接記述した方が手っ取り早いです。その際、テンプレートはバッククォート(`)で区切る決まりがあります。

演習2:プルダウンメニュー(ループ処理)

では、今度は繰り返し処理の実例としてプルダウンメニューの生成とイベント処理をそれぞれ記述していきます。そして、共通の動作としてプルダウンメニューをオブジェクトから生成し、選択した値を表示させるという動きをそれぞれVue、React、Angularで再現してみます。

Vue

ループ処理をVueで記述するとこうなります。

lesson2.vue
<script setup>
import { ref,reactive } from 'vue';
const sel = ref("");
const state = reactive({ary_data :[
	{id:0,name: "React"},
	{id:1,name: "Angular"},
	{id:2,name: "Vue"},
	{id:3,name: "Svelte"},
	{id:4,name: "solidJS"},
	{id:5,name: "Lit"},
]})
</script>

<template>
	<select v-model="sel">
		<option value="">選択</option>
			<option v-for="(data,idx) in state.ary_data" :value="data.name" :key="idx">{{ data.name }} 
		</option>
	</select>
	<p>選択された値:<span id="txt" >{{ sel }}</span></p>
</template>

非常にすっきりした内容となりました。後述するReactと比較してもVueはフォーム制御に向いており、何よりv-modelディレクティブを使用するだけで値を一発で同期できるのが魅力的です。

前述したように、ループ箇所はv-for="(data,idx) in ary_data"という部分であり、これは配列分のoptionタグに対してvalueプロパティとテキスト値を付与するループ処理となっています。forはPHPのfor文などでもお馴染みですが、よく知られる「~のために」という意味のほかにも 「~している間」 という意味があり、その名のとおり配列ary_dataを展開している間はdataという変数に格納しているわけです。しかもVueディレクティブによってスクリプトに数式を記述することなくループ処理が行えるので、かなり記述が楽になります。なお、ループできるのは何もoptionタグだけでなく、リスト作成に用いるliタグやテーブル作成のtdタグ、はたまたテンプレートタグを用いれば複数のタグをひとまとまりでループさせることも可能です。

ちなみに、ループさせる際には一意性を保つために:key(v-bind:keyの省略形。後述)が必須となり、これを忘れると警告が表示されます。

■Vueディレクティブの省略記法

  • v-bind:xxxx → :xxxx
    上記のoptionにあるvalueと:valueは別物で、:valueはVueディレクティブにおけるv-bind:valueの省略記法です。v-bindは非常に使用頻度が高いので、このように省略記法を使うことが多く、慣れてきたら使ってみるといいでしょう。なお、v-bindとはhtmlタグ内のプロパティに、任意の値を同期させるプロパティで、この場合v-forを展開している間に、valueプロパティにその値を格納させる働きを持っています(あくまでhtmlの内側です。外側でvueディレクティブは使えないので、そちらでは前述したように {{ hoge }} (マスタッシュ)を使用します。

  • v-on:xxxx → @xxxx
    もう一つ使用頻度が高い省略記法が上記のもので、これはイベントを実行するときに使用し、同じく使用頻度が高いv-onを省略させるものでイベンドハンドラと呼びます。これは次章で採り上げます。

reactive

変数をリアクティブ制御するメソッドは、reactiveというものもあり、これは一まとまりのデータをproxy制御によってリアクティブにしてしまうというものです。したがって、このreactiveで展開する場合、value指定は不要ですが、出力用の変数から展開する必要があります(公式ではstateに統一していますが、任意の変数を利用できます)。

React

同じようにプルダウンメニューの生成をReactでも記述してみます。記述は関数コンポーネントを使っています。

lesson2.jsx
import { useState } from "react"; //ローカルの場合で、useStateを使用するための定義
function Lesson(){
	//変数定義
	const [sel,setMenu] = useState(true);
	const ary_data = [
		{id: 1,name: "React"},
		{id: 2,name: "Angular"},
		{id: 3,name: "Vue"},
		{id: 4,name: "Svelte"},
		{id: 5,name: "solidJS"},
		{id: 6,name: "Lit"},
	];
	//値の設定処理(いわばVue.jsの算出プロパティに当たる部分)
	const setList = ary_data.map((data,idx)=>(
		<option key={idx} value={data.name}>{data.name}</option>
	))
	//レンダリング
	return(
		<>
		<select onChange={(e)=>setMenu(e.target.value )}>
			<option>選択</option>
			{ setList }
		</select>
		<p>選択された値:<span>{sel}</span></p>
		</>
	);
}
export default Lesson

Reactが採用しているJSXは、JavaScriptの延長線で記述できることを売りとしています。そのため、ループ制御における特有のコントロールフローといった部品はなく、map関数を用います。後は、他の2例と同様にvalueプロパティやテキスト値にidやnameを配列順に代入していきます。

※ただしJSX上でのmapによる展開は、keyプロパティを忘れると警告が出ます。必ず一意の値(他と被らないもの)を指定するようにしてください。

//設定処理 ary_dataを順々に展開し、data変数に格納。該当するプロパティをそれぞれ当てはめていく。
	const setList = ary_data.map((data,idx)=>(
		<option key={idx} value={data.id}>{data.name}</option>
	))

●オブジェクトリテラルについて

Reactでは、JSXと変数、あるいは計算式などを区別するために、オブジェクトリテラルという、**{ ... }**で記述される波括弧を用います。

Angular

Angularでもプルダウンを作成してみます。

lesson.component.ts
import { Component,signal } from '@angular/core';
import {FormsModule } from '@angular/forms';
const data = [
	{id: 1,name: "React"},
	{id: 2,name: "Angular"},
	{id: 3,name: "Vue"},
	{id: 4,name: "Svelte"},
	{id: 5,name: "Solid"},
	{id: 6,name: "Lit"},
]
@Component({
  selector: 'app-lesson',
  imports:[FormsModule],
  template: `
		<select [(ngModel)]="sel">
			<option>選択</option>
			@for(data of ary_data ; let i = $index ;track i){
				<option [value]="data.name">{{data.name}}</option>
			}
		</select>
		 <p>選択された値:<span>{{sel}}</span></p>  `,
})

export class LessonComponent{
	ary_data = data
	sel = ''
}

プルダウンの場合はoptionタグのvalueプロパティをngModelが紐づけ、それを展開すると取得できます。また、ループ式に関してはAngular17から推奨記法が大きく変化しました。

◆ビルトインコントロールフロー

Angular17からはビルトインコントロールフローという埋め込み構文を利用できます。今回はループ文に@forを使用していますが、@forにはtrackが必須となっています。trackは一意な値を指定するのがセオリーなので、今回はidを利用しています。

@for( 展開したい値 of 展開対象のオブジェクト; track 一意となる値){
   //ループ対象のテンプレート
}

これが基本文です。インデックスを使用したい場合は以下のように$indexから取得します(iは任意の変数)。ループ文で、一意になる値が存在しない場合は、以下の記述が必須となります。

@for( 展開したい値 of 展開対象のオブジェクト; let i = $index; track i){
}

◆プロパティバインディング

optionタグのvalueに[value]="data.key"としているのはプロパティバインディングといってデータからビューへの偏方向を操作するために用います。

演習3:検索フォーム(値の連動)

今回は、テキストボックスに入力された文字をもとに、リスト一覧から該当する要素を出力するという機能をそれぞれVue、ReactそしてAngularで実装してみます。なお、これがよくある検索システムにおける基本の動きとなります。

Vue

Vueで記述すると、以下のような記述になります。今回は算出プロパティ、メソッド、そして監視プロパティが登場します。

lesson3.vue
<script setup>
import {ref,reactive,computed,watch } from 'vue';
const word = ref("");
const filterData = ref("")
let items = reactive({
      ary_data:[
        {id: 1,name: "モスクワ",img:"Moscow.jpg"},
        {id: 2,name: "サンクトペテルブルク",img:"Sankt.jpg"},
        {id: 3,name: "エカテリンブルク",img:"Yekaterin.jpg"},
        {id: 4,name: "ムンバイ",img:"Mumbai.jpg"},
        {id: 5,name: "ベンガルール",img:"Bengaluru.jpg"},
        {id: 6,name: "コルカタ",img:"Kolkata.jpg"},
        {id: 7,name: "サンパウロ",img:"SaoPaulo.jpg"},
        {id: 8,name: "リオデジャネイロ",img:"Rio.jpg"},
        {id: 9,name: "ブラジリア",img:"Brasilia.jpg"},
      ]
});
//算出プロパティ
const filter_data = computed(()=>{
	return  items.ary_data.filter( (item) =>{
		return item.name.indexOf(word.value) !== -1 && word.value != ''
	})
})
//メソッド(関数として記述する)
//値のクリアはボタンが押されたときだけ反応すればよい(リアクティブに対応しない。ケース1)
const clear = ()=>{
  console.log("clear")
  word.value = '' //returnを使わない場合最終行が評価される
}
//画像のパスを読み込む(画像はキャッシュされると同じ画像を返してしまうため、メソッドで制御する。ケース2)
const imagePath = (imgsrc)=>{
  if(imgsrc != ''){
	return `./image/${imgsrc}`
  }
}
//監視プロパティ
watch([word],()=>{
  const chr = hiraToKata(word.value)
  word.value = chr
})
//入力された平仮名をカタカナに変換する(システム内部で処理する関数。ケース3)
const hiraToKata = (chr)=>{
  return chr.replace(/[\u3041-\u3096]/g, function(match) {
	chr = match.charCodeAt(0) + 0x60;
	return String.fromCharCode(chr);
  })
}
</script>

<template>
<input type="text" v-model="word" />
<p>検索結果{{ filter_data.length }}</p>
<button @click="clear">文字のクリア</button><!-- clearはmethodsのケース1-->
<ul>
	<li v-for="(data,idx) in filter_data" :key="idx">
		<dl>
			<dt>{{ data.name }}</dt>
			<dd ><img :src="imagePath(data.img)"></dd>
		</dl>
	</li>
</ul>
</template>

■算出プロパティ(computed)

これはv-modelなどでリアクティブに依存している値が更新された場合のみ、即座にその更新データをキャッチして処理を行い、データの同期をとるものです。裏返せば、値が不変の場合は何も反応しないという優れた性能を持っています。

具体的には検索用のテキストボックスはv-modelディレクティブで値をバインドしているため、逐次検索文字が変わるごとにfilterDataメソッドが実行され、その結果を返します。当該メソッド内ではindexOf()メソッドによって値のマッチングを調べているので-1(検索結果なし)以外は反応するようにし、それに従いspliceメソッドによって検索結果を新たなlistオブジェクトに代入するようにしています。そしてその結果をv-forディレクティブの初期値に設定し、値をループさせています。

ちなみに、今回の場合は一番基本的な制御となり、ゲッタとセッタを設定していません(この2つについては後述します)。また、この算出プロパティは値がキャッシュされるので、それによって高速な値のリアクティブ処理が可能になっています(それによる弊害もあるのですが、それは次の説明で紹介します)。

■メソッド(methods)

算出プロパティに対して、メソッドというのものもあります。Vueでは算出プロパティで処理できるものが多いのですが、以下の場合に用いることが多いです。

  • (1)リアルタイムな値のバインドが不要なイベント制御
  • (2)画像ファイルなどブラウザに値をキャッシュされては困るものの再計算
  • (3)算出プロパティなど関数内での、内部で再計算などの処理が必要な場合

具体的な例を挙げましょう。

lesson3.vue
<script setup lang="ts">
/*中略*/
//メソッド(関数として記述するだけ)
//値のクリアはボタンが押されたときだけ反応すればよい(リアクティブ制御が不要な処理。ケース1)
const clear = ()=>{
  word.value = '' //returnを使わない場合最終行が評価される
}
//画像のパスを読み込む(画像はキャッシュされると同じ画像を返してしまうため、メソッドで制御する。ケース2)
const imagePath = (imgsrc)=>{
  if(imgsrc != ''){
	return `./image/${imgsrc}`
  }
}
//入力された平仮名をカタカナに変換する(システム内部での再計算処理。ケース3)
watch([word],()=>{
  const chr = hiraToKata(word.value) //ここから呼び出している
  word.value = chr
})
const hiraToKata = (chr)=>{
  return chr.replace(/[\u3041-\u3096]/g, function(match) {
	chr = match.charCodeAt(0) + 0x60;
	return String.fromCharCode(chr);
  })
}
</script>

ここでメソッドを使用しているのは以下の3箇所です。

  • (1)検索文字のクリア
  • (2)画像の呼び出し
  • (3)入力文字のカタカナ変換

(1)検索値のクリアを実行するにはボタンのクリックが必要ですが、クリックに際して@clickイベントは、あくまでクリックされた場合のみ処理が実行されればいいものです。そこでこのメソッドに@click(v-on:clickの略称)ディレクティブを記述しておき、そのメソッドclear内には検索文字を空白化しておきます(dataに値を返すのを忘れないで下さい)。

(2)画像の呼び出しはブラウザによって値がキャッシュされてしまうので、次別の値を呼び出そうとしても同じ画像キャッシュを保持する現象が起きてしまい、画像更新ができなくなってしまいます。そこで算出プロパティではなくメソッドを:src(v-bind:srcの略)に記述しておくことで画像のソースに対して値をバインドし、キャッシュを保持させることなく、それぞれの画像を表示できます。

※requireはNode.js依存のメソッドです

(3)入力文字のカタカナ変換はプログラム内の内部関数として使用しています。検索文字は、そのまま入力しても平仮名のままなので、カタカナに変換する処理関数hiraToKataを算出プロパティfilteredList内で呼び出しています。そして結果を元の算出プロパティに返し、それを検索条件としているので、ひらがなを入力してもカナ検索ができます。

■監視プロパティ

名の通り、イベントそのものを監視する機能です。今回はv-model="word"イベントそのものを監視しているので、このv-modelに動きがあった場合(computedの違いは内部の同期データを監視しているものではないということ)、内部でカナ変換メソッドhiraToKanaを呼び出し、変換されたカタカナを返す変数chrを返すようにしています。また、その受け皿として:valueには変数chrを代入しています(これを設定しないと値がクリアされません)。

vue
//入力された平仮名をカタカナに変換する
watch([word],()=>{
  const chr = hiraToKata(word.value)
  word.value = chr
})

また、複数のイベントを監視したい場合は監視対象をオブジェクト化すれば対応できます。

vue
    watch([イベント1,イベント2],()=>{
        //処理を行う
    })

※監視プロパティはほかに、値更新の有無にかかわらず処理を施すwatchEffectなどもあります。

■v-ifとv-else

ここで、エレメント部分をこのように書き換えてみてください。

     <div id="app">
        <input type="text" v-model="word">
        <p v-if="filterData.length > 0">検索結果{{filterData.length}}件</p>
				<p v-else>検索結果なし</p>
        <ul>
            <li v-for="data in filterData">{{ data.name }}</li>
        </ul>
    </div>

このv-if文とv-else文は同一エレメント内で分岐処理を行いたい場合に用います。ここでは判定文として検索結果を返しており、filter.lengthで検索件数が表示されるので、それが存在しない場合はv-else以下のタグが表示されます。

ただ、範囲としては単一のタグとなってしまうので、複数のタグを分岐させたい場合はtemplateタグが役立ちます。

<template v-if="filterData.length > 0">
    <p>検索結果{{filterData.length}}件</p>
</template>
<template v-else>
    <p>検索結果なし</p>
    <p>※全角カナを入力してください</p>
</template>

React

Reactで同じ処理を記述してみます。それにしたがい、今回は新たにuseEffectフックを使用します。

lesson.jsx
import { useState,useEffect } from "react";
function Lesson(){
	//変数定義
	const [word,setWord] = useState(""); //フックの使用(検索文字のリアクティブ制御)
	const [searched ,setSearched] = useState([]); //フックの使用(検索文字のエフェクト制御)
	const ary_data = [
		{id: 1,name: "モスクワ",img:"Moscow.jpg"},
		{id: 2,name: "サンクトペテルブルク",img:"Sankt.jpg"},
		{id: 3,name: "エカテリンブルク",img:"Yekaterin.jpg"},
		{id: 4,name: "ムンバイ",img:"Mumbai.jpg"},
		{id: 5,name: "ベンガルール",img:"Bengaluru.jpg"},
		{id: 6,name: "コルカタ",img:"Kolkata.jpg"},
		{id: 7,name: "サンパウロ",img:"SaoPaulo.jpg"},
		{id: 8,name: "リオデジャネイロ",img:"Rio.jpg"},
		{id: 9,name: "ブラジリア",img:"Brasilia.jpg"},
	];
	//ループ処理の分離
	const setList = searched.map((data,idx)=>{
			return(
			<li key={idx}>
				<dl>
					<dt>{ data.name }</dt>
					<dd ><img src={data.img}/></dd>
				</dl>
			</li>
			)
	})
	//useEffectフックによって、検索値がバインドされたときに処理が実行される
	useEffect( ()=>{
		const searched = ary_data.filter((item,idx)=>{
			return item.name.search(word)!== -1 && word
		});
		setSearched(searched);
	},[word]);
	//検索文字をカタカナに置換して返す
	const bindWord = (e)=>{
		let word = e.target.value
		word = word.replace(/[\u3041-\u3096]/g, function(match) {
			let chr = match.charCodeAt(0) + 0x60;
			return String.fromCharCode(chr);
		})
        setWord(word); //検索文字のバインド
	}
    //検索文字のクリア
	const clear = ()=>{
	    let word = ''
		setWord(word);
	}
	//レンダリング
	return(
		<>
		<input type="text" onChange={bindWord} value={word}/>
		<p>検索結果{searched.length}</p>
		<button onClick={ clear }>文字のクリア</button>
		<ul>
		{ setList }
		</ul>
		</>
	);
}
export default Lesson

非常に複雑に見えますが、流れとしてはこうなっています。

  1. JSX内のsetListオブジェクトにリストの値を代入する。
  2. テキストに文字が入力されたら、bindWordメソッドが実行され、setWordメソッドによって検索値が更新される。
  3. 検索値の更新をキャッチしたuseEffectが働き、リストの値(ary_data)に対してマッチングを行いながら、検索値にマッチした値のみを、searchedオブジェクトに格納さる。
  4. 検索結果が格納されたsearchedはsetSearchedメソッドによってバインドされ、リストの値が更新される。
  5. クリアボタンが押された場合は、検索値が初期化され、それに対しバインドが行われるため、リストは初期化される。

以上の流れになります。ここで鍵となり、そして関数コンポーネントから新たに導入されたuseStateフックとuseEffectフックについて説明しようと思いますが、Vue.jsで言うところのv-modelディレクティブと算出プロパティのような働きだと認識しています。

●フック(hooks)

フックはReact16.8、※関数コンポーネント制御から導入された新機能で、日本語に直すと接続のことです。

※それ以前はクラスコンポーネントというものでした

●useState

useStateは実は演習1にも登場していましたが、あまり触れませんでした。これはどういうものかというと、値の変更前後で同期をとるためのメソッドで、以下のような記述ルールがありますが、これを把握していないと引っかかります(前後の変数を代入するものではありません)。
const [検索値,セッタ関数] = useState(初期値)

つまり、検索値を処理する場合、初期値は何もないのでuseState("")と空白を代入し、1つ目の戻り値は検索値を格納するのは変数word、それに対して2つめの戻り値に代入するのはそれをリアクティブ処理するためのセッタ関数、setWordとなります。同様に、検索結果に対しても同じように記述しますが、注意点として初期値がオブジェクトになる場合は引数にuseState([])と初期値(今回は空オブジェクト)を代入する必要があります。また、セッタ関数については、奇を衒わずリアクティブ制御を行うメソッド名はsetXxxxxXxxxxは検索値と対応)とした方が迷いが起こらずいいでしょう。

※このuseStateの戻り値はJavascriptの分割代入です。分割代入というのは[元の値,追加したい値]とすることで、オブジェクトの値を分解して、個別の変数を戻り値に代入していくものなので、useStateメソッドが実行される度に、オブジェクトの中身を分解し、更新された差分データを処理する仕組みとなっています。

●useEffect

名の通りエフェクトを与えるフックで、リアクティブ制御した値が変化したときに処理を行うもので、第二引数に監視対象の値を代入しておきます。なお、公式サイトではeffectを副作用と物々しげな(日本語では問題解決の方法によって起きた、新たな弊害を表すマイナスの言葉)和訳を使っていたため、それが一般化してしまっていましたが、React19の説明からはエフェクトと言葉を改めていました。

このuseEffectの記述方法ですが、このようになります。
useEffect(()=>{...監視対象によって連動させたい処理...},[監視対象の値])

元となる値は必ずオブジェクトに格納してください。もし、今回のように単発の値であっても、オブジェクトに格納しないと処理エラーが起きます。

※フックは他に頻用されるものとしてuseContextフック、useReducerフック、useCallbackフック、useMemoフック、useRefフックなどがありますが、これらは続編で紹介します。

●Reactで分岐処理する場合

ReactはVue、Angularのようにif構文を埋め込んだりはできないのですが、大きく分類して3種類の方法があるようです。

1.関数コンポーネント
2.即時関数(説明は省略)
3.三項演算子

※即時関数は拡張性がなく見づらい上、使用している人もほとんどないので説明を省略します。

●関数コンポーネントで分岐

react-sample3func.tml
//前略
    //ループ処理の分離
    const setList = searched.map((data,key)=>{
            return(
            <li key={data.key}>
                <dl>
                    <dt>{ data.name }</dt>
                    <dd ><img src={data.img}/></dd>
                </dl>
            </li>
            )
    })
		//分岐処理
		const Result = ()=>{
			let result
			if(searched.length > 0){
				result = <p>検索結果{searched.length}件</p>
			}else{
				result = <p>検索結果なし</p>
			}
			return result
		}
		
//中略
    //レンダリング
    return(
        <React.Fragment>
        <input type="text" onChange={bindWord} value={word}/>
        <Result />
        <button onClick={ clear }>文字のクリア</button>
        <ul>
        { setList }
        </ul>
        </React.Fragment>
    );

//後略

JSX内に<Result />という見慣れないタグが描かれていますが、実はこれもコンポーネントの一種でカスタムコンポーネントといいます。そして、カスタムコンポーネントはJSXで再描写が行われる毎に分岐処理が行われるので、そこにはResultメソッド内で処理された変数resultが随時返されることになります。

ただ、入れ子にならない場合は次に説明する記述法の方が楽です。

●三項演算子で分岐

昨今では、三項演算子での表記が主流となっています。この三項演算子は**複数行に跨っていても使用可能(ただし、コンポーネントが複数になる場合は空タグでラップすること)**です。またReact17から埋め込みのJSXに対してreturn記述が不要になったので、記述もすっきりするようになりました。

※JSXにコメントを付与したい場合オブジェクトリテラル内に/コメント/とします。

//前略
    //レンダリング
    return(
        <>
        <input type="text" onChange={bindWord} value={word}/>
        { searched.length > 0? 
        (<>
        {/*肯定の処理*/}
        </>)
        :
         (<>
        {/*否定の処理*/}
        </>)
        }
        </ul>
        </>
    );
//後略

●(参考)&&接続

※なお、if判定がtrueの場合のみ、&&で接続することもできます(trueといってもbool型でないと評価されないというものではなく、要は条件一致の場合のみ処理が可能というものです)

   return(
        <>
        <input type="text" onChange={bindWord} value={word}/>
        <p>検索結果{searched.length}</p>
	{searched.length == 0 && <p>別の検索条件でやり直してください</p>}
        <button onClick={ clear }>文字のクリア</button>
        <ul>
        { setList }
        </ul>
        </>
    );

Angular

では、これをAngularで記述してみます。Angularはスタンドアロンコンポーネントになってから、単一コンポーネントでの同期制御が必須となりました。その処理を手助けしてくれるのが、Angular16から導入されたsignalです。

lesson3.component.ts
import { Component,signal } from '@angular/core';
import {FormsModule } from '@angular/forms';
export type Item={
	id: number,
	name: string,
	img: string,
}
export interface Items{
	items:Item[]
}
const items = [
	{id: 1,name: "モスクワ",img:"Moscow.jpg"},
	{id: 2,name: "サンクトペテルブルク",img:"Sankt.jpg"},
	{id: 3,name: "エカテリンブルク",img:"Yekaterin.jpg"},
	{id: 4,name: "ムンバイ",img:"Mumbai.jpg"},
	{id: 5,name: "ベンガルール",img:"Bengaluru.jpg"},
	{id: 6,name: "コルカタ",img:"Kolkata.jpg"},
	{id: 7,name: "サンパウロ",img:"SaoPaulo.jpg"},
	{id: 8,name: "リオデジャネイロ",img:"Rio.jpg"},
	{id: 9,name: "ブラジリア",img:"Brasilia.jpg"},
];
@Component({
  selector: 'app-lesson',
  imports:[FormsModule],
  template: `
	<div id="app">
		<input type="text" (input)="filterData($event)" [value]="word()" />
		@if(lists().length > 0){
			<p>検索結果{{lists().length}}件</p>
		}@else if(lists().length == 0 && word() != ''){
			<p>検索結果なし</p>
		}@else{
			<p>キーワードを入力してください</p>
		}
		<ul>
			@for(data of lists(); let i=$index; track i){
				<li>{{ data.name }}</li>
			}
		</ul>
	</div>
	`,
})

export class Lesson3Component{
	ary_data = items
	lists = signal([]);
	word = signal(''); 
	filterData(event){
		let search = event.target.value
		search = this.hiraToKata(search); //メソッドを呼び出し
		if(search != ""){
			this.lists.set(
				this.ary_data.filter((elem)=>{
					return elem.name.indexOf(search) !== -1
				})
			);
		}
		this.word.set(search);
	}
	
    //検索のひらがなをカタカナに直す。
	hiraToKata(word: string){
		return word.replace(/[\u3041-\u3096]/g, (match)=>{
			const chr = match.charCodeAt(0) + 0x60;
			return String.fromCharCode(chr);
		})
	}
}

◆signal

Angular16から登場した、リアクティブ操作のためのコンパイラで、変数に対しsignalを設定することで変数がリアクティブとなり、任意のタイミングで更新処理ができます。

値の更新はsetメソッドで行います。また、値の展開はコールバック関数となっているので、hoge()となります。

word = signal(初期値); //signalによる制御
this.word.set(更新後の値); //
<!-- 値はコールバック関数となっている -->
<p>{{ word() }}</p>

setのほかにupdateというものもあり、これは元の値を再計算する場合に用います。

◆イベントバインディング

ここでイベント部分が(input)となっていますが、(hoge)は前述したようにビューからデータに偏方向でデータを受け渡すので、Angularにおけるイベントトリガーはこのように記述するだけで作動します。これをAngularではイベントバインディングと呼んでおり、引数を渡すこともできます($event)とすれば、引数.target.プロパティとしてタグ上のプロパティ(今回はe.target.value)を取得できる。

◆分岐について

Angularは@ifコントロールフローが実装され、それと並行して@else ifと@elseも実装されました。これはelse構文が存在しなかったng-templateより、格段に記述しやすく、見やすくなっています。

sample3-angular.component.html
		@if(lists().length > 0){
			<p>検索結果{{lists().length}}件</p>
		}@else if(lists().length == 0 && word() != ''){
			<p>検索結果なし</p>
		}@else{
			<p>キーワードを入力してください</p>
		}

◆型の定義

AngularはTypeScriptを使用しています。そのため、型の指定に厳格なので、型も事前に定義しておく必要があります。

search.ts
export type Item={
	id: number,
	name: string,
	img: string,
}
export interface Items{
	items:Item[]
}

※型については続編で触れています。

演習4: データリストの追加、削除、修正(DOMの再生成)

いわゆるCRUD(Create、Read、Update、Delete)と呼ばれる操作です。これについても各フレームワークの癖も掴みながら解説していきたいと思います。

※また、データ修正においては本来、修正ページへ遷移、データの更新という二段階の作業を行いますが、今回はあくまで演習なので単一コンポーネントで記述していきます。

Vue

Vue.jsを用いて新規作成と削除を実装するとこのようになります。Reactとの違いは、元のオブジェクトの値も操作できる点があります。ただ、それだと元のオブジェクトの値を破壊してしまうので、移し替えておくようにしましょう。ただ、それにあたって新たな技術、ライフサイクルフックが必要となります。

vue3-lesson4.html
<script setup>
import {ref,reactive,onMounted} from 'vue';
    let ins_item = ref('');
    let datalist = reactive([])
    const ary_data =[
        {id: 1,name: "モスクワ",img:"Moscow.jpg"},
        {id: 2,name: "サンクトペテルブルク",img:"Sankt.jpg"},
        {id: 3,name: "エカテリンブルク",img:"Yekaterin.jpg"},
        {id: 4,name: "ムンバイ",img:"Mumbai.jpg"},
        {id: 5,name: "ベンガルール",img:"Bengaluru.jpg"},
        {id: 6,name: "コルカタ",img:"Kolkata.jpg"},
        {id: 7,name: "サンパウロ",img:"SaoPaulo.jpg"},
        {id: 8,name: "リオデジャネイロ",img:"Rio.jpg"},
        {id: 9,name: "ブラジリア",img:"Brasilia.jpg"},
    ]
    //ライフサイクルフック
    onMounted(()=>{
      Object.assign(datalist,ary_data) //空オブジェクトで分割代入する方法
    })
    //登録
	const ins = ()=>{
		let key = datalist.length;
		let items = {
			"key": key,
			"name": ins_item.value
		}
        datalist[key] = items //データの挿入
	}
	//削除
	const del = (idx)=>{
        datalist.splice(idx,1)
	}
	//修正
    const upd = (idx,name)=>{
      const items = [...datalist]
      const item = {...items[idx]}
      item.name = name
      datalist[idx] = item //修正は分割代入で対応する
    }
</script>

<template>
	<input type="text" v-model="ins_item">
			<button type="button" @click="ins">新規</button>
	<ul>
		<li v-for="(data,idx) in datalist">
			<dl>
				<dt><input type="text" v-model="data.name"></dt>
				<dd ><button type="button" @click="upd(idx,data.name)">修正</button></dd>
				<dd ><button type="button" @click="del(idx)">削除</button></dd>
			</dl>
		</li>
	</ul>
</template>

■ライフサイクルフック

Vueにはライフサイクルフックというものが用意されており、一定のタイミングで処理を実行できます。今回は最もスタンダードなonMountedを使用しています。これは、テンプレート読込後に、一度だけ処理を実行してくれます。

   //ライフサイクルフック
    onMounted(()=>{
        //テンプレートレンダリング後の処理
    })

ライフサイクルフックはほかにonBeforeMountというものもあり、これはテンプレート読込前に処理を行います(その他数種類のライフサイクルフックが用意されています)。

※createフックはset upに集約されているので廃止されています。

※v-forを用いる場合の注意点

一瞬、v-ifとv-elseの各ディレクティブを用いて値を制御したくなるでしょうが、それはやってはいけません。マニュアルにはv-forディレクティブの中にv-ifやv-elseを用いると想定外の動作が起きることを警告しています。

v-for と一緒に v-if を使うのを避ける

reactiveで参照渡しする

reactiveは以下のように空オブジェクトを代入しておく方法もあります。refはv-modelディレクティブを直に参照できるので便利なのですが、値を展開する際にはvalueプロパティを付与しないといけません。対してreactiveは以下のように空オブジェクトを代入しておくだけで参照渡しになるので、変数を同期することができます。

しかし、そのままでは分割代入ができないので、解決の方法を探っているとライフサイクルフックでObject.assignという方法があるみたいです。

React

ReactでもVue.jsと同様にオブジェクトを操作してみます。

lesson.jsx
import { useState,useEffect } from "react"; //ローカルの場合で、useStateを使用するための定義
function Lesson(){
    //変数定義
		const [insitem, setInsitem] = useState('');
		const [upditem, setUpditem] = useState('');
    const [bind ,setBind] = useState([]); //フックの使用(データリストの更新)
		const	ary_data = [
        {id: 1,name: "モスクワ"},
        {id: 2,name: "サンクトペテルブルク"},
        {id: 3,name: "エカテリンブルク"},
        {id: 4,name: "ムンバイ"},
        {id: 5,name: "ベンガルール"},
        {id: 6,name: "コルカタ"},
        {id: 7,name: "サンパウロ"},
        {id: 8,name: "リオデジャネイロ"},
        {id: 9,name: "ブラジリア"},
		];
		const ins = ()=>{
			setBind([
				...bind,
			{
				id:  bind.length + 1,
				name: insitem
			}
			])
		}
		const del = (idx,name)=>{
			setBind(bind.filter((_, i) => i !== idx))
		}
		const upd = (idx,name)=>{
			changeValue(idx,name)
		}
		const changeValue = (idx,item)=>{
			let edit = []
			bind.map((elem,i)=>{
				if(i !== idx){
					edit = [...edit,elem]
				}else{
					edit =  [...edit,{id:i + 1, name: item}]
				}
			})
			setBind(edit)
		}
    //ループ処理の分離
    const taglist = bind.map((data,idx)=>{
				return(
					<li key={idx}>
							<dl>
									<dt><input type="text" onChange={ (e)=>{ changeValue(idx,e.target.value) } } value={ data.name } /></dt>
									<dd><button type="button" onClick={ ()=> upd(idx,data.name) }>修正</button></dd>
									<dd><button type="button" onClick={ ()=> del(idx) }>削除</button></dd>
							</dl>
					</li>
				)
    })
		
		
    //useEffectフックでコンストラクタ的な働きを行う
    useEffect( ()=>{
				setBind(...bind,ary_data)
    },[]);
    //レンダリング
    return(
        <>
			<label>新規<input type="text" onChange={ (e)=>{ setInsitem(e.target.value) } } value={insitem}/></label>
			<button type="button" onClick={ ins }>新規</button>
			<ul>
			{ taglist }
			</ul>
        </>
    );
}
export default Lesson

●ロード時の制御

意外と引っかかりがちなのが、読込直後の制御で、いわばオブジェクト指向のコンストラクタっぽいことをしたい場合にどう記述するがですか、これもuseEffectフックを利用します。初期値に空オブジェクトを入れておけば、読込直後にフックが働き、setBind変数の働きによって、空の変数bindにary_dataのオブジェクトが代入されることになります。

●登録

登録機能において厄介な点は、新規挿入する値はinputタグのテキストの値なのに対し、メソッドの実行はbuttonタグのonCickイベント内の関数insになります。したがって、リアクティブにテキスト操作は行いません。だとするとonChangeイベントの付記は不要かと思われますが、それをするとReactのルール違反になってしまい、警告が出ます。なので、テキストを直接操作しない場合でも、フックを実装しておきましょう(readOnlyと記述する方法もありますが、そうするとテキストへの入力ができなくなってしまいます)。

メソッドinsの中身についてですが、これはロード時の制御と同じ理屈で、リアクティブに制御しているオブジェクトbindに対し、随時分割代入で、オブジェクトを追加しているだけです。そして、テキストの値はvalueプロパティでリアクティブ制御しているので、直接取得することができます。

●削除

この削除における制御も色々と注意点があります。

●コンポネントの内側で関数を呼び出す場合

削除ボタンに実装しているonClickイベントは無名関数を呼び出しています。こうしないと何度もイベントを呼び出してしまい、React limits the number of renders to prevent an infinite loopというエラーが発生してしまいます。なので、コンポネントの内側のイベントでメソッドを呼び出す場合は無名関数(インライン関数)を用いて、メソッドを制御しておきましょう。

●削除の仕組み

削除用のメソッドはdelですが、この中ではarray.filter関数(任意のオブジェクトで引数と一致するものだけを抽出するメソッド)を用いています。そして

bind.filter((_,i) => i !== idx ))

とだけ記述されていますが、これは

bind.filter(function(item,i){ i !== idx }))

と同様で、i !== idx に当てはまる(つまり、削除しようとしたインデックスidxとループしているインデックス番号iが一致している)もの以外をデータとして残す動きをしています。そして、ここでもVue.jsと同様に間違ってもインデックス番号を元に削除操作をしないようにしてください。そんなことをすると値がどんどんずれて、誤操作を招きます。

●修正

表示データをテキストボックスに変更し修正できるようにしてみました。ところが、リアクティブに変更させるということは、データの値を書き換えた時点でリストを再生成することになるので、結局更新ボタンの有無に関係なく処理が行われることになります(手前の値を削除すれば更新されているかはわかります)。

注意点はオブジェクトの値を入れ替えるときで、次のような分割代入を用いれば、対象箇所だけの修正を簡潔に処理できます。

bind.map((elem,i)=>{
    const edit = [...bind] //スプレッド構文によって(更新対象でないオブジェクト上の値を補完)
    if(i === idx){
       elem.name = item
       edit[i] = elem //対象の値を更新する
       setBind(edit) //更新対象の値を同期する
    }
})

分割代入について押さえておきたい大事なポイントは...hogeという表記ですが、これはスプレッド構文と呼び、更新対象でないオブジェクト内のデータを補完する役割を持ちます。具体的には、更新したい情報はelem.nameだけですが、更新情報にいちいち他のプロパティも書いていられません。そこで、このスプレッド構文を分割代入に用いることで、他の更新対象でない値も一度に同期を取りたいオブジェクトに格納してくれるわけです。

Angular

同じくAngularで制御してみます。

lesson.component.ts
import { Component,signal } from '@angular/core';
import {FormsModule } from '@angular/forms';
export type Item={
	id: number,
	name: string,
}
export interface Items{
	items?:Item[]
}
let items = [
	{id: 1,name: "モスクワ"},
	{id: 2,name: "サンクトペテルブルク"},
	{id: 3,name: "エカテリンブルク"},
	{id: 4,name: "ムンバイ"},
	{id: 5,name: "ベンガルール"},
	{id: 6,name: "コルカタ"},
	{id: 7,name: "サンパウロ"},
	{id: 8,name: "リオデジャネイロ"},
	{id: 9,name: "ブラジリア"},
];
@Component({
  selector: 'app-lesson',
  imports:[FormsModule],
  template: `
	<div id="app">
			<input type="text" [(ngModel)]="ins_name" />
			<button type="button" (click)="ins(ins_name)" >新規</button>
			<ul>
				@for(data of items; let i = $index ; track i ){
					<li id="li">
						<input type="text" [(ngModel)]="data.name">
						<button type="button" (click)="upd(i,data.name)">修正</button>
						<button type="button" (click)="del(i)">削除</button>
					</li>
				}
			</ul>
	</div>
	`,
})

export class LessonComponent{
	ins_name = ""
	items = items
        //新規
	ins(ins_name){
		if(ins_name != ""){
			const idx = Number(this.items.length + 1);
			this.items.push({"id":idx,"name":ins_name});
		}
	}
	//修正
	upd(idx: number,upd_item: string){
		this.items.splice(idx,1,{id:idx,name:upd_item});
	}
	//削除
	del(idx){
		this.items.splice(idx,1);
	}
}

Angularの場合は、これで動きます。今回はリアクティブ制御の必要性が薄いので、ダイレクトにオブジェクトを代入していますが、Angularの場合はディバイディングで制御するためproxy制御がかからないので、これで問題ありません。

※signalはあくまでプリミティブなデータの同期やそれに関連する操作に対し、処理を施すものです。

総括

今まではあくまで基礎なので、単一コンポーネントで演習してきましたが、続編からは複数のコンポーネントを用いた処理がメインとなります(親子コンポーネント処理、ルーティング処理、スタイル処理、TypeScript)。容量が圧迫したために続編を作成していますので、ぜひ宜しくお願いいたします。

ただ、ここまで履修された方なら、たぶんソースの読解力、理解力も格段に上がり、サーバサイドでの開発も抵抗なく付いていけると思いますので、ご健闘お祈りします。また、昨今ではVueもReactもTypeScriptを用いた記述が主流になってきている部分もありますが、基本はやはりJavaScriptでの記述をこなしてからです。

追伸:当時は、まさかここまで再生数と評価数が上がるとは思いませんでした。嬉しさと同時に大凡な記事は作れないな、という責任感と少しばかりの重圧も抱いております。至らないところもあるかも知れませんが、ご指導ご鞭撻の程を宜しくお願い致します。

なお、自分はこのように知らない言語は、知っている言語と知らないもの同士最低2セットを並行しながら学習していき、共通点と相違点を洗い出していくということをよくします。ですので、今回は当初の記述にあったjQueryに合わせ、VueとReactを敢えて並行して学習し、そして2つに慣れたところでAngularも加えて演習していくという手段をとっており、今はSvelte、そしてsolidJSでも同じような実践を行っております。

慣れてきたら、この5種類のフレームワークで全く同じ働きをするプログラムを作ってみるのも面白いと思いますし、更に理解力が高まると思います。

587
646
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
587
646

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?