Reactは最近人気のJavaScriptライブラリです。ダイナミックかつレスポンシブなアプリケーションの作成に使用でき、高いパフォーマンスを誇り、簡単に拡張することができます。基本的なロジックは、使い回しのできるコンポーネントに基づくもので、同じコードを何度も書く手間を削減可能です。つまり、Reactを使えば、効率的に強力なアプリケーションを作成することができます。

Reactアプリケーションの作成方法を学ぶなら今です。

しかし、JavaScriptの主要な機能をしっかり理解していないと、Reactアプリケーションの構築は困難になります。

このため、Reactを使い始める前に知っておくべきJavaScriptの機能と概念をご紹介します。これをよく理解すれば、高度なReactアプリケーションを簡単に構築できるようになるはずです。

それでは、以下の目次に沿って進んでいきましょう。

JavaScriptとECMAScript

JavaScriptHTMLやCSSとともに、動的なウェブページを構築するのに使用される一般的なスクリプト言語です。HTMLはウェブページの構造を、CSSはその要素のスタイルやレイアウトを作成するのに使用されますが、JavaScriptはページに動きを追加する、つまり機能やインタラクティブ性をもたらす言語です。

その後、この言語は主要なブラウザに採用され、JavaScriptの動作を記述する文書、ECMAScriptが作成されました。

ECMAScriptは基本的には毎年更新されており、JavaScriptにはその都度新しい機能が追加されています。

ECMAScript 2015は標準の6番目のリリースであるため、ES6とも呼ばれます。それ以降のバージョンは進行形で表記されるため、ECMAScript 2016をES7、ECMAScript 2017をES8、といった具合に表記しています。

規格に新機能が追加される頻度が高いため、すべてのブラウザでサポートされていないものもあります。では、JSアプリに追加した最新のJavaScript機能が、すべてのウェブブラウザで期待通りに動作するためには、どうすればよいのでしょうか。

選択肢は3つあります。

  1. すべての主要なブラウザが新機能をサポートするようになるまで待つ。しかし、開発中のアプリにそのJSの新機能が絶対に必要な場合、これは選ぶべき選択肢ではありません。
  2. ポリフィルを利用する。つまり、「ネイティブに対応していない古いブラウザで最新の機能を利用するためのコード(通常はウェブ上のJavaScript)」を使うことができます(mdn web docsも参照のこと)。
  3. BabelTraceur など、ECMAScript 2015+のコードをすべてのブラウザでサポートされているJavaScriptバージョンに変換するJavaScriptトランスパイラを使用する。

文と式の違い

Reactアプリケーションを構築する際には、文と式の違いを理解することが重要です。そこで、少しプログラミングの基本的な概念に立ち返ってみましょう。

コンピュータプログラムは、コンピュータが実行する命令です。この命令は文(ステートメント)と呼ばれます。

文とは異なり、式(エクスプレッション)は値を生成するコードの断片です。文において、式は値を返す部分であり、通常、等号の右側に式が位置しています。

それに対して

JavaScriptの文は、通常セミコロンで終わるか、中括弧で囲まれたコードのブロックまたは行にすることができます。

以下は、JavaScriptの文の簡単な例です。

document.getElementById("hello").innerHTML = "Hello World!";

上記の文は、"Hello World!"id="hello"のDOM要素に書き込んでいます。

すでに述べたように、式は値を生成するか、それ自体が値です。次のような例を考えてみましょう。

msg = document.getElementById("hello").value;

document.getElementById("hello").valueは、値を返すので式です。

以下の例で、式と文の違いがさらに明確になるはずです。

const msg = "Hello World!";
function sayHello( msg ) {
	console.log( msg );
}

上の例では

  • 最初の行は文で、"Hello World!"は式です。
  • 関数宣言は文で、関数に渡されるパラメータmsgは式です。
  • コンソールにメッセージを表示する行は文で、ここでもパラメータmsgは式です。

Reactで式が重要な理由

Reactアプリケーションを構築する際、JavaScriptの式をJSXコードに挿入することができます。例えば、変数を渡したり、イベントハンドラや条件を書いたりすることができます。これを行うには、JSコードを中括弧で囲む必要があります。

例えば、以下のように変数を渡すことができます。

const Message = () => {
	const name = "Carlo";
	return <p>Welcome {name}!</p>;
}

つまり、中括弧はトランスパイラに、中括弧で囲まれたコードをJSコードとして処理するように指示する役割を果たします。<p>タグの前と、</p>タグの後に来るものはすべて、通常のJavaScriptコードです。<p>タグと</p>タグの中にあるものは、すべてJSXコードとして処理されます。

別の例を挙げます。

const Message = () => {	
	const name = "Ann";
	const heading = <h3>Welcome {name}</h3>;
	return (
		<div>
			{heading}
			<p>This is your dashboard.</p>
		</div>
	);
}

以下のようにオブジェクトを渡すこともできます。

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22',
		description: 'Content Writer'
	}

	return (
		<div>
			<h2>Welcome {person.name}</h2>
			<img
				className="card"
				src={person.avatar}
				alt={person.name}
			/>
			<p>Description: {person.description}.</p>
		</div>
	);
}

また、以下はもう少し分かりやすい例です。

render(){
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: {
			boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', width: '200px'
		}
	}

	return (
		<div style={person.theme}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

要素imgdivstyle属性に二重の中括弧があることに注目してください。カードのスタイルと画像のスタイルを含む2つのオブジェクトを渡すために二重括弧を使用しています。

Reactで作成したカードの例
Reactで作成したカードの例

上記のすべての例で、JSXにJavaScriptの式が含まれていることにお気づきでしょうか。

Reactにおける不変性

オブジェクト指向プログラミングと関数型プログラミングには、不変性と変性という2つの重要な概念があります。

不変性は、値が作成された後に変更できないことを意味します。一方で変性とは、もちろんその逆を意味します。

JavaScriptでは、プリミティブ値不変であり、プリミティブ値を一度作成すると変更することができません。逆に、配列やオブジェクトは、新しい値を再び割り当てすることなく、プロパティや要素を変更できるため、変性ということになります。

JavaScriptで不変性オブジェクトを使用する理由はいくつかあります。

  • パフォーマンスの向上
  • メモリ消費量の削減
  • スレッドセーフ
  • コーディングとデバッグが容易になる

不変性のパターンに従って、変数やオブジェクトが一度割り当てられると、再割り当てや変更ができません。データを変更する必要がある場合は、そのコピーを作成し、元の内容を変更せずに、その内容を調整する必要があります。

不変性はReactのキーコンセプトでもあります。

Reactのドキュメントでは、次のように説明されています。

クラスコンポーネントの状態は、this.stateとして利用できます。stateフィールドはオブジェクトでなければなりません。状態を直接変異させないでください。状態を変更するには、新しい状態を指定してsetStateを呼び出します。

コンポーネントの状態が変わるたびに、Reactはコンポーネントを再レンダリングして仮想DOMを更新するかどうか考慮します。Reactが以前の状態を把握していなければ、コンポーネントを再レンダリングするかどうかを判断することはできません。Reactのドキュメントには、わかりやすい例が記載されています。

Reactで状態オブジェクトの不変性を保証するには、どのようなJavaScriptの機能を使えばよいのでしょうか。具体的に見てみましょう。

変数の宣言

JavaScriptで変数を宣言するには、3つの方法があります。varletconstです。

varは、JavaScriptの初期から存在します。これは、関数スコープまたはグローバルスコープの変数を宣言するために使用され、オプションでそれを値に初期化します。

varを使って変数を宣言すると、グローバルスコープとローカルスコープの両方で、その変数を再び宣言したり更新したりすることができます。次のようなコードが考えられます。

// Declare a variable
var msg = "Hello!";

// Redeclare the same variable
var msg = "Goodbye!"

// Update the variable
msg = "Hello again!"

varは、コードが実行される前に処理されます。その結果、コード内の任意の場所で変数を宣言することは、先頭で宣言することと同じになります。この動作をホイスティング(巻き上げ)と呼びます。

注目すべき点として、変数の宣言だけがホイストされ、初期化はされません。これは、制御フローが該当する代入文に到達したときに起こります。その時点まで、変数はundefinedになります。

console.log(msg); // undefined
var msg = "Hello!";
console.log(msg); // Hello!

JS関数内で宣言されたvarのスコープは、その関数本体の全体です。

つまり、変数はブロックレベルではなく、関数全体のレベルで定義されることになります。これは、JavaScriptのコードにバグをもたらし、保守を困難にする様々な問題を引き起こします。

そんな問題を解決するために、ES6ではletが導入されました。

letの宣言は、ブロックスコープのローカル変数を宣言し、オプションでそれを値に初期化します。

letvarよりも優れている点は以下の通りです。

  • letはブロック文のスコープに変数を宣言するものです。一方でvarは、ブロックスコープに関係なく、関数全体に対してグローバルまたはローカルに変数を宣言します。
  • グローバル変数letは、windowオブジェクトのプロパティではありませんwindow.variableNameでアクセスすることはできません。
  • letは、その宣言に達した後にのみアクセスすることができます。変数は、制御フローが宣言されたコード行に到達するまで初期化されません(letの宣言は非ホイストです)。
  • letで変数を再宣言すると、SyntaxError を投げます。

varを使って宣言した変数はブロックスコープを作らないため、ループ内やif文の内部でvarを使って変数を定義すると、ブロック外からアクセスしてしまう可能性があり、コードのバグにつながることがあります。

最初の例のコードは、エラーなく実行されます。では、上で見たコードのブロックのvarletに置き換えてみましょう。

console.log(msg);
let msg = "Hello!";
console.log(msg);

2番目の例では、varの代わりにletを使用すると、Uncaught ReferenceErrorが生成されます。

Chromeのエラー(Uncaught TypeError: Assignment to constant variable )
Chromeのエラー(Uncaught TypeError: Assignment to constant variable )

ES6では、3つ目としてconstも導入されています。

constは、letとよく似ていますが、重要な違いがあります。

次のような例を考えてみましょう。

const MAX_VALUE = 1000;
MAX_VALUE = 2000;

上記のコードでは、以下のようなTypeErrorが発生します。

Google Chromeのエラー(Uncaught TypeError: Assignment to constant variable)
Google Chromeのエラー(Uncaught TypeError: Assignment to constant variable)

さらに、以下のようになります。

値を与えずにconstを宣言すると、次のSyntaxErrorが投げられます(ES6 In Depth: letとconstも参照してください)。

Chromeのエラー(Uncaught SyntaxError: Missing initializer in const declaration)
Chromeのエラー(Uncaught SyntaxError: Missing initializer in const declaration)

しかし、定数が配列やオブジェクトである場合、その配列やオブジェクト内のプロパティや項目を調整することができます。

例えば、配列の項目を変更したり、追加したり、削除したりすることができます。

// Declare a constant array
const cities = ["London", "New York", "Sydney"];

// Change an item
cities[0] = "Madrid";

// Add an item
cities.push("Paris");

// Remove an item
cities.pop();

console.log(cities);

// Array(3)
// 0: "Madrid"
// 1: "New York"
// 2: "Sydney"

しかし、配列を再割り当てすることはできません。

const cities = ["London", "New York", "Sydney"];

cities = ["Athens", "Barcelona", "Naples"];

上記のコードでは、TypeErrorが発生します。

Chromeのエラー(Uncaught TypeError: Assignment to constant variable)
Chromeのエラー(Uncaught TypeError: Assignment to constant variable)

オブジェクトのプロパティやメソッドも追加、再割り当て、削除することができます。

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language',
	content: 'JavaScript is a scripting language that enables you to create dynamically updating content.'
};

