(1)まず、{...form}
は、オブジェクトリテラルでのスプレッド構文です。(参照)
オブジェクトを表す{}
の中で...form
と書くことで、formオブジェクトのキー(プロパティ名)と値の組が展開されます。
{}
の中に、カンマで区切って、他のプロパティ名:値の組を書くことも可能です。
またスプレッド構文を書くことも可能です。
展開した結果プロパティ名が重複する場合は、マージされ、値は上書きされます。
例:
js
1let obj1 = { foo: "bar", x: 42 };
2let obj2 = { ...obj1, y: 13 }; // obj1 のプロパティ名と値の組が展開される。
3console.log(obj2);
4>>> { foo: "bar", x: 42, y: 13 }
5
6let obj3 = {x: 50};
7let obj4 = {...obj2, ...obj3}; // スプレッド構文を並べて書ける。それぞれのプロパティ名と値の組が展開される。
8console.log(obj4);
9>>> { foo: "bar", x: 50, y: 13 } // 展開した結果、x が重複するのでマージされ、値は50になる。
(2)次に、[e.target.name]: e.target.checked
の部分ですが、これは前半でプロパティ名を式で表現しているものです。(参照:計算プロパティ名)
たとえば、e.target.name
が「agreement」、
e.target.checked
が「false」ならば、
{[e.target.name]: e.target.checked}
は
{agreement : false}
というオブジェクトとして評価されます。
なぜ[ ]でプロパティ名を囲むかというと、そうしないと識別子であるプロパティ名にピリオドが含まれていると解釈されて、構文エラーになるからです。
({'e.target.name': e.target.checked}
と書けばエラーにはなりませんが、これだとプロパティ名が単なる「e.target.name」という文字列になってしまい、式として評価されません)
(3)スプレッド構文を使うと、使われた元のオブジェクトのプロパティ名が、新しいオブジェクトにコピーされます。
(1)~(3)を念頭に置いて元のコードを追ってみます。
js
1 const [form, setForm] = useState({
2 agreement:true
3 });
ここで、form オブジェクトの初期値として、 {agreement: true}がセットされています。
同時にformのステート変更用の関数の名前として、setFormが設定されます。
jsx
1 <input id='agreement' name='agreement' type='checkbox'
2 checked={form.agreement}
3 onChange={handleFormCheck} /><br />
チェックボックスの動作・外観を定義している部分です。
checked
つまりチェック状態を form オブジェクトの agreement プロパティに紐付け、チェックボックスをクリックした時のハンドラを handleFormCheck 関数に設定しています。
js
1const handleFormCheck = e => {
2 setForm({
3 ...form,
4 [e.target.name]: e.target.checked
5 });
6};
チェックボックスをクリックするとこの部分が実行されます(前述 onChange={handleFormCheck} )。
チェックボックスのname属性が「agreement」となっているので、クリックしたときに e.target.name
は「agreement」と評価されます。
また、 e.target.checked
はクリックされた後のチェック状態(チェックが入ればtrue、外されればfalse)になります。
仮に、チェックボックスにチェックが入っている状態でクリックしたと仮定しましょう。
このとき、このコードの部分は下記のように評価されます。
setForm({
...form,
agreement: false // チェックが外されたのでfalseになる。
})
そして、form は もともとagreement というプロパティを持っているので、
setForm({
agreement: true,
agreement: false
})
となり、重複しているプロパティ名 agreement がマージされて、その値は false に上書きされます。
setForm({
agreement: false
})
結局、setFormには、{agreement:false} という新しいオブジェクトが渡されることになります。
この結果、useState に設定していた form という名前のオブジェクトの状態変化が React によって検知され、コンポーネントが再レンダリングされることになります。
再レンダリングの結果、 checked={form.agreement} なので、 checked=false 、つまりチェックが外れた状態で描画されます。
ここで、スプレッド構文を使わず下記のようにすればよいと思われるかもしれません。
const handleFormCheck = e => {
form.agreement = e.target.checked;
setForm(form);
};
しかし、この場合、formオブジェクトを直接変更しているため変更が検知されず、再レンダリングされません。
参照:https://ja.react.dev/learn/updating-objects-in-state
「1つのチェックボックスを切り替えるだけなのになんでこんなめんどくさいことをやってるのだろう」と思われるかもしれません。
たしかに元のコードでは、計算プロパティ名を使わずとも、送信時にチェックボックスの状態を調べれば十分な気もします。
下記のようなコードだと計算プロパティ名の便利さが少しだけわかるかもしれません。
js
1import { useState } from 'react';
2
3export default function FormTextarea() {
4 const [form, setForm] = useState({
5 agreement:true, concent:true
6 });
7
8const handleFormCheck = e => {
9 setForm({
10 ...form,
11 [e.target.name]: e.target.checked
12 });
13};
14
15 const show = () => {
16 console.log(`同意確認:${form.agreement ? '同意': '反対'}`);
17 };
18
19 return (
20 <form>
21 <label htmlFor='agreement'>同意項目:</label>
22 <input id='agreement' name='agreement' type='checkbox'
23 checked={form.agreement}
24 onChange={handleFormCheck} /><br />
25 <p> (同意しているか? {form.agreement ? '同意している': '反対している'})</p>
26
27 <label htmlFor=''>納得感:</label>
28
29 <input id='concent' name='concent' type='checkbox'
30 checked={form.concent}
31 onChange={handleFormCheck} /><br />
32 <p> (納得しているか? {form.concent ? '納得している': '不服である。'})</p>
33 <button type='button' onClick={show}>送信</button>
34 </form>
35 );
36}
この例では、「同意」に加えて「納得」のチェックボックスを追加しています。それぞれ、agreementとconcent という名前を指定しており
各チェックボックスをクリックした結果が、そのチェック状況に応じて表示されます。
handleFormCheck 関数の中身は質問から変えていません。
計算プロパティ名によって自動的にどのチェックボックスがチェックされたのかを判別しています。
(スプレッド構文を使うことの有用性に着目するならば、公式のこの例の方がもっとわかりやすいと思います)
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2023/12/07 00:01