対象読者とこの記事から何が得られるのか
そろそろReactの勉強をはじめてみたい、勉強したいと思っているけど、以下のようなことを考えて前に進めない方向けの記事です。
- JavaScriptの知識レベルはこれくらいで足るのか?
- 他に知っておくことはないのかな?
この記事を読むことでReact入門の前に知っておきたいNode.jsとJavaScriptの知識を得ることができると思います。
Node.jsをざっと知る
Node.jsは簡単に言うと
JavaScriptをブラウザではなくPythonなどと同じようにターミナル上で動かすことができるようにするための実行環境のことであり、バックエンド言語としてJavaScriptを使うことができ、ファイル操作などのOSの機能にアクセスできます。
公式ページだと以下のように記載されています。
Node.js はスケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動の JavaScript 環境です。
Node.jsが注目を浴びたのはバックエンドも同じ言語(JavaScript)で書けたら効率的ではないかというのが最大の理由です。フロントエンド開発はJavaScriptほぼ一択ということもあり、こういう流れになったのではないかと思います。
なぜReactアプリ開発でNode.jsが必要なのか
ではなぜバックエンド開発のために開発されたNode.jsがReactアプリ(フロントエンド)開発で必要になってきたのでしょうか。
Reactのような大規模なアプリケーションを開発するとなると、様々なパッケージが必要になり、そのパッケージたちが特定のバージョンで依存しあっています。Node.jsはそのパッケージのインストールと整合性の管理を解決するために必要であり、npm(Node Package Manager)がその解決を担っています。
もともとはバックエンド開発のためにnpmは使われるパッケージ管理システムであったが、フロントエンド用のパッケージを提供するのにも使われるようになり、最近では使用用途としてはフロントエンドのほうが多くなってきているそうです。
その他以下のような用途でも使われます。
- JavaScriptやCSSファイルをバンドルできる
- ブラウザ実行時にpolyfill※するのではなく 最初からコンパイルしておける
- ローカル環境で開発用のHTTPサーバを起動してアプリケーションを稼働させることができる
- ユニットテストやE2Eテストなどのテストを実行できる
- ローカル環境で構文解析等実行できる
※polyfillとは最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためのコードです。
Node.jsをインストール
公式サイトからインストールしたり、MacならHomebrew・Windowsならwingetなどでインストールできますが、プロジェクトごとに異なるバージョンの環境を共存させる必要があるので、バージョンマネージャー(nvmやnodenv)を使ってインストールしましょう。
Node.jsでプロジェクト作成
作成したいディレクトリに移動してターミナルで以下コマンドでプロジェクトを作成できます。
対話形式でプロジェクト名など聞かれますので、全て入力するとpackage.jsonが作成されます。
$ npm init
-y
をつければ、全てデフォルトのままプロジェクト作成できます。
$ npm init -y
npm-scripts
package.jsonのscriptsに記載されているコマンドを以下で実行できます。
$ npm run xxx
では試しにnpm-scriptsでプログラムを実行してみましょう。
まずindex.js
を作成する。
console.log("Hello World!!");
package.jsonにstart
でのコマンドを追加する。
node.jsではnode
+ファイル名
でそのファイルのプログラムを実行できます。
"scripts":{"start":"node index.js"}
以下コマンドで実行する。
$ npm run start
以下出力されれば成功です。
Hello World!!
start,stop,restart,testの場合はrun
を省略して実行できます。
$ npm start
Reactアプリ開発ではcreate-react-appというコマンドで新規プロジェクトが作成できますが、こちらで作られたプロジェクトだと以下が記載されています。たとえばnpm run start
を実行するとreact-scripts start
が実行され、ローカル環境でReactアプリが起動できます。react-scripts
はcreate-react-appで作られたプロジェクトの中でBabelやwebpackなどが裏で動かすことができます。
"scripts":{"start":"react-scripts start","build":"react-scripts build","test":"react-scripts test","eject":"react-scripts eject"},
Yarnとは
npmコマンドのFacebookによる改良版で記述が短い・実行が速いなどのメリットがある。
Yarnがグローバルインストールされていた場合はCreate React App使用時にyarnがデフォルトになるので、npmコマンドを使いたいなら--use-npm
オプションを指定しましょう。
Yarnのコマンド
- yarn (install) はプロジェクトのルートディレクトリに存在するpackage.jsonの内容を参照して、依存関係のあるパッケージをすべてインストールする
- yarn add xxx で指定したパッケージをインストールする
- yarn remove xxx で指定したパッケージをアンインストールする
- yarn upgrade xxx で指定したパッケージを最新バージョンに更新する
- yarn info xxx で指定したパッケージの情報を表示する
上記のnpm run xxx
は以下コマンドで同じことができます。
yarn xxx
JavaScriptをざっと知る
変数宣言について
- varは再宣言も再代入も可。スコープはブロックをすり抜ける。
- letは再代入のみ可。スコープはブロック内。
- constはどちらも不可スコープはブロック内。
constを第一選択肢として、どうしても仕方ない場合のみletを使う。
varは使用しないようにしましょう。
varname1="瀬戸熊";name1="佐々木"// 再代入可varname1="滝沢"// 再宣言可letname2="瀬戸熊";name2="佐々木"// 再代入可letname2="滝沢"// 再宣言不可:'name2' has already been declaredconstname3="瀬戸熊";name3="佐々木"// 再代入不可:"name3" is read-onlyconstname3="滝沢"// 再宣言不可:'name3' has already been declared
スコープに関してもvarを使うと以下のようになり、安全ではないのでletやconstを使うべきである。
varname1="高宮";if(true){varname1="二階堂";varname2="魚谷";console.log(name1);// 二階堂console.log(name2);// 魚谷}console.log(name1);// 二階堂(ブロック内でも書き換え可能なので危険)console.log(name2);// 魚谷(ブロック内で定義したものがブロック外でも参照できる)
letだとブロック内で書き換えや定義しても、ブロック外に影響を与えない。
letname1="高宮";if(true){letname1="二階堂";letname2="魚谷";console.log(name1);// 二階堂console.log(name2);// 魚谷}console.log(name1);// 高宮console.log(name2);// name2 is not defined
データ型について
プリミティブ型
以下7種類ある。
- Boolean型
- Number型
- BigInt型
- String型
- Symbol型
- Null型
- Undefined型
falsyな値
MDNによると、falsyとは偽値 (falsy または falsey な値) は、 Boolean コンテキストに現れたときに偽とみなされる値です。
以下8種類あります。
- false
- 0
- -0
- 0n
- Nan
- ""(空文字)
- null
- undefined
以下はifブロックを実行しません。
if(false){}if(null){}if(undefined){}if(0){}if(-0){}if(0n){}if(NaN){}if(""){}
関数
関数宣言文と関数式
定義の方法は関数宣言文による定義と関数式による定義があるがconstを使った関数式
による定義が推奨されています。
無名関数は定義時に名前を与えられない関数のことで、関数式は変数に無名関数を入れているようなもの。
変数に関数式を代入することになるので、関数宣言文と違って先に定義しておかないと使えません。
// 関数宣言文による定義functionIntroduce1(name){return`私の名前は${name}です。`}// 関数式による定義 constIntroduce2=function(name){return`私の名前は${name}です。`};
アロー関数
アロー関数は引数が一つだと括弧の省略ができる。(推奨はされていないらしいが・・・)
また、retern文が1行だとreturnも省略できます。
// アロー関数式constIntroduce3=(name)=>{return`私の名前は${name}です。`};// アロー関数式、さらに省略記法constIntroduce4=name=>`私の名前は${name}です。`;
デフォルト引数
デフォルト値が設定された引数は省略が可能。
以下の例では第二引数を省略するとage
が18で代用される。
constIntroduce5=(name,age=18)=>`私の名前は${name}です。${age}歳です。`;console.log(Introduce5("白鳥",32));// 私の名前は白鳥です。32歳です。console.log(Introduce5("松本"));// 私の名前は松本です。18歳です。
Rest Parameters
最後の引数に...を付けることで残りの引数を配列として受け取れる。
constNames=(name1,name2,...rest)=>{console.log(name1);// 瀬戸熊console.log(name2);// 佐々木console.log(rest);// ["滝沢","二階堂","高宮"]};Names('瀬戸熊','佐々木','滝沢','二階堂','高宮');
Reactで開発する上で良く使う構文
プロパティ名のショートハンド
プロパティのキー名と値を同じにする。
constname="瀬戸熊";constobj={name}; // = const obj = { name: name };console.log(name);// { name: "瀬戸熊" }
分割代入
配列とオブジェクトの値を分割して代入する。
constdata=[180,70]const[height,weight]=data;console.log(`私の身長は${height}cmで、体重は${weight}kgです。 `);// 私の身長は180cmで、体重は70kgです。constuser={name:"瀬戸熊",age:50};const{name,age}=user;console.log(`私の名前は${name}です。${age}歳です。`);// 私の名前は瀬戸熊です。50歳です。
スプレッド構文
constnames1=["瀬戸熊","佐々木","滝沢"];constnames2=[...names1,"二階堂","高宮"];console.log(names2);// [ "瀬戸熊","佐々木","滝沢","二階堂","高宮" ]constusers1={name:"瀬戸熊",age:48,sex:"男"};constusers2={...users1,group:"renmei",grade:"A1"};console.log(users2);// { name: "瀬戸熊", age: 50, sex: "男", group: "renmei", grade: "A1" }
オブジェクトのコピー
分割代入を用いてオブジェクトをコピーできます。
しかし、こちらはシャローコピーと言ってオブジェクトの深さが1段階までしか有効ではない。
オブジェクトの深さが2段階目の値を変更すると、コピー元の値まで変更されてしまう。
constuser1={name:"瀬戸熊",age:50,sex:"男"};constuser2={...user1};console.log(user2);// { name: "瀬戸熊", age: 50, sex: "男" }console.log(user2===users1);// falseconstuser3={name:"瀬戸熊",age:50,sex:"男",group:{group:"連盟",grade:"A1"}};constuser4={...user3};console.log(user4);// user4: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '連盟', grade: 'A1' } }user4.group.group="協会";console.log(user3);// user3: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '協会', grade: 'A1' } }console.log(user4);// user4: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '協会', grade: 'A1' } }
完全なコピー(ディープコピー)の方法はいくつかあるが、JSONパースを用いる方法は以下の通り。
プロパティにDateオブジェクトや関数が入ってた場合はうまく動かないので注意。
constuser3={name:"瀬戸熊",age:50,sex:"男",group:{group:"連盟",grade:"A1"}};constuser4=JSON.parse(JSON.stringify(user3));console.log(user4);// user4: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '連盟', grade: 'A1' } }user4.group.group="協会";console.log(user3);// user3: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '連盟', grade: 'A1' } }console.log(user4);// user4: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '協会', grade: 'A1' } }
他にはLodashのcloneDeep()を使う方法などある。
ショートサーキット評価(短絡評価)
- || は左辺がfalsyな値だと評価が右辺に渡される。
- ?? は左辺がnullかundefinedだと評価が右辺に渡される。
- && は左辺がtruthyな値だと評価が右辺に渡される
|| はちょっと前に賛否ありましたが、一応よくでてくるので記載しておきます・・・。
constname1='瀬戸熊';constname2='佐々木';true&&console.log(name1);// 瀬戸熊false&&console.log(name1);// 出力なしtrue||console.log(name2);// 出力なしfalse||console.log(name2);// 佐々木null??console.log(name2);// 佐々木undifined??console.log(name2);// 佐々木
配列・オブジェクトの処理
map,filter,find,findIndex,every,some
constdataset=[1,2,3,4,5,6,7,8,9];console.log(dataset.map((data)=>data*3));// [ 3, 6, 9, 12, 15, 18, 21, 24, 27 ]console.log(dataset.filter((data)=>data>5));// [ 6, 7, 8, 9 ]console.log(dataset.find((data)=>data>5));// 6console.log(dataset.findIndex((data)=>data>5));// 5console.log(dataset.every((data)=>data>5));// falseconsole.log(dataset.some((data)=>data>5));// true
- map():与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成する。
- filter():与えられた関数によって実装されたテストに合格したすべての配列からなる新しい配列を生成する。
- find():提供されたテスト関数を満たす配列内の 最初の要素の値を返します。見つからなかった場合はundefinedを返す。
- findIndex():配列内の指定されたテスト関数を満たす最初の要素の位置を返します。テスト関数を満たす要素がない場合を含め、それ以外の場合は-1 を返します。
- every():配列内のすべての要素が指定された関数で実装されたテストに合格するかどうかを真偽値で返します。
- some():配列の少なくとも一つの要素が、指定された関数で実装されたテストに合格するかどうかを真偽値で返します。
reduce,sort
- reduce():配列の各要素に対して (引数で与えられた) reducer関数を実行して、単一の出力値を生成します。
- sort(): 配列の要素をソートします。既定のソート順は昇順で、要素を文字列に変換してから、UTF-16コード単位の値の並びとして比較します。
constdataset=[1,2,3,4,5,6,7,8,9];console.log(dataset.reduce((a,b)=>a+b));// 45console.log(dataset.sort((a,b)=>a>b?-1:1));// [ 9, 8, 7, 6, 5, 4, 3, 2, 1 ]
forEach,for...of
- forEach():メソッドは与えられた関数を、配列の各要素に対して一度ずつ実行します。
- for...of:反復可能オブジェクトなどに対して、反復的な処理をするループを作成します。
本来なら使わないほうがいいが、どうしても使う場合はforEach推奨。
constdataset=[1,2,3,4,5,6,7,8,9];dataset.forEach((data)=>{if(data%2===0){console.log(`${data}は偶数です。`);}});for(letdataofdataset){if(data%2===0){console.log(`${data}は偶数です`);}}
includes
特定の要素が配列に含まれているかどうかを true または false で返します。
constdataset=[1,2,3,4,5];console.log(dataset.includes(1));// trueconsole.log(dataset.includes(7));// false
また、以下のような||演算子が繰り返し使われるコードを完結に書けます。
if(x==='a'||x==='b'||x==='c'){console.log('ok')}if(['a','b','c'].includes(x)){console.log('ok')}
Object.keys,values,entries
- Object.keys():プロパティのキーのリストを配列で取得できる
- Object.values():プロパティ値のリストを配列で取得できる
- Object.entries():プロパティのキーと値が対になった2次元配列を取得できる
constuser={id:1,name:'瀬戸熊',age:50,};console.log(Object.keys(user));// [ 'id', 'name', 'age']console.log(Object.values(user));// [ 1, '瀬戸熊', 50 ]console.log(Object.entries(user));// [// [ 'id', 1 ],// [ 'name', '瀬戸熊' ],// [ 'age', 50 ],// ]
非同期処理
Promise
PromiseはES2015から導入されたJavaScriptの組み込みオブジェクトで、非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現するものであり、Promiseを使うことによって、非同期処理の完了を待って次の処理を行うというのがJavaScriptでもできるようになります。
以下例文
- 最初のresolve()に渡したものが.then()の引数のvalueになり、その.then()内でreturnしたものが次の.thenのvalueになる。
- reject()に渡したものが、errorとしてcatch()で受け取れる。
- finally()に渡された関数は必ず最後に実行される。(ES2018から)
constpromise=newPromise((resolve,reject)=>{if(isSuccess){resolve('成功1');}else{reject(newError('失敗'));}});promise.then((value)=>{console.log(value);return'成功2';}).then((value)=>{console.log(value);}).catch((error)=>{console.error(error);}).finally(()=>{console.log('完了');});
axiosを使ってAPIをたたいてみる
async/awaitを使わない場合
constgetUser=(userId)=>{returnnewPromise(function(resolve,reject){axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`).then((response)=>resolve(response.data)).catch((error)=>reject(error.response.status));});};getUser(1).then((user)=>{console.log(user);}).catch((error)=>{console.log(error);}).finally(()=>{});
async/awaitを使った場合
関数宣言時にasyncキーワードを付与するとその関数は、返される値がPromise.resolve()によってラップされたものになる。
asyncをつけた非同期関数内では他の非同期関数をawaitをつけて呼び出すことができる。
constgetUser2=async(userId)=>{try{constresponse=awaitaxios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);returnresponse.data;}catch(error){throwerror.response.status;}};constmain=async()=>{try{constuser=awaitgetUser2(1);console.log(user);}catch(error){console.error(error);}finally{}};main();
モジュール
名前付きエクスポート
1ファイルでいくらでもエクスポートできる。
constNAME="瀬戸熊";constAGE=50;constIntroduce=(name)=>`私の名前は${name}です`export{NAME,AGE,Introduce};orexportconstNAME="瀬戸熊";exportconstAGE=50;exportconstIntroduce=(name)=>`私の名前は${name}です`
名前付きエクスポートの場合は{}をつけてインポートする
import{NAME,AGE,Introduce}from"./export"
デフォルトエクスポート
1ファイル1回までしかエクスポートできない
exportconstNAME="瀬戸熊";exportconstAGE=50;constIntroduce=(name)=>`私の名前は${name}です`exportdefaultIntroduce;
デフォルトエクスポートの場合は{}はつけないでインポートする。名前も自由に命名できる。
importIntroduce,{NAME,AGE}from"./export"
まとめ
React入門の前に知っておきたいNode.jsとJavaScriptの知識を簡単にまとめました。
今回記載した項目が大体頭に入っていれば、Reactに入門してもよいのではないかと思います。
このほかにもクラスやthisの扱いなど学ぶべきものはいっぱいありますが、Reactが関数コンポーネント主体になってきたのであまり使わない印象があるので、一旦は飛ばしてもよいかと思い省略しました。(理解はしておいたほうがいいですが)
あとTypeScriptはReactでの開発においてマストなものになりつつあるので、早めにTypeScriptでReactを書けるように学んでいったほうがいいと思います。
間違っていたり、これも理解しておくべきなどありましたら、指摘していただけると幸いです。
最後まで読んでいただきありがとうございました!!
参考
MDN
Polyfill (ポリフィル)
Falsy (偽値)
Array.prototype.map()
Array.prototype.filter()
Array.prototype.find()
Array.prototype.findIndex()
Array.prototype.every()
Array.prototype.some()
Array.prototype.reduce()
Array.prototype.sort()
Array.prototype.includes()
Promise
Qiita
Node.jsとはなにか?なぜみんな使っているのか?
Promiseの使い方、それに代わるasync/awaitの使い方