// add a new property
post.slug = "javascript-is-awesome";

// Reassign property
post.id = 5;

// Delete a property
delete post.excerpt;

console.log(post);

// {id: 5, name: 'JavaScript is awesome', content: 'JavaScript is a scripting language that enables you to create dynamically updating content.', slug: 'javascript-is-awesome'}

しかし、オブジェクトそのものを再割り当てすることはできません。次のコードは、Uncaught TypeErrorを返します。

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};

post = {
	id: 1,
	name: 'React is powerful',
	excerpt: 'React lets you build user interfaces'
};

Object.freeze()

constを使っても、必ずしも強力な不変性が保証されるわけではない(特にオブジェクトや配列を扱う場合)ことは、ここまでの説明で理解できたと思います。では、Reactアプリケーションで不変性のパターンを実装するにはどうすればよいのでしょうか。

まず、配列の要素やオブジェクトのプロパティが変更されるのを防ぎたいときは、静的メソッドObject.freeze() を使用できます。

オブジェクトを凍結すると、拡張ができなくなり、既存のプロパティを書き込み不可、設定不可にすることができます。新しいプロパティを追加したり、既存のプロパティを削除したり、列挙可能性、設定可能性、書き込み可能性、値を変更したり、オブジェクトのプロトタイプを再割り当てすることはできません。freeze()は渡されたものと同じオブジェクトを返します。

プロパティを追加、変更、削除しようとすると、TypeErrorを投げて(またはそれが明示されることなく)処理が失敗します。これは、特にstrict(厳格)モードでよく見られる事象です。

Object.freeze()は以下のように使用することができます。

'use strict'
// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};
// Freeze the object
Object.freeze(post);

そして、プロパティを追加しようとすると、Uncaught TypeErrorが返されます。

// Add a new property
post.slug = "javascript-is-awesome"; // Uncaught TypeError
Chromeのエラー(Uncaught TypeError: can’t define property “slug”: Object is not extensible )
Chromeのエラー(Uncaught TypeError: can’t define property “slug”: Object is not extensible )

プロパティを再割り当てしようとすると、別の種類のTypeErrorが発生します。

