Next.js で ビジネス定型文生成アプリを作り直す
why
hashicorp の人のいつ next.js を使うべきではないか?というセッション、オチが use next.js for everything だった#NextJsConf
— SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED (@mizchi) October 27, 2020
- Next.conf stage X で "全て Next.js で作れ" ってあった
- その気になった、結局あれ React で作ってたからな
- 設計が汚かったので綺麗にコンポーネント作りたくなった
- ts はやめとく
upgrade to latest
npm install -g next@latest react@latest react-dom@latest + react@17.0.1 + react-dom@17.0.1 + next@10.0.0 added 814 packages from 440 contributors in 60.126s
- react@17, next@10, に更新
start
npx create-next-app biz-text-generator ✔ Pick a template › Default starter app Creating a new Next.js app in /Users/kaede/code/biz-text-generator. Installing react, react-dom, and next using yarn... Success! Created biz-text-generator at /Users/kaede/code/biz-text-generator
- なぜ yarn?
next dev Port 3000 is already in use.
- blitz のが残ってるのか...
- next では3000 使ってるから3001 って割り振ってくれない
- VScode の統合ターミナルで動いてたのを切った
next dev ready - started server on http://localhost:3000
- えええ...
- yarn じゃないとダメなの?
Success! Created biz-text-generator at /Users/kaede/code/biz-text-generator Inside that directory, you can run several commands: yarn dev Starts the development server. yarn build Builds the app for production. yarn start Runs the built app in production mode. We suggest that you begin by typing: cd biz-text-generator yarn dev
- って出てたしなぁ
yarn dev yarn run v1.22.5 $ next dev ready - started server on http://localhost:3000 event - compiled successfully
- yarn ならいけたぜ!
- next dev が走ってるらしいけど、まぁいいや
import Head from 'next/head' export default function Home() { return ( <div className="container"> <Head> <title>biz-text-gen</title> <link rel="icon" href="/favicon.ico" /> </Head> <h1>ビジネス文章ジェネレーター</h1> </div> ) }
- pages/index.js を変更
- 読み込みを確認
- GitHub に push もした
Layout 適用コンポーネントの作成
layout.module.css
.container { max-width: 36rem; padding: 0 1rem; margin: 3rem auto 6rem; }
layout.js
import styles from './layout.module.css' export default function layout ({children}) { return <div className={styles.container}>{children}</div> }
- 同階層から css を持ってきて、引数の children に style.container を適用するだけのjs
Layout.js の index への適用
- index.js には
import Head from 'next/head' export default function Home() { return ( <div className="container"> <Head> <title>biz-text-gen</title> <link rel="icon" href="/favicon.ico" /> </Head> <h1>ビジネス文章ジェネレーター</h1> </div> ) }
- が書いてあるこれに
import Layout from '../components/layout'
- で Layout を持ってきて
<Layout> <h1>ビジネス文章ジェネレーター</h1> </Layout>
- これで Layout の module css が適用された h1 になった
- ご覧の通り
MUI Paper で綺麗な 大枠を作りたかった
.container { display: grid; height: 100%; place-items: center center; }
- 前回の container クラスの css に変更
- ちょっと上が空いた
そもそもの話ですが、Next.jsのデフォルト設定でMaterial UIを使えない理由は、Material UIがサーバーサイドレンダリング(SSR)に対応できないからです。
ということで、Material UIをSSRに対応させる設定が必要です。
公式から
pages/app.js, pages/document.js
とこれらが使用している
- src/theme.js
を paste しないといけないらしい。
コードを肥大化させたくないので MUI Paper は諦める?
やはり MUI Paper 使いたい
npm install @material-ui/core + @material-ui/core@4.11.0
コンテナと紙を見てみる
import Container from '@material-ui/core/Container'; import Paper from '@material-ui/core/Paper';
- Layout.js で import
export default function layout ({children}) { return ( <div className={styles.container}> <Container> <Paper> {children} </Paper> </Container> </div> ) }
- Layout.js で children を包む
- カードに入っている。
- これでとりあえずコンテナの style は分離できそう。多少綺麗なコードにできた
- 次はこうやって、
- 相手の組織と名前
- 自分の組織と名前
- を入るようにする
ユーザーの 入力エリアと文章の返答エリアの作成
- これを参考にする
- next js blog みたいに 使う
汚い state たち
const [theirName, setTheirName] = useState('人事 太郎'); const [myName, setMyName] = useState('面接 やる太郎'); const [theirOrg, setTheirOrg] = useState('GAFA 株式会社'); const [myOrg, setMyOrg] = useState('すごい大学'); const [mtgDateOne, setMtgDateOne] = useState('2020-10-01T10:30'); const [mtgDateTwo, setMtgDateTwo] = useState('2020-10-02T10:30'); const [mtgDateThree, setMtgDateThree] = useState('2020-10-03T10:30');
- こんなコードをかいていた
const [state, setState] = useState({ fName: "", lName: "" }); const handleChange = e => { const { name, value } = e.target; setState(prevState => ({ ...prevState, [name]: value })); }; <input value={state.fName} type="text" onChange={handleChange} name="fName" /> <input value={state.lName} type="text" onChange={handleChange} name="lName" />
- これで短くできそう
NameForm を index.js で読み込む
import NameForm from '../components/NameForm' <Layout> <h1>ビジネス文章ジェネレーター</h1> <NameForm/> </Layout>
- NameForm を読み込むようにする
state の自動生成コードを使う
const [state, setState] = useState({ fName: "", lName: "" }); const handleChange = e => { const { name, value } = e.target; setState(prevState => ({ ...prevState, [name]: value })); };
- NameForm にこれを 書いて
return ( <div> <input value={state.fName} type="text" onChange={handleChange} name="fName" /> <input value={state.lName} type="text" onChange={handleChange} name="lName" /> <h2>{state.fName}, {state.lName}, </h2> </div> )
- これで読み込めた
- これで簡単に state を増やしまくれる
Paper の二重読み込み
- MUI Paper を重ねる
<Paper elevation={5}> <h2>{state.hisCompany}, {state.hisName}, 様</h2> </Paper>
- elevatoin 5 で重なったけど、マージンを効かせなければいけない
<Box m={2} pt={3}> <Button color="default"> Your Text </Button> </Box>
- このようにwrapper が必要
- free area を作ってみた
- 何がしたいのかわからなくなった...
const hisCompanyInput = () => { return ( <input value={state.hisCompany} type="text" onChange={handleChange} name="hisCompany" /> ) }
- input の定義
const output = () => { const firstGreeding = `お世話になっております` const finalGreeding = `以上、よろしくお願いいたします` return ( <Box m={2}> <Paper elevation={5}> <h2>{state.hisCompany}, {state.hisName}, 様</h2> <h2>{state.myCompany}, {state.myName}, です</h2> <h2>{firstGreeding}</h2> <h2>{state.freeArea}</h2> <h2>{finalGreeding}</h2> </Paper> </Box> ) }
- out put に input した state と定型文を並べる
return ( <div> {hisCompanyInput()} {hisNameInput()} {myCompanyInput()} {myNameInput()} <br/> {freeAreaInput()} {output()} </div>
- return で input と output を並べる
- copy btn 実装するのに改行記号処理がわからん
deploy to vercel
React + gh-pages より断然早いな
今回学んだこと
- mui paper は elevation 増やせば重ねられる
- mui の 部品は Box で wrap して margin や padding を適用する
- 全体の中央揃えなどは layout.js layout.modules.css を噛ませる
- state を宣言しまくりたい時は楽に書く方法がある
- return で返す html は 変数に入れて分割しまくったほうが、行数は長くなるけど見通しがいい