Next13のApp dirでGSAP使いたいときに試行錯誤したやつ

# はじめに
この記事は私（超絶初心者）が最近リリースされたNext.js 13 で GSAP(GreenSock Animation Platform）を動かすために、AppディレクトリやHooksやらで試行錯誤したので備忘録として纏め、ついでに誰かのお助けになれればと思い記述しています。


# そもそもNext13で何が変わったのか

https://nextjs.org/blog/next-13

https://zenn.dev/karibash/articles/eaba1b51fc757e


・app/ Directory (beta): より簡単に、より速く、クライアント側JSのサイズをより小さく。
　 ・Layouts
　　　・React Server Components
　　　・Streaming
・Turbopack (alpha): Rustで実装したWebpackの代替バンドラーで最大700倍高速化。
・New next/image (stable): ネイティブブラウザの遅延ロードを利用し、より高速に。
・New @next/font (beta): レイアウトシフトをゼロにする自動セルフホストフォント。
・Improved next/link: APIを簡素化し、aタグを自動的に表示するようにしました。
(Next.js 13 和訳　から引用)

今回は目玉（だよね？）のAppディレクトリを用いて開発を試みることにしました。



## Appによる今までとの変更点

まず、ルートディレクトリが`pages`から`app`へ変わり、今までのindex ファイルが`index.tsx`から`page.tsx`に変更されました。
```app/page.tsx
export default function Page() {
  return(
  <div>
    <p>indexがindex.tsxからpage.tsxに変わりました！</p>
  <div>
 )
}
```
ここでの問題点：
既存の`pages`と新規の　`app`で役割が重複しておりrun devを行うとエラーが発生します。

解決方法：
pagesは使用しないので配下のファイルを削除します。
(ポチポチ消す or ```rm -rf pages```)

## Layout
これを利用することで複数のページ間でレイアウトおよび内部状態を共有することが可能になる。
詳しくは公式ドキュメントか和訳をご覧ください。（上手く説明出来ないので）

https://beta.nextjs.org/docs/routing/pages-and-layouts

ページのHeader情報を`head.tsx`として管理できたり、stateの状態管理がページ間で共有できたり色々あるらしいです。

# よくわからないがとりあえず書いてみる
よくわからない（初心者）なのでとりあえず書きます。
```
npx create-next-app nextjs13-sample（プロジェクト名） --ts（Typescriptを使うなら入れる） --experimental-app --use-npm
```
でAppディレクトリ機能が使用可能な状態で開発環境の生成。

componentsフォルダをとりあえず作成し、`rfce`雛形でファイルを作成。
```app/components/Gsap.tsx
import React from 'react'

function Gsap() {
  return <div>じーさっぷ</div>
}

export default Gsap
```

### GSAPのインストール
GSAPを利用したいので、yarn addします。npmでもいいですが猫アイコンが可愛いので今回はyarnで行きます。
```
yarn add gsap	
```

### GSAPのインポート

上らへんに　import文を書いておきます。
```app/components/Gsap.tsx
import React from 'react'
import { gsap } from 'gsap'


function Gsap() {
  return  <div>じーさっぷ</div>
}

export default Gsap
```
これで　GSAP自体は利用可能になりました！

### GSAPしたい要素を取得
今回は`<div>じーさっぷ</div>`をGSAPしたいので`useRef()`で取得し、`const textRef`に格納しておきます。
`useRef()`等Hooksはfunction内に記述します。
```app/components/Gsap.tsx
import React, { useRef } from "react";
import { gsap } from "gsap";

function Gsap() {
  const textRef = useRef<HTMLDivElement | null>(null);

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;

```
これで取得することができ・・・ません！！！！！

:::note alert
You're importing a component that needs useLayoutEffect. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
:::
とエラーが発生しました！


#### エラーの理由はReact Server Components
Next 13からReact Server Componentsが利用できるようになりました。
これは、
表示系（一覧等)= Server Components
操作系（作成や削除等)= Client Components
と分けて使うことが推奨されています。(引用:next13 和訳より)

今回の`useRef`は操作型のためClient Componentsにあたります。
ですが、デフォルトのレンダリング方法がサーバーサイドのため、useRefでエラーが発生しました。

