関数を値として扱う

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()(括弧あり)
"こんにちは"

greetfunction、つまり関数そのものです。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)あとで runTwice が呼んでくれるsay() say()✕ 先に実行して、結果を渡すrunTwice(say())渡る前に実行が終わっているundefined が渡る
括弧をつけた時点で実行される。渡っているのは関数ではなく結果

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();