// Reassign property
post.id = 5; // Uncaught TypeError
読み取り専用のプロパティに再割り当てしようとするとエラーが発生する
読み取り専用のプロパティに再割り当てしようとするとエラーが発生する
Chromeのエラー(Uncaught TypeError: Cannot assign to read only property ‘id’ of object ‘#<Object>’ )
Chromeのエラー(Uncaught TypeError: Cannot assign to read only property ‘id’ of object ‘#<Object>’ )

また、プロパティを削除しようとしても同じです。結果は、TypeErrorになります。

// Delete a property
delete post.excerpt; // Uncaught TypeError
Firefoxのエラー(Uncaught TypeError: property “excerpt” is non-configurable and can’t be deleted)
Firefoxのエラー(Uncaught TypeError: property “excerpt” is non-configurable and can’t be deleted)

テンプレートリテラル

JavaScriptで文字列と式の出力を結合する必要がある場合、通常は加算演算子+を使用します。しかし、加算演算子を使わずに文字列の中に式を含める機能もあります。それが、テンプレートリテラルです。

テンプレートリテラルは、バッククオート(`)で区切られた特殊な文字列です。

テンプレートリテラルには、プレースホルダーを含めることができます。プレースホルダーには、ドル文字と中括弧を使用します。

以下はその例です。

const align = 'left';
console.log(`This string is ${ align }-aligned`);

文字列とプレースホルダーが、デフォルトの関数に渡され、そこで文字列の補間処理により置き換えが実行されます。そして、最終的には連結した1つの文字列になります。デフォルトの関数を独自の関数に置き換えることもできます。

テンプレートリテラルは、以下の用途に使用できます。

複数行の文字列:改行文字がテンプレートリテラルに反映されます。

console.log(`Twinkle, twinkle, little bat!
How I wonder what you’re at!`);

文字列の補間:テンプレートリテラルなしでは、式の出力を文字列と結合するために加算演算子を使用することになります。次の例をご覧ください。

const a = 3;
const b = 7;
console.log("The result of " + a + " + " + b + " is " + (a + b));

ちょっとわかりにくい印象を受けます。そこでテンプレートリテラルを使えば、このコードをもっと読みやすく、メンテナンスしやすい方法で書くことができます。

const a = 3;
const b = 7;
console.log(`The result of ${ a } + ${ b } is ${ a + b }`);

しかし、この2つの構文には違いがあることに留意してください。

テンプレートリテラルは、いくつかの用途に適しています。次の例では、三項演算子を使用して、class属性に値を割り当てています。

const page = 'archive';
console.log(`class=${ page === 'archive' ? 'archive' : 'single' }`);

以下では、簡単な計算を行っています。

const price = 100;
const VAT = 0.22;

console.log(`Total price: ${ (price * (1 + VAT)).toFixed(2) }`);

また、${expression}プレースホルダーの中にテンプレートリテラルを入れてネストすることも可能です(ただし、複雑な文字列構造は読み取りや保守が困難になる可能性があるため、ネストしたテンプレートリテラルの使用は慎重に)。

タグ付きテンプレート:前述のように、文字列の連結を行う独自の関数を定義することも可能です。このようなテンプレートリテラルをタグ付きテンプレートと呼びます。

タグを使用すると、テンプレートリテラルを関数で解析できます。タグ関数の最初の引数には、文字列リテラルの配列を含みます。残りの引数は式に関連付けられます。

タグを使用すると、独自の関数でテンプレートリテラルを解析することができます。この関数の第一引数には、テンプレートリテラルに含まれる文字列の配列が、他の引数には式が入ります。

独自の関数を作成することで、テンプレートの引数に対して任意の操作を行い、操作された文字列を返すことができます。以下は、タグ付きテンプレートリテラルのごく基本的な例です。

const name = "Carlo";
const role = "student";
const organization = "North Pole University";
const age = 25;

function customFunc(strings, ...tags) {
	console.log(strings); // ['My name is ', ", I'm ", ', and I am ', ' at ', '', raw: Array(5)]
	console.log(tags); // ['Carlo', 25, 'student', 'North Pole University']
	let string = '';
	for ( let i = 0; i < strings.length - 1; i++ ){
		console.log(i + "" + strings[i] + "" + tags[i]);
		string += strings[i] + tags[i];
	}
	return string.toUpperCase();
}

const output = customFunc`My name is ${name}, I'm ${age}, and I am ${role} at ${organization}`;
console.log(output);

上のコードは、stringstagsの配列要素を表示し、文字列を大文字にしてからブラウザコンソールに出力しています。

アロー関数

アロー関数は、JavaScriptの無名関数(名前のない関数)に代わるものですが、いくつかの違いや制限があります。

以下の宣言は、すべて有効なアロー関数の例です。

// Arrow function without parameters
const myFunction = () => expression;

// Arrow function with one parameter
const myFunction = param => expression;

// Arrow function with one parameter
const myFunction = (param) => expression;

// Arrow function with more parameters
const myFunction = (param1, param2) => expression;

// Arrow function without parameters
const myFunction = () => {
	statements
}

// Arrow function with one parameter
const myFunction = param => {
	statements
}

// Arrow function with more parameters
const myFunction = (param1, param2) => {
	statements
}

関数に渡すパラメータが1つだけの場合は、丸括弧を省略することができます。2つ以上のパラメータを渡す場合は、括弧で囲む必要があります。以下はその例です。

const render = ( id, title, category ) => `${id}: ${title} - ${category}`;
console.log( render ( 5, 'Hello World!', "JavaScript" ) );

一行のアロー関数は、デフォルトで値を返します。複数行の構文を使用する場合は、手動で値を返す必要があります。

const render = ( id, title, category ) => {
	console.log( `Post title: ${ title }` );
	return `${ id }: ${ title } - ${ category }`;
}
console.log( `Post details: ${ render ( 5, 'Hello World!', "JavaScript" ) }` );

通常の関数とアロー関数の重要な違いとして、アロー関数はthisに対する独自のバインディングを持ちません。アロー関数でthisを使用しようとすると、関数スコープ外に出てしまいます。

アロー関数の詳細な説明と使用例については、mdn web docsも参照してください。

クラス

JavaScriptのクラスは設計図のようなものであり、プロトタイプの継承メカニズムを使用する(プロトタイプオブジェクトから継承する)オブジェクト/インスタンスを作成することができます。

mdn web docsによると、以下の通りです。

継承に関して言えば、JavaScriptにはオブジェクトという1つの構成要素があるだけです。各オブジェクトは、そのプロトタイプと呼ばれる別のオブジェクトへのリンクを保持します。そのプロトタイプオブジェクトは、それ自身のプロトタイプを持ち、nullをプロトタイプとするオブジェクトに到達するまで、そのプロトタイプのオブジェクトを持つことになります。

関数と同様に、クラスを定義する方法は2つあります。

  • クラス式
  • クラス宣言

次の例のように、classを使用して、式の中でクラスを定義することができます。

const Circle = class {
	constructor(radius) {
		this.radius = Number(radius);
	}
	area() {
		return Math.PI * Math.pow(this.radius, 2);
	}
	circumference() {
		return Math.PI * this.radius * 2;
	}
}
console.log('Circumference: ' + new Circle(10).circumference()); // 62.83185307179586
console.log('Area: ' + new Circle(10).area()); // 314.1592653589793

クラスにはボディ(中括弧内のコード)があります。ここでは、コンストラクタやメソッド(クラスメンバーとも呼ばれる)を定義します。クラスのボディは、'strict mode'ディレクティブを使わなくとも、厳格モードで実行されます。

constructorメソッドは、クラスで作成されたオブジェクトの生成と初期化に使用され、クラスがインスタンス化されたときに自動的に実行されます。クラス内でコンストラクタメソッドを定義しない場合、JavaScriptは自動的にデフォルトのコンストラクタを使用します。

クラスは、extends(継承)を使用し拡張することができます。

class Book {
	constructor(title, author) {
		this.booktitle = title;
		this.authorname = author;
	}
	present() {
		return this.booktitle + ' is a great book from ' + this.authorname;
	}
}

class BookDetails extends Book {
	constructor(title, author, cat) {
		super(title, author);
		this.category = cat;
	}
	show() {
		return this.present() + ', it is a ' + this.category + ' book';
	}
}

const bookInfo = new BookDetails("The Fellowship of the Ring", "J. R. R. Tolkien", "Fantasy");
console.log(bookInfo.show());

コンストラクタは、superを使用して、親コンストラクタを呼び出すことができます。super()メソッドに引数を渡すと、この引数は親コンストラクタクラスでも利用できるようになります。

JavaScriptのクラスについての詳しい説明や、いくつかの使用例については、mdn web docsも参照してください。

クラスは、Reactコンポーネントを作成するのによく使われます。通常、独自のクラスを作成することはなく、組み込みのReactクラスを拡張することになります。

Reactのクラスには、Reactの要素を返すrender()メソッドがあります。

class Animal extends React.Component {
	render() {
		return <h2>Hey, I am a {this.props.name}!</h2>;
	}
}

上の例では、Animalがクラスコンポーネントです。

  • コンポーネントの名前は、大文字で始まる必要があります。
  • コンポーネントには、extends React.Componentが必要です。これにより、React.Componentのメソッドにアクセスすることができます。
  • render()メソッドはHTMLを返すので必須です。

クラスコンポーネントを作成したら、ページ上にHTMLをレンダリングすることができます。

const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <Animal name="Rabbit" />;
root.render(element);

下の画像は、ページ上の結果を示しています(CodePenで実際に動作しているところを確認可能)。

シンプルなReactのクラスコンポーネント
シンプルなReactのクラスコンポーネント

ただし、Reactでクラスコンポーネントを使うことは推奨されておらず、コンポーネントを関数として定義することが望ましいとされています。

this

JavaScriptでは、thisは、通常オブジェクト、クラス、関数の内部で使用される一般的なプレースホルダーであり、コンテキストやスコープによって異なる要素を参照します。

thisは、グローバルスコープで使用することができます。ブラウザのコンソールでthisを使うと、結果は次のようになります。

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

Windowオブジェクトのすべてのメソッドとプロパティにアクセスすることができます。そこで、ブラウザのコンソールでthis.location を実行すると、次のような結果が得られます。

Location {ancestorOrigins: DOMStringList, href: 'https://kinsta.com/', origin: 'https://kinsta.com', protocol: 'https:', host: 'kinsta.com', ...}

そして、オブジェクトの中でthisを使用すると、そのオブジェクト自体を参照することになります以下のように、オブジェクトの値をオブジェクト自体のメソッドで参照することが可能です。

const post = { 
	id: 5,
	getSlug: function(){
		return `post-${this.id}`;
	},
	title: 'Awesome post', 
	category: 'JavaScript' 
};
console.log( post.getSlug );

では、thisを関数の中で使ってみましょう。

const useThis = function () {
	return this;
}
console.log( useThis() );

厳密モードでない場合は、次のようになります。

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

しかし、厳密モードを使うと、違う結果になります。

const doSomething = function () {
	'use strict';
	return this;
}
console.log( doSomething() );

ここでは、関数はundefinedを返します。これは、関数内のthisが、その明示的な値を参照しているからです。

では、関数の中でthisを明示的に設定するにはどうすればよいのでしょうか。

まず、手動でプロパティやメソッドを関数に割り当てます。

function doSomething( post ) {
	this.id = post.id;
	this.title = post.title;
	console.log( `${this.id} - ${this.title}` );
}
new doSomething( { id: 5, title: 'Awesome post' } );

callapplybindメソッドや、アロー関数も使うことができます。

const doSomething = function() {
	console.log( `${this.id} - ${this.title}` );
}
doSomething.call( { id: 5, title: 'Awesome post' } );

call()メソッドはどのような関数でも使用でき、その言葉通り、関数を呼び出します。

さらに、call()は、関数で定義されたその他のパラメータを受け付けます。

const doSomething = function( cat ) {
	console.log( `${this.id} - ${this.title} - Category: ${cat}` );
}
doSomething.call( { id: 5, title: 'Awesome post' }, 'JavaScript' );
const doSomething = function( cat1, cat2 ) {
	console.log( `${this.id} - ${this.title} - Categories: ${cat1}, ${cat2}` );
}
doSomething.apply( { id: 5, title: 'Awesome post' }, ['JavaScript', 'React'] );
const post = { id: 5, title: 'Awesome post', category: 'JavaScript' };
const doSomething = function() {
	return `${this.id} - ${this.title} - ${this.category}`;
}
const bindRender = doSomething.bind( post );
console.log( bindRender() );

上述した方法の代わりに、アロー関数も使用できます。

独自のthisを持たないため、アロー関数式は非メソッド関数にのみ使用する必要があります。

このため、アロー関数はイベントハンドラで特に有用です。

これは、「インラインイベントハンドラ属性からコードが呼び出されると、そのthisはリスナーが配置されているDOM要素に設定される」(mdn web docs参照)ためです。

しかし、アロー関数では、次の理由から状況が変わります。

… アロー関数は、アロー関数が定義されているスコープに基づいてthisを設定し、thisの値は、関数の呼び出し方によって変わります。

Reactでイベントハンドラに’this’をバインドする

Reactには、イベントハンドラがコンテキストを失わないようにする方法がいくつかあります。

1. renderメソッド内でbind()を使用する

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage.bind( this ) }>Show message from state!</button> );
	}
}
export default MyComponent;

2. コンストラクタでイベントハンドラにコンテキストをバインドする

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	constructor(props) {
		super(props);
		this.showMessage = this.showMessage.bind( this );
	}

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage }>Show message from state!</button> );
	}
}
export default MyComponent;

3. アロー関数を用いてイベントハンドラを定義する

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage = () => {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={this.showMessage}>Show message from state!</button> );
	}
}
export default MyComponent;

4. レンダーメソッドでアロー関数を使用する

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage() {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={()=>{this.showMessage()}}>Show message from state!</button> );
	}
}
export default MyComponent;

どの方法を選択しても、ボタンをクリックすると、ブラウザコンソールに次のような出力が表示されます。

This refers to:  MyComponent {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}
The message is:  Hello World!

三項演算子

条件演算子(または三項演算子)を使って、JavaScriptで簡単な条件式を記述することができます。3つの被演算子をとります。

  • 条件─その後にはてなマーク(?)が続く
  • 条件がであった場合に実行する式─その後にセミコロン(:)が続く
  • 条件がの場合に実行される2番目の式
const drink = personAge >= 18 ? "Wine" : "Juice";

また、以下のように複数の式を連結することも可能です。

const drink = personAge >= 18 ? "Wine" : personAge >= 6 ? "Juice" : "Milk";