クライアントサイドのレンダリングの場合、文頭に`"use client"`と記述することでエラーが解消されます。
```app/components/Gsap.tsx
"use client";

import React, { useRef } from "react";
import { gsap } from "gsap";

function Gsap() {
  const textRef = useRef<HTMLDivElement | null>(null);

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;

```
これでエラーが解消されました！

https://beta.nextjs.org/docs/rendering/server-and-client-components#when-to-use-server-vs-client-components

### 動いたことだしGSAPしていく
とりあえず動いたことだしGSAPしていきます。今回はマウント後にアニメーションさせたいと思います。
記述方法ですが、Nextだからといって　vanillaと変わることはありません。

注意点としては取得した要素は`useRef`の`current`に格納されているので、
今回の場合、指定パスは`textRef.current`とするか別の変数にcurrentごと格納して利用してください。

```app/components/Gsap.tsx
"use client";

import React, { useRef } from "react";
import { gsap } from "gsap";

function Gsap() {
    const textRef = useRef<HTMLDivElement | null>(null);
     gsap.to(textRef.current, {
      x: "20%",
      color: "red",
    });

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;
```

これで動き・・・
![img](https://s4.aconvert.com/convert/p3r68-cdx67/at9n2-cqrdh.jpg)
ません！！！！なんで！！！！！！

### 公式がドキュメント出してるので見てみる
公式がGetting Started with GSAP + React.としてドキュメントを出してるので見てみます。

https://greensock.com/react/

しばらく読みすすめるとTriggering animation on mount - useLayoutEffect()と書いてあるのでここを読んでみます。

>useLayoutEffect() フックは、React がすべての DOM ミューテーションを実行した直後  に 実行されます。要素がレンダリングされ、アニメーション化の準備が整っていることが保証されるため、これはアニメーションにとって非常に便利なフックです。一般的な構造は次のとおりです。(google翻訳）
```
const comp = useRef(); // create a ref for the root level element (we'll use it later)

useLayoutEffect(() => {
  
  // -- ANIMATION CODE HERE --
  
  return () => { 
    // cleanup code (optional)
  }
  
}, []); // <- empty dependency Array so it doesn't re-run on every render!
```
つまるところ、`useLayoutEffect()`を利用することによりマウント後にアニメーションが開始されるようなので、したがって記述し直します。
記述する際はuseEffectと同じく、空の依存配列( [] ) を第2引数に記述します。
```app/components/Gsap.tsx
"use client";

import React, { useRef, useLayoutEffect } from "react";
import { gsap } from "gsap";

function Gsap() {
  const textRef = useRef<HTMLDivElement | null>(null);

  useLayoutEffect(() => {
    gsap.to(textRef.current, {
      x: "20%",
      color: "red",
    });
  }, []);

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;
```
これで・・・
![image2](https://s4.aconvert.com/convert/p3r68-cdx67/aykiq-heghy.jpg)
動きました！！

### GSAP、２回行動する。
実は、見えてないだけで　GSAPは二回行動しています。
`useLayoutEffect()`の中にconsole.log("マウントされました！")と書いて確認してみます。
![img3](https://s4.aconvert.com/convert/p3r68-cdx67/axyad-kkzne.jpg)
”２”とカウントがマークされています。
原因はReactの仕様。React18からデフォルト開発環境で`Strict mode`になっており、これにより開発環境だとマウントが二回発生します。
アニメーションは一回でいいので、`didEffect`として一回だけのマウントにしてあげます。
以下のように追記します。
```app/components/Gsap.tsx
"use client";

import React, { useRef, useLayoutEffect } from "react";
import { gsap } from "gsap";

function Gsap() {
  const textRef = useRef<HTMLDivElement | null>(null);
  const didEffect = useRef(false);

  useLayoutEffect(() => {
    if (didEffect.current) return;
    didEffect.current = true;
    console.log("マウントされました");
    gsap.to(textRef.current, {
      x: "20%",
      color: "red",
    });
  }, []);

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;
```
こうすることにより一回のみマウントされるようになります


参考:

https://blog.techscore.com/entry/2022/06/10/080000


### 参考記事一覧
https://nextjs.org/blog/next-13

https://zenn.dev/karibash/articles/eaba1b51fc757e

https://greensock.com/react/

https://blog.techscore.com/entry/2022/06/10/080000

https://zenn.dev/thetalemon/articles/f759b1acd0053a