先週アニメのレコメンドサービスを作ったばかりなのですが、
業務でNext.jsとReact hooksが必要になり、
勉強がてらソースコードの少ないものをリメイクしてみました。
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)
ファイルシステムの構造がそのままWEBシステムのパスになります。
例外として、_app.tsx
と_document.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>
);
}
いわゆる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;
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を格納する....はずなのですが、ビューとロジック分離するの失敗してるので
またどこかで使うでしょう(笑)
今回は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
};
};
個々のコンポーネントでも状態を持っています。
がっつり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.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
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント