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

【React Native】バージョン0.64を使ってみる【ダークモードも】

React Native 0.64 がリリース

2021年3月12日、React Native 0.64 が正式にリリースされました。

大きな変更点は以下の通りです。

  • iOSでのHermesエンジンサポート
  • Hermesがプロキシをサポート(非対応だったライブラリが少なくなった)
  • Reactのバージョンが 17へ(機能的な変更はなし)

使ってみる

npx react-native init projectname
cd ios && pod install

# [!] `React` requires CocoaPods version `>= 1.10.1`, which is not satisfied by your current version, `1.10.0`.

CocoaPodsのバージョンが低く、ポッドインストールできない。

Homebrewで入れていたので、確認してアップグレードする。

brew outdated
# cocoapods (1.10.0) < 1.10.1
brew upgrade cocoapods
# ==> Upgrading cocoapods 1.10.0 -> 1.10.1

再度、Pod Installする

cd ios && pod install

起動する

yarn ios
yarn android

Hermesの有効化

Podfile
use_react_native!(
    :path => config[:reactNativePath],
    # to enable hermes on iOS, change `false` to `true` and then install pods
+   :hermes_enabled => true
)
cd ios && pod install
# Installing hermes-engine (0.7.2)

完了。

スクリーンショット 2021-03-14 13.15.34.png

ダークモード

何気にダークモードがデフォルトで入っている。

設定から Dark Appearance を ONにする。

スクリーンショット 2021-03-14 13.31.51.png

自動でアプリ画面に反映される。

スクリーンショット 2021-03-14 13.31.31.png

API自体は以前からあったもの(useColorSchemeフック)と同じ。

import { useColorScheme } from 'react-native';

const App: () => Node = () => {
+ const isDarkMode = useColorScheme() === 'dark';

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

ベンチマーク

Hermesは開発モードでは有効になていないので、ipa作成して配布しないとと分からない。

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

Redux学び_3

今回も前回の続きからです。

ルーティングの設定(middlewareの導入)

ルーティング用ライブラリー
1、react-router(Reactのルーティング用ライブラリー)
2、connected-react-router(ReduxのStoreでルーティング管理)

middlewareの導入
store.js

import {
  createStore as reduxCreateStore,
  combineReducers,
  applyMiddleware
} from "redux";
import{ connectRouter, routerMiddleware} from "connected-react-router";

export default function createStore(history) {
  return reduxCreateStore(
    combineReducers({
      router: connectRouter(history),
      users: UsersReducer
    }),
    applyMiddleware(
      routerMiddleware(history)
    )
  )
}

//historyは今いるURLの場所、パスの状態ということ?
//以下はReduxのStoreにstateを生成してhistoryのパスはどこなのかを指している。
combineReducers({
      router: connectRouter(history),
      users: UsersReducer
    }),

// 以下はrouterをMiddlewareとして使いますよ〜と宣言している。
applyMiddleware(
   routerMiddleware(history)
)

StoreとRouterの接続
⇨以下までできたら、storeにルーティングの情報を持たせることができる。

index.js

import {ConnectedRouter} from 'connected-react-router';
import * as History from 'history';

const history = History.createBrowserHistory();
export const store = createStore(history);

<ConnectedRouter history={history}>
   <App />
</ConnectedRouter>

//createBrowserHistory()は現在のブラウザのULR(パス)を取得する。
//Appコンポーネントをラッピングしてパスが変わった時点でstateも変更できる

Routerコンポーネントを作る
⇨ブラウザのURLによってコンポーネントが切り替わる。

src/Router.jsx

import React from 'react';
import {Switch, Route} from 'react-router';
import {Login, Home} from './templates';

const Router = () => {
  return(
    <Switch>
      <Route exact path={ "/login" } component={Login} />
      <Route exact path={ "(/)?" } component={Home} />
    </Switch>
  )
}

Switchとexactはセットの考え方でいいが、
⇨もしexactがない場合、pathが部分一致で遷移するということだけ頭に入れたほうがいい。
⇨セットとして使う場合はURLと完全一致した時に遷移するということ。

使わない場合は例である。
ブログ記事のような投稿にそれぞれにidが付いている時など。

<Route path="/posts/:id" component={Post} />

templatesファイル作成

import React from 'react';
import {useDispatch} from 'react-redux';
import {push} from "connected-react-router";

const Login = () => {
  const dispatch = useDispatch();
  return (
    <div>
      <h2>ログイン</h2>
      <button onClick={() => dispatch(push('/'))} >ログイン</button>
    </div>
  );
}

export default Login

re-ducksパターンでファイル管理

メリット
1、actionsとreducersがシンプルになる
2、ファイルが肥大化しにくくなる。
3、ファイルごとの役割が明確になり管理しやすい。

各ファイルの役割

operations

1、複雑な処理を任せられる
2、redux-thunkで非同期処理の制御を行う
3、Actionsを呼び出す。

onClickイベントでoperationsに定義している関数呼び出し、何か処理を行い、このoperationsファイルからActionsを呼び出す。Actionsは単純にreducersにどんなデータを渡すか、そしてreducersはStoreのstateを変更してくださいね〜という流れ。

types
1、TypeScriptで使う
2、型定義を記述するしてexport

selectors
1、Storeで管理しているstateを参照する関数
2、reselectというnpmモジュールを使う。

users/selectors.js

import {createSelector} from 'reselect';

const useSelector = (state)  => state.users;

export const getUserId = createSelector(
  [useSelector],
  state => state.uid
)

//Storeのstateのusersのuidをこの関数で呼び出している。

例(今回はHome.jsxに上記のgetUserIdを呼び出し、uidの表示)

import React from 'react';
import {getUserId} from '../reducks/users/selectors';
import {useSelector} from 'react-redux';

const Home = () => {
  const selector = useSelector(state => state);
  const uid = getUserId(selector);

  return(
    <div>
      <h2>HOME</h2>
      <p>{uid}</p>
    </div>
  )
}

export default Home;

//selectorにStoreのstateを入れる。
//getUserIdの引数にselectorを入れて使う形。

redux-thunk
⇨Reduxで非同期処理を制御するライブラリー

store.js

import thunk from 'redux-thunk';


applyMiddleware(
   routerMiddleware(history),
   thunk
)

Reduxは学習は基本的に終了です。
ありがとうございました。

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

CRAで作られたReactアプリにStoryshotsを導入する

バックエンドだけでなくフロントエンドも最近はテストを導入しようという流れが出てきてます。
今回はstoryshotsを用いたテストのスナップショットテストの導入について書きます。

storyは作られてる前提とします。これから説明しますが、storyがあれば導入の手間はあまり掛からないです。

環境

yarn -v
1.22.10

node -v
v14.11.0

react@16.13.1

@storybook/react@6.1.20

などなど

入れる手順

storyshotsのインストール

yarn add -D @storybook/addon-storyshots

下記を追加

src/Storyshots.test.js
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots();

yarn testを実行すると、以下のようにテストが実行され、

storyshots.png

__snapshots__
以下にファイルが作成されました。

スナップショットの更新

コンポーネントに変更を加えた場合、保存してあるスナップショットとの差分が出るので、テストが落ちるようになるかと思います。

スナップショットテスト落としたときのやつ.png

正しい動きをしている場合はスナップショットを更新する必要があります。

スナップショットの更新は以下のコマンドです。

yarn test --updateSnapshot

ハマったポイント

テストを実行しようとしたときに下記のエラー文に遭遇しました。

$ yarn test                              
yarn run v1.22.10
$ react-scripts test

There might be a problem with the project dependency tree.
It is likely not a bug in Create React App, but something you need to fix locally.

The react-scripts package provided by Create React App requires a dependency:

  "babel-loader": "8.1.0"

Don't try to install it manually: your package manager does it automatically.
However, a different version of babel-loader was detected higher up in the tree:

  /Users/hideokaizuka/dev/PrAhaChallenge/test/storybook/my-app/node_modules/babel-loader (version: 8.2.2) 

Manually installing incompatible versions is known to cause hard-to-debug issues.

If you would prefer to ignore this check, add SKIP_PREFLIGHT_CHECK=true to an .env file in your project.
That will permanently disable this message but you might encounter other issues.

To fix the dependency tree, try following the steps below in the exact order:

  1. Delete package-lock.json (not package.json!) and/or yarn.lock in your project folder.
  2. Delete node_modules in your project folder.
  3. Remove "babel-loader" from dependencies and/or devDependencies in the package.json file in your project folder.
  4. Run npm install or yarn, depending on the package manager you use.

In most cases, this should be enough to fix the problem.
If this has not helped, there are a few other things you can try:

  5. If you used npm, install yarn (http://yarnpkg.com/) and repeat the above steps with it instead.
     This may help because npm has known issues with package hoisting which may get resolved in future versions.

  6. Check if /Users/hideokaizuka/dev/PrAhaChallenge/test/storybook/my-app/node_modules/babel-loader is outside your project directory.
     For example, you might have accidentally installed something in your home folder.

  7. Try running npm ls babel-loader in your project folder.
     This will tell you which other package (apart from the expected react-scripts) installed babel-loader.

If nothing else helps, add SKIP_PREFLIGHT_CHECK=true to an .env file in your project.
That would permanently disable this preflight check in case you want to proceed anyway.

P.S. We know this message is long but please read the steps above :-) We hope you find them helpful!

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

また,

npm list --depth=0

パッケージのバージョンを確認したときにも下記のようになりました。

npm list --depth=0
my-app@0.1.0 /Users/hideokaizuka/dev/PrAhaChallenge/test/storybook/my-app
├── UNMET PEER DEPENDENCY @babel/core@^7.9.6
├── @babel/plugin-syntax-typescript@7.8.3 extraneous
├── @babel/plugin-transform-flow-strip-types@7.9.0 extraneous
├── @babel/plugin-transform-runtime@7.9.0 extraneous
├── @emotion/react@11.1.5
├── @storybook/addon-actions@6.1.20
├── @storybook/addon-essentials@6.1.20
├── @storybook/addon-links@6.1.20
├── @storybook/addon-storyshots@6.1.21
├── @storybook/node-logger@6.1.20
├── @storybook/preset-create-react-app@3.1.6
├── @storybook/react@6.1.20
├── @testing-library/jest-dom@4.2.4
├── @testing-library/react@9.5.0
├── @testing-library/user-event@7.2.1
├── @types/jest@24.9.1
├── @types/node@12.12.34
├── @types/react@16.9.32
├── @types/react-dom@16.9.6
├── react@16.13.1
├── react-dom@16.13.1
├── react-scripts@3.4.1
└── typescript@3.7.5

npm ERR! peer dep missing: @babel/core@^7.9.6, required by @storybook/addon-essentials@6.1.20

///長すぎたので割愛

何かパッケージの依存関係に問題があるよう。

解決策

yarn testを実行したときにエラー文に下記のように書いてある。

To fix the dependency tree, try following the steps below in the exact order:

  1. Delete package-lock.json (not package.json!) and/or yarn.lock in your project folder.
  2. Delete node_modules in your project folder.

なので、yarn-locknode_modulesを削除してからyarn installを実行したところ解決した。

似たような問題に当たっている人の助けになれば幸いです。

参考

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

useReducer を書きながら学ぶ [ React Hooks 入門 ]

Reactの組み込みフックであるとuseReducerの説明をします。

useReducer とは

  • useStateと同じ様な状態管理用のhook
    • 基本的に、useStateでできることはuseReducerにもできます
  • statedispatch(actionを送信する関数)を返す

構文

const [state, dispatch] = useReducer(reducer, initialState)
  • reducer: state を 更新するための関数
  • dispatch: reducer を 実行するための呼び出し関数  - dispath には action(何を実行するかをまとめたオブジェクト) を引数を渡す

実践1...カウントアプリ(state 1つ)

useStateでも実装できるカウントアプリを今回はuseReducerを使って実装していきましょう。

手順

  • initialStateの定義
  • reducerの定義
  • useReducerの定義
  • JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)

