Last active
February 10, 2025 03:37
-
-
Save kawaz/ec938f21d9b937c09900c48b4400ab48 to your computer and use it in GitHub Desktop.
React等が利用されているとinputやtextareaのvalueに値をセットしてもWEBアプリ側に変更が伝わらないことがあるが、それを上手くやる。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* React等が利用されているサイトでは value プロパティに値をセットしてもWEBアプリに変更が伝わらないことがあるが、それを上手くやる。 | |
* valueやcheckedプロパティを変更する際はフレームワーク側で差し替えられてるケースの対応として、ネイティブパーツのsetterを直接使って値を書き換えるのが確実 | |
* | |
* @param {string | Element | Array<string | Element>} target - 値を設定する対象の要素。文字列(セレクタ)、Element、またはそれらの配列 | |
* @param {string | boolean} value - 設定する値。input/textareaの場合は文字列、checkboxの場合は真偽値でもOK | |
* @param {boolean} [immediate=false] - trueの場合は即座に値を更新、falseの場合は次のイベントループで更新(特別な理由がない限りはfalseでOK) | |
*/ | |
function setValue( | |
target, | |
value, | |
immediate = false | |
) { | |
if (Array.isArray(target)) { | |
for (const t of target) { | |
setValue(t, value, immediate) | |
} | |
return | |
} | |
if (typeof target === 'string') { | |
setValue([...document.querySelectorAll(target)], value, immediate) | |
return | |
} | |
if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) { | |
return | |
} | |
const f = () => { | |
let changed = false | |
switch (target.type) { | |
case 'radio': | |
case 'checkbox': { | |
if(! (target instanceof HTMLInputElement)) { | |
return | |
} | |
const newCheckedValue = (typeof value === 'boolean') ? value : target.value == value | |
// 現在の checked 状態と異なる場合のみ更新 | |
if (target.checked !== newCheckedValue) { | |
const checkedDescriptor = Object.getOwnPropertyDescriptor( | |
Object.getPrototypeOf(target), | |
"checked" | |
) | |
if (checkedDescriptor?.set) { | |
checkedDescriptor.set.call(target, newCheckedValue) | |
} else { | |
target.checked = newCheckedValue | |
} | |
changed = true | |
} | |
break | |
} | |
default: { | |
// 現在の value と異なる場合のみ更新 | |
if (target.value !== value) { | |
const valueDescriptor = Object.getOwnPropertyDescriptor( | |
Object.getPrototypeOf(target), | |
"value" | |
) | |
if (valueDescriptor?.set) { | |
valueDescriptor.set.call(target, value) | |
} else { | |
target.value = value | |
} | |
changed = true | |
} | |
} | |
} | |
// 値が実際に変更された場合のみイベントを発火 | |
if (changed) { | |
target.dispatchEvent(Object.assign(new Event('input', { bubbles: true }), { simulated: true })) | |
target.dispatchEvent(Object.assign(new Event('change', { bubbles: true }), { simulated: true })) | |
target.dispatchEvent(Object.assign(new Event('blur', { bubbles: true }), { simulated: true })) | |
} | |
} | |
// イベントリスナ内から入れ子でイベントを発生さてしまうことを避けるため、 | |
// 特別理由がなければ次のイベントループで実行させる。 | |
if (immediate) { | |
f() | |
} else { | |
setTimeout(f, 0) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* React等が利用されているサイトでは value プロパティに値をセットしてもWEBアプリに変更が伝わらないことがあるが、それを上手くやる。 | |
* valueやcheckedプロパティを変更する際はフレームワーク側で差し替えられてるケースの対応として、ネイティブパーツのsetterを直接使って値を書き換えるのが確実 | |
* | |
* @param {string | Element | Array<string | Element>} target - 値を設定する対象の要素。文字列(セレクタ)、Element、またはそれらの配列 | |
* @param {string | boolean} value - 設定する値。input/textareaの場合は文字列、checkboxの場合は真偽値でもOK | |
* @param {boolean} [immediate=false] - trueの場合は即座に値を更新、falseの場合は次のイベントループで更新(特別な理由がない限りはfalseでOK) | |
*/ | |
export function setValue( | |
target: string | Element | Array<string | Element>, | |
value: string | boolean, | |
immediate = false | |
): void { | |
if (Array.isArray(target)) { | |
for (const t of target) { | |
setValue(t, value, immediate) | |
} | |
return | |
} | |
if (typeof target === 'string') { | |
setValue([...document.querySelectorAll(target)], value, immediate) | |
return | |
} | |
if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) { | |
return | |
} | |
const f = () => { | |
let changed = false | |
switch (target.type) { | |
case 'radio': | |
case 'checkbox': { | |
if(! (target instanceof HTMLInputElement)) { | |
return | |
} | |
const newCheckedValue = (typeof value === 'boolean') ? value : target.value == value | |
// 現在の checked 状態と異なる場合のみ更新 | |
if (target.checked !== newCheckedValue) { | |
const checkedDescriptor = Object.getOwnPropertyDescriptor( | |
Object.getPrototypeOf(target), | |
"checked" | |
) | |
if (checkedDescriptor?.set) { | |
checkedDescriptor.set.call(target, newCheckedValue) | |
} else { | |
target.checked = newCheckedValue | |
} | |
changed = true | |
} | |
break | |
} | |
default: { | |
// 現在の value と異なる場合のみ更新 | |
if (target.value !== value) { | |
const valueDescriptor = Object.getOwnPropertyDescriptor( | |
Object.getPrototypeOf(target), | |
"value" | |
) | |
if (valueDescriptor?.set) { | |
valueDescriptor.set.call(target, value) | |
} else { | |
target.value = value as string | |
} | |
changed = true | |
} | |
} | |
} | |
// 値が実際に変更された場合のみイベントを発火 | |
if (changed) { | |
target.dispatchEvent(Object.assign(new Event('input', { bubbles: true }), { simulated: true })) | |
target.dispatchEvent(Object.assign(new Event('change', { bubbles: true }), { simulated: true })) | |
target.dispatchEvent(Object.assign(new Event('blur', { bubbles: true }), { simulated: true })) | |
} | |
} | |
// イベントリスナ内から入れ子でイベントを発生さてしまうことを避けるため、 | |
// 特別理由がなければ次のイベントループで実行させる。 | |
if (immediate) { | |
f() | |
} else { | |
setTimeout(f, 0) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ネイティブセッターを使う方法で問題なければそちらの方が良いが一応メモとして別のやり方も残しておく | |
// Reactの内部実装スペシャル対応の謎更新手順 | |
if(target._valueTracker && target._valueTracker.setValue) { | |
const oldValue = target.value | |
target.value = value | |
target._valueTracker.setValue(oldValue) | |
} else { | |
target.value = value | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment