かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript 配列から要素を取り出す slice と splice のメモ

state を扱っていると配列の操作をする機会が多いのですが配列から値を取り出す時の slicesplice 似てる。

Array.prototype.slice

arr.slice([begin[, end]])

slice は取り出す位置 (begin) から終了位置 (end) までの要素を取り出す。
戻り値は取り出された値の配列。
終了位置の要素は含まれず、end が省略さた場合は、begin から配列の最後 (length) までが指定される。
配列は shallow コピーされるので元の配列は変更されない。

e.g.

const arr = ['星宮いちご', '霧矢あおい', '紫吹蘭', '大空あかり', '氷上すみれ', '新条ひなき'];
const akari = arr.slice(3, 4); // => ['大空あかり']
const luminas = arr.slice(3); // => ['大空あかり', '氷上すみれ', '新条ひなき']

// 新しい配列が返されるので元の配列に変化はなし
console.log(arr);
// => ['星宮いちご', '霧矢あおい', '紫吹蘭', '大空あかり', '氷上すみれ', '新条ひなき']

// 引数なしで配列のコピーができる
const clone = arr.slice();
clone.reverse().push('神崎美月');
console.log(clone);
// => ['新条ひなき', '氷上すみれ', '大空あかり', '紫吹蘭', '霧矢あおい', '星宮いちご', '神崎美月']
console.log(arr);
// => ['星宮いちご', '霧矢あおい', '紫吹蘭', '大空あかり', '氷上すみれ', '新条ひなき']
shallow copy なので object などの参照になってる値には注意が必要
const arr = [
  { name: '星宮いちご', type: 'cute' },
  { name: '霧矢あおい', type: 'cool' },
  { name: '紫吹蘭', type: 'sexy' },
  { name: '有栖川おとめ', type: 'pop' },
];

// 配列をコピー
const copy = arr.slice();
// コピーした配列の操作やインデックスの値を丸っと置き換えるのは問題ない
copy.pop();
copy[2] = { '新条ひなき', type: 'pop' };
console.log(copy);
/*
[
  {name: "星宮いちご", type: "cute"},
  {name: "霧矢あおい", type: "cool"},
  {name: "新条ひなき", type: "pop"},
]
*/
console.log(arr);
/*
[
  {name: "星宮いちご", type: "cute"},
  {name: "霧矢あおい", type: "cool"},
  {name: "紫吹蘭", type: "sexy"},
  {name: "有栖川おとめ", type: "pop"},
]
*/

// 配列内のオブジェクトは参照のままなので変更すると元の配列の値に影響がある
copy[0].name = '大空あかり';
copy[1].name = '氷上すみれ';
console.log(copy);
/*
[
  {name: "大空あかり", type: "cute"},
  {name: "新条ひなき", type: "cool"},
  {name: "新条ひなき", type: "pop"},
]
*/
console.log(arr);
/*
[
  {name: "大空あかり", type: "cute"},
  {name: "氷上すみれ", type: "cool"},
  {name: "紫吹蘭", type: "sexy"},
  {name: "有栖川おとめ", type: "pop"},
]
*/

Array.prototype.splice

array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

splice は開始位置 (start)と取り出す個数 (deleteCount) を指定して配列から要素を削除して取り出す。
戻り値は取り出された値の配列で、取り出された値は元の配列から削除される。(破壊的変更)

const arr = ['星宮いちご', '霧矢あおい', '紫吹蘭', '大空あかり', '氷上すみれ', '新条ひなき'];
const luminas = arr.splice(3, 3);
console.log(luminas);
// => ["大空あかり", "氷上すみれ", "新条ひなき"]

// 元の配列も変更される
console.log(arr);
// => ["星宮いちご", "霧矢あおい", "紫吹蘭"]

// 第2引数以降に値を渡すと取り出した位置に値を追加できる
const soleil = arr.splice(0,2, '神崎美月', '藤堂ユリカ');
console.log(soleil);
// => ["星宮いちご", "霧矢あおい"]

console.log(arr);
// => ["神崎美月", "藤堂ユリカ", "紫吹蘭"]

所感

配列な state を変更する際に、元の state を変更せずに要素を取り出すには slice 、値を取り出した残りの配列を使いたい場合は splice を使うのが良さそう。 splice を使う場合も [...state].splice として元の配列をコピーして操作するのが immutable な感じで良いかなーと思います。
それにしても何で slice は終了位置で、 splice は要素数なんだろう。紛らわしくない?


[参考]

確かな力が身につくJavaScript「超」入門 第2版

確かな力が身につくJavaScript「超」入門 第2版

アイカツ!成分が足りない…

JavaScript async / await で同期的に setTimeout (sleep) したい

