Part 0 · React の前に
関数を値として扱う
「関数を渡す」が分かると onClick が読めるようになる
React でボタンを作ると、必ずこう書きます。
<button onClick={handleClick}>
ここで handleClick のうしろに括弧を付けてしまうと、ボタンを押していないのに勝手に実行されます。 初めて React を書く人がほぼ必ず一度は踏む落とし穴です。
原因は React ではなく JavaScript にあります。この章で、その正体をはっきりさせておきます。
関数は「まとめた処理」に名前をつけたもの
何度も使う処理は、まとめて名前をつけておけます。それが関数です。
// 昔からある書き方
function greet() {
return "こんにちは";
}
// 同じことを短く書いたもの(アロー関数)
const greet = () => "こんにちは";下の書き方をアロー関数と呼びます。=> が矢印に見えることからこの名前が付いています。 React のコードではこちらが主役なので、見慣れておいてください。
注目してほしいのは、アロー関数が const greet = ... という形をしていることです。これは前の章でやった変数そのものです。 つまり関数も、変数に入れられる「値」のひとつなのです。
function-value.ts
// 関数を作って、greet という名前をつける
export const greet = () => "こんにちは";
// 名前だけ書くと「関数そのもの」
export const theFunction = greet;
// 括弧をつけると「実行した結果」
export const theResult = greet();括弧をつけると「実行」になる
関数が値だとすると、名前だけ書いたときは何が起きるのでしょうか。
greet… 関数そのものgreet()… 関数を実行した結果
この 2 つはまったく別の値です。実際に中身を見てみると、はっきり違います。
片方は関数、もう片方は文字列になっている
- greet(括弧なし)
- function
- greet()(括弧あり)
- "こんにちは"
greet は function、つまり関数そのものです。greet() はすでに実行が終わっていて、残っているのは その結果である文字列だけです。
function-value.ts
// 関数を作って、greet という名前をつける
export const greet = () => "こんにちは";
// 名前だけ書くと「関数そのもの」
export const theFunction = greet;
// 括弧をつけると「実行した結果」
export const theResult = greet();関数は、他の関数に渡せる
関数が値なら、他の関数に引数として渡すこともできます。 これが React を読むうえで欠かせない感覚です。
say を渡しただけなのに、2 回実行されている
記録された内容
- 1. よばれました
- 2. よばれました
runTwice(say) と書いた時点では、say はまだ一度も実行されていません。 渡した先の runTwice が、好きなタイミングで2 回呼んでいるだけです。
ここで括弧を付けてしまうと、意味がまるごと変わります。
runTwice(say()) と書くと、say() がその場で 1 回だけ実行され、その結果 (この関数は何も返さないので undefined)が渡ります。runTwice は関数ではないものを受け取ってしまい、 呼ぼうとしてエラーになります。
pass-function.ts
export const logs: string[] = [];
// 呼ばれたら記録するだけの関数
const say = () => {
logs.push("よばれました");
};
// 関数を受け取って、それを 2 回呼ぶ関数
const runTwice = (fn: () => void) => {
fn();
fn();
};
// 関数そのものを渡す。ここで括弧をつけると、
// 渡すより先に実行されてしまい、渡るのは実行結果になる
runTwice(say);これが onClick の正体
最初に出した React のコードに戻ります。
// ○ 関数を渡している。押されたときに React が呼んでくれる
<button onClick={handleClick}>押す</button>
// ✕ その場で実行して、結果を渡している
<button onClick={handleClick()}>押す</button>onClick は「押されたときに呼んでほしい関数」を受け取る場所です。runTwice がそうだったのと、まったく同じ仕組みです。
引数を渡したいときは
「押されたときに handleClick(1) を呼びたい」という場合はどうするか。 括弧を付けたら、その場で実行されてしまいます。
こういうときは、それを実行するだけの新しい関数を作って渡します。
<button onClick={() => handleClick(1)}>押す</button>渡しているのは () => handleClick(1) という関数です。 この関数が呼ばれたときに、はじめて handleClick(1) が実行されます。
pass-function.ts
export const logs: string[] = [];
// 呼ばれたら記録するだけの関数
const say = () => {
logs.push("よばれました");
};
// 関数を受け取って、それを 2 回呼ぶ関数
const runTwice = (fn: () => void) => {
fn();
fn();
};
// 関数そのものを渡す。ここで括弧をつけると、
// 渡すより先に実行されてしまい、渡るのは実行結果になる
runTwice(say);理解できたか確かめる
確認クイズ
const greet = () => 'こんにちは'; と書いたとき、greet と greet() の違いは?
確認クイズ
ボタンを押したときに handleClick(1) を呼びたい。正しいのはどれ?
function-value.ts
// 関数を作って、greet という名前をつける
export const greet = () => "こんにちは";
// 名前だけ書くと「関数そのもの」
export const theFunction = greet;
// 括弧をつけると「実行した結果」
export const theResult = greet();この章のまとめ
- 関数も値のひとつで、変数に入れたり他の関数に渡したりできる
greetは関数そのもの、greet()は実行した結果- 括弧は「いま実行しろ」の合図。付けるかどうかで意味が変わる
onClickには関数を渡す。引数が必要なら() => f(1)の形で包む
function-value.ts
// 関数を作って、greet という名前をつける
export const greet = () => "こんにちは";
// 名前だけ書くと「関数そのもの」
export const theFunction = greet;
// 括弧をつけると「実行した結果」
export const theResult = greet();