サンプルコード
import React, { useReducer } from "react";

const initialState = 0;

const reducer = (state, action) => {
  switch (action) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <p>{state}</p>
      <button onClick={() => {dispatch('increment')}} >+</button>
      <button onClick={() => {dispatch('decrement')}} >-</button>
      <button onClick={() => {dispatch('reset')}} >reset</button>
    </div>
  );
};

export default App;

275b5aeca20974dab556afc12e2e19a7.gif

initialStateが0で、各reducerがちゃんと機能してることがわかります。

実践2...カウントアプリ(複数のstate)

実践1を少し発展させた内容で、複数のstateを持たせていきましょう。

usereduceruseStateより好ましいのは、複数の値を管理する複雑なステートロジックがある場合や、前のステートに基づいて、次のステートを決める必要がある場合です。

これを頭に入れておけば最適な使い分けの一助になるでしょう。

手順

  • initialStateの定義
  • ② JSXにinitialStateの配置
  • ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる
  • reducerの定義
    • 今回はactionにtypeとvalueをセットしているので、Switch分のcaseの宣言はactionaction.typeに変更
    • secondCounter用にcaseを増やす
    • 複数のstateを含むので、spread構文を用いてprevStateを展開し、更新します
  • useReducerの定義(前と同じ)
  • JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)

サンプルコード
import React, { useReducer } from "react";

// ① `initialState`の定義
const initialState = {
  firstCounter: 0,
  secondCounter: 10
};

const reducer = (state, action) => {
  // ④ actionにtypeとvalueをセットしているので、Switch分のcaseの宣言は`action`→`action.type`に変更
  switch (action.type) {
    case "increment1":
      return { ...state, firstCounter: state.firstCounter + action.value }; // 複数のstateを含むので、spread構文を用いて`prevState`を展開し、更新します
    case "decrement1":
      return { ...state, firstCounter: state.firstCounter - action.value }; // 上に同じ
    case "increment2":
      return { ...state, secondCounter: state.secondCounter + action.value }; // 上に同じ
    case "decrement2":
      return { ...state, secondCounter: state.secondCounter - action.value }; // 上に同じ
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      {/* ② JSXにinitialStateの配置 */}
      <p>カウント1: {state.firstCounter}</p>
      <p>カウント2: {state.secondCounter}</p>
    {/* ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる */}
      {/* ③ action 内を Object にして、typeとvalueをセットする */}
      <button onClick={() => {dispatch({ type: "increment1", value: 1 })}}>
        +increment1
      </button>
      <button onClick={() => {dispatch({ type: "decrement1", value: 1 })}}>
        -decrement1
      </button>
      <button onClick={() => {dispatch({ type: "increment2", value: 10 })}}>
        +increment2
      </button>
      <button onClick={() => {dispatch({ type: "decrement2", value: 10 })}}>
        -decrement2
      </button>
      <button onClick={() => {dispatch({ type: "reset" })}}>
        reset
      </button>
    </div>
  );
};

export default App;

eca7242260f0b62c9a0b9ed96826c0e5.gif

実践3...useReducer × useContext

useReducer で(ローカルに)扱ってきたStateを useContext を使って (グローバルに)Stateを扱っていきたいと思います。

IMG_7DA18755378F-1.jpeg

今回も前回の実践2と同様カウントアプリを作ります。(グローバルに)Stateにカウント値を持たせて各、子コンポネントで値の参照・更新ができるようにします。

