Closed11

Next.jsでアプリ作成(できた)

ピン留めされたアイテム
shuentshuent

Todo + Counterアプリができた。

最初スタイルが崩れるのは原因不明。todoとcounterを行ったり来たりすると治る。
コードとプレビューを公開。

CodeSandboxすごい。
プレビューを見られない場合はこちら: https://codesandbox.io/s/unruffled-stonebraker-zwbf8

本番ビルド&公開にはVercelがやばい

  • Vercelにプロダクションをビルドしたら直った。開発のときだけっぽい。
  • ボタン一発でデプロイできるの凄すぎる。。
    https://csb-zwbf8-ltnxfx1wz.vercel.app

技術構成

  • Next.js
  • TypeScript
  • React Hooks
  • Redux
  • Redux ToolKit
  • Material-UI

学び

  • redux toolkit がとにかく便利。機能別でsliceを分けて管理すると見通しが良い。
  • reduxのスタイルガイドを読み込んでstoreやreducerの設計を学ぶ。
  • 今までreduxが難しそうと避けてたのがもったいない。FlutterのProviderパターンよりも、わかりやすい。今までボイラープレートを書く読むのがめんどくさかっただけ。非同期は今のところミドルウェアではなく、React Hooksの useEffect使った方がわかりやすい。HooksとRedux Toolkit(RTK)さまさま。
  • redux DevToolsを使うにはRTKの configStore を使えばいいだけなのでそれも良い。
  • Next.jsが環境やらrouterやら用意してくれるおかげで1秒で開発し始められるのすごい。
  • material-uiが分かった。次はレイアウトもきっちりやりたい。気合が足りない。
shuentshuent

Goal

Next.js で自社プロダクトをリリースする

習得したいこと

  • TypeScript
  • Redux
  • Redux Toolkit
  • 非同期処理にはHooks
  • ドメイン駆動っぽくロジックを分離する
  • material UI
  • Firebase Auth
  • Firestore をロジック分離して呼び出し
  • styled-jsx+atomic Designによるコンポーネント指向UI組み立て

学習順

  • Next.js, React Hooks, Redux, Redux Toolkit のチュートリアル、ガイドを読む
  • Counter App を作る
    • hooksで作る
    • redux を適用
  • Todo App を作る
  • Firebase Authでログイン
  • Firestoreでtodo
shuentshuent

進捗と詰まったところを投稿していく

shuentshuent
  • Next.js上でTypeScriptとReact Hooks でuseReducerを使いRedux風にカウンターアプリを作るまでできた
  • _app.tsx上からreducerをページにpropsで渡そうと思ったが、無理そう。[できるかわからない]
// _app.tsx
function MyApp({ Component, pageProps }: AppProps) {
	const [state, dispatch] = useReducer(countReducer, initialState)
	
	return <Component {...pageProps} state={state} dispatch={dispatch} /> //こうやって渡すのは無理
}

// index.tsx
const Home: React.FC<Props> = ({ state, dispatch }) => {
  console.log(state)
  return (
    <>
      <h1>{state.number}</h1>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  )
}
export default Home
  • 直接渡すのは無理なので、Provider, Contextで渡す。
shuentshuent
  • Context + Provider で, _app.js上からreducerとグローバルステートを渡す。
// _app.tsx
...
export type State = {
  number: number
}
export type Action = {
  type: string
}
const initialState = { number: 0 }
const countReducer = (state: State, action: Action): State => {...}

export const CounterContext = React.createContext(
  {} as { state: State; dispatch: React.Dispatch<Action> }
)

function MyApp({ Component, pageProps }: AppProps) {
  const [state, dispatch] = useReducer(countReducer, initialState)

  return (
    <CounterContext.Provider value={{ state, dispatch }}>
      <Component {...pageProps} />
    </CounterContext.Provider>
  )
}


// index.tsx
...
export const Home: React.FC = () => {
  const { state, dispatch } = useContext(CounterContext)

  return (
    <>
      <h1>{state.number}</h1>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  )
}
shuentshuent
  • Material UIを導入。レイアウトのやり方が不慣れ。Flutterはシンプルでよかったな、、

import React, { useContext } from 'react'
import { CounterContext } from './_app'
import { Typography, Button, Grid } from '@material-ui/core'

export const Home: React.FC = () => {
  const { state, dispatch } = useContext(CounterContext)

  return (
    <>
      <Grid container alignItems="center" direction="column">
        <Typography variant="h1">{state.number}</Typography>
        <Grid container justify="center" spacing={2}>
          <Button
            variant="contained"
            color="secondary"
            onClick={() => dispatch({ type: 'decrement' })}
          >
            -
          </Button>
          <Button
            variant="contained"
            color="primary"
            onClick={() => dispatch({ type: 'increment' })}
          >
            +
          </Button>
        </Grid>
      </Grid>
    </>
  )
}
export default Home
shuentshuent
  • redux, redux-toolkitを導入し、グローバルstoreで状態を管理。
  • redux-toolkitを使えば思ったより簡単にreduxが使える!楽!
// redux.ts

import { configureStore, createAction, createReducer } from '@reduxjs/toolkit'

export type State = {
  number: number
}

const increment = createAction('increment')
const decrement = createAction('decrement')

const initialState = { number: 0 }
const countReducer = createReducer(initialState, {
  [increment.type]: (state) => ({ number: state.number + 1 }),
  [decrement.type]: (state) => ({ number: state.number - 1 }),
})

export const store = configureStore({ reducer: countReducer })

// _app.tsx
function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Provider store={store}>
      <ThemeProvider theme={theme}>
        <Component {...pageProps} />
      </ThemeProvider>
    </Provider>
  )
}

// index.tsx

export const Home: React.FC = () => {
  const count = useSelector((state: State) => state.number)
  const dispatch = useDispatch()
  ...
}

shuentshuent
  • createSlice でactions, reducerを一気に生成して、関心ごとで分離したコードが書ける。便利い
  • 少し書き直し。
// redux.ts
const counterSlice = createSlice({
	name: 'count',
	initialState,
	reducers: {
		increment: (state)=>{
			state.number++
		},
		decrement:(state)=>{
			state.number--
		},
	},
})

export const { increment, decrement } = counterSlice.actions
export const store = configureStore({ reducer: counterSlice.reducer })
shuentshuent
  • Todo リストも追加するにあたり、
    • ページごとにredux moduleを分け、combineReducer で結合
    • Material UI でAppBarでページを移動できるようにした
  • rootStateの型は、store.ts で型をexportできる。
const reducer = combineReducers({
	counter: counterReducer,
})
export type RootState = ReturnType<typeof reducer>
export const store = configureStore({ reducer })

shuentshuent

count+todo AppをFirebase上で動かす

  • ログイン機能をつける
  • データをfirestoreでユーザーごとに管理
このスクラップは2021/01/03にクローズされました