まず、ことわっておきますが、jQueryは非常に優秀なライブラリです。自分がメインとするWEBシステムの世界ではかなり重宝していますので、時代の潮流だからといって理由もなくjQueryを切り捨てろとは一言も言いませんし、もっと技術は培養すべきです。
ですが、使用できる選択肢を増やす、武器を増やすためにVue.js、Reactを学習するのは非常に有効です。ただ、これらの活用は学習コストが高いといわれています。その原因はフロントエンドありきで話が進みすぎているからだと考えています。したがって、自分の投稿記事は、jQueryを多用するWEBシステムエンジニアに向けた、フォーム操作をメインに置いた半備忘録兼自分なりに解釈した解説です。
ちなみに自分はサーバ構築からバックエンドまでこなしているワンオペエンジニア(フリーランス、非正規雇用に非ず)です。
§1:Vue.jsとReact、そしてAngular
その前に、vue.jsとReactとはどういったもので、どんな意図で開発されたものかを知っておく必要があります。そして、その理念を知っていたら、スクリプト記法の理屈もわかりやすいからです。
Vue.js
Vue.jsはもともとGoogleが開発したAngularJSの開発者の一人が、個人で開発を始めたJSフレームワークです。そのため、小規模の開発に向いた柔軟な利用が可能です(デフォルトでjQueryとの混用も可能)。そのVue.jsとは一言でどんな技術かというと
html内の部品に対して、Vueディレクティブという魔法をかける
こういうものです。ディレクティブとは双方向という意味で、データのインプットとアウトプットをリアルタイムで行うことを意味しています。これをVue.jsやAngularJSなどでは双方向バインディングと呼んでおり、今のAngularでもその技術はある程度継承されています。そして、AngularJSはhtml側でngというプロパティやメソッドを使ってバックエンドの処理をしており、かなり明解だった反面、開発が進むとかなりhtmlソースが汚されてしまうので、技術をある程度継承し、Angularというパッケージ単位のフレームワークを作ったわけです。しかし、このAngularJSの双方向バインディングと簡潔さを捨てるのはもったいないと、飛躍的に発展させたのがVue.jsでありVueディレクティブというもので、これはソースが汚れる原因となったhtml側でのバックエンド処理を、スクリプト側(コンポーネントを作成して管理)で処理させるようにしました。つまり
- AngularJS: ディレクティブ処理をhtml側で処理できたために、ソースが汚れてしまい、敬遠されてしまった。
- Vue.js : ディレクティブ処理をスクリプト側に記載させるようにし、html側は基本、メソッドとプロパティだけ入出力させるようにした(つまり、JavaSrciptの基本に返った)
この理念を覚えておけば、今後の学習にも役立ちます。
React
ReactはFacebookが開発したコンポーネント型のJSフレームワークで、その鍵となるのはJSX
という記法です。そのJSXとはなにかというと、スクリプトの中に直接htmlタグを記述できてしまうものです。ですが、その独特の記法によって技術者や入門者に違和感を与え敬遠されてしまっていたのも事実で、そのために何度も記法が変更されてきており、今日ではだいぶ見やすく、そして記述しやすくなっています。それでもReactの基本的な理念と動きは変わっておらず、一言でいうと
html内に魔法結社(JSXの拠点)を作り、そこでディレクティブな部品(htmlタグ)を錬金する
こういうものです。つまり、Vue.jsだとディレクティブとなる部品自体はhtml上にあったのに対し、Reactの場合は、ディレクティブな部品はhtmlになく、バックエンド上のコンポーネントで作成されることになります。そして、ディレクティブな処理を行う際もそのバックエンド内だけで処理するので、非常に動きが高速で、部品もバックエンドにしか存在しないので、開発を分担しやすく中規模の開発に向いています。
- React :部品の管理を徹底するために、ディレクティブ処理をhtml側で一切できないようにしている。部品の調達と管理はすべてJSXに則ったコンポーネント内で行う。
これが基本です。あと、コンポーネントの記法もいろいろあって、しかもしょっちゅう変更されているので、それが却って敬遠させている(初心者がどこから手を付けたらいいのかわからない)気もしますが、基本となる部分は全くぶれていないことを踏まえておいてください。
Angular(参考)
AngularはAngularJSでの失敗を教訓に、その失敗の原因となったソースの汚れを解消させたものです。そしてvue.js、Reactと違い、RubyのRailsやPHPのLaravel、PythonのDjangoのような、それ一つでパッケージとなっているフルスタックフレームワークとなっており、TypeScriptがベースです。そして、これも一言で表すと
プロジェクト自体がAngularという魔法世界(フレームワーク)である
なので、そのパッケージ内では自由自在にAngularの技術を利用できます。ですが、前述したようにソースの汚れを反省して作ったフレームワークなので、ディレクティブな部品は各アーキテクチャ内で実行するようになっています。ただ、基本はAngularJS時代とそこまで変わっていないためにあまり難しくなく、どちらかというとVue.jsに近いですが、その正直Vue.jsより理屈は解りやすいので、フレームワーク開発に慣れているバックエンドエンジニアなら、上記2つより学習は楽かもしれないです。ただ、けっこう容量があるので、大規模開発向きです(これを省力化、小規模化したJSフレームワークも存在します)。
- Augular:ディレクティブ処理を行う部品はhtml上にあるが、処理は外部のアーキテクチャ内で行う。
なお、現在もAngularJSはマイナーチェンジを続けていますが、本来のAngularJSの目的はVue.jsが担っていると考えていますので、本記事でAngularJSは採り上げません。
§2:バックエンドのための基本文法
前述したように、これらのJSフレームワークの学習コストが肥大してしまった原因は、フロントエンドありきで解説していることが多いためでしょう。そのため、バックエンドエンジニアが基礎の基礎である文法もわからずに、そっちばかりに目が行ってしまっていることで混乱を招き、これらの技術に手を出す気力を与えず、比較的記述が簡単なjQuery依存から脱皮できないと考えています。
したがって、自分はjQueryで操作してきたことを、Vue.js、Reactではどう記述するのかを重点において説明したいと思います。
演習1:フォーム操作:テキスト文字を表示させる
フォーム操作の基本の基本です。ですが、この基本だけでかなり記法の根本が理解できるのも事実です。ただ、単純に表示させるだけなのも面白くないので、jQueryでフォームに対し、キーの打鍵ごとに値を表示させるようにしましょう。
<body>
<input type="text" id="f_inp">
<p>入力された文字<span id="mes"></span></p>
<script>
$(function(){
$('#f_inp').on("keyup",function(){
let mes = $(this).val(); //値を取得する
$("#mes").text(mes); //打ち込んだ値を反映させる
})
})
</script>
</body>
これで、打ち込んだ文字をそのまま下の#mesに表示させることができます。これとほぼ同じ挙動をVue.js、Reactで再現してみます。
Vue.jsで再現
Vue.jsで記述するとこうなります。そしてVueですが、こっちはコンテンツの後に制御部分を記述してください。
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="mes">
<p>入力された文字<span>{{ mes }}</span></p>
</div>
<!-- 制御部分はコンテンツの後 -->
<script>
new Vue({
el: '#app',
data:{
mes: '',
},
});
</script>
</body>
jQueryより更に簡潔になっているのは一旦置いといて、目の付け所はelとv-modelいうプロパティであり、これこそがまさしくVue.jsの双方向バインディング技術になります。そして、それが適用される部品は#appに囲んだ単一のブロック要素のみとなります。これをエレメントと呼び、el(elementの略)プロパティで指定した範囲のみ、ディレクティブになるわけです。
もっと詳しく文法を解説する
Vue.jsの場合は、html部分とスクリプト部分は区別しない方が説明もしやすいです。もう一度さっきのhtmlファイルを確認してみましょう。そして、コメントを付与します。
<body>
<div id="app"><!--#app内のブロック要素にVueディレクティブを適用(1) -->
<input type="text" v-model="mes"><!-- v:modelはフォームの値を監視するVueディレクティブ(2) -->
<p>入力された文字<span>{{ mes }}</span></p><!-- {{hoge}}はマスタッシュ -->
</div>
<script>
new Vue({
el: '#app', //適用対象となる要素名(1)
//dataはVue.jsの操作に必要な変数を格納するオブジェクト
data:{
mes: '', //今回は変数mesを使うので定義しておく(中は空白)。(2)
},
});
</script>
</body>
このようになります。まずは、部品をディレクティブにするために(1)のように、どこまで適用するのか、その場所を定義し、そこから具体的な動作(2)が行われます。v-modelプロパティはフォーム部品の動きを監視する働きを持っているので、inputの動きに変化(新たに文字が入力された)があると即座に反応し、そしてその結果を{{ mes }}に返すようになっています。
補足1:dataプロパティ
dataプロパティはVueディレクティブで作業するための変数置き場で、これを定義しておかないと、hoge is not definedと処理中に未定義のエラーが起きます。今回はmesという変数を使用しているので、これを定義しておき、そして初期値を空っぽにしておきます。
補足2:マスタッシュ
マスタッシュとは英語で口髭のことで、{{ }}という記号です。そしてVue.jsではVueディレクティブの外側(v-modelなどv-xxxxというプロパティ)に値を適用する場合は{{変数名}}とすることで、その値を受けとることができます。ここでは{{mes}}はVueディレクティブの外側なのでマスタッシュで記述しています(エレメントの外側ではないので注意。エレメントの外側にマスタッシュを記述しても、そこはVue.jsの適用外なので、ただの文字列として認識されるだけです)。
Reactで再現
では、全く同じ動作をReactでも再現してみます。ただ、Reactは少し説明をわかりやすくするために、敢えて最短の記述にしていません(もっとスリム化した記法も使えるのですが、今日、一番見ることが多い記法です)。
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.10.3/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component{
constructor(){
super();
this.state = {
mes: ''
};
this.changeText = this.changeText.bind(this);
}
changeText(e){
this.setState({ mes :e.target.value });
}
render(){
return(
<div>
<input type="text" onChange={this.changeText} />
<p>入力された文字<span>{ this.state.mes }</span></p>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
</script>
</head>
<body>
<div id="root"></div>
</body>
初見の人が見たら、「なんだこりゃ?」となること請け合いです。スクリプトの中にhtmlタグが入っているのが生理的に受け付けないのでしょう。しかし、Reactも初期と比較すると、だいぶ記述はスリム化しており、そして解りやすくなっています。では、この処理の流れがわかるようにコメントを付与してみます。なお、Vue.jsでは、スクリプトをbodyタグの内側、コンテンツの後に書くのがセオリーでしたが、Reactはコンテンツの前、headタグの中に記述するのがセオリーです(普通は外部ファイル化します)。
<script type="text/babel">
//Reactコンポネントを作成するクラス(1)
class App extends React.Component{
//A:定義部分。簡単にいえば、処理前に準備する変数やメソッドなど。
constructor(){
super();
this.state = {
mes: ''
};
this.changeText = this.changeText.bind(this); //(3)値をバインドさせる準備
}
//B:処理関数部分。renderの外側に書く方が見やすい。
//入力された文字を返す関数(2)
changeText(e){
this.setState({ mes :e.target.value }); //setStateは値をバインドさせる処理(3)
}
//C:レンダリング部分。JSX記法のhtmlタグを描画する。
render(){
//JSX記法で返す部品(2)
return(
//onChangeハンドラ以下はReactでバインディング処理を行う関数(3)
<div>
<input type="text" onChange={this.changeText} />
<p>入力された文字<span>{ this.state.mes }</span></p>
</div>
);
}
}
ReactDOM.render(
<App />, //コンポネント作成を行う部品(1)。記述方法にルールがある。
document.getElementById("root") //外側に部品を表示させる(4)
);
</script>
</head>
<body>
<div id="root"><!-- ここに処理部品が入る(4)--></div>
</body>
だいたいこのような流れになっています。16.8からはもっと簡略化した記法も使えるのですが、一般的に知られている記法をマスターしてからの方がいいでしょう。見た目は複雑に見えますが、まずは次のように分類しましょう。
- React.Component() //コンポネントを作成します
- ReactDOM.render() //作成したコンポネントを反映させます
renderとはいろいろな意味がありますが、レンダリングという言葉通り、描写という言葉で覚えればいいでしょう。そして、その名の通り、ReactDOM.render内ではJSX記法に則ったhtmlタグを描写しているのです。そしてその部品を作成するのがReactDOM.Componentであり、いわば、前者がシステムの納入、後者がシステムの開発や保守管理だと思えばいいでしょう。したがって、htmlタグ側にはリアクティブ処理された部品しか存在していなく、スクリプトの処理部分は、Vue.jsと違って部品すら見えません。
では、今度はReact.Componentの中身を3つに分類してみます。
- constructer() //定義部分
- 処理用の関数 //処理部分
- render() //レンダリング部分
だいたいはこのように分類するとわかりやすいです。そして、その作業の流れを文章化してみると
- Appという部品を作成する命令を出す(1)
- 命令を受けたコンポネントはコンストラクタから部品を用意してレンダリング領域にある通り、タグを作成し、外側に返す(2)(4)
- onChangeイベントが発火したら、用意していた関数を使って処理を行う(3)
- 処理を行った状態で再度レンダリングを行い、ReactDOM.renderを使用して処理の外側に返す(4)
それを踏まえた上で処理を追ってみるとやっていることはJavaScriptとそこまで相違ないと気づくはずです。
ただ、決定的な違いとして処理関数内にreturnが存在しない(レンダリング部分ではない)、そしてreturn処理を行う代わりにsetStateで制御することです。そして、これによってバインディングが可能になります。
オブジェクト、変数の利用
Reactでもう一つ躓きがちなのが、オブジェクトや変数の利用に際しては、同じコンポーネント内であっても、外部の存在として扱わないといけないということです。具体的に言うと、thisという代名詞を置いているのがそれで、それぞれ、定義部分、レンダリング部分、関数部分で変数や関数をやりとりする際には、thisという代名詞を使っているのがわかると思います。
- 関数部分のchangeTextをレンダリング部分で使用する場合、this.changeTextとして呼び出し
- レンダリング部分でmesを使用するために、定義部分のstate.mesをthis.state.mesとして呼び出し
このような具合です。またsetStateのようにコンポーネントで用意された部品を使用する際も、外部から借用することになるので、this.setStateと代名詞を付与しています。
補足:バインディングのルール
定義部分で
this.changeText = this.changeText.bind(this);
とあると思いますが、これは別におまじないではなく、利用する関数において値をバインド(同期)したいときに記述するルールで、これを記述しないと値をバインドできません。そして、基本は同じオブジェクト名にしておくだけで、左側は変数を代入しているだけですので、定義さえしておけば、別名でも大丈夫です(したところでメリットが薄いので同名にしおくべきですが)。
また、setStateはReactにおいて極めて重要性の高いメソッドですが、簡単にいえば、元の値と新しい値をバインドさせたいときに設定するものです。具体的には先程入力した文字と新たに入力した文字が異なる場合、随時setStateメソッドが実行されることになります。
また、e.target.valueはJSX内にある部品の、任意のフォームに対して取得した値(value)を受けとるもので、e(jQueryではelemと書くことが多い)とは、任意のオブジェクト変数に過ぎません。そして、受け取った値を先程のsetStateメソッドを使って、値の変化を処理しているわけです。
最新の書き方(16.8以降)
上記の方法でもかなり簡潔になりました(ECMA5時代はコンポネントも逐一作成する必要がありました)が、それでも至るところにthisばかりあったりと、色々と無駄があるようにも見えました。それをスリム化させるとこのようになります。
<script type="text/babel">
//定義部分
const { useState } = React; //ローカルの場合で、useStateを使用するための定義
const App =()=>{
//処理部分
const [mes,setMes] = useState(true); //バインディング処理
//一度メソッドで準備する
const changeText = (e)=>{
setMes( e.target.value ); //値の比較
}
//レンダー部分
return(
<div>
<input onChange={changeText} />
<p>入力された文字<span>{ mes }</span></p>
</div>
);
}
const elem = <App />; //ワンクッション置かないと警告が出る
//レンダリング処理
ReactDOM.render(
elem,
document.getElementById("root")
);
</script>
代名詞のthisが消えてかなり明白になったと思いますが、基本的な動作は変わっていません。ただ、変数定義部分、処理部分、レンダリング部分が一つの関数に収まったので、すごくすっきりします。
ただ、注意することとして
const App =()=>{....}
const.elem = <App />; //ワンクッション置く
ReactDOM.render(
elem,
document.getElementById("root")
);
//ダイレクトに呼び出す以下の書き方は推奨されていない(警告が出ます)
const App =()=>{....}
ReactDOM.render(
<App />,
document.getElementById("root")
);
この部分で、このようにrenderメソッドに対して、ワンクッション置かないと警告メッセージが表示されることになります。
演習2:プルダウンメニューの制御
では、今度は繰り返し処理の実例としてプルダウンメニューの生成とイベント処理をそれぞれ記述していきます。そして、共通の動作としてプルダウンメニューをオブジェクトから生成し、選択した値を表示させるという動きをそれぞれjQuery、Vue.js、Reactで再現してみます。
そうすれば、共通点と押さえどころが見えてくるはずです。
jQuery
まずはjQueryで記述してみます。ループ式の記述方法は色々ありますが、今回は敢えて$.eachメソッドを使います。なお、jQueryはあくまでJavaScriptライブラリなので、ECMA6でも普通に記述できます(jQueryでは敢えて見慣れた記述法にしています)。
<script>
$(function(){
//配列の値
let ary_data = [
{key:0,name: "cakePHP"},
{key:1,name: "Laravel"},
{key:2,name: "Code Igniter"},
{key:3,name: "Symfony"},
{key:4,name: "Zend Framework"},
{key:5,name: "Yii"},
];
let sel = $("#sel");
//プルダウンメニューの生成
$.each(ary_data,function(key,data){
opt = $('<option>').val(item.key).text(item.name);
sel.append(opt);
})
sel.appendTo("#f_sel");
//プルダウンの操作
$("#sel").on("change",function(){
let selkey = $(this).val();
$("#txt").text(ary_data[selkey].name);
})
})
</script>
</head>
<body>
<div id="f_sel">
<select id="sel">
<option value="">選択</option>
</select>
</div>
<p>選択された値:<span id="txt"></span></p>
</body>
このシステムのポイントは前述した通り
$.each(ary_data,function(key,item){
opt = $('<option>').val(item.key).text(item.name);
})
このループ式の部分です。これはary_dataという配列をitemという変数に格納しながら順番に展開させ、item.keyをvalueプロパティに、item.nameをoptionタグのテキスト値に代入している課程になりますが、実はこれと全く同じ工程が次のVue.jsとReactにも現れます。
Vue.js
同じ動作をVue.jsで記述するとこうなります。
<body>
<div id="app">
<select v-model="sel">
<option value="">選択</option>
<option v-for="data in ary_data" :value="data.name" >{{ data.name }}</option>
</select>
<p>選択された値:<span id="txt" >{{ sel }}</span></p>
</div>
<script>
new Vue({
el: "#app",
data:{
sel: "",
ary_data :[
{name: "cakePHP"},
{name: "Laravel"},
{name: "Code Igniter"},
{name: "Symfony"},
{name: "Zend Framework"},
{name: "Yii"},
]
}
})
</script>
非常にすっきりした内容になりました。後述するReactと比較してもVue.jsはフォーム操作に向いており、何よりv-modelプロパティを使用するだけで値を一発で同期できるのが魅力的ですね。
さて、先程申し上げたように、この記述での目の付け所はv-for="data in ary_data"
という部分であり、これは先程のjQueryの$.eachと同様に配列分のoptionタグに対してvalueプロパティとテキスト値を付与する動作となっています。forはPHPのfor文などでもお馴染みですが、よく知られる「~のために」という意味のほかにも「~している間」という意味があり、そのとおり配列ary_dataを展開している間はdataという変数に格納しているわけです。しかもjQueryでは逐一スクリプト文で記述していましたが、Vue.jsはこのVueディレクティブの魔法の力(?)でスクリプトに数式を記述することなくループ処理が行えるので、かなり記述が楽になります。なお、ループできるのは何もoptionタグだけでなく、リスト作成に用いるliタグやテーブル作成のtdタグ、はたまたテンプレートを用いればブロック単位をループさせることも可能です。
React
同じようにプルダウンメニューの生成をReactでも記述してみます。
<script type="text/babel">
const {useState } = React;
const App =()=>{
//変数定義
const [sel,setMenu] = useState(true);
const ary_data = [
{key: 1,name: "cakePHP"},
{key: 2,name: "Laravel"},
{key: 3,name: "Code Igniter"},
{key: 4,name: "Symfony"},
{key: 5,name: "Zend Framework"},
{key: 6,name: "Yii"},
];
//値の設定処理(いわばVue.jsの算出プロパティに当たる部分)
const setList = ary_data.map((data)=>(
<option key={data.key} value={data.name}>{data.name}</option>
))
//イベント処理
const changeMenu = (e)=>{
setMenu(e.target.value );
}
//レンダリング
return(
<React.Fragment>
<select onChange={changeMenu}>
<option>選択</option>
{ setList }
</select>
<p>選択された値:<span>{sel}</span></p>
</React.Fragment>
);
}
const elem = <App />;
ReactDOM.render(
elem,
document.getElementById("root")
);
</script>
</head>
<body>
<div id="root"></div>
</body>
Vue.jsと比較すると色々とややこしくは見えますが、それでも前述したReactとそこまで動作は変わっていませんし、やはりこれも同様にループ式によって配列を展開しています。その箇所がmapメソッドでこのmapメソッドはどちらかというとjQueryの$.eachメソッドに近く、コールバック関数を使って値を展開する働きを持っています。後は同様にvalueプロパティやテキスト値にkeyやnameを配列順に代入していっているだけです。
//設定処理 ary_dataを順々に展開し、data変数に格納。該当するプロパティをそれぞれ当てはめていく。
const setList = ary_data.map((data)=>(
<option key={data.key} value={data.name}>{data.name}</option>
))
オブジェクトリテラルについて
さて、先程までは触れていませんでしたが、Reactの記法を見ていているとJavaScriptやVue.jsで見られたクォートはどこにも書かれておらず、代わりに{ ... }という波括弧があちこちにあり、気になったと思います。これはオブジェクトリテラルというもので、JSX内において外側に置いている変数(オブジェクト、メソッドなども含む)を展開するために用いるものです。それを踏まえて動きを追ってみるとsetListという変数を展開し、optionタグを代入していることがわかります。また、changeイベントの実行時のメソッド、changeMenuはプルダウンメニューの値が変更した際に行うイベント処理を展開しており、それによって先程と同様、直前と現在の値を比較し、値を出力させるようになっています。
補足
Reactでは、JSXにおけるレンダリングのルールとして、単一の親要素しか作れないというものがあります。ですが、今回のようにプルダウンメニューとPタグなど複数のタグを生成したい場合は、上述のように<React.Fragment>
というテンプレートで括る方法や、オブジェクトに格納して返す方法などがあるようです。
演習3:検索フォーム
演習3ではよくある検索フォームの基礎、部分一致検索を採り上げたいと思います。そしてVue.jsでは初心者が最も引っかかりがちな算出プロパティとイベントメソッドの違いを主に採り上げたいと思います。
to be continued...
追伸:まさかここまで再生数と評価数が上がるとは思いませんでした。嬉しさと同時に大凡な記事は作れないな、という責任感と少しばかりの重圧も抱いております。至らないところもあるかも知れませんが、今後も宜しくお願い致します。