2020-05-16に投稿

アニメのレコメンドサービス、NextJS化しました。

先週アニメのレコメンドサービスを作ったばかりなのですが、
業務でNext.jsReact hooksが必要になり、
勉強がてらソースコードの少ないものをリメイクしてみました。

サービスのURL

Annictさじぇすと!

Next.js

Next.jsはReactJSを用いたフレームワークです。
Next.jsの環境構築【これからはじめるNext.js】

Next.jsのメリットとしては読み込むファイルが軽量になった分パフォーマンスが向上することでしょうか。

環境構築

今回環境構築の参考にしたのはこちら

(プロジェクトルート)
 ├ components (コンポーネント類)
 ├ context (React contextを格納するフォルダ)
 ├ hooks (React hooksを格納するフォルダ)
 ├ lib
 │ └ gtag.js (analytics)
 ├ pages
 │ ├ _app.tsx (レイアウト)
 │ ├ _document.tsx (index.htmlに相当)
 │ ├ index.tsx
 │ └ works
 │   └ [id].tsx (idは可変なのでdynamic routes)
 ├ public
 │ └ images
 └ styles (CSS)

pages

ファイルシステムの構造がそのままWEBシステムのパスになります。
例外として、_app.tsx_document.tsxがあります。

_app.tsx

レイアウトを担当。

const App = ({Component,pageProps}:AppProps) => {
    Router.events.on('routeChangeComplete', url => gtag.pageview(url));
    return(
        <storeContext.Provider value={useSeasonName()}>
            <Layout>
                <Component {...pageProps} />
            </Layout>
        </storeContext.Provider>
    );
}

_document.tsx

いわゆるindex.htmlに相当。
あれ、これclass構文だったわ。(コピペ)

class MyDocument extends Document<Props> {
  render() {
    return(
      <Html lang="ja-JP">
        <Head>
         <title></title>
        </Head>
        <body>
        <Main />
        <NextScript />
        </body>
      </Html>
      )
  }
}

export default MyDocument;

index.tsx

export interface Work{
      image:{
        recommendedImageUrl:string
      },
      twitterUsername:string,
      annictId:number,
      watchersCount:number,
      title:string,
      seasonYear:string,
      seasonName:string,
      wikipediaUrl:string,
      syobocalTid:string
  }


const Index:NextPage = () => {
    const [pageNum,setPageNum] = useState(1);
    const [data,setData] = useState<Work[]>();
    const store = useContext(storeContext);

    useEffect(() => {
      const f = async()=>{
        let resData;
        if(store.query.length === 0){
          resData = await fetchSeasonWorks(store.seasonName);
        }else{
          resData = await fetchByTitle(store.query);
        }
        setData(resData);
        console.log(resData);
      }
      f();
    },[store.seasonName,store.query]);

    const myRef = useRef<HTMLInputElement>(null);

    ....

hooks

hooksを格納する....はずなのですが、ビューとロジック分離するの失敗してるので
またどこかで使うでしょう(笑)

context

今回はreduxの代わりにcontextで全体の状態を管理します。
【React + Typescript】useContext の値を子コンポーネントから更新

type StoreContext = {
  seasonName: string;
  query:string;
  setSeasonName: (seasonName: string) => void;
  setQuery: (query: string) => void;
};

const defaultContext: StoreContext = {
  seasonName: CURRENT_SEASON,
  query:'',
  setSeasonName: () => {},
  setQuery: () => {},
};

export const storeContext = createContext<StoreContext>(defaultContext);

export const useSeasonName = (): StoreContext => {
  // state名はThemeContext typeのプロパティに合わせる。
  const [seasonName, setSeason] = useState(CURRENT_SEASON);
  const [query, setQ] = useState('');
  // 関数名はThemeContext typeのプロパティに合わせる。
  const setSeasonName = useCallback((current: string): void => {
    setSeason(current);
  }, []);
  const setQuery = useCallback((current: string): void => {
    setQ(current);
  }, []);
  return {
    seasonName,
    query,
    setSeasonName,
    setQuery
  };
};

個々のコンポーネントでも状態を持っています。

gtag

Next.jsでGoogle Analyticsを適用する

lintに苦戦...

がっつりtypescriptを書いていたわけではなかったので、
nextjsはなかなかビルドを通してくれませんでした。
ParsedUrlQuery typings should allow undefined values

function isString(value: unknown): value is string {
      return typeof value === "string"
    }
    const id = isString(router.query.id) ? router.query.id : ""
    useEffect(()=>{
      const f = async () => {
        if(router !== undefined && router.query !== undefined){
          const resData = await fetchByWorkId(id);
          setData(resData);
        }
      }
      f();
    },[router.query]);

netlifyにデプロイ

netlify.tomlに必要なルーティングを記述します。

[build]
  base = "/"

  # next exportの出力先
  publish = "out/"

  # next buildからのnext exportでファイル生成
  command = "yarn run build && yarn run export"

[context.production]
  command = "NODE_ENV=production yarn run build && yarn run export"

[[redirects]]
  from = "/"
  to = "/index.html"
  status = 200
[[redirects]]
  from = "/works/*"
  to = "/works/[id].html"
  status = 200
ツイッターでシェア
みんなに共有、忘れないようにメモ

ckoshien

個人開発4年目。普段はアプリケーションエンジニア。 ReactJS/NodeJS/ReactNative/Java

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!
ボードとは?

関連記事

コメント