20200516のReactに関する記事は12件です。

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;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js × TypeScriptの同期・非同期処理をHooksを使って書く

概要

こんにちは、よしデブです。

今回、TypeScript×Next.jsとRedux Hooksの組み合わせに初めて挑戦したので、同期処理と非同期処理の書き方紹介しようと思います!
TypeScript×Next.jsとRedux Hooksの組み合わせの記事はいくつかあったのですが、 非同期処理のHooks についての記事が少ないように思いました。せっかくなので現在の私の中でのReduxの書き方ベストプラクティスを共有したいと思います。 あくまでも私の思うベストプラクティス なので、もっと良い書き方があるよ!という方はコメントお待ちしておりますm(_ _)m

今回は4部構成でいきたいと思います。

  1. Reduxを始めるの準備
  2. 同期処理でTodo追加・完了機能を作る
  3. 非同期処理でログイン機能を作る(メイン)
  4. (おまけ)その他のライブラリ紹介

ReduxやNext.js基本的な考え方は軽く触れますが、その詳細やTypeScriptの書き方の説明は割愛させていただきます。
TypeScriptでReduxの書き方で悩んでいる方Reduxの非同期処理の書き方に悩んでいる方の手助けになれれば幸いです。

作成したデモがこちら

Todoリストを追加、完了機能と簡単な認証機能を実装しました。
Todoのタスク追加、完了はReduxで同期的、ログイン機能は非同期処理で実現しています。
demo.gif

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.lock

Todoリストを作るためのReduxの準備

Todoリストの状態を管理するためのReduxの準備をしていきます。

ActionType、ActionCreatorを定義

早速作っていきましょう。
まずはTodoのActionType、ActionCreatorを作っていきます。
この辺のActionType、ActionCreateorの作り方はいくつかあると思いますが、私はこんな感じで書いてます。
ポイントはActionTypeは一意に決まるように定義するようにします。 これはReducerでActionTypeを見て次に返すStateを決めるからです。

src/store/todos/types.ts
export default {
  ADD_TODO: 'ADD_TODO',
  DONE_TODO: 'DONE_TODO'
} as const

ActionCreatorにはロジックを書かず、素直にActionを返します。