しかし、複数の式を連鎖させることは、保守が困難な乱雑なコードにつながる可能性があるので注意が必要です。

三項演算子は、React、特に中括弧内の式しか受け付けないJSXのコードで特に有用です。

たとえば、三項演算子を使うと、特定の条件に基づいて属性の値を設定することができます。

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/...',
		description: 'Content Writer',
		theme: 'light'
	}

	return (
		<div
			className='card' 
			style={
				person.theme === 'dark' ? 
				{ background: 'black', color: 'white' } : 
				{ background: 'white', color: 'black'} 
			}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

上記のコードでは、コンテナdivstyle 属性の値を設定するために、person.theme === 'dark'という条件をチェックしています。

短絡評価

AND (&&) 演算子は、被演算子を左から右に評価し、すべての被演算子がtrueである場合にのみ、trueを返します。

AND演算子は短絡演算子です。各被演算子はブール値に変換され、変換結果がfalseの場合、AND演算子は停止し、falsyな被演算子の元の値を返します。すべての値がtrueであれば、最後の被演算子の元の値を返します。



短絡評価は、特定の条件に基づいてコードのブロックを出力することができるため、Reactでよく使用されるJavaScriptの機能です。以下の例をご覧ください。

{
	displayExcerpt &&
	post.excerpt.rendered && (
		<p>
			<RawHTML>
				{ post.excerpt.rendered }
			</RawHTML>
		</p>
	)
}

上記のコードで、displayExcerptpost.excerpt.renderedtrueとして評価されると、ReactはJSXの最終ブロックを返します。

要約すると「条件がtrueの場合、&&の直後の要素が出力に表示されます。falseの場合は、Reactはそれを無視しスキップ」します。

スプレッド構文

JavaScriptでは、配列やオブジェクトなどの反復可能な要素を、関数引数、配列リテラル、オブジェクトリテラルに展開することができるスプレッド構文があります。

次の例では、関数呼び出しの中で配列を展開しています。

function doSomething( x, y, z ){
	return `First: ${x} - Second: ${y} - Third: ${z} - Sum: ${x+y+z}`;
}
const numbers = [3, 4, 7];
console.log( doSomething( ...numbers ) );

配列の複製(多次元配列も可)や配列の連結には、スプレッド構文を使用することができます。以下の例では、2つの配列を2つの異なる方法で連結しています。

const firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray.push( ...secondArray );
console.log( firstArray );

別の方法は以下の通りです。

let firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray = [ ...firstArray, ...secondArray];
console.log( firstArray );

スプレッド構文を使用して、2つのオブジェクトを複製または結合することもできます。

const firstObj = { id: '1', title: 'JS is awesome' };
const secondObj = { cat: 'React', description: 'React is easy' };

// clone object
const thirdObj = { ...firstObj };

// merge objects
const fourthObj = { ...firstObj, ...secondObj }

console.log( { ...thirdObj } );
console.log( { ...fourthObj } );

分割代入

Reactでよく使われるもう一つの構文が、分割代入です。

次の例では、配列から値を分解しています。

const user = ['Carlo', 'Content writer', 'Kinsta'];
const [name, description, company] = user;
console.log( `${name} is ${description} at ${company}` );

また、オブジェクトを使った分割代入の簡単な例を示します。

const user = {
	name: 'Carlo',
	description: 'Content writer',
	company: 'Kinsta'
}
const { name, description, company } = user;
console.log( `${name} is ${description} at ${company}` );

これにとどまらず、多くのことを行うことができます。次の例では、あるオブジェクトのいくつかのプロパティを取り出し、残りのプロパティをスプレッド構文を使用し別のオブジェクトに割り当てます。

const user = {
	name: 'Carlo',
	family: 'Daniele',
	description: 'Content writer',
	company: 'Kinsta',
	power: 'swimming'
}
const { name, description, company, ...rest } = user;
console.log( rest ); // {family: 'Daniele', power: 'swimming'}

また、配列に値を代入することもできます。

const user = [];
const object = { name: 'Carlo', company: 'Kinsta' };
( { name: user[0], company: user[1] } = object );
console.log( user ); // (2) ['Carlo', 'Kinsta']

なお、宣言を伴わないオブジェクトリテラルによる分割代入の場合は、代入文の周りに括弧が必要になります。

分割代入の詳細な説明と使用例については、mdn web docsを参照してください。

filter(), map(), reduce()

JavaScriptには、Reactでよく使われる便利なメソッドがいくつかあります。

filter()

次の例では、numbersの配列にフィルタを適用して、要素が5より大きい数値である配列を取得しています。

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.filter( number => number > 5);
console.log(result); // (4) [6, 8, 9, 23]

次の例では、タイトルに「JavaScript」という単語が含まれる投稿の配列を取得しています。

const posts = [
	{id: 0, title: 'JavaScript is awesome', content: 'your content'},
	{id: 1, title: 'WordPress is easy', content: 'your content'},
	{id: 2, title: 'React is cool', content: 'your content'},
	{id: 3, title: 'With JavaScript to the moon', content: 'your content'},
];

const jsPosts = posts.filter( post => post.title.includes( 'JavaScript' ) );

console.log( jsPosts );
タイトルに'JavaScript'が含まれる投稿の配列
タイトルに’JavaScript’が含まれる投稿の配列

map()

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.map( number => number * 5 );
console.log(result); // (7) [10, 30, 40, 10, 25, 45, 115]

Reactコンポーネントでは、map()メソッドがリストを構築するために使用されることがよくあります。次の例では、WordPressのpostsオブジェクトを対象にして、投稿のリストを構築しています。

<ul>
	{ posts && posts.map( ( post ) => {
		return (
			<li key={ post.id }>
				<h5>
					<a href={ post.link }>
						{ 
							post.title.rendered ? 
							post.title.rendered :
							__( 'Default title', 'author-plugin' )
						}
					</a>
				</h5>
			</li>
		)
	})}
</ul>

reduce()

reduce()は、2つのパラメータを受け取ります。

  • 配列の各要素に対して実行されるコールバック関数:この関数は、次の呼び出しで蓄積パラメータの値となる値を返します。最後の呼び出しで、この関数からはreduce()の戻り値となる値が返されます。
  • コールバック関数に渡される蓄積の最初の値である初期値

コールバック関数はいくつかのパラメータをとります。

  • アキュムレータ:コールバック関数を前回呼び出したときに返された値です。最初の呼び出しでは、指定された場合、初期値に設定されます。そうでない場合は、配列の最初の項目の値をとります。
  • 現在の要素の値:初期値が設定されている場合は配列の1番目の要素(array[0])、それ以外の場合は2番目の要素(array[1])の値が設定されます。
  • 現在のインデックス:現在の要素のインデックス位置を指します。

これを理解するために、例を挙げます。

const numbers = [1, 2, 3, 4, 5];
const initialValue = 0;
const sumElements = numbers.reduce(
	( accumulator, currentValue ) => accumulator + currentValue,
	initialValue
);
console.log( numbers ); // (5) [1, 2, 3, 4, 5]
console.log( sumElements ); // 15

各反復で何が起こるか、詳しく調べてみましょう。前の例に戻って、initialValueを変更します。

const numbers = [1, 2, 3, 4, 5];
const initialValue = 5;
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log('Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index);
		return accumulator + currentValue;
	},
	initialValue
);
console.log( sumElements );

