nextTickを使ったレイアウト調整のコツ

# はじめに

こんにちは、Gakken LEAP のエンジニアの Minami です。
Vue.js で開発をしていると、「データを更新したのに、DOM に反映されるのが遅い…」と感じたことはありませんか？
特に、要素の高さを取得したり、スクロール位置を調整したりするときに、この問題が発生しやすいです。

そんなときに便利なのが、Vue の `nextTick` です！
`nextTick` を使えば、Vue の非同期レンダリングのタイミングを理解しつつ、意図したタイミングで DOM 操作を行うことができます。

本記事では、`nextTick` の基本的な使い方から、実際のレイアウト調整で役立つテクニックまでを詳しく解説していきます。

## `nextTick` とは？

`nextTick` は、Vue のリアクティブな変更が DOM に適用された後に実行されるコールバックを登録するためのユーティリティです。

```vue
<script setup>
import { ref, nextTick } from 'vue';

const showElement = ref(false);

const toggleElement = async () => {
  showElement.value = !showElement.value;
  await nextTick();
  console.log('DOM 更新後の状態:', showElement.value);
};
</script>

<template>
  <button @click="toggleElement">トグル</button>
  <div v-if="showElement">表示されました！</div>
</template>
```

`nextTick` を使うことで、`showElement` の変更が DOM に反映された後に処理を実行できます。

## レイアウト調整で `nextTick` を活用する方法

### 1. 要素の高さを取得する

コンテンツを開閉するアニメーションを実装する際、要素の高さを取得する必要があります。しかし、リアクティブなデータが更新されても、DOM の変更がまだ反映されていない場合があります。

```vue
<script setup>
import { ref, nextTick } from 'vue';

const show = ref(false);
const contentHeight = ref('0px');
const contentRef = ref(null);

const toggleContent = async () => {
  show.value = !show.value;
  await nextTick();
  contentHeight.value = show.value ? `${contentRef.value.scrollHeight}px` : '0px';
};
</script>

<template>
  <button @click="toggleContent">トグル</button>
  <div ref="contentRef" :style="{ height: contentHeight, overflow: 'hidden', transition: 'height 0.3s' }">
    <p>このコンテンツが開閉されます</p>
  </div>
</template>
```

`nextTick` を使わないと `contentRef.value.scrollHeight` を取得する前に `div` が表示されておらず、正しい高さが得られない可能性があります。

### 2. レンダリング後にスクロール位置を調整する

リストの追加やタブの切り替え時に、スクロール位置を適切に設定するのにも `nextTick` が役立ちます。

```vue
<script setup>
import { ref, nextTick } from 'vue';

const items = ref(['アイテム 1', 'アイテム 2']);
const listRef = ref(null);

const addItem = async () => {
  items.value.push(`アイテム ${items.value.length + 1}`);
  await nextTick();
  listRef.value.scrollTop = listRef.value.scrollHeight;
};
</script>

<template>
  <button @click="addItem">アイテムを追加</button>
  <div ref="listRef" style="height: 100px; overflow-y: auto; border: 1px solid #ddd;">
    <div v-for="item in items" :key="item">{{ item }}</div>
  </div>
</template>
```

これにより、新しいアイテムが追加された後にリストの一番下までスクロールするようになります。

## `nextTick` のメリットとデメリット

### メリット

1. **正確なDOM操作が可能**
   - Vue の非同期レンダリングによって生じるタイミングのズレを解消し、正しく要素のサイズやスクロール位置を取得できる。
2. **アニメーションの精度向上**
   - `nextTick` を使用することで、CSS トランジションやアニメーションの開始タイミングを正しく制御できる。
3. **非同期処理との相性が良い**
   - `await nextTick()` を使うことで、API 呼び出し後の DOM 更新などを適切に管理できる。

### デメリット

1. **不要な使用でパフォーマンスが低下する可能性**
   - `nextTick` を過剰に使用すると、余計な再描画が発生し、アプリケーションのレスポンスが低下する可能性がある。
2. **同期処理のつもりが非同期になることがある**
   - `nextTick` を使うと、コードの実行タイミングが非同期的になり、予期せぬ動作を招く場合がある。
3. **Vue の更新フローを理解していないと誤用しやすい**
   - Vue のリアクティブシステムを正しく理解していないと、`nextTick` を適切に使えず、不要なバグを引き起こす可能性がある。

## まとめ

Vue.js の `nextTick` は、DOM 更新後に処理を実行するための便利なツールです。特にレイアウト調整が必要な場面では、

- 要素のサイズを取得する
- スクロール位置を調整する
- アニメーションの制御を行う

といった用途で活用できます。ただし、無駄に使いすぎるとパフォーマンスが低下するため、適切なタイミングで利用することが重要です。

この記事を参考に、Vue のレンダリングを理解しながら効果的に `nextTick` を活用してください！

---

# エンジニア募集中

Gakken LEAP では教育をアップデートしていきたいエンジニアを絶賛大募集しています！！

https://gakken-leap.co.jp/recruit/