useState Tips
- useStateの初期値は初回レンダリング時にのみ行われる。以降の更新では実行されない
- 常に新しい値で演算
conste handleClick = () => { setCount(c=>c+1); setCount(c=>c+1); }; - (要確認)子コンポーネントで更新するとき、親コンポーネントからset関数を直接渡すのは避ける
オブジェクトを変更
例えば、配列[a,b,c,d] ※a,b,c,dはオブジェクト
という状態を管理してるとする
このとき、bを新しいオブジェクトに更新するときは
- 配列自体を新しく作る
- bを新しく作る。他のa,c,dは同じでOK
- 上記で作ったオブジェクトを設定する
ネストの深いプロパティを変更するときは、深いコピーが必要。スプレッド構文は浅いコピーなので注意。
なので、状態は極力フラットにする!かImmerライブラリを使う
NG
const [user, setUser] = useState({
name: 'Alice',
address: {
city: 'Tokyo',
},
});
function updateCity() {
const newUser = { ...user }; // 浅いコピー
newUser.address.city = 'Osaka'; // ネストされたオブジェクトを変更
setUser(newUser); // userオブジェクトの参照は変わっていないため、再レンダリングされない!
}
OK
const [user, setUser] = useState({
name: 'Alice',
address: {
city: 'Tokyo',
},
});
function updateCity() {
const newUser = {
...user, // userオブジェクトをコピー
address: {
...user.address, // addressオブジェクトもコピー
city: 'Osaka', // cityプロパティを更新
},
};
setUser(newUser); // newUserオブジェクトは新しい参照を持つため、再レンダリングされる
}
useRef Tips
- コアなイベント登録ができる
const btnRef = useRef(null);
useEffect(()=>{
const btn = btnRef.current;
btn.addEventListener('click',handleClick,{once:true});
return (()=>{
// コンポーネント破棄時に明示的に除去する必要あり
btn.removeEventListener('click',handleClick,{once:true});
})
},[])
return <button ref={btnRef}>ボタン</button>
- Uncontrolled Inputに使える
useStateを使ったControlled Inputは入力するたびに再レンダリングが行われる。
入力フォーム自身はユーザがキーボードで入力するので、再レンダリングいらなくない?って時に使える。
バリデーションなど、リアルタイムに対応するのに不向き
デフォルト値はdefaultValueやdefaultCheckedを使う。valueやcheckedなどを使うと、固定されてしまう。
import {useRef} from 'react';
export default function Sample() {
const name = useRef(null);
const onClick = () => console.log(`Hello ${name.current.value}`);
return (
<form>
<div>
<input id="name" name="name" type="text" ref={name}/>
</div>
</form>
)
}
ファイル入力ボックスは、再レンダリング不要なのでUncontrolled InputでOK
useId Tips
タグのidをハードコーディングするのは、重複する可能性があるので好ましくない
import {useId} from 'react';
export default function Sample() {
const id = useId(); // コンポーネント毎のid
return (<>
<div id={`${id}-sample1`}>sample</div>
<div id={`${id}-sample2`}>sample2</div>
</>)
}
複数のReactアプリを動かすと、アプリ間で重複があるかもなのでプレフィックスをつける
// アプリAをid="root-a"の要素にマウント
const rootA = ReactDOM.createRoot(document.getElementById('root-a'),{identifierPrefix: 'root-a-'});
rootA.render(<AppA />);
// アプリBをid="root-b"の要素にマウント
const rootB = ReactDOM.createRoot(document.getElementById('root-b'), {identifierPrefix: 'root-b-'});
rootB.render(<AppB />);
再描画タイミング
- State更新
- 渡されたPropsの変更。渡されたコンポーネントがPropsを変更するのはご法度
- 親コンポーネントの再描画
リストのキー
- リストなどはkey属性がないと、Reactは配列の変更を検知できない
- <></>ではキー設定できないので、<Fragment>を使う
- indexはリストの追加削除ソートで変わるので、使わない。なければUUIDとか使うといいかも
- key属性が必要となるのは、map呼び出しの中に現れる要素に対して。子コンポーネント内ではない
イベントオブジェクト
- ReactでのイベントオブジェクトはJavascriptのそれではなく、ブラウザ間の仕様差を吸収したSyntheticEvent(合成イベント)
- keyプロパティはイベント発生原因のキーを示す
Ctrl押しっぱなしでqキーは「e.ctrlKey && e.key=="q"」で判定。「e.key == "Control" && e.key == "q"」ではない
その他
JSXの(return (...))は、Reactのレンダリングサイクルに従って毎回最新の値が使われます。
useEffectは「副作用の処理(例:状態の更新やAPI呼び出し)」に使うもので、
JSX内でuseStateの値を参照する場合は、常に最新の値が使われるため、
古い値が使われる心配はありません。
- 副作用はイベントハンドラーに集約する。もしくはuseEffect
- childrenはReact要素の配列なので、配列のメソッドが使える
- reactのonChangeはjsのinputイベントと同じ。jsのchangeイベントはonBlurか直接addEventListenerする