setTimeout を同期的に使いたい場合、setTimeout は Promise を返さないので async / await を使って呼び出したい場合は Promise を返す関数にしてやればOK

Promise を返し内部で setTimeout する関数にすればOK

const sleep = (callback, delay = 1000) => {
  return new Promise((resolve) => {
    return setTimeout(() => {
      callback();
      return resolve();
    }, delay);
  });
};

async function myFunction() {
  console.log('start');
  await sleep(() => { console.log('timeout!') });
  console.log('end');
}

myFunction();
// => start
// -- 1000ms --
// => timeout!
// => end 

処理が単純なら別関数にしなくても OK

async function myFunction() {
  const callback = () => console.log('timeout!');
  console.log('start');
  await new Promise((resolve) => setTimeout(() => {
    return resolve();
  }, 1000));
  callback(); // setTimeout 内で実行する必要なし
  console.log('end');
}

myFunction() を実行したら同じ結果になる。

カリー化して同期的に setTimeout して処理を実行できる関数を作る

setTimeout 後に実行したい関数を保持させて呼び出し時に引数を渡す関数を作ってみます。

const sleepCallback = (callback, delay = 500) => (...props) => {
  return new Promise((resolve) => setTimeout(() => {
    return resolve( callback(...props) );
  }, delay));
};

const sum = (...props) => props.reduce((a, v) => a += v);
const sleepAfterSum = sleepCallback(sum);

async function myFunction(...props) {
  const a = await sleepAfterSum(...props);
  console.log(a);
}

myFunction(1,2,3,4,5,6,7,8,9,10);
// --- 500ms ---
// => 55

setTimeout で単純に処理を止める関数にしてみる

const sleep = (delay = 500) => new Promise(resolve => setTimeout(resolve, delay));

const sum = (...props) => props.reduce((a, v) => a += v);

async function myFunction(...props) {
  await sleep();
  const a = sum(...props);
  console.log(a);
}

myFunction(1,2,3,4,5,6,7,8,9,10);
// --- 500ms ---
// => 55

この方が汎用性高そう?

setTimeout で debounce に state を変更するサンプル

See the Pen setTimeout with async / await by KIKIKI (@kikiki_kiki) on CodePen.

 
おわり


[参考]

React Warning: A component is changing an uncontrolled input になったとき

React で form を作っていたら次のような Warning が発生しました。

Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component

長い…

React のフォームパーツには controlled と uncontrolled があり、コンポーネントが有効な状態でこれが切り替わるのはダメということで、今回は controlled なフォームが uncontrolled に変更されている事が問題のようです。

controlled と uncontrolled

HTML では <input><textarea>、そして <select> のようなフォーム要素は通常、自身で状態を保持しており、ユーザの入力に基づいてそれを更新します。React では、変更されうる状態は通常はコンポーネントの state プロパティに保持され、setState() 関数でのみ更新されます。
React の state を “信頼できる唯一の情報源 (single source of truth)” とすることで、上述の 2 つの状態を結合させることができます。そうすることで、フォームをレンダーしている React コンポーネントが、後続するユーザ入力でフォームで起きることも制御できるようになります。このような方法で React によって値が制御される入力フォーム要素は「制御されたコンポーネント」と呼ばれます。
cf. React フォーム 制御されたコンポーネント

つまり input タグなどの value が React の state で管理されていて setState で変更されるコンポーネントが controlled で、そうでないものが uncontrolledコンポーネントということのようです。

state で controlled にしているフォームパーツで warning が発生してしまう場合

今回は下記のようなフォームで warning が発生していました。

function Input() {
  const [value, setValue] = useState();
  return (
    <input change={(e) => setValue(e.target.value)} value={value} />
  );
}

一見 state で value が制御された controlled なコンポーネントのように見えますが、フォームの値を変更したりすると Warning: A component is changing an uncontrolled input な警告が発生してしまいます。

State が null, undefined になる場合がある

フォームコンポーネントは State が null または undefined になると uncontrolled になるようで、初期値が設定されていないフォームコンポーネントは一見して問題がないようにみえてもこの Warning が発生してしまうようです。

先のフォームの場合は useState()useState('') のように修正すればOK

function Input() {
  const [value, setValue] = useState('');
  return (
    <input change={(e) => setValue(e.target.value)} value={value} />
  );
}

 
どこに問題があるのかすぐに分からなくてハマリました。


[参考]

みんなでアジャイル ―変化に対応できる顧客中心組織のつくりかた

みんなでアジャイル ―変化に対応できる顧客中心組織のつくりかた

  • 作者:Matt LeMay
  • 発売日: 2020/03/19
  • メディア: 単行本(ソフトカバー)