次の画像は、ブラウザのコンソールに出力された結果です。

初期値を5に設定しreduce()を使用
初期値を5に設定しreduce()を使用

では、initialValueパラメータを指定しない場合、どうなるかを見てみましょう。

const numbers = [1, 2, 3, 4, 5];
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log( 'Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index );
		return accumulator + currentValue;
	}
);
console.log( sumElements );
初期値なしでreduce()を使用する
初期値なしでreduce()を使用する

その他の使用例については、mdn web docsをご覧ください。

エクスポートとインポート

ECMAScript 2015 (ES6) の時点で、JavaScript モジュールから値をエクスポートし、別のスクリプトにインポートすることが可能です。Reactアプリケーションでは、インポートとエクスポートを広範囲に使用することになるため、その仕組みをよく理解しておくことが重要です。

次のコードでは、機能的なコンポーネントを作成しています。最初の行で、Reactライブラリをインポートしています。

import React from 'react';

function MyComponent() {
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: 'dark'
	}
 
	return (
		<div
			className = 'card'
			style = {
				person.theme === 'dark' ?
				{ background: 'black', color: 'white' } :
				{ background: 'white', color: 'black'}
			}>
			<img
				src = { person.avatar }
				alt = { person.name }
				style = { { width: '100%' } }
			/>
			<div
				style = { { padding: '2px 16px' } }
			>
				<h3>{ person.name }</h3>
				<p>{ person.description }.</p>
			</div>
		</div>
	);
}
export default MyComponent;