src/store/todos/actions.ts
import 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.ts
type 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.ts
import { 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の強みですね。
image.png

RootState、RootReducer、RootStoreを定義

最後にRootState、RootReducer、RootStoreを定義しましょう。

RootReducerは複数定義したreducerを一つにまとめます。まとめるには combineReducers を用います。今回はTodoに関するState、Reducerしかありませんが、今後、機能ごとにこれらを切り分けたい時に便利です。次に続く認証機能で新たなState、Reducerを作る予定なので最初からRootReducerを用意しておきます。

src/store/reducer.ts
import {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.ts
import { 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.tsx
import 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をガンガン使っていきたいと思います!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js × TypeScriptの同期・非同期処理をHooksを使って書く ~Reduxの準備編~

概要

こんにちは、よしデブです。

今回、TypeScript×Next.jsとRedux Hooksの組み合わせに初めて挑戦したので、同期処理と非同期処理の書き方紹介しようと思います!
TypeScript×Next.jsとRedux Hooksの組み合わせの記事はいくつかあったのですが、 非同期処理のHooks についての記事が少ないように思いました。せっかくなので現在の私の中でのReduxの書き方ベストプラクティスを共有したいと思います。 あくまでも私の思うベストプラクティス なので、もっと良い書き方があるよ!という方はコメントお待ちしておりますm(_ _)m

今回は4部構成でいきたいと思います。

  1. Reduxを始めるの準備(今回はここ)
  2. 同期処理でTodo追加・完了機能を作る
  3. 非同期処理でログイン機能を作る(メイン)
  4. (おまけ)その他のライブラリ紹介

ReduxやNext.js基本的な考え方は軽く触れますが、その詳細やTypeScriptの書き方の説明は割愛させていただきます。
TypeScriptでReduxの書き方で悩んでいる方Reduxの非同期処理の書き方に悩んでいる方の手助けになれれば幸いです。

作成したデモがこちら

Todoリストを追加、完了機能と簡単な認証機能を実装しました。
Todoのタスク追加、完了はReduxで同期的、ログイン機能は非同期処理で実現しています。
demo.gif

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.lock

Todoリストを作るためのReduxの準備

Todoリストの状態を管理するためのReduxの準備をしていきます。

ActionType、ActionCreatorを定義

早速作っていきましょう。
まずはTodoのActionType、ActionCreatorを作っていきます。
この辺のActionType、ActionCreateorの作り方はいくつかあると思いますが、私はこんな感じで書いてます。
ポイントはActionTypeは一意に決まるように定義するようにします。 これはReducerでActionTypeを見て次に返すStateを決めるからです。

src/store/todos/types.ts
export default {
  ADD_TODO: 'ADD_TODO',
  DONE_TODO: 'DONE_TODO'
} as const

ActionCreatorにはロジックを書かず、素直にActionを返します。

src/store/todos/actions.ts
import 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.ts
type 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.ts
import { 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の強みですね。
image.png

RootState、RootReducer、RootStoreを定義

最後にRootState、RootReducer、RootStoreを定義しましょう。

RootReducerは複数定義したreducerを一つにまとめます。まとめるには combineReducers を用います。今回はTodoに関するState、Reducerしかありませんが、今後、機能ごとにこれらを切り分けたい時に便利です。次に続く認証機能で新たなState、Reducerを作る予定なので最初からRootReducerを用意しておきます。

src/store/reducer.ts
import {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.ts
import { 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.tsx
import 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を使って書く ~同期的処理編~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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以上あり廃れにくそう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
const 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.js
import 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.js
import 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.js
import 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.js
import 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.js
import 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.js
import React, { Component } from 'react';

class Greeting extends Component {  // React.Componentを継承
  constructor() {
    .
    .  // ここでは省略します
    .
  }

  render() {  // JSXを返すrenderメソッド
    return(
      <h1>Hello, world.</h1>
    );
  }
}

関数コンポーネント

関数コンポーネントは、コンポーネントを関数で定義することができます。定義の仕方はfunctionでも書けますが、アロー関数で書くほうが記述量的にベターな気がします。

src/App.js
import React from 'react';

const Greeting = () => {
  return(
    <h1>Hello, world.</h1>
  );
}

このようになります。
関数コンポーネントの中身は基本的にrender()メソッドとなるので、クラスコンポーネントで記述していたrender()は省略できます。

以前は、後述する状態(state)やライフサイクルを関数コンポーネントでは持つことができなかったので、その場合にはクラスコンポーネントを作成するといった区別がありました。
しかしReact 16.8で実装されたHooksを用いることで関数コンポーネントでもクラスコンポーネントと同等の機能を持つことができるようになったので、今は基本的にコンポーネントはHooksを用いて記述されることが多いようです。
Hooksに関する記事はまた別に出します。

props

propsとは、コンポーネントに引数として渡せるプロパティ(オブジェクト)です。

propsがどんな役割を担うのか例で見てみましょう。

src/App.js
const 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.js
const 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.js
import 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.js
import 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つずつ理解していけばそこまで難しくないと思います。

statepropsと同様にコンポーネントを扱ううえで必要な機能なのでここでしっかり理解しておきましょう。最も重要な注意点は、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.propsthis.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種類ある

今回は自身の知識の土台を固めるつもりでこのような記事を書きましたが、今後も必要に応じて色々とまとめてみたいと思います。

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Typescript で React useState の引数を明示的にアノテートする

useStateの引数にstringnumberでなく、定義したオブジェクトやオブジェクトの配列を渡す場合、うまく推論できないことがある。

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の例では、空配列のみ引数に渡していたので適切に型推論できていなかった。そのため、明示的なアノテーションが必要だったというわけ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10分でReact × Dockerの環境構築

概要

React×Dockerの環境構築ではまったのでメモとして書きました:pencil2:

環境構築

作業ディレクトリを作成
$ mkdir react-docker
Dockerfile作成
Dockerfile
FROM node:8.16.0-alpine
WORKDIR /usr/src/app
docker-compose.ymlを作成
docker-compose.yml
version: "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 build
create-react-appとReactのインストール(数分かかります)
$ docker-compose run --rm web sh -c "npm install -g create-react-app && create-react-app react-sample"
コンテナ起動
$ docker-compose up
終了!

スクリーンショット 2020-05-16 11.55.23.png

ちょっとはまったところ

stdin_open: trueの記述をなしで構築するとコンテナ起動後にすぐコンテナが停止してしまう。
docker run の「-it」オプションに当たるもの。これを指定しないと、コンテナを起動してもすぐ終了してしまう:sob:

$ 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.yml
version: "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"

参考記事

-Reactの開発環境をDockerで構築してみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsとReactをdocker-composeを使って立ち上げる

GraphQLをRails, Reactで使うための第1歩として、RailsのAPIモードとReactをdocker-composeを使って同時に立ち上げられる環境を構築したのでその手順をメモしておきます

※このやり方で動くことは確認済みですが慣れておらず試行錯誤した部分もあるので効率的でない部分も多いかと思います。もしそのような箇所を発見したら指摘していただけると助かります

Railsをdocker-composeで立ち上げる

まずはAPIモードでRailsを立ち上げます

※筆者はこちらの記事の内容を参考にディレクトリ内にbundle installの内容を残す形で作成しています

Gemfile
source "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起動画面.png

※ちなみに画像にもありますが動作環境はRails 6.0.3, Ruby 2.7.1です

つづいてこのRailsをdockerで立ち上げられるようにします

docker-compose.yml
version: '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
Docker_rails
FROM ruby:2.7.1

これでsudo docker-compose up -dを行うとdockerでrails環境が立ち上がります

※docker-composeの実行は最初はsudoが必要です。sudoを外す方法はこちらの記事を参考にしてください

Reactをdocker-composeで立ち上げる

Reactも同じように作っていきます (同じディレクトリ内に作ります)

create-react-app frontend

Reactの一番簡単な構築はこれだけでできます

cd frontend
yarn start

で起動します

react起動画面.png

これもdocker-composeで起動できるようにします

先程のdocker-compose.ymlに追記します

docker-compose.yml
version: '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:3000
Docker_react
FROM node:8.16.0-alpine

これでdocker-compose-up -dをすると

http://localhost:3000→Rails

http://localhost:4000→React

というようにRailsとReactを同時に立ち上げることができます

課題点

Railsを立ち上げるのに時間がかかる (毎回bundle installし直すのなんとかしたい)

Dockerfileが余計 (image取ってくる方法があるはず)

この辺は分かり次第追記していこうと思います

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsとreactをdocker-composeを使って立ち上げる

GraphQLをRails, Reactで使うための第1歩として、RailsのAPIモードとReactをdocker-composeを使って同時に立ち上げられる環境を構築したのでその手順をメモしておきます

※このやり方で動くことは確認済みですが慣れておらず試行錯誤した部分もあるので効率的でない部分も多いかと思います。もしそのような箇所を発見したら指摘していただけると助かります

Railsをdocker-composeで立ち上げる

まずはAPIモードでRailsを立ち上げます

※筆者はこちらの記事の内容を参考にディレクトリ内にbundle installの内容を残す形で作成しています

Gemfile
source "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起動画面.png

※ちなみに画像にもありますが動作環境はRails 6.0.3, Ruby 2.7.1です

つづいてこのRailsをdockerで立ち上げられるようにします

docker-compose.yml
version: '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
Docker_rails
FROM ruby:2.7.1

これでsudo docker-compose up -dを行うとdockerでrails環境が立ち上がります

※docker-composeの実行は最初はsudoが必要です。sudoを外す方法はこちらの記事を参考にしてください

Reactをdocker-composeで立ち上げる

Reactも同じように作っていきます (同じディレクトリ内に作ります)

create-react-app frontend

Reactの一番簡単な構築はこれだけでできます

cd frontend
yarn start

で起動します

react起動画面.png

これもdocker-composeで起動できるようにします

先程のdocker-compose.ymlに追記します

docker-compose.yml
version: '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:3000
Docker_react
FROM node:8.16.0-alpine

これでdocker-compose-up -dをすると

http://localhost:3000→Rails

http://localhost:4000→React

というようにRailsとReactを同時に立ち上げることができます

課題点

Railsを立ち上げるのに時間がかかる (毎回bundle installし直すのなんとかしたい)

Dockerfileが余計 (image取ってくる方法があるはず)

この辺は分かり次第追記していこうと思います

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

firebaseにデプロイが反映されない時

問題

Reactでアプリをfirebaseにデプロイしようとしたところ
デプロイは成功しているのにURLを開くと何も変更が反映されておらず....

スクリーンショット 2020-05-16 10.46.43.png

状況

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しないといけないだけでした。

今日も見てくれてホンマありがとな!!
記事がええなと思ったらフォローといいねホンマよろしくな!!
(ジョーブログ風に)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

firebaseデプロイで変更が反映されない時

問題

Reactでアプリをfirebaseにデプロイしようとしたところ
デプロイは成功しているのにURLを開くと何も変更が反映されておらず....

スクリーンショット 2020-05-16 10.46.43.png

状況

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しないといけないだけでした。

今日も見てくれてホンマありがとな!!
記事がええなと思ったらフォローといいねホンマよろしくな!!
(ジョーブログ風に)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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;
}  

完成イメージ

こんな感じになると思います。
これで少しはましになったかなぁ。
スクリーンショット 2020-05-16 9.07.50.png

スタイルの変更後もテストだ。

まずは、これまで作ったテストを流してみます。
(間違ってvisible: hiddenになってしまったりなどの影響がないことが確認できます。)

で、狙ったスタイルが適用されているかもテストしてみます。

test/system/whiteboards_test.rb
  test "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

こういうテストを作っておくとスタイルシートの書き間違いなどで、狙ったスタイルが適用されてないことを検出することができます。
さらに、期日が近くなったら背景を赤くするなどの仕組みを取り入れたら、そういうのも自動でテストできるようになります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む