29f613e15e8518fdecc5d72a170823e6.gif

手順

  • 3つの子コンポネント A〜Cの作成
  • 親コンポネントから子コンポネント3つをimport
  • reducerinitialStateの定義
  • useReducerreducerinitialStateを渡すことでcountStateとdispatch関数を作成
  • createContextを使ってcountContextを作成(exportする)
  • countContextを使って Provider を用意して valueには countとdispatchをセットする
  • 各子コンポネントでは
    • 渡されたcountContextをuseContextしてもちいる
    • valueからcountを表示させとdispatch関数からincrement, decrementを使えるようにする

サンプルコード
App.js
import React, { useReducer, createContext, createElement } from "react";
import { ComponentA } from "./components/ComponentA";
import { ComponentB } from "./components/ComponentB";
import { ComponentC } from "./components/ComponentC";

export const CountContext = createContext();

const initialState = {
  firstCounter: 0
};

const reducer = (state, action) => {
  switch (action.type) {
    case "increment1":
      return { ...state, firstCounter: state.firstCounter + action.value };
    case "decrement1":
      return { ...state, firstCounter: state.firstCounter - action.value };
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [count, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <h1>カウント{count.firstCounter}</h1>
      <CountContext.Provider
        value={{ countState: count, countDispatch: dispatch }}
      >
        <ComponentA />
        <ComponentB />
        <ComponentC />
      </CountContext.Provider>
    </div>
  );
};

export default App;
components/ComponentA
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentA = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentA</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};
components/ComponentB
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentB = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentB</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};
components/ComponentC
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentC = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentC</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};

実行結果

29f613e15e8518fdecc5d72a170823e6.gif

それぞれの子コンポネントの動作が確認できました。こうすることで、useReduceruseContextを使ってグローバルにstateを管理することができます。

実践4...useReducer × 非同期処理(useEffect)

useReduceruseEffectを使って非同期処理させて外部APIからデータ(記事の擬似データ)を取得・表示させてみましょう。

b2e512d2cb1ee20debac0bb334563ba6.gif

手順

  • useReduceruseEffectのimport
  • 非同期処理はaxiosを使う
  • initialStateの定義(loading, error, post)
  • reducerの作成
    • 引数(state,action
    • return 新しいstate
    • switch ケースは HTTPリクエストの成功時(FETCH_SUCCESS) or 失敗時(FETCH_ERROR)で処理を分けます
  • useReducerinitialStatereducer関数を読み込ませて、statedispatch関数を利用できるようにします
  • useEffect内にHTTPリクエスト非同期処理を記載する
  • JSXに、FETCH_SUCCESS時とFETCH_ERROR時の場合のコンポーネントを書く

サンプルコード
App.js
import React, { useEffect, useReducer } from "react";
import axios from "axios";

const initialState = {
  loading: true,
  error: "",
  post: {}
};

const reducer = (state, action) => {
  switch (action.type) {
    case "FETCH_SUCCESS":
      return {
        loading: false,
        error: "",
        post: action.payload
      };
    case "FETCH_ERROR":
      return {
        loading: false,
        error: "データ取得に失敗",
        post: {}
      };
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/posts/1")
      .then((res) => {
        dispatch({ type: "FETCH_SUCCESS", payload: res.data });
      })
      .catch(() => {
        dispatch({ type: "FETCH_ERROR" });
      });
  }, []);

  return (
    <div>
      <h1>{state.loading ? "Loading..." : state.post.title}</h1>
      <p>{state.error ? state.error : null}</p>
    </div>
  );
};

export default App;

実行結果

b2e512d2cb1ee20debac0bb334563ba6.gif

非同期処理なので、時間差で 「Loading」がtitleに変わっていることがわかります。

シーン別 useState と useReducer 使い分け

結論、シンプルなstateにはuseStateが向いていて、複雑なstate管理にはuseReducerがいいでしょう。

表にして、特徴をまとめました。

useState useReducer
扱うといい state の型 Number, String, Bool などのプリミティブ型を更新する時 Array, Object の更新に使う時
どんなシーンで使うといい? 単独のstate(複数でもできるっちゃできる) 複数のstateを同時に更新したい時
stateの更新に複雑なビジネスロジックがある場合 好ましくない 好ましい
ストアーの種類 ローカルな管理に向いてる useContextと合わせてグローバルに管理するとよい

以上です!

参考

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

useReducer を書きながら学ぶ(React Hooks 入門シリーズ 6/6)

ロードマップ

React 16.8 で追加された機能であるReactのHooksについて書いてあります。

書きながら学ぶ React Hooks 入門シリーズとして書き下ろしました。

これが最後です。

はじめに

Reactの組み込みフックであるとuseReducerの説明をします。

useReducer とは

  • useStateと同じ様な状態管理用のhook
    • 基本的に、useStateでできることはuseReducerにもできます
  • statedispatch(actionを送信する関数)を返す

構文

const [state, dispatch] = useReducer(reducer, initialState)
  • reducer: state を 更新するための関数
  • dispatch: reducer を 実行するための呼び出し関数  - dispath には action(何を実行するかをまとめたオブジェクト) を引数を渡す

実践1...カウントアプリ(state 1つ)

useStateでも実装できるカウントアプリを今回はuseReducerを使って実装していきましょう。

手順

  • initialStateの定義
  • reducerの定義
  • useReducerの定義
  • JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)

サンプルコード
import React, { useReducer } from "react";

const initialState = 0;

const reducer = (state, action) => {
  switch (action) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <p>{state}</p>
      <button onClick={() => {dispatch('increment')}} >+</button>
      <button onClick={() => {dispatch('decrement')}} >-</button>
      <button onClick={() => {dispatch('reset')}} >reset</button>
    </div>
  );
};

export default App;

275b5aeca20974dab556afc12e2e19a7.gif

initialStateが0で、各reducerがちゃんと機能してることがわかります。

実践2...カウントアプリ(複数のstate)

実践1を少し発展させた内容で、複数のstateを持たせていきましょう。

usereduceruseStateより好ましいのは、複数の値を管理する複雑なステートロジックがある場合や、前のステートに基づいて、次のステートを決める必要がある場合です。

これを頭に入れておけば最適な使い分けの一助になるでしょう。

手順

  • initialStateの定義
  • ② JSXにinitialStateの配置
  • ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる
  • reducerの定義
    • 今回はactionにtypeとvalueをセットしているので、Switch分のcaseの宣言はactionaction.typeに変更
    • secondCounter用にcaseを増やす
    • 複数のstateを含むので、spread構文を用いてprevStateを展開し、更新します
  • useReducerの定義(前と同じ)
  • JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)

サンプルコード
import React, { useReducer } from "react";

// ① `initialState`の定義
const initialState = {
  firstCounter: 0,
  secondCounter: 10
};

const reducer = (state, action) => {
  // ④ actionにtypeとvalueをセットしているので、Switch分のcaseの宣言は`action`→`action.type`に変更
  switch (action.type) {
    case "increment1":
      return { ...state, firstCounter: state.firstCounter + action.value }; // 複数のstateを含むので、spread構文を用いて`prevState`を展開し、更新します
    case "decrement1":
      return { ...state, firstCounter: state.firstCounter - action.value }; // 上に同じ
    case "increment2":
      return { ...state, secondCounter: state.secondCounter + action.value }; // 上に同じ
    case "decrement2":
      return { ...state, secondCounter: state.secondCounter - action.value }; // 上に同じ
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      {/* ② JSXにinitialStateの配置 */}
      <p>カウント1: {state.firstCounter}</p>
      <p>カウント2: {state.secondCounter}</p>
    {/* ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる */}
      {/* ③ action 内を Object にして、typeとvalueをセットする */}
      <button onClick={() => {dispatch({ type: "increment1", value: 1 })}}>
        +increment1
      </button>
      <button onClick={() => {dispatch({ type: "decrement1", value: 1 })}}>
        -decrement1
      </button>
      <button onClick={() => {dispatch({ type: "increment2", value: 10 })}}>
        +increment2
      </button>
      <button onClick={() => {dispatch({ type: "decrement2", value: 10 })}}>
        -decrement2
      </button>
      <button onClick={() => {dispatch({ type: "reset" })}}>
        reset
      </button>
    </div>
  );
};