上の例では、importの後に、インポートするものに割り当てたい名前を付け、その後にpackage.jsonファイルで参照されているインストールしたいパッケージの名前を付けています。

上記のMyComponent()関数では、前のセクションで説明した JavaScriptの機能のいくつかを使用しています。中括弧の中にプロパティ値を含め、条件演算子の構文を使ってstyleプロパティの値を代入しています。

最後にカスタムコンポーネントのエクスポートを行っています。

インポートとエクスポートについて大枠を理解したところで、その仕組みについて詳しく見ていきましょう。

Export

すべてのReactモジュールで、2種類のエクスポートが利用できます。それが、名前付きエクスポートデフォルトエクスポートです。

例えば、1つのexport文で複数の機能を一度にエクスポートすることができます。

export { MyComponent, MyVariable };

また、個々の機能をエクスポートすることもできます (functionclassconstlet)。

export function MyComponent() { ... };
export let myVariable = x + y;

しかし、デフォルトのエクスポートは1つしかできません。

export default MyComponent;

また、個々の機能に対してデフォルトのエクスポートを使用することも可能です。

export default function() { ... }
export default class { ... }

Import

一度エクスポートされたコンポーネントは、他のモジュールと一緒に別のファイル、例えばindex.jsファイルにインポートすることができます。

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import MyComponent from './MyComponent';

const root = ReactDOM.createRoot( document.getElementById( 'root' ) );
root.render(
	<React.StrictMode>
		<MyComponent />
	</React.StrictMode>
);

上のコードでは、importをいくつかの方法で使用しています。

最初の2行では、インポートするリソースに名前を付け、3行目では名前を付けず、単に./index.cssファイルをインポートしています。最後のimport文では、./MyComponentファイルをインポートして名前を割り当てています。

それでは、これらのインポートの違いを確認してみましょう。

インポートには全部で4種類あります。

名前付きインポート

import { MyFunction, MyVariable } from "./my-module";

デフォルトインポート

import MyComponent from "./MyComponent";

namespaceインポート

import * as name from "my-module";

side effectインポート

import "module-name";

index.cssにスタイルを適用すると、以下の画像のように(対応するHTMLコードとあわせて)カードが表示されます。

シンプルなReactコンポーネント
シンプルなReactコンポーネント

importは、トップレベルのモジュールでのみ使用できることに注意してください(関数やクラスなどの内部では使用できません)。

importexportについてのより包括的な説明については、以下のリソースも参照してください。

まとめ

Reactは現在注目のJavaScriptライブラリであり、ウェブ開発の世界で最も求められるスキルの1つです。

Reactを使えば、ダイナミックなウェブアプリケーションや高度なインターフェースを構築することが可能です。再利用可能なコンポーネントを武器に、大規模で動的かつインタラクティブなアプリケーションを簡単に作成することができます。

とは言え、ReactはJavaScriptライブラリであり、まずはJavaScriptの主な機能をよく理解することが不可欠です。そこで、Reactでよく使われるJavaScriptの機能をまとめてご紹介しました。これをマスターすることで、React学習の基礎が固められるはずです。

また、結果として、ウェブ開発におけるJS/ReactからWordPressへの移行も、スムースに進めることができます。

Reactの開発で便利なJavaScriptの機能は何だと思いますか?記事で紹介されているものの他に付け加えるとしたら?以下のコメント欄でお聞かせください。

Carlo Daniele Kinsta

ウェブデザインとフロントエンド開発をこよなく愛し、WordPress歴は10年以上。イタリアおよびヨーロッパの大学や教育機関とも共同研究を行う。WordPressに関する記事を何十件も執筆しており、イタリア国内外のウェブサイトや雑誌に掲載されている。詳しい仕事情報はXとLinkedInで公開中。