JavaScript調べ物メモ
日付のあれこれ
toISOString
toJSONString
, toISOString
以外はブラウザ依存。
JSON.stringfy
はtoISOString
を使う。Dateの関数はISOStringの書式をパースできる。
日付フォーマットあれこれ
jsには標準でDateを文字列にフォーマットするライブラリがない。
雑にまとめると、現時点では以下の2つから選んでおけばよさそう。
date-fnsのほうがスポンサーとか利用状況からちょっとだけ高評価?
- date-fns
- day.js
2020年12月時点の日付ライブラリ比較記事
JSの日付ライブラリは、どれを使えばいいのかまとめ
setStateしてstateは更新されてるけどDOM再描画されない。
DebugツールのHooks>Stateも更新されない…?
ソース適当に触って強制的に再描画させると反映される。Why...
import React, { FC, useState } from 'react';
interface Family {
name: string;
age: string;
}
const MutableList: FC = () => {
const [families, setFamilies] = useState<Family[]>([]);
const handleAdd = () => {
const len = families.length;
const newFamilies = families;
newFamilies.push({ name: 'xxx', age: len.toString() });
console.log(newFamilies.length);
setFamilies(newFamilies);
};
return (
<div>
<li>
{families.map((x, index) => (
<div key={`key_${index.toString()}`}>
<ul>
<span>{x.name}</span>
<span>{x.age}</span>
</ul>
</div>
))}
</li>
<button type="button" onClick={handleAdd}>
Add
</button>
</div>
);
};
export default MutableList;
setFamilies([{ name: 'xxx', age: '20' }]);
べた書きしたら反映されたのでsetFamiliesに渡してる値が悪いのか。
const newFamily = { name: 'xxx', age: 10 };
setFamilies([...families, newFamily]);
これで解決した。
const newFamilies = families;
newFamilies.push({ name: 'xxx', age: len.toString() });
setFamilies(newFamilies);
この書き方だと結局stateとして持ってるFamily配列にpushしてるのと同じ?
stateの値自体は更新されてるけどsetStateによるDOM再描画が走ってない。
setStateの仕組みがわかってないな。
Reactがrenderの内容を更新してくれなくてハマった - アルゴリズムとかオーダーとか
一つはまりやすい点としてsetStateの引数に渡す値が配列やオブジェクトの場合、中身だけ変更して次回もそのまま渡すと、配列やオブジェクトそのものは同一と判定されてしまい、更新チェックがされずレンダリングが発生しません。
というのがそれっぽいんだけど、ドキュメント見てもそれっぽいこと見つけられない…
...
で展開して追加して、要するに新規オブジェクトとして作って与えればいいのは分かったのだけど、なぜ。
公式だと
useState は自動的な更新オブジェクトのマージを行いません。この動作は関数型の更新形式をスプレッド構文と併用することで再現可能です
とあって、置換しちゃうから追記したければ...
で展開して追加しろとあるけど、同一オブジェクトで置換すること自体には言及してない気がするが、そういうものなのか…
Hooksのソース追った記事
React HooksのuseStateがどういう原理で実現されてるのかさっぱりわからなかったので調べてみた
これを手がかりにjsもReactも怪しいけど、更新処理であるupdateReducer読んでみた。
// Mark that the fiber performed work, but only if the new state is
// different from the current state.
if (newState !== hook.memoizedState) {
markWorkInProgressReceivedUpdate();
}
ここ(newState !== hook.memoizedState
)で前回のオブジェクトと比較して、不一致のときのみ更新ありとみなすのかな。
const handleAdd = () => {
// setStateの更新用関数は同一オブジェクトだと差分なしを見なす。
// スプレッド演算子やコピーなどで新規オブジェクトとして渡す必要がある。
// ドキュメントでは単に「マージではなく置換」とあるが、同一オブジェクトだとDOM再描画されない。
// ※Stateの値自体は更新されるので、何かしら別の要因で再描画されると反映される。
setFamily([...families, { name: '', age: 0 }]);
};
フォームの入力行(名前、年齢)を追加可能にしたフォーム
コンポーネント化すべきとか色々あるけど、フォームとstate制御以外の要素を省くためべた書き。
import React, { FC, useState } from 'react';
interface Family {
name: string;
age: string;
}
const MyForm: FC = () => {
const [families, setFamily] = useState<Family[]>([]);
const handleSubmit = (e: React.MouseEvent) => {
e.preventDefault();
alert(JSON.stringify(families));
};
// フォーム入力イベントハンドラ;
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// nameに連番を付与して、handlerで文字列的に取得してフォーム行数とstateの配列番号を対応付ける
const props = e.target.name.split('__');
const name = props[0];
// eslint-disable-next-line no-eval
const index: number = eval(props[1]);
// もっとスマートな書き方がある気がする...
const newFamilies = [...families];
const family = newFamilies[index];
const newFamily = { ...family, [name]: e.target.value };
newFamilies[index] = newFamily;
setFamily(newFamilies);
};
// フォーム行追加ハンドラ
const handleAdd = () => {
setFamily([...families, { name: '', age: '' }]);
};
return (
<div>
<form>
<table>
<thead>
<tr>
<td>Name</td>
<td>Age</td>
</tr>
</thead>
<tbody>
{families.map((x, index) => (
<tr key={`key_${index.toString()}`}>
<td>
<input
name={`name__${index.toString()}`}
type="text"
value={x.name}
onChange={handleChange}
/>
</td>
<td>
<input
name={`age__${index.toString()}`}
type="number"
value={x.age}
onChange={handleChange}
/>
</td>
</tr>
))}
<tr>
<td>
<button type="button" onClick={handleAdd}>
Add
</button>
</td>
<td>
<button type="button" onClick={handleSubmit}>
Send
</button>
</td>
</tr>
</tbody>
</table>
</form>
</div>
);
};
export default MyForm;