export default App;

eca7242260f0b62c9a0b9ed96826c0e5.gif

実践3...useReducer × useContext

useReducer で(ローカルに)扱ってきたStateを useContext を使って (グローバルに)Stateを扱っていきたいと思います。

IMG_7DA18755378F-1.jpeg

今回も前回の実践2と同様カウントアプリを作ります。(グローバルに)Stateにカウント値を持たせて各、子コンポネントで値の参照・更新ができるようにします。

29f613e15e8518fdecc5d72a170823e6.gif

手順

  • 3つの子コンポネント A〜Cの作成
  • 親コンポネントから子コンポネント3つをimport
  • reducerinitialStateの定義
  • useReducerreducerinitialStateを渡すことでcountStateとdispatch関数を作成
  • createContextを使ってcountContextを作成(exportする)
  • countContextを使って Provider を用意して valueには countとdispatchをセットする
  • 各子コンポネントでは
    • 渡されたcountContextをuseContextしてもちいる
    • valueからcountを表示させとdispatch関数からincrement, decrementを使えるようにする

サンプルコード
App.js
import React, { useReducer, createContext, createElement } from "react";
import { ComponentA } from "./components/ComponentA";
import { ComponentB } from "./components/ComponentB";
import { ComponentC } from "./components/ComponentC";

export const CountContext = createContext();

const initialState = {
  firstCounter: 0
};

const reducer = (state, action) => {
  switch (action.type) {
    case "increment1":
      return { ...state, firstCounter: state.firstCounter + action.value };
    case "decrement1":
      return { ...state, firstCounter: state.firstCounter - action.value };
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [count, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <h1>カウント{count.firstCounter}</h1>
      <CountContext.Provider
        value={{ countState: count, countDispatch: dispatch }}
      >
        <ComponentA />
        <ComponentB />
        <ComponentC />
      </CountContext.Provider>
    </div>
  );
};

export default App;
components/ComponentA
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentA = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentA</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};
components/ComponentB
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentB = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentB</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};
components/ComponentC
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentC = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentC</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};

実行結果

29f613e15e8518fdecc5d72a170823e6.gif

それぞれの子コンポネントの動作が確認できました。こうすることで、useReduceruseContextを使ってグローバルにstateを管理することができます。

実践4...useReducer × 非同期処理(useEffect)

useReduceruseEffectを使って非同期処理させて外部APIからデータ(記事の擬似データ)を取得・表示させてみましょう。

b2e512d2cb1ee20debac0bb334563ba6.gif

手順

  • useReduceruseEffectのimport
  • 非同期処理はaxiosを使う
  • initialStateの定義(loading, error, post)
  • reducerの作成
    • 引数(state,action
    • return 新しいstate
    • switch ケースは HTTPリクエストの成功時(FETCH_SUCCESS) or 失敗時(FETCH_ERROR)で処理を分けます
  • useReducerinitialStatereducer関数を読み込ませて、statedispatch関数を利用できるようにします
  • useEffect内にHTTPリクエスト非同期処理を記載する
  • JSXに、FETCH_SUCCESS時とFETCH_ERROR時の場合のコンポーネントを書く

サンプルコード
App.js
import React, { useEffect, useReducer } from "react";
import axios from "axios";

const initialState = {
  loading: true,
  error: "",
  post: {}
};

const reducer = (state, action) => {
  switch (action.type) {
    case "FETCH_SUCCESS":
      return {
        loading: false,
        error: "",
        post: action.payload
      };
    case "FETCH_ERROR":
      return {
        loading: false,
        error: "データ取得に失敗",
        post: {}
      };
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/posts/1")
      .then((res) => {
        dispatch({ type: "FETCH_SUCCESS", payload: res.data });
      })
      .catch(() => {
        dispatch({ type: "FETCH_ERROR" });
      });
  }, []);

  return (
    <div>
      <h1>{state.loading ? "Loading..." : state.post.title}</h1>
      <p>{state.error ? state.error : null}</p>
    </div>
  );
};

export default App;

実行結果

b2e512d2cb1ee20debac0bb334563ba6.gif

非同期処理なので、時間差で 「Loading」がtitleに変わっていることがわかります。

シーン別 useState と useReducer 使い分け

結論、シンプルなstateにはuseStateが向いていて、複雑なstate管理にはuseReducerがいいでしょう。

表にして、特徴をまとめました。

useState useReducer
扱うといい state の型 Number, String, Bool などのプリミティブ型を更新する時 Array, Object の更新に使う時
どんなシーンで使うといい? 単独のstate(複数でもできるっちゃできる) 複数のstateを同時に更新したい時
stateの更新に複雑なビジネスロジックがある場合 好ましくない 好ましい
ストアーの種類 ローカルな管理に向いてる useContextと合わせてグローバルに管理するとよい

以上です!これで最後です。

連載の最後に

シリーズ全部読んでくれた方へ。

どうもありがとうございました。

参考

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

React DnDライブラリを使ってみた

現職でReactとJS(今はTS)だけであらゆる機能を実装してきましたが、最近アサインされる機能がライブラリやAPI抜きでは太刀打ちできないケースが増えてきていて、実際去年の秋に担当したある機能は外部APIを使って実装しました。

ライブラリを選定して目指す機能が実現できるかの可否を判断する「技術検証」を頼まれる事が増えてきていますが、如何せんライブラリを導入した経験がまだ浅いので、選定→インストール→実装までを一度自分で経験してライブラリを扱う勘所を掴んでおきたいと思いました。

そこで目をつけたのが「ドラッグ&ドロップ」

どんなアプリでも幅広く使われるこのUIは3年前プログラミングの勉強してる時にプレーンなJSで実装した事もありましたが(記事)開発現場ではライブラリを使う事がほとんどだと思います。

前置きが長くなりましたが、本題に入ります。

まずドラッグ&ドロップを実装する上でどのライブラリがいいかの選定、そのライブラリでやろうとしてる事が実現できるかの検証をこの記事で紹介します。

ライブラリの選定:

まずライブラリはドキュメントが豊富か、と関連する記事が多いかで判断し、このReact DnDを選定しました。
https://react-dnd.github.io/react-dnd/about

react-beautiful-dndも候補にありましたが、情報量ではReact DnDには敵わなかったです。

今回実現したい操作:

今回自分がやろうとしたのはソリティアでカードをドラッグして移動したいカードにドロップした時に何がどこにドロップされたのか?のデータを受け取り、それを元に移動の許可判定をするという処理です。ただ単にリストをソートして順番を入れ替えるとか、カラムに移動させるだけだったら公式ドキュメントのDemoに載ってるコードをそのまま使えば事足りますが、こういう実装のサンプルはなかなか無いので、公式ドキュメント、参考になりそうな記事を漁って見つけた点の情報を徐々につないでいき、なんとかこのライブラリの使い方を掴みました。GitHubにサンプルが上がっていてもバージョンが古かったりして苦戦しました。。

まずこのライブラリの登場人物は3つ。

1. DnD Provider

Drag SourceとDrop Targetをラップする大元の親Component

2. Drag Source

ドラッグして動かすもの

3. Drop Target

ドロップを受け付ける場所。

