この記事は、マイナビ Advent Calendar 2021 14日目の記事です。
はじめに
この記事は、Reactを触る上で必要なJavaScriptの基本知識をいくつか紹介します。
ifやforは扱える上で、JavaScript特有の書き方などをつらつら書いています。
今はJavaScriptよりTypeScriptの方が主流ですが、TypeScriptもJavaScriptから派生した言語なので、ここら辺の基礎知識はTypeScriptを扱う上でも大切になってくると思います。
これからReactを触る方が、少しでも参考になれば幸いです。
基本知識
var・const・let
今までは、変数・定数はすべてvarで宣言していたものを、constを定数、letを変数として扱えるようになりました。
ここで言いたいことは、「varは使わないようにしよう」です。
下記のコードでは、constを使用して、書き換えが出来ないようにしています。
// varの場合
var js = true;
js = false;
console.log(js); // false
// constの場合
const js = true;
js = false;
console.log(js); // Uncaught TypError: ...
下記のコードでは、ifの中で、外側と同じ変数をvarで二重に宣言し、意図していない動きをしています。
これは、letキーワードを使用することで解決できます。
// varの場合
var js = "javascript";
if (js) {
var js = "java";
console.log(js); // java
}
console.log(js); // java
// letの場合
var js = "javascript";
if (js) {
let js = "java";
console.log(js); // java
}
console.log(js); // javascript
また、varとletでは以下のような違いもあります。
// varの場合
var div;
var container = document.getElementById("container");
for (var i = 1; i <= 5; i++) {
div = document.createElement("div");
div.onclick = function () {
alert("i");
};
container.appendChild(div);
}
// letの場合
var div;
var container = document.getElementById("container");
for (let i = 1; i <= 5; i++) {
div = document.createElement("div");
div.onclick = function () {
alert("i");
};
container.appendChild(div);
}
上記の違いは、for文で利用するiを、varとletどちらで宣言しているか、だけです。
このプログラムで生成されるdivをクリックすると、varで宣言した場合ではどのdiv要素でも6が、letで宣言された場合では生成された順番の整数値が表示されます。
文字列
変数と文字列を一文で表す方法は、主に二通りの方法があります。
加算演算子を用いる方法と、バッククォーテーションを用いる方法です。
const java = "java";
const script = "script";
// 換算演算子を用いた場合
console.log("This language is " + java + script + "!!"); // This language is javascript!!
// バッククォートと${}を用いた場合
console.log(`This language is ${java}${script}!!`); // This language is javascript!!
下記のコードのようにバッククォートを用いると、改行も認識してくれます。
メール文面のテンプレートを作成したり、HTMLを記述したりするのに便利です。
const js = "javascript";
console.log(`
This
language
is
${js}
!!
`);
// This
// language
// is
// javascript
// !!
関数
関数は、functionキーワードとともに作成します。関数名を呼び出すことで、関数を実行できます。
function test() {
console.log("javascript");
}
test(); // javascript
JavaScriptでは変数の中に関数を入れることが出来ます。これを関数式と言い、下記のコードは上記のものと全く同じです。
const test = function() {
console.log("javascript");
};
test(); // javascript
また下記コードのように、関数に引数を設定する場合、デフォルト値を設定することが出来ます。
const test = function(name="javascript") {
console.log(`My name is ${name}.`)
};
test(); // My name is javascript.
test("ruby"); // My name is ruby.
関数の引数に関しては、文字列だけでなく他のデータ型も指定することが出来ます。
const defaultPersona = {
name: {
firstname: "hoge",
lastname: "fuga"
},
old: 22
};
function searchPersona(persona=defaultPersona) {
console.log(`${persona.name.firstname} ${persona.name.lastname} is ${persona.old} years old.`);
// hoge fuga is 22 years old.
}
アロー関数
JavaScriptには、functionやreturnなしで関数を記述できるアロー関数が存在します。
アロー関数を定義する際には=>を用い、これが矢に見えることからアロー関数と呼ばれています。
アロー関数を使うことで、関数を一文で記述することが出来ます。
関数の戻り値が単一の式であらわされる場合はreturn文も不要になります。
// 関数式の場合
const test = function(name) {
return `My name is ${name}.`;
};
console.log(test("javascript")); // My name is javascript.
// アロー関数の場合
const test = name => `My name is ${name}.`;
console.log(test("javascript")); // My name is javascript.
また、引数が二つの場合は、二つの引数をカッコで囲む必要があります。
const test = (firstname, lastname) => `My name is ${firstname} ${lastname}.`;
console.log(test("hoge", "fuga")); // My name is hoge fuga.
アロー関数でオブジェクトを戻り値とする関数を作成することもできます。
const test = (firstname, lastname) => ({
firstname: firstname,
lastname: lastname
});
console.log(test("hoge", "fuga")); // { firstname: 'hoge', lastname: 'fuga' }
気を付けるべきポイントとして、戻り値を()でくくる必要があります。
オブジェクトを返すアロー関数を一文で記述するときは、戻り値を()で囲みましょう。
最後にアロー関数のスコープについてです。実際にコードを確認したほうが分かりやすいと思います。
// print関数とsetTimeout内の関数をfunctionで実装
const test() = {
languageList = ["javascript", "java", "ruby", "python", "C"],
print: function(seconds = 10) {
setTimeout(function() {
console.log(this.languageList.join(", ")); // TypeError: Cannot read property 'join' of undefined
}, seconds);
}
};
// setTimeout内の関数をアロー関数に変更
const test() = {
languageList = ["javascript", "java", "ruby", "python", "C"],
print: function(seconds = 10) {
setTimeout( () => {
console.log(this.languageList.join(", ")); // javascript, java, ruby, python, C
}, seconds);
}
};
// print関数もアロー関数に変更
const test() = {
languageList = ["javascript", "java", "ruby", "python", "C"],
print: (seconds = 10) => {
setTimeout(function() {
console.log(this.languageList.join(", ")); // TypeError: Cannot read property 'join' of undefined
}, seconds);
}
};
上記のコードで正しい出力が得られるのはsetTimeout内の関数をアロー関数にした場合のみです。
理由は、アロー関数は通常の関数とは違い、独自のスコープを持たないため、アロー関数の外側と内側でスコープが保持されます。
結果的にthisの指すものはprint関数の呼び出しコンテキストであるtestになります。
スコープの部分に関しては、自分も詳しく分かっていないので、これから使っていく内に分かることを期待します。
デストラクチャリング
デストラクチャリングとは、オブジェクトを変数に代入したり、必要なプロパティだけを取捨選択できる機能です。
下記のコードは、menuプロパティを分解してmeatとfishをローカル変数に代入しています。
このように、オブジェクトから必要最低限の変数のみを宣言することができます。
const menu = {
meat: "steak",
fish: "salmon",
vegetable: "tomato",
};
const { meat, fish } = menu;
console.log(meat, fish); // steak salmon
また、デストラクチャリングは変数の代入だけでなく、関数の引数にも適用できます。
const menu = {
meat: "steak",
fish: "salmon",
vegetable: "tomato",
};
// デストラクチャリングを使わない場合
const test = menu => {
console.log(`I like ${menu.meat} and ${menu.fish}!`);
};
// デストラクチャリングを使った場合
const test = ({ meat, fish }) => {
console.log(`I like ${meat} and ${fish}!`);
};
オブジェクトがさらにネストされていた場合でも、デストラクチャリングは利用できます。
const menu = {
meat: "steak",
fish: "salmon",
vegetable: "tomato",
mealSet: {
meatSet: "steak and bread",
fishSet: "tuna and rice",
}
};
const test = ({ meat, mealSet: { fishSet } }) => {
console.log(`I ate ${meat} and ${fishSet}!`);
};
test(menu); // I ate steak and tuna and rice!
配列に対するデストラクチャリング
下記コードのように、不要な要素を読み飛ばして必要な要素のみを取得する(リストマッチング)ことが出来ます。
配列のデストラクチャリングは、この後紹介するスプレッド構文と組み合わせて使われることが多いです。
const [firstAnimal] = ["dog", "cat", "turtle"];
console.log(firstAnimal); // dog
const [, , lastAnimal] = ["dog", "cat", "turtle"]:
console.log(lastAnimal); // turtle
オブジェクトリテラル
変数をオブジェクトのプロパティとして記述する場合、プロパティ名を省略することが出来ます。
const name = "javascript";
const birthday = 1995;
const print = () => `javascript test`;
const programmingLang = { name, birthday, print };
// プロパティ名を書くと { name: name, birthday: birthday, print: print } のようになる
console.log(programmingLang); // { name: 'javascript', birthday: 1995, print: [Function: print] }
console.log(programmingLang.print()); // javascript test
また、オブジェクトリテラル内に関数を記述する際のfunctionキーワードを省略することが出来ます。
// 省略しなかった場合
const persona = {
firstname: firstname,
lastname: lastname,
age: age,
gender: gender,
words: function() {
console.log("I like javascript!");
}
};
// 省略した場合(プロパティ名も省略)
const persona = {
firstname,
lastname,
age,
gender,
words() {
console.log("I like javascript!");
}
};
javascriptでは、このようにオブジェクトが簡素に記述できます。
スプレッド構文
...(ドット3つ)で記述されるスプレッド構文は、いくつかの異なる用途があります。
まず、配列の連結で使用されます。
const langList1 = ["javascript", "ruby"];
const langList2 = ["python", "html"];
const unionList = [...langList1, ...langList2];
console.log(unionList.join(", ")); // javascript, ruby, python, html
次に、イミュータブルな配列を実現するためにスプレッド構文が使用されます。
下記のコードでは、元のtestListがreverseメソッドによって書き換えられてしまっています(破壊的メソッド)。
const testList = ["javascript", "ruby", "python"]
const [last] = testList.reverse();
console.log(last, testList); // python [ 'python', 'ruby', 'javascript' ]
これを防ぐために、スプレッド構文で元の配列のコピーを作成し、それを使用するようにします。
そうすることで、元の配列が変更されることなく、配列の最後の要素を取得することが出来ます。
const testList = ["javascript", "ruby", "python"]
const [last] = [...testList].reverse();
console.log(last, testList); // python [ 'javascript', 'ruby', 'python' ]
スプレッド構文はまた、配列の要素数が分かっていない場合の「残り全部」を簡単に表現することが出来ます。
const listLang = ["javascript", "ruby", "python", "C"];
const [first, ...others] = listLang;
console.log(others.join(", ")); // ruby, python, C
関数の引数を配列として受け取るにも、スプレッド構文が使われます(残余引数)。
以下のコードは、可変長の引数を残余引数として受け取る関数です。
このコードでは、残余引数としてだけでなく、配列の先頭と末尾の要素を変数に格納するのにもスプレッド構文を使用しています。
function searchList(...args) {
let [start, ...remain] = args;
let [last, ...others] = remain.reverse();
console.log(`The length of the array of arguments is ${args.length}.`);
console.log(`The first element is ${start}.`);
console.log(`The last element is ${last}.`);
console.log(`The length of the array of remaining is ${others.length}.`);
};
searchList("first", "second", "third", "forth", "fifth");
最後に、スプレッド構文は配列だけでなく、オブジェクトにも使用できます。
const morningAndLunch = {
breakfast: "bread",
lunch: "tuna and rice",
};
const dinner = "ramen";
const mealOfTheDay = {
...morningAndLunch,
dinner
};
console.log(mealOfTheDay); // { breakfast: 'bread', lunch: 'tuna and rice', dinner: 'ramen' }
非同期処理
非同期処理とは、一つのタスクが実行中であっても他のタスクを実行できる実行方式です。
今まで紹介したコードはすべて同期処理です。
同期処理とは、実行したい命令を箇条書きにするようなイメージです。
const header = document.getElementById("heading");
header.innerHTML = "Hello World!";
上記のコードを言葉で表すと、「idの値がheadingである要素を選択し、それが終わったあと、要素のデータをHello Word!に書き換えてください」となります。
これは逐次的に処理されるため、この命令が実行中は他の命令は実行されません。
非同期処理は、ある命令の処理中に他の命令も実行できるようにした実行方式です。
これを実現するために、JavaScriptにはPromise・fetchとasync・awaitがあります。
Promise・fetch
fetchとは、HTTPのリクエストを送信してレスポンスを受信するためのJavaScript APIです。
ここで、https://javascript.api.sample?results=1は、サンプルのURLとします。
console.log(fetch("https://javascript.api.sample?results=1"));
上記では、fetchの戻り値をログ出力していますが、出力されるのはPending(保留中)のPromiseオブジェクトです。
ここで、Promiseとは、非同期処理の状態を表すオブジェクトで、Pending(保留中)、Resolved(成功)、Rejected(失敗)の3つの状態を表します。
ブラウザがfetch呼び出しに対する値をそのまま返さずに、データが取得できてもできなくても結果をお伝えします、という約束をするようなイメージです。
fetchの戻り値のPromiseオブジェクトは、thenメソッドを使用することで扱うことが出来ます。
fetchの取得処理が完了したら実行してほしい処理をthenを使用して指定するようなイメージです。
fetch("https://javascript.api.sample?results=1").then(res =>
console.log(res.json());
);
thenメソッドに記載した関数(コールバック関数)は、非同期処理が成功した際に呼ばれます。
このコールバック関数がさらに戻り値を返した場合、さらにthenメソッドのコールバック関数に引き継ぐことが出来ます。
fetch("https://javascript.api.sample?results=1")
.then(res => res.json())
.then(json => json.results)
.then(console.log)
.catch(console.error);
上記のコードでは、初めにfetch内に記載したURLに対してリクエストを送信しています。
成功した次は、レスポンスの文字列をjsonオブジェクトに変換します。
それが成功した後、jsonオブジェクトのresultsプロパティを取り出します。
最後にログ出力をします。
これらの処理のどこかでエラーが発生した場合は、catch内に設定したコールバック関数が呼ばれ、エラーオブジェクトが出力されます。
またPromiseオブジェクトは、自分で生成することも可能です。
下記コードでは、Promiseオブジェクトがコールバック関数とともに生成されています。
コールバック関数の引数ではresolvesとrejectsは、成功した場合はresolves、失敗した場合にはrejectsにデータが渡ります。
const test = count =>
new Promise((resolves, rejects) => {
const api = `https://javascript.api.sample?results=${count}`;
const request = new XMLHttpRequest();
request.open("GET", api);
request.onload = () =>
request.status === 200
? resolves(JSON.parse(request.response).results)
: reject(Error(request.statusText));
request.onerror = err => rejects(err);
request.send();
});
この関数の使用方法は、Promiseオブジェクトに対して、下記コードのようにthen/catchメソッドを呼び出す必要があります。
test(5)
.then(testVariable => console.log(testVariable))
.catch(error => console.error(error.message);
async・await
Promiseを扱う別の方法として、async関数を使う方法もあります。これを使用することで、非同期関数を同期関数のように呼び出すことが出来ます。
Promiseオブジェクトを受け取って、thenメソッドで値を取り出す代わりに、awaitキーワードを書くことで、Promiseが成功するまで処理が止まります。
const test = async () => {
try {
const res = await fetch("https://javascript.api.sample?results=1");
const { results } = await res.json();
console.log(results);
} catch (error) {
console.log(error);
}
};
test();
上記のコードではasyncキーワードを使って関数宣言をしているため、非同期関数を呼び出す際にawaitを使用することが出来ます。
ここでは、awaitを使用してfetchを呼び出しているため、以降のコードはPromiseが成功するまで実行されません。
また、エラー処理に関してはtry/catchを使用することで、例外処理を実装できます。
Promise/fetchで、自らPromiseを生成しました。
その際は、thenを使用して、非同期処理を実装しましたが、async/awaitを使用しても同じように非同期処理を実装することが出来ます。
const test = count =>
new Promise((resolves, rejects) => {
const api = `https://javascript.api.sample?results=${count}`;
const request = new XMLHttpRequest();
request.open("GET", api);
request.onload = () =>
request.status === 200
? resolves(JSON.parse(request.response).results)
: reject(Error(request.statusText));
request.onerror = err => rejects(err);
request.send();
});
async function testUseAsyncAndAwait() {
try {
const members = await test(5);
console.log(members);
} catch (error) {
console.error(error.message);
}
}
モジュール
JavaScriptにおいて、モジュールとは再利用可能なコードで、他のJavaScriptのファイルからインポートすることで利用できます。
たとえば、以下のモジュールはprintとlogの2つの関数をエクスポートしています。
export const print = message => log(message, date());
export const log = (message, timestamp) =>
console.log(`${timestamp.toString()}: ${message}`);
const date = () => new Date();
変数や関数の宣言にexportをつけることで、他のモジュールからインポートすることが出来るようになります。
一方、単一のオブジェクトをエクスポートする場合は、export defaultと記述します。
下記のコードは、Testクラスのインスタンスを生成して、defaultキーワードを付けてエクスポートしています。
それにより、他のモジュールからこのインスタンスをインポートして使用することが可能になります。
export default new Test("javascript", 1, ["ruby", "python"]);
これらのモジュールをインポートして使用する場合は以下のようにします。
import { print, log } from "./testExport";
import test from "./testDefaultExport";
print("printing a message");
log("logging a message");
test.print();
上記コードのとおり、エクスポートする際のdefaultキーワードの有無により、インポートの記述が異なります。
最初の行では、デストラクチャリングを使用して複数の値を受け取っているのに対し、2行目のdefaultを使用してエクスポートされたモジュールは単一の変数を使ってインポートされています。
また、エクスポート側とは異なる名前でインポートすることも可能です。
import { print as p, log as l } from "./testExport";
p("printing a message");
l("logging a message");
また、以下のように、エクスポートされたすべての値を単一のオブジェクトで受け取ることも可能です。
import * as exportObject from "./testExport";
別のモジュール使用方法としてCommonJSがあります。
CommonJSでは、module.exportsという特別なオブジェクト経由でモジュールをエクスポートします。
const print = message => log(message, new Date());
const log = (message, timestamp) =>
console.log(`${timestamp.toString()}: ${message}`);
module.exports = { print, log };
そして、それらをインポートするにはimport文ではなくrequire関数を使います。
const { print, log } = require("./testExport")
まとめ
今回はreactを使うために憶えておいて損はないJavaScriptの知識について書きました。
reactを触る前に、少なくともこの知識を学んでおけば、すこしは書いてあることが分かるかと思います。
以上になります。
ありがとうございました。
参照