Next.js サーバーアクションの bind の挙動を整理する
Next.js のサーバーアクションを使っていて、bind の書き方がよくわからないと感じることがありました。
特に「引数はどう渡るのか」「null って何なのか」が最初はつかみにくかったです。
この記事ではそれを順番に整理します。
サーバーアクションに引数を渡す方法
サーバーアクションをフォームに渡すとき、次のように bind を使って引数を埋め込めます。
<form action={updateUser.bind(null, userId)}>
<input name="name" />
<button type="submit">Save</button>
</form>
サーバー側の関数はこう書けます。
'use server';
async function updateUser(userId: string, formData: FormData) {
const name = formData.get('name')?.toString();
// userId は bind で渡した値
}
このときの引数の順番は以下の通りです。
- bind で渡した値が前から順に入る
- フォームの入力値は FormData として最後に入る
つまり updateUser(userId, formData) という形になります。
複数引数を bind した場合
複数渡したいときはそのまま列挙します。
<form action={updateUser.bind(null, userId, role)}>
...
</form>
'use server';
async function updateUser(userId: string, role: string, formData: FormData) {
...
}
結果的に (userId, role, formData) という並びになります。
bind を繰り返した場合
bind は通常の JavaScript の仕様通り、あとから bind した引数がさらに前に積まれます。
function action(a, b, formData) {}
const a1 = action.bind(null, 'X');
const a2 = a1.bind(null, 'Y');
// a2 をフォームに渡すと...
// 引数は (Y, X, formData) になる
「前置して積み重なる」と覚えておくとわかりやすいです。
bind の第1引数は何?
bind のシグネチャは次のようになっています。
func.bind(thisArg, ...args)
最初の thisArg は「関数内での this を何にするか」を決めるものです。
ただし、サーバーアクションは普通の関数で、this を参照しないので実際には意味がありません。
そのため慣習的に null を書いています。undefined でも問題ありません。
<form action={updateUser.bind(null, userId)}>
この null は「this は使わないので空でいい」という意味です。
オブジェクトのメソッドに bind するときは注意
通常の関数なら null で良いですが、もしオブジェクトのメソッドを取り出して使う場合は話が変わります。
const obj = {
x: 42,
getX() { return this.x; }
};
obj.getX(); // 42
obj.getX.bind(obj)(); // 42
obj.getX.bind(null)(); // ❌ this が null なのでエラー
このように this を使うメソッドは、正しくオブジェクトを渡す必要があります。
サーバーアクションではこのケースはほぼないので気にしなくて大丈夫です。
まとめ
- サーバーアクションの引数は
bindしたものが前から入り、フォーム入力は最後のFormDataになる -
bindを繰り返すとあとから渡したものがさらに前に積まれる - 第1引数の
nullはthisArgで、サーバーアクションでは不要なのでnullで良い - オブジェクトメソッドに
bindするときだけはnullではなくそのオブジェクトを渡す必要がある
これで「bind の挙動がいまいちわからない」という疑問は解消できると思います。
私も最初は FormData がどこに入るのか、null は何なのか、混乱したので記事にまとめました。
参考
Discussion