実際のコードはこんな感じです。細かいところは省いてます。
大元の親となるDnD ProviderがApp.js

import React from 'react';
import { HTML5Backend } from 'react-dnd-html5-backend'
import { DndProvider } from 'react-dnd'

const App = () => {
  return (
    <DndProvider backend={HTML5Backend}>
      <>
        //この中にDragSourceとDropTragetを置く
      </>
    </DndProvider>
  )
};

動かすDrag SourceのCard.js (これでDraggableになる)

import React, { Component } from 'react';
import { DragSource } from 'react-dnd';

const itemSource = {
  beginDrag(props) {
    return {
      card: props.card,
      parentIndex: props.parentIndex,
      isFromFoundation: props.isFromFoundation
    };
  },
  canDrag(props) {
    retrun props.card.face;
  }
}

function collect(connect, monitor) {
  return {
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging(),
  }
}

class Card extends Component {
  render() {
    const { isDragging, connectDragSource, card, index } = this.props;
    const opacity = isDragging ? 0 : 1;

    //returnする大元の親は必ずプレーンなdivでくくる必要がある、StyledComponentなどは不可
    return connectDragSource(
      card.face ? (
        <div style={{ opacity }}><CardImage index={index} src={`${process.env.PUBLIC_URL}/${card.pattern}/${card.number}.png`} /></div>
      ) : (
        <div><CardImage index={index} src={`${process.env.PUBLIC_URL}/cardFace.png`} /></div>
      )
    );
  }
}

export default DragSource('item', itemSource, collect)(Card);

Drop TargetのSingleColumn.js

import React, { Component } from 'react';
import { DropTarget } from 'react-dnd';

const itemSource = {
  drop(props, monitor) {
    const dragProps = monitor.getItem();
    //↑でCard.jsでbeginDragでreturnしたオブジェクトが取れる
    return props.onDrop(dragProps, props.index);
  }
}

function collect(connect, monitor) {
  return {
    connectDropTarget: connect.dropTarget(),
    hovered: monitor.isOver(),
    item: monitor.getItem(),
  }
}

class SingleColumn extends Component {
  renderCards = (column, parentIndex) => {
    return column.map((card, index) => {
      return <Card index={index} parentIndex={parentIndex} key={card.id} card={card} />
    })
  }
  render() {
    const { connectDropTarget, column, index } = this.props;

    //returnする大元の親は必ずプレーンなdivでくくる必要がある、StyledComponentなどは不可
    return connectDropTarget(
      <div>
        <Column index={index}>
          {this.renderCards(column, index)}
          <ColumnFrame />
        </Column>
      </div>
    );
  }
}

export default DropTarget('item', itemSource, collect)(SingleColumn);

SingleColumn.jsをまとめるColumn.js

const Columns = () => {
  const { state, dispatch } = useContext(store);

  const onDrop = (dragProps, targetIndex) => {
    //dragPropsにはCard.jsのbeginDragでreturnしたオブジェクトが入ってます
    {
      card,
      parentIndex,
      isFromFoundation
    };
   //これらの情報を元に判定処理をここで行います
  }

  const renderColumns = () => {
    return state.columns.map((column, index) => {
      return <SingleColumn index={index} key={index} column={column} onDrop={onDrop} />
    })
  }

  return (
    <>
      {renderColumns()}
    </>
  )
};

export default Columns;

DragSourceとDropTargetの関係を図にまとめるとこんな感じです。
スクリーンショット 2021-03-14 15.12.18.png

この仕組みを応用すれば画像、動画などを管理するページとかで、フォルダAをフォルダBに移動とかという実装にも応用できそうです。

ちなみにこちらが今回このドラッグ&ドロップを組み込んだソリティアです。
https://upbeat-panini-974f90.netlify.app/
まだバグありますが、ドラッグドロップでカードの移動はできます。

ソースコードはこちら
https://github.com/eiciemm/react-solitarire/tree/main/src

まとめ:

ライブラリはフレームワーク程じゃ無いにしても使いこなすのにルールを覚える点が少し似てると思いました。そしてルールを完璧に掴めば応用が効いて変わった使い方やカスタマイズも容易にできる。もちろん限度があるでしょうが。

もともとライブラリに頼るというのは悪いイメージがありました。公式ドキュメントで公開されてるDemoと全く同じように使うのであればそれは凄いラッキーで、それによって浮いた工数を他の部分の作り込みに時間を割けばいいと思いますし、Demoと違う使い方をする場合はライブラリのルールを理解しないといけなく、これが容易い作業では無いので、ライブラリ=手抜きとは一概には言えないかなと思いました。

今後はこれ以外にも話題のライブラリを触ってみようと思います。

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

Material-UIのLinkコンポーネントとreact-router-linkでページ遷移させる

Reactのmaterial-uiのLinkとreact-router-linkを使ってページ遷移させる書き方が微妙にわからなかったので備忘録がてら。
ルーティングの記述は書いてある前提とします。

Link.jsx
import { Link } from "@material-ui/core";
import { Link as RouterLink } from "react-router-dom";

const LinkComponent = (props) => {
  return (
    <Link
     component={RouterLink}
     to={props.to} //遷移先
     color="primary"
    >
     {props.text} //表示するテキスト
    </Link> )
}

解説

ポイントはMaterial-UIのLinkのcompnentにreact-router-domのLinkを渡すことです。
Linkで名前が被ってしまうので、そこはasを使って別名にしてます。

同じような悩みを持っている方のお役に立てれば幸いです。

参考

https://material-ui.com/components/links/
https://material-ui.com/guides/composition/#link

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

React + TypeScriptでreact-responsiveを使うときのメモ

はじめに

どうもこんにちはフル単進級を決めて春休みぬくぬく生きているげんしです。
課題は寮においてきました。もどってから頑張ります。

なにが起こった?

Vue / Nuxt しか触っていなかったのでReact入門するか〜〜と急に思いたったのでやっていきすることになりました。
そして適当に作ったページでメディアクエリを使いたいとなり調べてみるとreact-responsiveなるものがあると知りました。

そして使おうと思い調べてみると

$ yarn add react-responsive --save

でインストールした後

import MediaQuery from 'react-responsive';

でimportすれば使えるとありましたがCould not find a declaration file for module 'react-responsive'. と叱られてしまいます。

解決方法

JavaScriptとTypeScriptでは使うパッケージが別のようでした。
TypeScriptの場合

$ yarn add @types/react-responsive

でインストールし

import MediaQuery from 'react-responsive';

でimportすると無事エラーがなくなり使うことができました。

おわりに

まとめると
JSは yarn add react-responsive
TSは yarn add @types/react-responsive
ということでした。

調べてすぐ記事がヒットしなかったのでメモとしてこの記事を書きました。
誰かの助けになってくれたら幸いです。

参考文献

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

useStateとか、Hookとかに拒否反応を示していた私が開眼した件?

useStateってよく目にするけどよく分からない(嫌い?)

私はプログラミング初学者です。現在はReactでTODOリストなど簡単なコードの実装をして勉強しています。
勉強する中で、「useState」という文字をよくコード内で目にするのですが、正直この子の正体がよく分かっていません。よく分かっていないまま勉強を進めてきたら、useStateさんを目にする度に拒否反応を示す私がいることに気づきました^^;
ここで克服しなければ!useStateさんと仲良くならなければ!と思い、今自身で調べながらQiitaへ投稿してみます。同じようにuseStateさんとまだ仲良くなれていない初学者の方の手助けになれば幸いです。
本投稿は、私個人の理解と偏見と感覚で作成していますので、多少単語の意味合いに相違があるかもしれませんが、ご理解ください?

開発環境

  • MacOS

  • VScode

  • React

  • Github

  • Chrom

useStateって一体何者なのか

Hook(フック)です。フックはいろんな種類があるのですが、そのうちの一つです。
この記事の下の方にHookをいくつか書きましたので参照ください。

Hook(フック)って一体何者なのか

フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。
フックとは、関数コンポーネントに state やライフサイクルといった React の機能を “接続する (hook into)” ための関数です。フックは React をクラスなしに使うための機能ですので、クラス内では機能しません。
要は、useStateはフックなんです、フックはいろんな種類があります。

  • 基本のフック
    useState
    useEffect
    useContext

  • 追加のフック
    useReducer
    useCallback
    useMemo
    useRef
    useImperativeHandle
    useLayoutEffect
    useDebugValue

とりあえず、初学者は上記のフックが存在するということだけ何となく分かっていればいいのではないでしょうか。私は今のところ、useStateしか知りません?

クラスコンポーネント と 関数コンポーネント

  1. まず、コンポーネントって何?
    コンポーネントとは、「(何かの)部品」。難しく考えず、そう信じていれば大丈夫。混乱するから初学者はこれ以上突き詰めなくてよし。
    関数コンポーネントは、簡潔なコードが書ける。だからクラスコンポーネントより好まれる傾向にある。

  2. クラスコンポーネント
    昔(そんなに昔でもないけど)は主流だったが、今はサブキャラとなったコンポーネント。

  3. 関数コンポーネント
    近年登場したコンポーネント。クラスコンポーネントに代わり今の主流となっている。useStateは、関数コンポーネントの派閥に属する。
    (関数コンポーネントは、Hookを使える⇨useStateはHookの一つ。)

  4. とりあえずの結論
    関数コンポーネントをどんどん使お!

useStateを使った実装例(超簡単なコード)

  • つべこべ言わずに公式を覚えましょう

【公式1】 setState関数をインポートする

import React, {useState} from 'react';

【公式2】useState関数を定義する

const [name, setName] = useState('yui');

【公式3】useState関数の呼び出し

<input /**中略**/ onClick = {()=> setName(name)} />
  • コード(useStateを使って適当にコードを書いてみました。)
import React, {useState} from 'react';
import "./styles.css";

const App = () => {
const [name, setName] = useState('yui');
  return (
    <div className="App">
      <h1>こんにちは、{name}さん</h1>
      <button onClick = {()=> setName(name + 'i')}>ボタン</button>
    </div>
  );
};

export default App;

  • ボタンを押すと名前の「yui」の「i」の字が増えるというなんの役にも立たないやつ(笑)です。

image.png

  • ボタンを1回押すとこんな感じ

image.png

  • ボタンを連打するとこんな感じ?

image.png

タイトルには「開眼した!」って書きましたが、まだ目は半開きです。もっと勉強します!!?

参考文献

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

React/Reduxでダイエット応援アプリを作成してみた

自己紹介

多くの人と人をつなぎ合わせる事ができるシステムを開発したいと思い独学でフロントエンドエンジニアの学習を開始しました。

主なフロントエンドの学習言語は

  • JavaScript
  • React
  • Redux

上記のスキルを中心に学習し、知識をアウトプットするためにこのアプリを作成しました。

学習時間

  • JavaScript学習時間:1.5週間 (30時間)

  • React 学習時間:1.5週間 (30時間)

  • Redux 学習時間:1週間(20時間)

  • 本アプリ作成時間:1週間(24時間)

アプリの概要

タイトル

Fai Fai Diet

開発経緯

自分は体重120kgから85kgまで減量した経験があり周りからダイエットの相談をうける事が多くなりました。

ダイエットで1番大切なのは食事管理です。しかし、面倒で適当になってしまい上手くダイエットが出来ない方が多いです。

そこで誰でも簡単に食事管理ができ、一日何をどのくらい食べそれの栄養素を簡単に管理できるアプリを開発しようと考えました。

ターゲット

  • ダイエット初心者

使用イメージ

食べた食材を選択し栄養成分の確認

ページのトップには各食材のボタンを設置しました。

ボタンを押すことによってその食材の栄養成分が表示されるようになっています。

このボタンにない食材を食べた時には自ら食材の名前、栄養成分を追加できるようにしました。

second

食べた食材のグラム数を記入

最初の表には各食材100gあたりの栄養成分を表示しています。

何g食べたか数量を記入することによってその数量に合わせた栄養成分を表示します。

third

1日の栄養素の合計を計算

食べた食材を追加していくことによって1日何を食べたか管理をする事ができます。

更に各栄養素ごとの合計値も出るようにしました。

間違えて追加してしまっても各項目ごとに削除できます。

five

使用技術

・HTML ・CSS ・JavaScript ・React ・Redux ・Material-ui

データ構成

  • トップのボタンを押すことによってdispatchでそのボタンに対応するURLに移動します。
  • URLが変更されたらそれに対応するページをRouter機能を使い表示するようにしています。
  • 栄養素は各ページのstateで管理しています。
  • 追加ボタンを押すことによってstoreで管理しているstateに値を追加しています。
  • 合計の箇所はstoreで管理されているstateの値を使用し表示を行っています。

工夫した点

1 エラーが起きたときは必ず自力で解決しました

  • エラーした部分をもう一度学習して動作原理の理解を深めて行きました。
      過去学習しているときはただ単にコードを写しているだけでしたが自分で実装してエラーが出て原因を調べ改善することにより知識の本当の意味でインプットをする事ができました。

  • 復習しても解決でき無いときはエラー部分をコピーしてネットで調べました。
      最初からエラーをコピーして調べていたらなんとなく解決してしまうので、この方法は上記の方法でどうしようもない時にこのような解決方法を取りました。
      ネットで紹介されている方法をコピーして使うのではなく、なぜそうなるのか自分なりに理解してエラーを改善することにより同様なエラーが起きても素早く改善する事ができました。

2 Stateの管理の仕方

  • 不要なエラーを防ぐためにstateの管理の仕方を工夫しました. 各ページで表示される値(今回のアプリでは各ページで表示される栄養素)は各ページでuseStateを用いてstateを管理するようにしいました。
      親のコンポーネントで使いたいState(今回のアプリで合計値に格納するデータ)のみReduxを利用しStoreで管理するようにしました。

3 ボタンの必要性に合わせて表示非表示を切り替えました。

  • JavaScriptの条件演算子をbuttonタグの箇所で活用し表示非表示の切り替えを行っています。
    それによりユーザーの使い易さを意識し不要なエラーを防いでいます。

苦労した点

1 配列の操作方法

最初は配列の各要素をどう取りだすか、指定した要素の削除の仕方などがわかりませんでした。

工夫した点の1で記述したよに、調べ理解することにより解決する事ができました。

こちらのQiitaの記事でアウトプットしました

2 Firebaseへのdeploy

最初はGithubPagesへdeployしていましたが途中でFirebaseの方へ環境を移しました。

初期にGithubPagesのURLをアプリに登録していたのでFirebaseにdeployできているのにページが表示され無いエラーが起きました.

登録しているURLをFirebaseで発行されるURLに変更したところ表示されるようになりました。

今後の課題

以下が残存課題です。随時修正していきます。

  • 1日分の栄養素しか計算でき無い事です。

    過去の栄養素を記録して1週間前に食べたものとかを参照できるようにしていきます。
  • 自分なりの食材を登録できるようにしたい。

    今は自分が登録した食材しかボタン表示され無いのですが、使う人によって様々な食材を登録でき、ボタンで表示できるようにしていきます。

# 学べた事

  • アプリの内容自体はレベルが高いとは言えませんが1つのアプリを自分なりに考えて作成できたことでWEBエンジニアに不可欠な自走力が身についたと実感します。

  • Reactの基礎及Reduxの理解を深めていく事ができました。

  • この経験を生かしエラーが出ても諦めず、常にか学び続けるエンジニアになります。

学習ステップ

主に、Udemy , YouTubeを活用し勉強を行いました

GitHub

JavaScript

React Redux 

Firebase

最後にここまで読んでいただきありがとうございました。

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

Redux学び_2

今回は前回の続きからです。
前回はReducerのところまでいきました。

Store

⇨stateを保存するところ

今日のやること
・StoreとReducerを関連づける
・Redux(Store)とReactを接続

Storeのimport

1、reduxのモジュール
2、Reducerのimport

import {
  createStore as reduxCreateStore,
  combineReducers
} from "redux";

import {UsersReducer} from "../users/reducers";

createStore関数の定義

1、reduxのcreateStoreメソッドをreturn
2、combineReducersでstateを生成

export default function createStore() {
  return reduxCreateStore( //reduxのcreateStoreメソッドを別名
    combineReducers({
      users: UsersReducer
    })
  )
}

combineReducers()とは?

1、分割したReducersをまとめる。
2、stateのカテゴリ毎
3、オブジェクトをreturnする。

initialStateの初期状態が入ってくるのかな?

▪️例

combineReducers({
            product: ProductsReducer
      users: UsersReducer
    })

⬇︎ 

{
    product: {
            .
            .
            .
    }
}

{
  users: {
    isSignedIn: false,
    uid: "",
    username: ""
  }
}

StoreとReactアプリの接続

src/index.js

import {Provider} from 'react-redux';
import createStore from './reducks/store/store';

export const store = createStore(); //ここでstoreが初めて作られる。

<Provider store={store}>
   <App />
</Provider>,

react-reduxのProviderとは?

1、propsにstoreを渡す。
 ⇨上記のようにラップしたコンポーネントにstoreの情報を渡す。
2、Reactコンポート内でreact-reduxのconnect関数を使えるようにする。
 ⇨ReactとReduxを接続してstoreを変更できるように

簡単に接続確認

export const SIGN_IN = 'SIGN_IN';
export const signInActions = (useState) => {
  return {
    type: "SIGN_IN",
    payload: {
      inSignedIn: true,
      uid: useState.uid,
      username: useState.username
    }
  }
}

初期状態は、console.logで確認したところ、以下のように。

Object
isSignedIn: false
uid: ""
username: ""
__proto__: Object
const dispatch = useDispatch(); //storeのstateの変更する
const selector = useSelector((state)=> state); //storeのstateがselectorに入っている状態

console.log(selector.users);


<button onClick={ () => dispatch(signInActions({uid:'00000',username: 'kodama'})) }>
    SIGN_IN
</button>

SIGN_INがクリックされた時に以下のようにstateが変更。

{isSignedIn: false, uid: "00000", username: "kodama", inSignedIn: true}
inSignedIn: true
isSignedIn: false
uid: "00000"
username: "kodama"
__proto__: Object

今回は簡単な接続までやりました。
大体Reduxの流れがわかってきたのでいい感じです。

以上、ありがとうございました。

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

【フロントエンド】Angularを1年間触ってみてたどり着いたコンポーネント設計

個人的に思う最適なコンポーネント設計とその提案

前置き

 Angularを始めたはいいものの、しばらくして躓くことがありました。それは文法や英語のドキュメントなどよりも、なにより適切なコンポーネントの分け方がわからないということでした。

 そこでいろいろ検索をしてみたのですが、しっくりくる答えにたどり着かなかったため、自分で考えることにしました。今回はその現状報告のようなものです。(コンポーネント設計の話なので、ReactやVue.jsでも通ずる話題かと勝手に思っています)

 結果、Page-Layout-Presenter構造というコンポーネント設計を考えるに至ったのですが、この記事ではそこに至るまでの経緯を紹介し、この設計については別記事で投稿することにしました。ご興味がございましたら併せてご覧ください。
【Angular】最適なコンポーネント設計について考えてみた: Page-Layout-Presenter構造

注意:飽くまで個人的現状、最もうまくいくと考えている設計なので、プロジェクトや人によってもっと良い方法があるかもしれません!よりよいアイデアがあればコメントをいただければありがたいです。

これまでAngularを触っていて感じてきた課題

思ったよりコンポーネントを分割できない

 コンポーネントの分け方に明確なルールを持ち合わせていないため、感覚で分けるか、そもそも分けないかのどちらかになりがちでした。感覚で分けた場合、複数人で開発していると認識を合わせるのが大変です。そして、開発とともにコンポーネントを分けるコストが高くなり、それによりさらにコンポーネントがfatになるという悪循環で、どんどん動けなくなります。
 このことから、コンポーネントの分け方には明確なルールが必要だと感じました。

サービスの扱いが雑になりがち

 コンポーネントにビジネスロジックを書いてしまい、サービスとコンポーネントの境界があいまいになってしまうことがありました。また、どのコンポーネントがどのサービスを呼び出しているかを管理できず、親コンポーネントと子コンポーネントで同じサービスを繰り返し呼び出してしまうなどの無駄が発生することもありました。
 このことから、コンポーネントにビジネスロジックを書かないように意識しやすい設計にする必要があると感じました。また、サービスを呼び出せるコンポーネント群を決める必要があると感じました。

気軽にスタイルシートを変更できない

 <div class="some-block__some-element">のように、スタイルとテンプレートをclass属性で紐づけることはごく一般的だと思いますが、デザインの修正を行うたびに、classを参照して、スタイルシートに移動して、対象のスタイルを探して変更する、というのが私としては意外と手間だと感じました。そして、パーツの内容を考慮した上で、認識のずれが起こりづらいような、一意なクラス名を英語で考えるというのも結構疲れる作業だと思いました。そして、スタイルシートとテンプレートの分離がエンジニアさんとデザイナーさんとの壁になっているのではと思いました。
 また、プロジェクトによってはスタイルがすべてstyles.scssなどグローバルな場所に集められている場合もあり、変更の影響範囲がすぐに把握できないこともありました。私の現職のプロジェクトは実際そうなっていて、専任のエンジニアさんしかスタイルシートを触れないという状態でした。(そしてその方はすでに退職済みという...)
 このことから、設計を考えるうえでデザインないしデザイナーさんとのかかわり方を考えることは切っても切り離せないと感じました。

コストが高すぎてテストコードを書けない

 Angularの場合、コンポーネントやサービスなどを新規作成すると必ずついてくる*.spec.tsというテストファイルがあると思います。テスト駆動開発などではこのファイルにテストコードを書いていくことでテストを極力自動化させるのだと思っています。しかし、実際はやろうと思っても後回しにされることも多いのではないかと推測します。それは、JasmineやKarmaについて学ぶ必要があるという点もあるとは思いますが、なにより、先述のことが原因でコンポーネントがfatになっていて、テストコードの実装の難易度が上がりすぎているのではないかと考えられます。一つのボタンを押すだけでそのコンポーネントでテストすべき項目は一体いくつあるのでしょうか。
 このことから、コンポーネントを適切に分割し、最小限の役割を持たせることで、テストの範囲も分担させ、最小限にすることができるのではないかと思いました。

参考にしてきた設計思想

 これら課題を解決すべく、よりよいコンポーネント設計について考えていくことにしました。まずは、そのために参考にした設計思想などを紹介します。

【Page-Container-Presentational構造】

知ったきっかけ: Angular Webアプリケーションの最新設計手法

 紹介されているコンポーネントの分け方から、ここでは仮にPage-Container-Presentational構造と呼ばせていただくことにします。(下画像は上記リンクより引用)

image.png

参考にした点

 関心の分離の観点から、コンポーネントをグルーピングして役割を与えるという考え方が非常に参考になりました。例えばContainer Componentは状態を扱い、Presentational Componentは見た目を扱うなど。

