2021/09/03

splice()メソッドを使ってみる。

@ 酒井悠宇

Next.jsでサイトを作っているのですが、自分が実装したい機能が配列の要素の入れ替えができれば実現できそうなので、それについて色々調べていきたいと思います。

http://www.htmq.com/js/array_splice.shtml
https://remoter.hatenablog.com/entry/2020/05/07/javascript%E3%81%A7%E9%85%8D%E5%88%97%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%85%A5%E3%82%8C%E6%9B%BF%E3%81%88%E3%82%8B

実装したい機能

実際にはそれぞれの枠内には画像が入っていると思ってください。
再現したい挙動としては、「右側のリストになっている画像のいずれかをクリックすると、その画像が左側のメインの枠に入っている画像と置き換わる」みたいな感じです。


例えばこんな感じになっていたとして、
3の画像をクリックすると、


こういう挙動になる感じです。

どうやって実現するか?

この機能を実現するためには、[1, 2, 3, 4, 5] とある配列を [3, 2, 1, 4, 5] みたいに並べ替えることができれば実現できそうなので、今回はJavascritpの配列の要素を入れ替えることができる splice() メソッドを使用して実装していきたいと思います。

「入れ替える」と一言に言いましたが、もうちょっと具体的にいうと「削除して追加する」みたいな感じです。

ベースを作成する

まずは画像のパスを配列にして、それをstateで管理しようと思います。
具体的には以下のようになりました。

const imgPaths: string[] = [
  "DSC_4780.jpg",
  "DSC06536.jpg",
  "DSC_7983-2.jpg",
  "DSC_5882.jpg",
  "DSC_3955.jpg",
  "DSC_2807.jpg",
  "DSC_4525.jpg",
];
const [spliceImg, setSpliceImg] = useState<string[]>(imgPaths);


表示するときはstateをmapで回してこんな感じにします。
あとはこれでいい感じにレイアウトを整えれば良さそうです。
説明が長くなるので今回はレイアウトに関しては説明を省きます。

{spliceImg.map((i) => (
  <Image src={`/images/${i}`} width={300} height={200}/>
))}


ベースはこれで完成しました。
あとは、画像がクリックされると、クリックされた画像のパスとインデックス番号0の画像のパスが入れ替わるロジックが作られれば完成です。
それではsplice()メソッドについてみていきましょう。

splice()メソッド

Arrayオブジェクトのsplice()メソッドは、配列空要素を削除・追加して組み替えます。

Arrayオブジェクトのspliceメソッドを実行すると、第一引数で指定した位置から、第二引数で指定した数の要素が配列から削除され、第三引数以降で指定した値が要素として配列に追加されます。

実行した結果には、削除された要素が配列形式で返ります。

構文

配列名.splice()

第一引数

追加・削除する位置(配列の要素数より大きい値を指定すると配列の末尾となる。負の数値を指定すると配列の末尾から引いた位置となる)

第二引数

削除する要素の数

第三引数以降

追加する要素

戻り値

削除された要素が配列形式で返される

使用例

HTML + JavaScriptソース

<script>
//元の配列を用意する
let ary = new Array("いちご", "にんじん", "さんだる")

//配列の1番目から2個の要素を削除して、同じ位置に"ごましお", "ろけっと"を追加
let spliced = ary.splice(1, 2, "ごましお", "ろけっと");
document.write(ary);
</script>


↓ブラウザ上の表示

いちご, ごましお, ろけっと


splice()を使って配列を操作する

それではこのsplice()メソッドを使って画像がクリックされた時に配列の要素を入れ替えるロジックを作っていきたいと思います。

こんな感じになりました。

//stateを直接書き換えるとレンダリングされないので展開してから書き換える
const imgList: string[] = [...spliceImg];

//画像クリック時に走る関数
const onClickImg = (i: number): void => {
  //クリックされたindexを削除して、そこにindex0の要素を入れる
  const deleteImg = imgList.splice(i, 1, imgList[0]);
  //spliceの返り値として削除された要素が配列形式で返ってくるので、それをimgListの[0]にぶち込む
  imgList[0] = deleteImg[0];
  //state書き換え
  setSpliceImg(imgList);
};


//jsx内の表示のロジック
{spliceImg.map((i, index) => (
  <Image src={`/images/${i}`} width={300} height={200} onClick={() => onClickImg(index)} />
))}


流れは以下のような感じです。

  1. imageのonClick時に走る関数に配列のindex番号を引数として渡しています。
  2. stateは直接書き換えるとレンダリングが走らないので、一旦展開してそれをimgListに代入しています。
  3. spliceメソッドでそのindex番号の要素を削除して、そこにindex[0]の要素を追加しています。
  4. spliceメソッドは返り値として削除した要素を配列形式で返すので、それを変数deleteImgに代入しています。
  5. imgList[0] に deleteImg[0] を代入しています。
  6. 最後にsetter関数でstateを書き換えています。


「stateを直接書き換えるとレンダリングが走らない」って?


最初はstateを直接書き換えていたのですが、それではレンダリングがはしらずに思い通りの挙動にならなかったので、色々調べてみると以下の記事を見つけました。

React Hooks で配列の要素を削除した後の再描画について

解決策としてはstateを一回スプレッドで展開して変数に代入してから配列操作を行えばいいみたいです。
最後にsetter関数でstateを書き換えるのもお忘れなく。

完成

思い通りの挙動を実現することができました。
これからも実現したい機能の実装方法がわからないときはこんな感じでブログに書いて勉強していきたいと思います
最後までごらんいただきありがとうございました!

おまけ:エラー共有

なんかNext.jsで画像を色々してたら以下のようなエラーが出たので共有しておきます。

Unhandled Runtime Error ChunkLoadError: Loading chunk nodemodulesnext_dist_client_dev_noop_js failed. (error: http://localhost:3000/_next/static/chunks/fallback/node_modules_next_dist_client_dev_noop_js.js)

https://stackoverflow.com/questions/67652612/chunkloaderror-loading-chunk-node-modules-next-dist-client-dev-noop-js-failed
このサイトに同じエラーが起こっている人を見つけました。

解決法
ルートディレクトリ直下にある「.next」というファイルを削除して「npm run dev 」でサーバー再起動で治りました。

© 2021 powerd by UnReact