- 投稿日:2020-05-16T23:14:07+09:00
ReactのAPIコールでHTTPステータスによる表示画面の切替え実装例
内容
ReactのFetchを利用したAPIをコールで、HTTPステータスの成功レスポンスが200以外存在し、ステータスによって表示画面を変更する場合の実装備忘メモ。もっとうまいやり方があるかもしれないが、試行錯誤の上たどり着いた方法。
NG例
最初のthenでsetStateしたみたが、この場合responseDataにうまくJsonが入ってくれなかった。
fetch("http://test.api.com", { method: 'GET' }).then(response => { if (response.status == 200) { this.setState({ isLoaded: true, flag: false, responseData: responseJson.json() //responseDataにうまくJsonが入らない }); } else { throw new Error(); } })成功例
いろいろ試行錯誤した結果、stackoverflowで同じような投稿があり、
response.json()
はpromiseを返すことが原因とのこと。回答を参考に実装してみた結果、レスポンスのHTTPステータスで画面表示の分岐ができた。import React from 'react'; class Rest extends React.Component { constructor(props) { super(props); this.state = { error: false, isLoaded: false, flag: true, responseData: null }; } componentDidMount() { fetch("http://test.api.com", { method: 'GET' }).then(response => { if (response.status >= 200 && response.status < 300) { const statusCode = response.status; const data = response.json(); //[ステータスコード, JSON]の配列をreturn return Promise.all([statusCode, data]); } else { throw new Error(); } }).then((responseJson => { if (responseJson[0] === 200) { //HTTPステータス200の場合 this.setState({ isLoaded: true, flag: false, responseData: responseJson[1].data }); } else if (responseJson[0] === 202){ //HTTPステータス202の場合 //処理 } })).catch((error => { this.setState({ isLoaded: true, error: true }); })); } render() { const { error, isLoaded, flag, responseData } = this.state; if (error) { return (<div>Error</div>); } else if (!isLoaded) { return (<div>Loading...</div>); } else if(falag) { return (<div>画面1 response:{responseData.data}</div>); } else if(!falag) { return (<div>画面2 response:{responseData.data2}</div>); } } } export default Rest;
- 投稿日:2020-05-16T21:57:10+09:00
Next.js × TypeScriptの同期・非同期処理をHooksを使って書く
概要
こんにちは、よしデブです。
今回、TypeScript×Next.jsとRedux Hooksの組み合わせに初めて挑戦したので、同期処理と非同期処理の書き方紹介しようと思います!
TypeScript×Next.jsとRedux Hooksの組み合わせの記事はいくつかあったのですが、 非同期処理のHooks についての記事が少ないように思いました。せっかくなので現在の私の中でのReduxの書き方ベストプラクティスを共有したいと思います。 あくまでも私の思うベストプラクティス なので、もっと良い書き方があるよ!という方はコメントお待ちしておりますm(_ _)m今回は4部構成でいきたいと思います。
- Reduxを始めるの準備
- 同期処理でTodo追加・完了機能を作る
- 非同期処理でログイン機能を作る(メイン)
- (おまけ)その他のライブラリ紹介
ReduxやNext.js基本的な考え方は軽く触れますが、その詳細やTypeScriptの書き方の説明は割愛させていただきます。
TypeScriptでReduxの書き方で悩んでいる方やReduxの非同期処理の書き方に悩んでいる方の手助けになれれば幸いです。作成したデモがこちら
Todoリストを追加、完了機能と簡単な認証機能を実装しました。
Todoのタスク追加、完了はReduxで同期的、ログイン機能は非同期処理で実現しています。
Hooksについて
私は趣味でReactを使ったアプリを作っています。
ReactやRedux自身は3年ほど前から使っており、Reduxを用いた状態管理は便利だなぁ...と思って感心していました。
しかし、ここ最近はTypeScriptでNext.jsを使った簡単なWebアプリケーション開発が多く、Reduxほどしっかり状態を管理する機会が少なかったです。React ver.16.8からHooks機能が加わり、stateなどのReactの機能を、クラスを書かずに使えるようになりました。React Hooks(公式)
これに伴ってReduxも独自のHooksを提供するようになり、stateの受け渡しもClass ComponentではなくFunctional Componentで書けるようになりました。Redux Hooks(公式)環境
- Node: v12.16.1
- yarn: v1.22.4
- TypeScript: v3.8.3
主要なパッケージのバージョンは以下の通りです。(各パッケージの@typesもインストールします)
- react: v16.13.0
- redux: v4.5.0
- react-redux: v7.2.0
- redux-thunk: v2.3.0
- next: v9.3.1
- next-compose-plugins: v2.2.0
- next-redux-wrapper: v5.0.0
- @material-ui/core: v4.9.13
- formik: v2.1.4
- styled-components: v5.1.0
ディレクトリ構造
Next.jsの仕様でpagesディレクトリでルーティング処理を書きます。Next.js Pages(公式)
srcディレクトリでは、commonで共通的なもの、componentsでは自作のコンポーネント(Atomic Design)、storeでReduxに必要なActionやReducerなどを置いています。
ここではNext.jsやAtomic Designについては触れません。tree. ├── pages │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx ├── server │ └── index.ts ├── src │ ├── common │ │ ├── Color.ts │ │ ├── api.ts │ │ └── theme.ts │ ├── components │ │ ├── atoms │ │ │ └── forms │ │ │ ├── ErrorText.tsx │ │ │ └── TextInput.tsx │ │ ├── molecules │ │ │ └── TextField │ │ │ └── TextField.tsx │ │ └── organisms │ │ ├── LoginForm │ │ │ └── LoginForm.tsx │ │ └── TodoForm │ │ └── TodoForm.tsx │ └── store │ ├── auth │ │ ├── actions.ts │ │ ├── asyncActions.ts │ │ ├── index.ts │ │ └── types.ts │ ├── todos │ │ ├── actions.ts │ │ ├── index.ts │ │ └── types.ts │ ├── index.ts │ ├── actions.ts │ └── reducer.ts ├── types │ └── svg.d.ts ├── package-lock.json ├── package.json ├── next-env.d.ts ├── next.config.js ├── tsconfig.json ├── tsconfig.server.json ├── tslint.json ├── yarn-error.log └── yarn.lockTodoリストを作るためのReduxの準備
Todoリストの状態を管理するためのReduxの準備をしていきます。
ActionType、ActionCreatorを定義
早速作っていきましょう。
まずはTodoのActionType、ActionCreatorを作っていきます。
この辺のActionType、ActionCreateorの作り方はいくつかあると思いますが、私はこんな感じで書いてます。
ポイントはActionTypeは一意に決まるように定義するようにします。 これはReducerでActionTypeを見て次に返すStateを決めるからです。src/store/todos/types.tsexport default { ADD_TODO: 'ADD_TODO', DONE_TODO: 'DONE_TODO' } as constActionCreatorにはロジックを書かず、素直にActionを返します。
src/store/todos/actions.tsimport uuid from 'uuid/v4' import types from './types' // ActionCreator export function addTodo(task: string) { // Actionを返す return { type: types.ADD_TODO, payload: { id: uuid(), done: false, task, }, } } export function doneTodo(id: string) { return { type: types.DONE_TODO, payload: { id }, } }Actionの型をActionCreator推論するためにCreatorsToActions型を定義
次にReducerで使用するActions型を定義します。
複数のActionCreatorからまとめてActionの型を推論するために、CreatorsToActions型を定義します。
CreatorsToActionsにActionCreatorを渡すと、ActionCreatorの返り値を見てActionの型を推論してくれます。src/actions.tstype Unbox<T> = T extends { [K in keyof T]: infer U } ? U : never type ReturnTypes<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? ReturnType<T[K]> : never } type CreatorsToActions<T> = Unbox<ReturnTypes<T>> // todoのActionCreatorを渡す export type Actions = CreatorsToActions<typeof import('./todos/actions')> /** Actionsの推論結果 type Actions = { type: 'ADD_TODO' payload: { id: string, done: boolean, task: string, } } | { type: 'DONE_TODO' payload: { id: string } } */Reducerを定義
次にReducerを定義します。Reducerは引数に与えられたActionを基に、新しいStateを返します。今回StateはTodoの配列を持ちます。
src/store/todos/index.tsimport { Actions } from '../actions' import types from './types' interface Todo { id: string done: boolean task: string } interface State { todos: Todo[] } export function initialState(injects?: State): State { return { todos: [], ...injects, } } export function reducer(state = initialState(), action: Actions): State { switch (action.type) { // todosの末尾にaction.payloadを追加して返す case types.ADD_TODO: return { ...state, todos: [...state.todos, action.payload] } // idがaction.idに一致するtodoのdoneをtrueにして返す case types.DONE_TODO: return {...state, todos: state.todos.map( todo => todo.id === action.payload.id ? {...todo, done: true} : todo) } default: return state } }先ほどきちんとActions型を定義しているおかげで図のようにActionTypeを補完をしてくれたりするのでコードミスを防ぐことができます。この辺が型を持つTypeScriptの強みですね。
RootState、RootReducer、RootStoreを定義
最後にRootState、RootReducer、RootStoreを定義しましょう。
RootReducerは複数定義したreducerを一つにまとめます。まとめるには
combineReducers
を用います。今回はTodoに関するState、Reducerしかありませんが、今後、機能ごとにこれらを切り分けたい時に便利です。次に続く認証機能で新たなState、Reducerを作る予定なので最初からRootReducerを用意しておきます。src/store/reducer.tsimport {combineReducers} from "redux"; import * as Todos from './todos'; // RootState(initialState) export function initialState() { // 今後、機能ごとにstateを追加していく return { todos: Todos.initialState() } } // RootReducer export const reducer = combineReducers({ // 今後、機能ごとにstateを追加していく todos: Todos.reducer, });RootStoreはアプリケーションにStateとReducerを渡すために作成します。
src/store/index.tsimport { applyMiddleware, createStore, Store } from 'redux' import { composeWithDevTools } from 'redux-devtools-extension' import thunkMiddleware from 'redux-thunk' import { initialState, reducer } from "./reducer"; export type StoreState = ReturnType<typeof initialState>; export type ReduxStore = Store<StoreState>; /** * initStore * Initialise and export redux store */ export const initStore = (state = initialState()) => { // RootStore return createStore( reducer, state, composeWithDevTools(applyMiddleware(thunkMiddleware)) ) }アプリケーションにStoreを渡す
Next.jsでは_app.tsxにアプリケーションのRootを書きます。Next.js Custom App(公式)
これがReactアプリケーションの一番外側(root)のコンポーネントなるので、ここにProviderで上記で定義したinitStoreを与えます。これで アプリケーション内のどこでもReduxにアクセスすることが可能になります。※今回、material-uiを使用しておりProviderの他にもThemeProviderやCssBaselineを呼び出していますが、Reduxとは別の設定になるのでここでは説明は割愛します。
pages/_app.tsximport theme from '@common/theme' import CssBaseline from '@material-ui/core/CssBaseline' import ThemeProvider from '@material-ui/styles/ThemeProvider' import { initStore, ReduxStore } from '@store/index' import withRedux from 'next-redux-wrapper' import App from 'next/app' import React from 'react' import { Provider } from 'react-redux' /** * withRedux HOC * NextJS wrapper for Redux */ export default withRedux(initStore)( class CustomApp extends App<{ store: ReduxStore }> { // (中略) public render() { const { Component, pageProps, store } = this.props // Providerの中にあるコンポーネントでreduxで定義したstoreに参照することができるため、一番外側にProviderを呼ぶ // material-uiを使用しているのでThemeProviderやCssBaselineを呼んでいるが、Reduxとは別の設定なので説明は割愛 return ( <Provider store={store}> <ThemeProvider theme={theme}> <CssBaseline /> <Component {...pageProps} /> </ThemeProvider> </Provider> ) } } )これでReduxを使用する準備は終わりになります!お疲れ様でした!!
終わりに
これで一通りのReduxの定義は終わりました。いやぁ、結構書くことあって大変です。。
次回は実際に今回定義したReduxをガンガン使っていきたいと思います!!
- 投稿日:2020-05-16T21:57:10+09:00
Next.js × TypeScriptの同期・非同期処理をHooksを使って書く ~Reduxの準備編~
概要
こんにちは、よしデブです。
今回、TypeScript×Next.jsとRedux Hooksの組み合わせに初めて挑戦したので、同期処理と非同期処理の書き方紹介しようと思います!
TypeScript×Next.jsとRedux Hooksの組み合わせの記事はいくつかあったのですが、 非同期処理のHooks についての記事が少ないように思いました。せっかくなので現在の私の中でのReduxの書き方ベストプラクティスを共有したいと思います。 あくまでも私の思うベストプラクティス なので、もっと良い書き方があるよ!という方はコメントお待ちしておりますm(_ _)m今回は4部構成でいきたいと思います。
- Reduxを始めるの準備(今回はここ)
- 同期処理でTodo追加・完了機能を作る
- 非同期処理でログイン機能を作る(メイン)
- (おまけ)その他のライブラリ紹介
ReduxやNext.js基本的な考え方は軽く触れますが、その詳細やTypeScriptの書き方の説明は割愛させていただきます。
TypeScriptでReduxの書き方で悩んでいる方やReduxの非同期処理の書き方に悩んでいる方の手助けになれれば幸いです。作成したデモがこちら
Todoリストを追加、完了機能と簡単な認証機能を実装しました。
Todoのタスク追加、完了はReduxで同期的、ログイン機能は非同期処理で実現しています。
Hooksについて
私は趣味でReactを使ったアプリを作っています。
ReactやRedux自身は3年ほど前から使っており、Reduxを用いた状態管理は便利だなぁ...と思って感心していました。
しかし、ここ最近はTypeScriptでNext.jsを使った簡単なWebアプリケーション開発が多く、Reduxほどしっかり状態を管理する機会が少なかったです。React ver.16.8からHooks機能が加わり、stateなどのReactの機能を、クラスを書かずに使えるようになりました。React Hooks(公式)
これに伴ってReduxも独自のHooksを提供するようになり、stateの受け渡しもClass ComponentではなくFunctional Componentで書けるようになりました。Redux Hooks(公式)環境
- Node: v12.16.1
- yarn: v1.22.4
- TypeScript: v3.8.3
主要なパッケージのバージョンは以下の通りです。(各パッケージの@typesもインストールします)
- react: v16.13.0
- redux: v4.5.0
- react-redux: v7.2.0
- redux-thunk: v2.3.0
- next: v9.3.1
- next-compose-plugins: v2.2.0
- next-redux-wrapper: v5.0.0
- @material-ui/core: v4.9.13
- formik: v2.1.4
- styled-components: v5.1.0
ディレクトリ構造
Next.jsの仕様でpagesディレクトリでルーティング処理を書きます。Next.js Pages(公式)
srcディレクトリでは、commonで共通的なもの、componentsでは自作のコンポーネント(Atomic Design)、storeでReduxに必要なActionやReducerなどを置いています。
ここではNext.jsやAtomic Designについては触れません。tree. ├── pages │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx ├── server │ └── index.ts ├── src │ ├── common │ │ ├── Color.ts │ │ ├── api.ts │ │ └── theme.ts │ ├── components │ │ ├── atoms │ │ │ └── forms │ │ │ ├── ErrorText.tsx │ │ │ └── TextInput.tsx │ │ ├── molecules │ │ │ └── TextField │ │ │ └── TextField.tsx │ │ └── organisms │ │ ├── LoginForm │ │ │ └── LoginForm.tsx │ │ └── TodoForm │ │ └── TodoForm.tsx │ └── store │ ├── auth │ │ ├── actions.ts │ │ ├── asyncActions.ts │ │ ├── index.ts │ │ └── types.ts │ ├── todos │ │ ├── actions.ts │ │ ├── index.ts │ │ └── types.ts │ ├── index.ts │ ├── actions.ts │ └── reducer.ts ├── types │ └── svg.d.ts ├── package-lock.json ├── package.json ├── next-env.d.ts ├── next.config.js ├── tsconfig.json ├── tsconfig.server.json ├── tslint.json ├── yarn-error.log └── yarn.lockTodoリストを作るためのReduxの準備
Todoリストの状態を管理するためのReduxの準備をしていきます。
ActionType、ActionCreatorを定義
早速作っていきましょう。
まずはTodoのActionType、ActionCreatorを作っていきます。
この辺のActionType、ActionCreateorの作り方はいくつかあると思いますが、私はこんな感じで書いてます。
ポイントはActionTypeは一意に決まるように定義するようにします。 これはReducerでActionTypeを見て次に返すStateを決めるからです。src/store/todos/types.tsexport default { ADD_TODO: 'ADD_TODO', DONE_TODO: 'DONE_TODO' } as constActionCreatorにはロジックを書かず、素直にActionを返します。
src/store/todos/actions.tsimport uuid from 'uuid/v4' import types from './types' // ActionCreator export function addTodo(task: string) { // Actionを返す return { type: types.ADD_TODO, payload: { id: uuid(), done: false, task, }, } } export function doneTodo(id: string) { return { type: types.DONE_TODO, payload: { id }, } }Actionの型をActionCreator推論するためにCreatorsToActions型を定義
次にReducerで使用するActions型を定義します。
複数のActionCreatorからまとめてActionの型を推論するために、CreatorsToActions型を定義します。
CreatorsToActionsにActionCreatorを渡すと、ActionCreatorの返り値を見てActionの型を推論してくれます。src/actions.tstype Unbox<T> = T extends { [K in keyof T]: infer U } ? U : never type ReturnTypes<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? ReturnType<T[K]> : never } type CreatorsToActions<T> = Unbox<ReturnTypes<T>> // todoのActionCreatorを渡す export type Actions = CreatorsToActions<typeof import('./todos/actions')> /** Actionsの推論結果 type Actions = { type: 'ADD_TODO' payload: { id: string, done: boolean, task: string, } } | { type: 'DONE_TODO' payload: { id: string } } */Reducerを定義
次にReducerを定義します。Reducerは引数に与えられたActionを基に、新しいStateを返します。今回StateはTodoの配列を持ちます。
src/store/todos/index.tsimport { Actions } from '../actions' import types from './types' interface Todo { id: string done: boolean task: string } interface State { todos: Todo[] } export function initialState(injects?: State): State { return { todos: [], ...injects, } } export function reducer(state = initialState(), action: Actions): State { switch (action.type) { // todosの末尾にaction.payloadを追加して返す case types.ADD_TODO: return { ...state, todos: [...state.todos, action.payload] } // idがaction.idに一致するtodoのdoneをtrueにして返す case types.DONE_TODO: return {...state, todos: state.todos.map( todo => todo.id === action.payload.id ? {...todo, done: true} : todo) } default: return state } }先ほどきちんとActions型を定義しているおかげで図のようにActionTypeを補完をしてくれたりするのでコードミスを防ぐことができます。この辺が型を持つTypeScriptの強みですね。
RootState、RootReducer、RootStoreを定義
最後にRootState、RootReducer、RootStoreを定義しましょう。
RootReducerは複数定義したreducerを一つにまとめます。まとめるには
combineReducers
を用います。今回はTodoに関するState、Reducerしかありませんが、今後、機能ごとにこれらを切り分けたい時に便利です。次に続く認証機能で新たなState、Reducerを作る予定なので最初からRootReducerを用意しておきます。src/store/reducer.tsimport {combineReducers} from "redux"; import * as Todos from './todos'; // RootState(initialState) export function initialState() { // 今後、機能ごとにstateを追加していく return { todos: Todos.initialState() } } // RootReducer export const reducer = combineReducers({ // 今後、機能ごとにstateを追加していく todos: Todos.reducer, });RootStoreはアプリケーションにStateとReducerを渡すために作成します。
src/store/index.tsimport { applyMiddleware, createStore, Store } from 'redux' import { composeWithDevTools } from 'redux-devtools-extension' import thunkMiddleware from 'redux-thunk' import { initialState, reducer } from "./reducer"; export type StoreState = ReturnType<typeof initialState>; export type ReduxStore = Store<StoreState>; /** * initStore * Initialise and export redux store */ export const initStore = (state = initialState()) => { // RootStore return createStore( reducer, state, composeWithDevTools(applyMiddleware(thunkMiddleware)) ) }アプリケーションにStoreを渡す
Next.jsでは_app.tsxにアプリケーションのRootを書きます。Next.js Custom App(公式)
これがReactアプリケーションの一番外側(root)のコンポーネントなるので、ここにProviderで上記で定義したinitStoreを与えます。これで アプリケーション内のどこでもReduxにアクセスすることが可能になります。※今回、material-uiを使用しておりProviderの他にもThemeProviderやCssBaselineを呼び出していますが、Reduxとは別の設定になるのでここでは説明は割愛します。
pages/_app.tsximport theme from '@common/theme' import CssBaseline from '@material-ui/core/CssBaseline' import ThemeProvider from '@material-ui/styles/ThemeProvider' import { initStore, ReduxStore } from '@store/index' import withRedux from 'next-redux-wrapper' import App from 'next/app' import React from 'react' import { Provider } from 'react-redux' /** * withRedux HOC * NextJS wrapper for Redux */ export default withRedux(initStore)( class CustomApp extends App<{ store: ReduxStore }> { // (中略) public render() { const { Component, pageProps, store } = this.props // Providerの中にあるコンポーネントでreduxで定義したstoreに参照することができるため、一番外側にProviderを呼ぶ // material-uiを使用しているのでThemeProviderやCssBaselineを呼んでいるが、Reduxとは別の設定なので説明は割愛 return ( <Provider store={store}> <ThemeProvider theme={theme}> <CssBaseline /> <Component {...pageProps} /> </ThemeProvider> </Provider> ) } } )これでReduxを使用する準備は終わりになります!お疲れ様でした!!
終わりに
これで一通りのReduxの定義は終わりました。いやぁ、結構書くことあって大変です。。
次回は実際に今回定義したReduxをガンガン使っていきたいと思います!!
次回はこちら Next.js × TypeScriptの同期・非同期処理をHooksを使って書く ~同期的処理編~
- 投稿日:2020-05-16T17:55:16+09:00
Redux-toolkitで何が便利になるか
どうも、ウマシバ(@UMASHIBA)といいます。redux-toolkitを触ってみたので何が便利になったのかという点を備忘録のために雑にまとめたいと思います。
redux-toolkitとは
最近できたreduxを簡単に書くためのライブラリです。
Reduxの公式ページでも紹介されているライブラリです。
- configureStore
- createAction
- createReducer
- createSlice
といったような簡単にreduxを書くための関数が用意されています。
詳しくはこちら
configureStore
// Before: const store = createStore(counter) // After: const store = configureStore({ reducer: counter })reduxのcreateStoreとほとんど書き方は変わりません。
ただRedux DevTools Extensionみたいな便利なミドルウェアをデフォルトでいれといてくれます。このミドルウェアをデフォルトで入れておいてくれる機能は便利だと思います。createAction
// Original approach: write the action type and action creator by hand const INCREMENT = 'INCREMENT' function incrementOriginal() { return { type: INCREMENT } } console.log(incrementOriginal()) // {type: "INCREMENT"} // Or, use `createAction` to generate the action creator: const incrementNew = createAction('INCREMENT') console.log(incrementNew()) // {type: "INCREMENT"}typeとactionの定義が同時にできるので便利
これで大量のactionとtypeを別々に書いてエクスポートしていたのが一つで済むようになりそうです。createReducer
const increment = createAction('INCREMENT') const decrement = createAction('DECREMENT') //BEFORE function counter(state = 0, action) { switch (action.type) { case increment.type: return state + 1 case decrement.type: return state - 1 default: return state } } //AFTER const counter = createReducer(0, { [increment.type]: state => state + 1, [decrement.type]: state => state - 1 })上のコードを見てみると相当コードを書く量が減っています。
書くコード量が多くなってしまうReduxにおいてこの利点は相当大きいと思います。createSlice
const counterSlice = createSlice({ name: 'counter', initialState: 0, reducers: { increment: state => state + 1, decrement: state => state - 1 } }) const store = configureStore({ reducer: counterSlice.reducer }) document.getElementById('increment').addEventListener('click', () => { store.dispatch(counterSlice.actions.increment()) })createSliceはaction, type, reducer全てを定義してくれます。
上記でやったcreateAction, createReducerを合わせたような関数です。
すごく書くコードの量が減る。便利!更にすごいのはこのcreateSlice関数を使った場合返す値がイミュータブルであるか気にする必要がありません。
createSliceで勝手にimmutableにしてくれます。つまり元のstate
に対してArray.push
メソッドとかが使えます。まとめ
redux-toolkitは
コード量を少なくReduxを書くためのライブラリ
という風にまとめられると思います。
Reduxの公式ページにも紹介されておりGitHubのStarも2020/5/16時点で3000以上あり廃れにくそう。
- 投稿日:2020-05-16T15:34:13+09:00
Reactの基礎を理解しよう
目次
概要
この記事はReact初心者である私がReactの基礎についてまとめたもので、私のようにReactをこれから学習しようとしている人向けの内容となっています。
Reactとは何か、Reactの開発環境の構築方法については別の記事にまとめてありますのでそちらを参考にしてみてください。
また私の記事は、より詳しい方が書いた記事等を参考にまとめていますので、さらに詳細に知りたい方は参考資料を見ることをおすすめします。Reactの基本文法
- ReactはJavaScriptのライブラリ
- 基本的な文法はJavaScriptベース
- JSXと呼ばれるJavaScriptの中に直接HTMLを記述できる技術がある
- アロー関数やclass構文などES6から実装された文法を取り入れている
React基本的な文法はこの辺りだと思います。
JSX
JSXとは「Reactの基本文法」でも述べたように、JavaScriptのコード内で
<div>...</div>
というHTMLのような記述方法で記述できる技術です。では実際の記述方法を見てみましょう。
最も簡単なJSXの例としては下記のような記述となります。src/App.jsconst element = <h1 className="greeting">Hello, world.</h1>;
element
に入っているタグ構文は文字列でもHTML要素でもなく、これがJSXです。このように1つのファイル内でJavascriptとHTMLの記述方法を併用できるのか疑問に思う人もいるはずです。コンピュータ(ここではブラウザ)は賢いようでそうではないので本来であればJavaScript内に記述されたHTML構文を解釈することができません。
そこで登場するのがJavaScriptトランスパイラであるBabelです。このBabelによって
React.createElement()
の形式つまりJavaScript形式に変換されます。Babelは他にもES2015などに対応していないブラウザに対して、対応できる形式に変換してくれる機能もあります。以下がBabelによって変換された後の内容です。
src/App.jsimport React from 'react'; const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world.' );Babelに頼らず
React.createElement()
を使って直接記述することもできますが、JSXの方がわかりやすいし書きやすいですよね。他にもJSXの記述方法にはいくつかのルールのようなものがあります。その中でも利用頻度の高いものは次の項目だと思います。
- 返す要素は1つにまとまった要素として記述する必要がある
- JSX内でJavaScriptを記載するときは{ }で囲む
- 関数の呼び出しもできる
- HTMLのclassはJSXではclassNameにする
src/App.jsimport React from 'react'; const name = '太郎'; const age = '10'; add(x, y){ return x + y; } render() { return ( <div> <h1 className="greeting">こんにちは</h1> <p>僕の名前は{name}です。{age}歳です。</p> <p>1 + 2 = {this.add(1, 2)}</p> </div> ); }このような感じになります。
return
で返している要素は<div>
タグの中にまとまっていますので1つの要素と見ることができます。1つの要素としてまとめる際に
<div>
タグを用いることで余計なDOM操作をすることになるので<React.Fragment>
というものを利用した方がいいです。src/App.jsimport React from 'react'; const name = '太郎'; const age = '10'; add(x, y){ return x + y; } render() { return ( <React.Fragment> <h1 className="greeting">こんにちは</h1> <p>僕の名前は{name}です。{age}歳です。</p> <p>1 + 2 = {this.add(1, 2)}</p> </React.Fragment> ); }Component
Component
(コンポーネント)とは、Reactの最終的な出力であるJSXを構成するUI部品のことで、一度作成すれば何度でも再利用することができます。例えばJSXで、
src/App.jsimport React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render( <React.Fragment> <div> <h1>Hello,World.</h1> <h2>Hi, everyone.</h2> </div> <div> <h1>Hello,World.</h1> <h2>Hi, everyone.</h2> </div> </React.Fragment>, document.getElementByID('root'); );という
<div>...</div>
の中身が同じものが2つあったとき、何度も同じ構文を記述することはめんどくさいし効率が悪いですよね。
(ReactDOM
などまだ紹介していないところもありますが今は気にしないでください。後ほど説明します。)そんなときに登場するのがコンポーネントです。言葉で説明してもイメージがつきにくいと思うので実際に先ほどの例を書き換えてみましょう。
src/App.jsimport React, { Component } from 'react'; class Greeting extends Component { render() { return( <div> <h1>Hello, world.</h1> <h2>Hi, everyone.</h2> </div> ); } } // ここ!! ReactDOM.render( <React.Fragment> <Greeting /> <Greeting /> </React.Fragment>, document.getElementById('root') );このように記述できます。
今回はコンポーネントの中身が少なかったので全体の記述量はほとんど同じですが、コンポーネントとしてまとめられる量が多かったり、何度も同じJSXを記述する場合ほどその効果は大きくなります。見た目もなんだかすっきりしていますよね。
複雑な処理になればなるほどコンポーネントの真価が発揮されるでしょう。またコンポーネントにはクラスコンポーネントと関数コンポーネントの2種類あります。それぞれ特徴をみていきましょう。
クラスコンポーネント
クラスコンポーネントはclass定義で作成されるコンポーネントです。クラスコンポーネントを作成する場合には、
React.Component()
を継承することで作成できます。前述した例のGreeting
もクラスコンポーネントです。
クラスコンポーネントではJSXをreturnするために必ずrender()
メソッドを定義する必要があります。メソッド以外にもクラスですのでconstructor(コンストラクタ)も定義することができます。src/App.jsimport React, { Component } from 'react'; class Greeting extends Component { // React.Componentを継承 constructor() { . . // ここでは省略します . } render() { // JSXを返すrenderメソッド return( <h1>Hello, world.</h1> ); } }関数コンポーネント
関数コンポーネントは、コンポーネントを関数で定義することができます。定義の仕方は
function
でも書けますが、アロー関数で書くほうが記述量的にベターな気がします。src/App.jsimport React from 'react'; const Greeting = () => { return( <h1>Hello, world.</h1> ); }このようになります。
関数コンポーネントの中身は基本的にrender()
メソッドとなるので、クラスコンポーネントで記述していたrender()
は省略できます。以前は、後述する状態(state)やライフサイクルを関数コンポーネントでは持つことができなかったので、その場合にはクラスコンポーネントを作成するといった区別がありました。
しかしReact 16.8で実装されたHooksを用いることで関数コンポーネントでもクラスコンポーネントと同等の機能を持つことができるようになったので、今は基本的にコンポーネントはHooksを用いて記述されることが多いようです。
Hooksに関する記事はまた別に出します。props
props
とは、コンポーネントに引数として渡せるプロパティ(オブジェクト)です。
props
がどんな役割を担うのか例で見てみましょう。src/App.jsconst App = () => { return( <React.Fragment> <h1>I am taro, 10 years old.</h1> <h1>I am hanako, 8 years old.</h1> <h1>I am yuta, 11 years old.</h1> </React.Fragment> ); }このようなJSXがあったとき、
<h1>
タグの中身は似たような内容ではありますが、全く同じではありません。でも共通する箇所が多いのでなんとかコンポーネントとしてまとめたいですよね。
そんなときに登場するのがprops
です。では
props
を用いて上記のコードを書き換えてみます。src/App.jsconst Greeting = props => { return( <h1>I am {props.name}, {props.age} years old.</h1> ); } const App = () => { return( <React.Fragment> <Greeting name="taro" age="10" /> <Greeting name="hanako" age="8" /> <Greeting name="yuta" age="11" /> </React.Fragment> ); }書き換え前に比べすっきりしました。
ここで親コンポーネントApp
が子コンポーネントGreeting
コンポーネントに渡しているname="..." age=...
がprops
です。このようにコンポーネントを利用するうえで欠かすことのできない機能なのでしっかり抑えておきましょう。
propsを用いる場合には次のような注意点があります。
- propsの受け渡しは親コンポーネント→子コンポーネントのみ
- 子コンポーネント側からpropsの値を直接変更することはできない
- 子コンポーネントでは
props.データ名
でデータを参照するstate
state
とは、コンポーネント内部の状態を表す値です。
props
とは違い、そのコンポーネント内でしか扱うことができず、かつsetState()
を使うことでコンポーネント内で変更可能なのが特徴です。クラスコンポーネントで
state
を使う場合は、基本的にコンポーネント作成時のコンストラクタ内で初期化します。this.state
オブジェクトに値を指定することで、同コンポーネント内のメソッドで取得することができます。src/Counter.jsimport React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = { // this.stateの初期化 count: 0, }; } render() { return( <div>count: {this.state.count}</div> ); } }このような記述になります。
constructor(props)
やsuper(props)
はおまじないみたいなものと覚えて大丈夫です。
this.state
の初期値設定ができたので、値をsetState()
で変更してみましょう。src/Counter.jsimport React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = { // this.stateの初期化 count: 0, }; } // this.state.countを+1するメソッド handleCountButton = () => { this.setState({count: this.state.count + 1}); // this.stateの更新 } render() { //Countボタンをクリックしたらcountが+1されるJSX return( <React.Fragment> <div>count: {this.state.count}</div> <button onClick={this.handleCountButton}>Count</button> </React.Fragment> ); } }ここでは
Count
というボタンを押すことで、this.state.count
が+1されるというメソッドhandleCountButton()
を用いて更新をしてみました。目で追うと若干複雑に見えますが、1つずつ理解していけばそこまで難しくないと思います。
state
もprops
と同様にコンポーネントを扱ううえで必要な機能なのでここでしっかり理解しておきましょう。最も重要な注意点は、this.state
を直接変更することができないことです。必ずsetState()
で変更しましょう。現在のReact
以前まではクラスコンポーネントでしか
state
を利用することができませんでしたが、React Hooksで追加された機能のuseState
を使うことで関数コンポーネントでもstate
を利用することができるようになりました。これにより最近のReactコンポーネントの主流は関数コンポーネントになっています。useState
などReact Hooksについては前述したように別の記事にまとめたいと思います。2種類のrender()
今までのコードに
render()
が何回も登場していますが、このrender()
にも2種類あります。
- React.Componentのrender()
- ReactDOMのrender()
それぞれについて見てみましょう。
React.Componentのrender()
Componentでも述べましたが、コンポーネント内で必ず定義する必要のあるメソッドが
render()
です。(ただし関数コンポーネントでは省略されることもあります。)class App extends Component { render() { return( /*返す要素*/ ); } }コンポーネントに用いる
render()
は呼び出されると、this.props
とthis.state
を調べReact要素などを返します。(詳細はこちら)ReactDOMのrender()
ReactDOM.render()
のイメージとしては最終的にブラウザへレンダリングするためのメソッドです。ReactDOM.render(element, container[, callback])よくReactの特徴として「仮想DOM」というワードを見かけると思いますが、それに起因するのが
ReactDOM.render()
です。
ReactDOM.render()
メソッドが飛び出されると指定されたcontainer
にReact要素をレンダリングします。このとき描画された結果のDOMを表したデータ構造が仮想DOMです。
再度ReactDOM.render()
が呼び出されると、変更前後の仮想DOMの差分を計算し、最低限の操作をブラウザ上の実際のDOMに対して行います。これら2種類の
render()
の役割をまとまると、
- 各Componentのrender()がそれぞれのパーツを作る
- ReactDOMのrender()がパーツを集めて1つにまとめてブラウザへ描画する
といったイメージかと思います。
まとめ
Reactの基礎についてまとめてみました。とは言ってもまだ基礎の基礎くらいで、本当はまだまだこんなものではありません。とりあえず今回の記事のまとめです。
- ReactではJSXが書ける
- ComponentはJSXを構成するUI部品
- propsは親コンポーネントから子コンポーネントへ渡せる引数オブジェクト(子では変更不可)
- stateはコンポーネント内部の状態を表す値(変更可)
- render()メソッドには2種類ある
今回は自身の知識の土台を固めるつもりでこのような記事を書きましたが、今後も必要に応じて色々とまとめてみたいと思います。
参考資料
- 投稿日:2020-05-16T13:22:37+09:00
Typescript で React useState の引数を明示的にアノテートする
useState
の引数にstring
やnumber
でなく、定義したオブジェクトやオブジェクトの配列を渡す場合、うまく推論できないことがある。const [examples, setExamples] = useState([]);その場合、関数呼び出し時にコンパイルエラーになる。こんな風に明示的にアノテートしてあげるとうまくいく。
type SomeExample = { id: number example: string } const [examples, setExamples] = useState<SomeExample[]>([]);これがどんなことをやっているのか解説する。例えば以下のような
map
関数があったとする。function map<T, U>(array: T[], f: (item: T) => U): U[] { let result = [] for (let i = 0; i < array.length; i++) { result[i] = f(array[i]) } return result }パラメータと戻り値がジェネリックで抽象化されているので、様々な引数を設定できる。
map(['a', 'b', 'c'], i => i === 'a') map([1, 2, 3], i => i === 1)引数を明示的に書くこともできる。
map<string, boolean>(['a', 'b', 'c'], i => i === 'a') map<number, boolean>([1, 2, 3], i => i === 1)上記の
useState
の例では、空配列のみ引数に渡していたので適切に型推論できていなかった。そのため、明示的なアノテーションが必要だったというわけ。
- 投稿日:2020-05-16T12:15:51+09:00
10分でReact × Dockerの環境構築
概要
React×Dockerの環境構築ではまったのでメモとして書きました
環境構築
作業ディレクトリを作成
$ mkdir react-dockerDockerfile作成
DockerfileFROM node:8.16.0-alpine WORKDIR /usr/src/appdocker-compose.ymlを作成
docker-compose.ymlversion: "3" services: web: build: context: . dockerfile: Dockerfile stdin_open: true volumes: - ./:/usr/src/app command: sh -c "cd react-sample && yarn start" ports: - "3000:3000"ビルド
$ docker-compose buildcreate-react-appとReactのインストール(数分かかります)
$ docker-compose run --rm web sh -c "npm install -g create-react-app && create-react-app react-sample"コンテナ起動
$ docker-compose up終了!
ちょっとはまったところ
stdin_open: true
の記述をなしで構築するとコンテナ起動後にすぐコンテナが停止してしまう。
docker run の「-it」オプションに当たるもの。これを指定しないと、コンテナを起動してもすぐ終了してしまう$ docker-compose up Creating docker-react_web_1 ... done Attaching to docker-react_web_1 web_1 | yarn run v1.15.2 web_1 | $ react-scripts start web_1 | ℹ 「wds」: Project is running at http://172.20.0.2/ web_1 | ℹ 「wds」: webpack output is served from web_1 | ℹ 「wds」: Content not from webpack is served from /usr/src/app/react-sample/public web_1 | ℹ 「wds」: 404s will fallback to / web_1 | Starting the development server... web_1 | web_1 | Done in 12.50s. docker-react_web_1 exited with code 0参考: https://github.com/facebook/create-react-app/issues/8688
docker-compose.ymlversion: "3" services: web: build: context: . dockerfile: Dockerfile stdin_open: true volumes: - ./:/usr/src/app command: sh -c "cd react-sample && yarn start" ports: - "3000:3000"参考記事
- 投稿日:2020-05-16T11:54:09+09:00
RailsとReactをdocker-composeを使って立ち上げる
GraphQLをRails, Reactで使うための第1歩として、RailsのAPIモードとReactをdocker-composeを使って同時に立ち上げられる環境を構築したのでその手順をメモしておきます
※このやり方で動くことは確認済みですが慣れておらず試行錯誤した部分もあるので効率的でない部分も多いかと思います。もしそのような箇所を発見したら指摘していただけると助かります
Railsをdocker-composeで立ち上げる
まずはAPIモードでRailsを立ち上げます
※筆者はこちらの記事の内容を参考にディレクトリ内にbundle installの内容を残す形で作成しています
Gemfilesource "http://rubygems.org" gem "rails"bundle install --path .bundle/ bundle exec rails new rails_test --api --skip-bundle mv rails_test/* . rm -rf rails_test/ bundle installこの時点で
rails s
をするとRailsを起動することができます※ちなみに画像にもありますが動作環境はRails 6.0.3, Ruby 2.7.1です
つづいてこのRailsをdockerで立ち上げられるようにします
docker-compose.ymlversion: '3' services: rails: build: context: . dockerfile: Docker_rails command: /bin/sh -c "cd app && rm -f /app/tmp/pids/server.pid && bundle install && bundle exec rails s -b 0.0.0.0" stdin_open: true tty: true volumes: - .:/app ports: - 3000:3000Docker_railsFROM ruby:2.7.1これで
sudo docker-compose up -d
を行うとdockerでrails環境が立ち上がります※docker-composeの実行は最初はsudoが必要です。sudoを外す方法はこちらの記事を参考にしてください
Reactをdocker-composeで立ち上げる
Reactも同じように作っていきます (同じディレクトリ内に作ります)
create-react-app frontendReactの一番簡単な構築はこれだけでできます
cd frontend yarn startで起動します
これもdocker-composeで起動できるようにします
先程のdocker-compose.ymlに追記します
docker-compose.ymlversion: '3' services: rails: build: context: . dockerfile: Docker_rails command: /bin/sh -c "cd app && rm -f /app/tmp/pids/server.pid && bundle install && bundle exec rails s -b 0.0.0.0" stdin_open: true tty: true volumes: - .:/app ports: - 3000:3000 react: build: context: . dockerfile: Docker_react command: /bin/sh -c "cd frontend && yarn && yarn start" stdin_open: true tty: true volumes: - ./frontend:/frontend ports: - 4000:3000Docker_reactFROM node:8.16.0-alpineこれで
docker-compose-up -d
をすると
http://localhost:3000
→Rails
http://localhost:4000
→ReactというようにRailsとReactを同時に立ち上げることができます
課題点
Railsを立ち上げるのに時間がかかる (毎回bundle installし直すのなんとかしたい)
Dockerfileが余計 (image取ってくる方法があるはず)
この辺は分かり次第追記していこうと思います
- 投稿日:2020-05-16T11:54:09+09:00
railsとreactをdocker-composeを使って立ち上げる
GraphQLをRails, Reactで使うための第1歩として、RailsのAPIモードとReactをdocker-composeを使って同時に立ち上げられる環境を構築したのでその手順をメモしておきます
※このやり方で動くことは確認済みですが慣れておらず試行錯誤した部分もあるので効率的でない部分も多いかと思います。もしそのような箇所を発見したら指摘していただけると助かります
Railsをdocker-composeで立ち上げる
まずはAPIモードでRailsを立ち上げます
※筆者はこちらの記事の内容を参考にディレクトリ内にbundle installの内容を残す形で作成しています
Gemfilesource "http://rubygems.org" gem "rails"bundle install --path .bundle/ bundle exec rails new rails_test --api --skip-bundle mv rails_test/* . rm -rf rails_test/ bundle installこの時点で
rails s
をするとRailsを起動することができます※ちなみに画像にもありますが動作環境はRails 6.0.3, Ruby 2.7.1です
つづいてこのRailsをdockerで立ち上げられるようにします
docker-compose.ymlversion: '3' services: rails: build: context: . dockerfile: Docker_rails command: /bin/sh -c "cd app && rm -f /app/tmp/pids/server.pid && bundle install && bundle exec rails s -b 0.0.0.0" stdin_open: true tty: true volumes: - .:/app ports: - 3000:3000Docker_railsFROM ruby:2.7.1これで
sudo docker-compose up -d
を行うとdockerでrails環境が立ち上がります※docker-composeの実行は最初はsudoが必要です。sudoを外す方法はこちらの記事を参考にしてください
Reactをdocker-composeで立ち上げる
Reactも同じように作っていきます (同じディレクトリ内に作ります)
create-react-app frontendReactの一番簡単な構築はこれだけでできます
cd frontend yarn startで起動します
これもdocker-composeで起動できるようにします
先程のdocker-compose.ymlに追記します
docker-compose.ymlversion: '3' services: rails: build: context: . dockerfile: Docker_rails command: /bin/sh -c "cd app && rm -f /app/tmp/pids/server.pid && bundle install && bundle exec rails s -b 0.0.0.0" stdin_open: true tty: true volumes: - .:/app ports: - 3000:3000 react: build: context: . dockerfile: Docker_react command: /bin/sh -c "cd frontend && yarn && yarn start" stdin_open: true tty: true volumes: - ./frontend:/frontend ports: - 4000:3000Docker_reactFROM node:8.16.0-alpineこれで
docker-compose-up -d
をすると
http://localhost:3000
→Rails
http://localhost:4000
→ReactというようにRailsとReactを同時に立ち上げることができます
課題点
Railsを立ち上げるのに時間がかかる (毎回bundle installし直すのなんとかしたい)
Dockerfileが余計 (image取ってくる方法があるはず)
この辺は分かり次第追記していこうと思います
- 投稿日:2020-05-16T11:08:37+09:00
firebaseにデプロイが反映されない時
問題
Reactでアプリをfirebaseにデプロイしようとしたところ
デプロイは成功しているのにURLを開くと何も変更が反映されておらず....状況
・
firebase init
時にはpublicではなく、buildを指定
・しっかりbuildフォルダは作成されている
・publicが反映されているのかと考えたがfirebase.jsonでhostingはbuildになっている
・npm start
でサーバーを立てると変更は反映されている
・デプロイは成功している解決
ググってもキャッシュを削除としか書かれていない記事ばかりで時間を使ってしまったが
下記のコマンドで解決した!!
npm run build
こうするとThe build folder is ready to be deployed.
と表示されるのでこの後に
firebase deploy
で完了!追記
FirebaseコンソールのHostingには2つURLがありますが、
下のURLに変更が反映されています。結果
ちゃんとデプロイ前にbuildしないといけないだけでした。
今日も見てくれてホンマありがとな!!
記事がええなと思ったらフォローといいねホンマよろしくな!!
(ジョーブログ風に)
- 投稿日:2020-05-16T11:08:37+09:00
firebaseデプロイで変更が反映されない時
問題
Reactでアプリをfirebaseにデプロイしようとしたところ
デプロイは成功しているのにURLを開くと何も変更が反映されておらず....状況
・
firebase init
時にはpublicではなく、buildを指定
・しっかりbuildフォルダは作成されている
・publicが反映されているのかと考えたがfirebase.jsonでhostingはbuildになっている
・npm start
でサーバーを立てると変更は反映されている
・デプロイは成功している解決
ググってもキャッシュを削除としか書かれていない記事ばかりで時間を使ってしまったが
下記のコマンドで解決した!!
npm run build
こうするとThe build folder is ready to be deployed.
と表示されるのでこの後に
firebase deploy
で完了!結果
ちゃんとデプロイ前にbuildしないといけないだけでした。
今日も見てくれてホンマありがとな!!
記事がええなと思ったらフォローといいねホンマよろしくな!!
(ジョーブログ風に)
- 投稿日:2020-05-16T10:16:44+09:00
Rails6+Reactで付箋アプリっぽいページを作ってみた。4 (UI作成編2)
記事について
前回まででUIの作成を行いましたが、見た目があまりに寂しいので、
スタイルシートを使って、それっぽく見せてみます。関連する記事
書いているうちに分量がすごくなって記事を分割したので、リンク先をまとめておきます。
その1(環境構築〜モデル作成編)
その2(API作成編)
その3(UI作成編1)
その4(UI作成編2)
おまけ(モデルのテスト編)スタイルシート
white_board/main.htmlで使われるwhite_board.scssを編集して、各DOM要素の見た目を目ます。
全体的なレイアウトにはflexレイアウト。
付箋の中身は段組が指定しやすかったのでgridレイアウトを使用しています。app/assets/stylesheets/white_board.scss// タイトル要素 div#WhiteBoardTitle { font-weight: bold; font-family: Monospace; font-size: 24px; border-collapse: collapse; } // ホワイトボード全体 // "display: flex"でflexレイアウトを指定しています。 // 左上からピチっと並べてもらいます。 div#WhiteBoard { width: 100vw; height: 100vh; display: flex; flex-flow: row wrap; justify-content: flex-start; align-items: flex-start; align-content: flex-start; } // ユーザ毎の箱 div.UserBox { margin: 1px; width: 45vw; height: 45vh; border: 1px solid; border-collapse: collapse; } // ユーザ名を表示する箱 div.UserName { width: 100%; height: 25px; font-weight: bold; font-size: 18px; font-family: Monospace; text-align: center; background-color: #FFEEAA; border-bottom: 1px solid; } // 付箋表示エリア // ここもflexレイアウト div.TaskArea { width: 100%; height: 100%; display: flex; flex-flow: row wrap; align-items: flex-start; align-content: flex-start; } // 付箋本体 // 影をつけてそれっぽく div.Sticky { display: grid; padding: 1px; margin: 5px; width: 100px; height: 80px; font-size: 10px; font-family: monospace; text-align: left; border: 1px #FFFF00 solid; background-color: #FFFF00; box-shadow: 0 5px 5px rgba(0, 0, 0, 0.3); } // タスクのタイトル div.TaskTitle { grid-row: 1; grid-column: 1 / 3; font-size: 12px; font-weight: bold; font-family: monospace; border-bottom: #FF0000 1px solid; } // タスクの本文 div.TaskDescription { grid-row: 2 / 6; grid-column: 1 / 3; font-size: 10px; font-family: monospace; } // タスクの期日 div TaskDueDate { grid-row: 7 / 8; grid-column: 1 / 3; font-size:8px; font-family: monospace; }完成イメージ
こんな感じになると思います。
これで少しはましになったかなぁ。
スタイルの変更後もテストだ。
まずは、これまで作ったテストを流してみます。
(間違ってvisible: hiddenになってしまったりなどの影響がないことが確認できます。)で、狙ったスタイルが適用されているかもテストしてみます。
test/system/whiteboards_test.rbtest "style check" do # white_board/mainを開く。 visit white_board_main_url; # タイトルはfont-weigt: bold, font-size: 24pxのはず。 # font-weightをマッチする時はboldでなく、数値(700)でマッチします。 find("div#WhiteBoardTitle").assert_matches_style("font-weight" => "700", "font-size" => "24px"); find("div#WhiteBoard").assert_matches_style("display" => "flex", "flex-flow" => "row wrap", "align-content" => "flex-start", "align-items" => "flex-start"); # ユーザ名部分 username_divs = all("div", class: "UserName"); username_divs.each do | username_div | username_div.assert_matches_style("font-weight" => "700", "font-size" => "18px"); end # 付箋 # 色はrgba(赤, 緑, 青, 透過率)で比較するようです。 stickies = all("div", class: "Sticky"); stickies.each do | sticky | sticky.assert_matches_style("background-color" => "rgba(255, 255, 0, 1)", "width" => "100px", "display" => "grid"); end endこういうテストを作っておくとスタイルシートの書き間違いなどで、狙ったスタイルが適用されてないことを検出することができます。
さらに、期日が近くなったら背景を赤くするなどの仕組みを取り入れたら、そういうのも自動でテストできるようになります。