課題

 課題というか、おそらく私の理解不足が問題なのですが、状態管理以外の点で考えた場合は、ContainerPresentationalの境界をどこで決めればよいかがあいまいに感じました。また、最小単位であるPresentationalがfatになることがあるので、共通処理などを抽出し、さらに分割したいと感じました。

【Atomic Designとドメイン駆動開発(DDD)】

知ったきっかけ: WEB+DB PRESS Vol.112
WEB+DB PRESS Vol.112の表紙

 この雑誌は発行が2019年と少し前ではありますが、最新設計手法としてAtomic DesignとDDDを両立させたコンポーネント設計について解説されていました。ちなみにAngularではなくReactとVue.jsのコードを例にして書かれていました。

Atomic Design

参考にした点

 コンポーネントを、ドメインを担うコンポーネントと、UIを担うコンポーネントに分ける考え方が参考になりました。これにより、特定のコンポーネントからしか参照しないドメイン依存なコンポーネントと、それに対し、不特定多数のコンポーネントから呼び出せるUIパーツなどの共通のコンポーネントを作れるため、再利用性が上がると考えられました。
 また、デザインの修正をページ単位よりも細かいコンポーネント単位で行えるため、ページ単位で修正を行うのに比べ、エンジニアがデザイナーを待つ時間とデザイナーがエンジニアを待つ時間が短縮されると雑誌で紹介されていました。これにより開発のサイクルがより小さく早くなります。デザイナーさんとの関わりを考慮した設計を考えるきっかけになり大変参考になりました。

課題

 Atomic Designの考えを取り入れると計5層の構造になり、さらに雑誌の手法にあるContainers層とLayout層を加える場合は7層以上になります。1つのページを作るだけでもコンポーネントの数が6つ以上必要で作るのが大変です。ほぼ記述がないコンポーネントをいくつも生成することになるので、フロントエンドのコンポーネント設計においては少し冗長に感じました。

Atomic Design + DDD Atomic Design
1 Pages Pages(ページ)
2 Containers
3 Domain Objects Templates(テンプレート)
4 Domain Elements Organisms(有機体)
5 Gui Groups Molecules(分子)
6 Gui Parts Atoms(原子)
- 必要な場所にLayout

【BEM記法】

 HTMLやCSSについて勉強しているとよく登場します。SASS記法と合わせてよく紹介されている印象があります。ざっくり言うと、自由度が高いHTMLのタグのclass属性の命名に、block__element--modifireの命名ルールを与えることで運用しやすくするという方法です。

BEM記法

参考にした点

 class属性値の命名方法にルールがあることで、他のエンジニアさんとの認識のずれを起こしづらくできることが非常にメリットと感じました。
 また、先述のAtomic Design + DDDと併用することで、スタイルのスコープをグローバルではなく上の表でいうDomain Elements単位で行えるようになりました。これにより、コンポーネントレベルでスタイルを表現できるようになり、さらに、スタイル修正のコストを減らすことにつながると考えられました。
 そして、スタイルの構造はテンプレートの構造とは関係がない、という考え方は自分にとって重要な学びでした(HTMLにどれだけ階層構造があったとしても、BEMで書くクラスはBlockとElement(Modified Element)との2層構造であるというルール)。これにより、スタイルをclass値単位で再利用可能にしたというわけです。

課題

 ただ、コンポーネント設計を考えるうえで、再利用性はコンポーネント側がすでに持っているので、もはやスタイル側に持たせる必要がありません。むしろ、異なるコンポーネントで見た目を少しだけ変えたいというような場合、再利用性があることが逆に修正を難しくする原因になりかねません。なので、スタイルの再利用はさせないほうがいいのではないかと考えています。
 そもそもクラス名を介してスタイルを紐づけるというやり方がよくないと思いました。なぜなら、その場合テンプレートからスタイルを参照する際に必ずスタイルシートに移動してさらにclass名を辿るという手順が発生するためです。これを毎度行うことになるので、やはり少々面倒です。また、class名はプロジェクトによって異なるものであり、共通認識のものではないため、ほかのエンジニアさんやデザイナーさんにとっては可読性を下げることになりかねません。また、大規模な場合は認識合わせを行ったり、ドキュメントを作成したりする必要が出てくると思うので、コストが意外と大きいのではないかと思っています。代わりに共通認識の何かで代用できれば、認識のすり合わせもドキュメント作成も不要になるのではないかと考えました。→ tailwindcss
 次に、実はコンポーネントを分割するという観点からいうと、BEMは相性がそこまでよくないという気がしています。Angularでは、親コンポーネントからは子コンポーネントのテンプレートを知ることはできません。なので、子コンポーネントのタグのclass属性も記述することができないため、コンポーネントの分割がそもそも不可能になってしまうからです。この点はangular-bemというモジュールで解決できます。これについても一苦労あったのですがその話はまたの機会とします。
 BEM記法はコンポーネントを分割しない場合には使いやすいですが、コンポーネント指向な開発にはあっていないと個人的に感じました。

【Windowsフォームアプリケーション】

知るきっかけ: 現職での仕事

Windows フォームとToolBox

参考にした点

 .NET FrameworkにWindowsフォームというものがあります。簡単に言えば一世代前の、Windows用アプリケーションを作るためのフレームワークです。Windowsフォームでアプリケーションを作成するときは、Visual StudioのToolBoxペインから、必要なコントローラ(UIパーツのようなもの)をドラッグ&ドロップすることで配置することができます。つまり、必要なUIのパーツはすでにフレームワークによって用意されていて、開発者はマウス操作一つで直感的にそれらを配置することができるわけです。
 Webアプリケーション開発でも、このように、より直感的にUIパーツを配置できれば楽なのにな、と考えるようになりました。

課題

 Angularやnpmによって、予めすべてのUIパーツが準備されていれば楽ですが、現状はそうはなっていません。また、Windowsフォームアプリケーションはスタイルの多様性が低く、スタイルをカスタマイズする場合についても考える必要があります。

【tailwindcss】

知ったきっかけ: The State of CSS 2020から読み解くWebフロントのトレンド

tailwindcss

参考にした考え方

 BEM記法はAngularで実用するには無理があると感じていたところ、tailwindを知りました。テンプレートに直接スタイルを記述できるため、頻繁に変更されると考えられるレイアウトに関するスタイルなどを効率よく操作でき、さらに、class名の運用に関する他のエンジニアさんとの認識のずれをなくせると考えられました。あと、ダークモードやレスポンシブ対応など向けの実装も簡単そうなのでいいと思いました。

課題

 現状、tailwindcssがclassとして提供できていないスタイルに関してはstyle属性か結局スタイルシートに書く必要があります。また、これはBEM記法でも同じ課題ですが、少しだけ見た目の違う場合に新しいコンポーネントとして分けるべきか悩ましく、まだ私の中で最善策が決まっていません。

【ディレクトリ構造】

中規模AngularアプリにおけるNgModule構成とディレクトリ構造

参考にした点

 アプリケーション全体で参照可能な共通のディレクトリと、同一feature内でのみ参照可能なfeaturesディレクトリを作るという点が参考になりました。また、ディレクトリ名を何にするか悩んだ時にいくつか参考にしました。

【その他参考にした記事など】

個人的に最適と思うコンポーネント設計

以上を踏まえ、できる限り課題を解決するような設計について私なりに考えてみました。
これについては別記事で投稿しましたので、よければ併せてご覧ください。
【Angular】最適なコンポーネント設計について考えてみた: Page-Layout-Presenter構造

おわりに

今後もフロントエンド開発に関して学びつつ、よりよい設計について考えていきたいと思います。
記事として未熟な点もあったかもしれませんが、一部でも参考になれば幸いです。

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