20200524のReactに関する記事は10件です。

Reactの基礎

webサイトやQiitaの投稿を基にReactを始めてみました。
インストールまでは理解できましたが、文字を打っていても、何がどうなっているのか理解できなかったので、自分なりにReactについて整理してみます。

Reactって?

上記で登場した基礎知識

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

ディレクトリ下の画像ファイルを動的に読み込む

やりたいこと

特定のディレクトリ下に画像ファイルを追加したらコード自体には一切手を加えずその画像が表示されるようにしたい。

結論

import React from 'react';

function App() {
  const importAll = (r: __WebpackModuleApi.RequireContext) => {
    return r.keys().map(v => r(v) as string);
  }

  const images = importAll(require.context('./assets/images', false, /\.(png|jpe?g|svg)$/));

  return (
    <>
      {images.map(image => <img src={image} key={image} alt={image} />)}
    </>
  );
}

require.context()で読み込むファイルを探すディレクトリと正規表現を指定して対象ファイルのパスを取得できる。

RequireContextの戻り値がanyなので、 公式ドキュメントのようにreturn r.keys().map(r) すると unknown[] となる。
今回は画像を読み込んでいるためsrcに突っ込むためにstringにキャストしている。

ドキュメント

https://webpack.js.org/guides/dependency-management/#context-module-api

参考記事

https://qiita.com/proudust/items/d716957e243f9e019fda
https://qiita.com/jkr_2255/items/d23e66323857d3189a00

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

ReactでSkyWayを試す

はじめに

この記事はとあるハッカソンのために事前勉強したものが特に使われることがなかったのでその供養のために書きます。
今回はskyway-jsReactで試してみたという内容です。実装した内容は本当にチュートリアル程度のものなのですが、意外とReact(nativeじゃない方)の記事がなかったので書いてみようと思いました。

つくったもの

こちらで公開しています。単純にお互いのIDを交換しあって1対1のビデオチャットができるというものです。SkyWayを用いることで簡単にビデオチャットをアプリに実装することができます。

開発環境

デプロイ環境としてVercelを使ってみました。理由は、もともとこのSkyWayをNext.jsでやろうと思っていてその際に公式から推奨されるものであったのがこれだったからです。しかしNext.jsはskyway-jsのインポートがうまく行かなかったので途中で断念しました。
なので今回もcreate-react-appを使ってReactの環境を構築しました。

実装内容

実装したのはこれだけです。

import React from 'react'
import { useState, useRef } from 'react'
import './App.css'
import Peer from 'skyway-js'
const peer = new Peer({ key: process.env.REACT_APP_SKYWAY_KEY })

const App = () => {
  const [myId, setMyId] = useState('')
  const [callId, setCallId] = useState('')
  const localVideo = useRef(null)
  const remoteVideo = useRef(null)
  peer.on('open', () => {
    setMyId(peer.id)
    navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(localStream => {
      localVideo.current.srcObject = localStream
    })
  })

  peer.on('call', mediaConnection => {
    mediaConnection.answer(localVideo.current.srcObject)

    mediaConnection.on('stream', async stream => {
      remoteVideo.current.srcObject = stream
    })
  })

  const makeCall = () => {
    const mediaConnection = peer.call(callId, localVideo.current.srcObject)
    mediaConnection.on('stream', async stream => {
      remoteVideo.current.srcObject = stream
      await remoteVideo.current.play().catch(console.error)
    })
  }
  return (
    <div>
      <div>skyway test</div>
      <div><video width="400px" autoPlay muted playsInline ref={localVideo}></video></div>
      <div>{myId}</div>
      <div>
        <input value={callId} onChange={e => setCallId(e.target.value)}></input>
        <button onClick={makeCall}>発信</button>
      </div>
      <div><video width="400px" autoPlay muted playsInline ref={remoteVideo}></video></div>
    </div>
  )
}

export default App

navigator.mediaDevices.getUserMedia({ video: true, audio: true })を使って、内蔵カメラ・マイクから取り込んだストリームを<video>srcObjectに取り込むReactでの良い方法がわからなかったのでuseRefを使って無理くりやりました。
コード全体はこちらで共有してますのでもう少し良い方法をご存知でしたらPRいただきたいです。

さいごに

いやー、SkyWayめちゃ簡単ですね!少し前に書いたこの記事のやつとかと組み合わせて何かやれたら面白そうだなと思いました。
とりあえず今回のハッカソンで使うことはなかったのですが、そのうちどこかで使おうかなと思います。

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

備忘:Monaca (Onsen UI V2) + React + ncmb

前提

  • MonacaでReactフレームワークを使ったプロジェクトがある状態
    • テンプレートはOnsen UI V2 Tabbar
  • ニフクラMobile Backendでアプリがある状態
    • アプリのapiKeyとclientKeyが必要

ncmbのインストール

ターミナルからインストールすると/node_modules/ncmbが追加される.

npm install ncmb -S

参考:https://www.npmjs.com/package/ncmb

ncmb APIを使うコードを追加

手っ取り早く試すために,/src/HomePage.jsxに以下を追加.
(まだReact勉強中だけど,本当はvarじゃなくてconstとかletを使うべき?)

 export default class HomePage extends React.Component {
   render() {
+    var apiKey    = "your_ncmb_apiKey";
+    var clientKey = "your_ncmb_clientKey";
+    var NCMB = require('ncmb');
+    var ncmb = new NCMB(apiKey, clientKey);
+    if (!ncmb) {
+      console.log("ncmb is null.");
+    } else {
+      console.log("ncmb is loaded.");
+    }
     return (
       <Page renderToolbar={() =>

fsの解決

このままだと

ERROR in ./node_modules/node-localstorage/LocalStorage.js
Module not found: Error: Can't resolve 'fs' in '...\node_modules\node-localstorage'

というエラーがでるので,/webpack.config.jsに以下を追加.

   performance: {
     hints: false
-  }
+  },
+
+  node: {
+    fs: 'empty'
   }
 };

参考:
https://github.com/lmaccherone/node-localstorage/issues/45
https://github.com/lmaccherone/node-localstorage/issues/35
https://qiita.com/Hoshito/items/f7acb1b2082f8c2d0bd3

apikey and clientkey requredの解決

コンパイル(トランスパイル?)は解決するけど,実行結果を見るとエラーが出る.

vendors~app.js:40 Error: apikey and clientkey required
    at Object.e (vendors~app.js:48)
    at Object.<anonymous> (vendors~app.js:48)
    at l (runtime~app.js:1)
    at t.value (app.js:1)
    at Ba (vendors~app.js:40)
    at Da (vendors~app.js:40)
    at vs (vendors~app.js:40)
    at lu (vendors~app.js:40)
    at su (vendors~app.js:40)
    at Qs (vendors~app.js:40)

これは新しいWebpackのDefineの使い方に問題があるっぽい?
とりあえず,/node_modules/ncmb/lib/ncmb.jsを編集して,問題箇所をコメントアウト.

     return NCMB;
   })();
+  /*
   if (typeof define === 'function' && define.amd) {
    define([], NCMB);
   }
+  */
   if(typeof window !== "undefined"){
     window.NCMB = NCMB;
   }

参考:
https://github.com/NIFCloud-mbaas/UserCommunity/issues/456
https://github.com/for-GET/know-your-http-well/issues/60
https://github.com/webpack/webpack/issues/5316#issuecomment-395778081
https://www.npmjs.com/package/amdefine#amdefineintercept-usage

結果

とりあえず,以上のステップで,ncmb APIが使えるようになった.
(確認したのは,login()だけ)

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

Reduxによる状態管理の仕組みを理解しよう

目次

概要

この記事では、状態管理を行うためのフレームワークであるReduxの基礎や状態管理の仕組みについてまとめています。現在Reduxについて勉強中の方の参考になるようでしたら幸いです。
なお本記事はReactを用いていることを前提条件としています。

(注意)
またReact ComponentとRedux Storeを関連づける手法として、現在はHooksとReduxを用いた手法もありますが、今回は従来のconnect関数を用いた手法で紹介しています。後日Hooksを用いた手法についても投稿予定です。

Reduxとは

Reduxとは、上記でも述べたようにReactの状態(state)を管理するフレームワークです。
またReduxはReactと併用することを想定して生み出されているため、Reactと非常に相性が良いとされています。

ReduxはFluxアーキテクチャの一つで、コンポーネントの数が多くなったときに簡単にstateを共有するための手段として利用されています。

状態(state)の共有イメージ
redux.001.png

またReduxなどのFluxアーキテクチャの最大の特長はデータフローが単方向で構築できることで、規模が大きくなった場合にもデータの流れを見失いにくくなります。(後ほど図解)

Reduxを使用するためには以下のコマンドでインストールしておく必要があります。

$ yarn add redux react-redux

Reduxの要素

Reduxによる状態管理を行うための構成要素には主に以下の4つが必要となります。

  • Action:アプリケーション内でなにが起きたのかを示すオブジェクト(データ)
  • Reducer:Actionのtype(種類)に応じてstateを変化させるメソッド
  • Store:アプリケーション内の全てのstateを保持している場所
  • State:アプリケーションの状態

Reduxのデータフロー

上記のような構成にすることで、先ほど述べたデータフローの単方向化を実現することが可能にすることができます。
redux.002.png

  1. ActionCreatorによってActionを生成
  2. Actionをdispatch
  3. ReducerでStore内のStateを更新
  4. Store内のStateをReact Componentで参照(ReactとReduxの連携)

では次にAction、Reducer、Store、ReactとReduxの連携方法について詳しく説明します。
(stateについては省略します)

Action

Actionの特徴は以下のことが挙げられます。

  • アプリケーションの中でなにが起きたかを示すオブジェクト(データ)である
  • typeとそれに対応する値を持つ(typeの値はユニークなものにする)
  • Storeの唯一の情報源
  • ActionCreatorによって生成される

ActionCreator

Actionを作成するメソッド
FluxにもActionCreatorがありますが、ReduxではActionを作成するのみでStoreへのdispatchは行わないという違いがあります。

例えばToDoリストアプリケーションでリストを追加したいときにはActionCreatorと組み合わせて次のように記述します。

const ADD_TODO = 'ADD_TODO';

// Action Creator
const addTodo = text => {
  return {
    // Action
    type: ADD_TODO,
    text
  }
}

前述の通り、ActionCreatorによって生成されたActionは、生成されたのみでStoreへdispatchされていません。
Reduxではdispatch()メソッドによってActionをStoreに送ることができます。

dispatch(addTodo(text));

Reducer

Reducerの特徴は以下のことが挙げられます。

  • Actionのtypeに応じて状態をどう変化させるのかを定義したメソッドである
  • 引数のstateを更新するのではなく、新しいStateを作成して返す
  • 純粋関数でないとならない(毎回必ず同じ結果を返す)

Reducerの実装手順は以下のようになります。

  1. 状態(state)はオブジェクトとして初期値を定義
  2. Reducerは関数として定義、引数は2つ(state, action)
  3. Actionのtypeに応じて状態を変化させ結果を返す

コードで見ると以下のようなイメージになります。

src/reducers/reducer1
import { ADD_TODO, REMOVE_TODO } from '../actions';

const initialState = { text: 'initial text' };

export default (state = initialState, action) => {
  switch(action.type) {
    case ADD_TODO:
      return {
        // ActionがADD_TODOの場合のState更新処理
      };
    case REMOVE_ADD:
      return {
        // ActionがREMOVE_TODOの場合のState更新処理
      };
    default:
      return state;  // Actionのtypeに当てはまらない場合はデフォルトを返す
  }
};

ToDoリストとして正しいinitialStateの設定かは分かりませんが、Reducerの実装イメージとしてはこんな感じになります。

またReduxではアプリケーション内に存在する全てのReducerを1つのReducerとして結合することできます。

src/reducers/index.js
import { combineReducers } from 'redux';  // このimportが必要
import reducer1 from './reducer1';
import reducer2 from './reducer2';

export default combineReducers({ reducer1, reducer2 });

Store

Storeの特徴は以下のことが挙げられます。

  • Storeはアプリケーション内で唯一のもの
  • アプリケーション内のStateが全て集約されている
  • 従来のReactではpropsを目的のコンポーネントまでバケツリレー形式で渡していたが、<Provider>によりその必要がなくなる(react-reduxからインポート)

Storeは以下のように作成します。

src/index.js
import {createStore} from 'redux';
import reducer from './reducers';

const store = createStore(reducer);

connect関数

ReactとReduxは互いに独立しているため、なにもしないままだとReact ComponentをReduxのフローに乗せることができません。そこでReact Reduxが提供しているconnect()関数を利用して、ReduxのStateやActionとReact Componentを接続します。このとき接続されたComponentはContainerと呼ばれることもあります。

このようにconnect()関数によってContainerではStoreから必要なデータ(Stateの一部)と、ActionをStoreに渡すためのdispatch()関数を使用することができます。
基本的な記述方法としては以下のように書きます。

connect(mapStateToProps, mapDispatchToProps)(App);

ここでconnect()関数の引数に渡されているmapStateToPropsmapDispatchToPropsについて詳しく見ていきましょう。

mapStateToProps

StoreにあるStateの情報からContainerで必要な情報を取り出し、Container内のpropsとしてマッピングする機能を持つ関数です。mapStateとして呼ばれることもあります。
上記でも示したようにconnect()関数の第1引数になります。

呼ばれるタイミング Storeの状態が変化したとき
第1引数 State(Storeの全ての状態)
第2引数(オプション) 自身のprops(ownProps)
戻り値 Containerが必要としているStateをpropsとして返す
const mapStateToProps = state => {
  return {
    // 全体のStateから必要な情報 (state.valueなど)
    // value: state.value
    // のように記述
  };
};

ownPropsを第2引数に設定した場合、最終的な戻り値にはownPropsと新たに得たデータが1つのpropsとしてマージされます。

mapDispatchToProps

ActionをStoreにdispatchするdispatch()関数をpropsにマッピングするために使用されます。mapDispatchとして呼ばれることもあります。
dispatch()関数とは、あるActionが発生したときにReducerにtypeに応じた状態遷移を実行させるための関数です。Storeに標準でdispatch()関数が用意されています。

mapDispatchToPropsconnect()関数の引数に指定しなくてもコンポーネントはデフォルトでprops.dispatchを受け取れるので、これを使用してStoreにActionをdispatchすることができます。

ではmapDispatchToPropsを使用する理由はなんでしょうか?
例として、「ボタンを押すとvalueが+1される」というContainerを見てみましょう。

mapDispatchToPropsを使用しない場合
Container側でActionCreatorによってActionを作成した段階ではdispatchされていないので以下のように記述する必要があります。

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increment } from '../actions';

class Counter extends Component {
  render(){
    const props = this.props;
    return (
      <React.Fragment>
        <div>count: {props.value}</div>
        <button onClick={() => dispatch(increment())}>+1</button>  {// ここ }
      </React.Fragment>
    );
  }
}

const mapStateToProps = state => ({ value: state.count.value});
export default connect(mapStateToProps /*, 第2引数なし(mapDispatchToProps) */)(Counter);

若干JSXの記述が複雑になっています。

mapDispatchToPropsを使用した場合

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increment } from '../actions';

class Counter extends Component {
  render(){
    const props = this.props;
    return (
      <React.Fragment>
        <div>value: {props.value}</div>
        <button onClick={props.increment}>+1</button>
      </React.Fragment>
    );
  }
}

const mapStateToProps = state => ({ value: state.count.value});
const mapDispatchToProps = ({ increment });
// 上記は下の記述と同じ意味
// const mapDispatchToProps = dispatch => ({
//   increment: () => dispatch(increment())
// });

export default connect(mapStateToProps, mapDispatchToProps)(App);

このように記述することができます。
mapDispatchToProps内ではActionCreatorでActionを生成しdispatchまで行う処理を関数に定義してpropsに渡しています

今回は処理が少ない場合でしたのであまり影響はありませんが、Actionの種類や作成の数が多くなった場合にはmapDispatchToPropsを使用することでContainer側で毎回dispatch処理を記述する必要がなくなります。

呼ばれるタイミング ActionCreatorが呼び出されたとき
(今回はクリックされたとき)
第1引数 dispatch()関数
第2引数(オプション) 自身のprops(ownProps)
戻り値 指定したActionのdispatch処理をpropsとして返す

第2引数としてownPropsを設定した場合、Containerが新しいpropsを受け取ったタイミングでmapDispatchPropsが呼び出されます。

Provider

Component階層の最上位のComponentを<Provider>でネストすることで全体のComponentをStoreに接続することが可能になります。
それによって任意のComponentでconnect()関数を用いてStoreと接続できます。

コードの記述イメージは以下のようになります。

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import { App } from './App';
import createStore from './createReduxStore';

const store = createStore();

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

まとめ

長くなりましたが、Reduxの基礎については以上にしたいと思います。
では簡単に今回の記事の内容をまとめます。

  • Redux:アプリケーションの状態管理を行うためのフレームワーク
  • Action:アプリケーションでなにが起きたのかを示すオブジェクトデータ
  • Reducer:Actionの種類に応じて状態を変化させるメソッド
  • Store:アプリケーションの全ての状態を保持している場所
  • connect():React ComponentとReduxを接続するためのメソッド
  • Provider:connect()でComponentをStoreに接続するために必要なもの

各要素の役割とデータフローをしっかり抑えておくことが重要です。
Reduxについては個人的に理解するのがなかなか大変で、まだ完全には理解できていない面もあるので今後も勉強していきたいと思います。

参考資料

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

React入門 第二章 ~Reactを使ってみよう Todoアプリ~

React入門目次

  1. 第一章 ~環境構築 Hello React!!~
  2. 第二章 ~Reactを使ってみよう~
  3. 第三章 ~Reactでhooksを使おう~
  4. 第四章 ~ReactでAPI通信をしてみよう(axios使用)~
  5. 第五章 ~ReactにReduxを組み込もう(hooks仕様)~
  6. 第六章 ~React × Reduxをtypescriptで書こう(hooks仕様)~
  7. 第七章 ~React × Redux × typescriptでAPI通信(redux-saga・axios・hooks仕様)
  8. 番外編 ~atomic designとは~

初めに

Reactを学びたい・使いたいと考えている人・SPA作りたい人向けにReactの基本からreduxやtypescript等の応用を導入するところまで説明していきます。(順次公開していきます)

第二章 ~Reactを使ってみよう~

今回はReactを実際に使ってReactの使い方を説明していきます。

第二章の流れ

  1. Reactとは
  2. コンポーネントを作ってみよう
  3. クラス記法とfunction記法
  4. todoアプリを作ろう
  5. 練習問題

1. Reactとは

Reactとはユーザーインターフェイス構築の為のJavaScriptライブラリです。
主にSPA(シングルページアプリケーション)の構築に使われています。
大きな特徴としては3つあります。
1. コンポーネント指向によりUIの高再利用性: 一度作ったUIを他の画面・アプリケーション等で使いまわせる
2. 仮想DOMによるレンダリングコストの削減: 変更箇所の差分検知=>差分箇所の変更をする事で従来の全てを作り直すDOM処理より変更が高速になる。
3. jsx記法: JavaScript内にHTMLを書くような書き方ができる。

2. コンポーネントを作ってみよう

まずプロジェクトを作ります。
今回はtodoアプリを作っていくのでプロジェクト名react-todoで作ります。
下記のコマンドでプロジェクトを作ります。

create-react-app react_todo

srcの配下にcomponentsという名前でフォルダーを作ります。
componentsにTodo.jsxというファイルを作ります。

コード内容の説明
constructor内でstateを初期化しています。
returnでtodoを表示するpタグを作っています。
export default TodoでTodoコンポーネントを別ファイルでimportできるようにしています。

src/components/Todo.jsx
import React from "react";
class Todo extends React.Component{
    constructor(props){
        super();
        this.state = {
            todo: "react勉強中"
        }
    }
    render(){
        return(
            <div>
                <p>{this.state.todo}</p>
            </div>
        );
    }
}
export default Todo;

App.jsでTodo.jsxをimportし、今作ったTodoコンポーネントを反映させます。

src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Todo from './components/Todo'

function App() {
  return (
    <div className="App">
      <Todo/>
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

では結果を見ます。
下記コマンドで起動してください。

npm start

結果画面

image.png

3. クラス記法とfunction記法

Reactにはいくつか書き方があります。
classでコンポーネントを作る記法とfunctionでコンポーネントを作る記法です。

先ほど作ったコンポーネントはclass記法で書いていましたが、
function記法でも同じ結果を作ることができます。

下記のようにTodo.jsxを書き直してみてください。
function記法とhooksを使った書き方です。

src/components/Todo.jsx
import React, {useEffect, useState} from 'react';
/*
class Todo extends React.Component{
    constructor(props){
        super();
        this.state = {
            todo: "react勉強中"
        }
    }
    render(){
        return(
            <div>
                <p>{this.state.todo}</p>
            </div>
        );
    }
}
*/
const Todo = (props) =>{
    const[todo, setTodo] = useState([]);
    useEffect(() => {
        setTodo("react勉強中 function")
    },[])
    return(
        <div>
            <p>{todo}</p>
        </div>
    );
}
export default Todo;

結果
image.png

ではどちらを使った方がいいのかという疑問が湧くと思います。
個人的にはfunction記法をお勧めします。

よく知りたいという方は下記URL等でご確認ください。
https://overreacted.io/ja/how-are-function-components-different-from-classes/

最初は記法が何種類かあるんだなと思ってもらい自分と違う記法を見ても驚かないようにしておけば大丈夫だと思います。
今回はclassコンポーネントで作っていきます。

4. todoアプリを作ろう

まずはAppjsxのファイルの一つのコンポーネントだけでTodoアプリを作ります。
App.jsxを下記のコードに書き換えます。

src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Todo from './components/Todo'

class App extends React.Component {
  constructor(props){
    super();
    this.state = {
        todoList: ["React勉強", "todoアプリ作成"]
    }
    this.handleAdd = this.handleAdd.bind(this);
  }
  handleAdd(e){
    e.preventDefault();
    this.state.todoList.push(e.target.todo.value);
    this.setState(this.state.todoList);
    e.target.todo.value = '';
  }
  render(){

      return(
          <div>
            <form onSubmit={this.handleAdd}>
              <input type="text" name="todo"/>
              <input type="submit" value="追加"/>
            </form>
              <ul>
              {this.state.todoList.map(
                (item, i) => {
                    return<li key={i}>{item}</li>
                }
              )}
              </ul>
          </div>
      );
  }
}

export default App;

結果
textBoxの中にtodoを入力して追加ボタンを押しtodoを追加してみてください。
image.png

これでTodoアプリ自体は完成ですが、これはReactらしい書き方では無くReactを活かせていません。
なぜなら、コンポーネント指向で作っていないからです。

ではコンポーネントを分けて作っていきます。
今回はForm部分とList部分でそれぞれコンポーネントを作りAppコンポーネントで合わせる方法でやります。

まず、componentsフォルダにForm.jsxとTodoList.jsxを作ってください。

それぞれのファイルに下記のコードを書いてください。

components/Form.jsx
import React from 'react';

class Form extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      input: ''
    };
  }
  handleChange = e => {
    this.setState({ input: e.currentTarget.value })
  };

  handleSubmit = e => {
    e.preventDefault();
    this.props.onSubmit(this.state.input);
    this.setState({ input: '' })
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="text" value={this.state.input} onChange={this.handleChange} />
        <button>追加</button>
      </form>
    );
  }

}

export default Form;
components/TodoList.jsx
import React from 'react';

class TodoList extends React.Component {
  render() {
    const {todoList} = this.props 
    return (
      <div>
            <ul>
              {todoList.map(
                (item, i) => {
                    return<li key={i}>{item}</li>
                }
              )}
            </ul>
      </div>
    )
  }
}
export default TodoList;

App.jsxを書き変えます。

App.jsx
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Todo from './components/Todo'
import Form from './components/Form';
import TodoList from './components/TodoList';

class App extends React.Component {
  constructor(props){
    super();
    this.state = {
        todoList: ["React勉強", "todoアプリ作成"]
    }
    this.handleAdd = this.handleAdd.bind(this);
  }
  handleAdd(value){
    this.setState({todoList: this.state.todoList.concat(value)});
  }
  render(){
      return(
          <div>
            <Form onSubmit={this.handleAdd}/>
            <TodoList todoList={this.state.todoList}/>
          </div>
      );
  }
}

export default App;

propsで状態ををバケツリレーしているイメージですね。
コンポーネントの分け方はいろいろな分け方できるので状況に応じてコンポーネントの切り分けをしてください。
例)今回はtodoListとFormで分けましたが、TodoListをさらにtodoとListに分けることやFormをさらにTextBoxとButtonのコンポーネントとして分けることもできます。

それではReact練習として今作成したTodoアプリにtodoの削除機能をつけてみてください。
ヒント 1:各Todoに削除ボタンを作り、onClickメソッドに削除機能をを入れます。
ヒント 2:TodoListの各Todoに削除で使う為のidを追加します。

答え:
下記のように各ファイルを変更します。

components/TodoList.jsx
import React from 'react';

class TodoList extends React.Component {
    handleDelete = e => {
        const id = e.target.value;
        const {onDelete} = this.props;
        onDelete(id);
    }
    render() {
        const {todoList} = this.props 
        return (
        <div>
                <ul>
                {todoList.map(
                    (item) => {
                    return<li key={item.id}>{item.content}<button value={item.id} onClick={this.handleDelete}>削除</button></li>
                    }
                )}
                </ul>
        </div>
        )
    }
}

export default TodoList;
App.jsx
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Todo from './components/Todo'
import Form from './components/Form';
import TodoList from './components/TodoList';

class App extends React.Component {
  constructor(props){
    super();
    this.state = {
        id: 3,
        todoList: [{id: 1, content: "React勉強"},{id: 2, content: "todoアプリ作成"}],
    }
    this.handleAdd = this.handleAdd.bind(this);
  }
  handleAdd(value){
    this.setState({todoList: this.state.todoList.concat({id: this.state.id, content: value})});
    this.state.id++;
  }
  handleClickDelete = id => {
    console.log(id);
    this.setState({ todoList: this.state.todoList.filter(todo => todo.id != id) });
  };
  render(){

      return(
          <div>
            <Form onSubmit={this.handleAdd}/>
            <TodoList todoList={this.state.todoList} onDelete={this.handleClickDelete}/>
          </div>
      );
  }
}

export default App;

結果
image.png

これで第2章は終わりです。
次回は第三章 ~Reactでhooksを使おう~です。第三章以降はfunction記法で説明していきます。

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

React入門 第一章 ~環境構築 Hello React!!~

React入門目次

  1. 第一章 ~環境構築 Hello React!!~
  2. 第二章 ~Reactを使ってみよう~
  3. 第三章 ~Reactでhooksを使おう~
  4. 第四章 ~ReactでAPI通信をしてみよう(axios使用)~
  5. 第五章 ~ReactにReduxを組み込もう(hooks仕様)~
  6. 第六章 ~React × Reduxをtypescriptで書こう(hooks仕様)~
  7. 第七章 ~React × Redux × typescriptでAPI通信(redux-saga・axios・hooks仕様)
  8. 番外編 ~atomic designとは~

初めに

Reactを学びたい・使いたいと考えている人・SPA作りたい人向けにReactの基本からreduxやtypescript等の応用を導入するところまで説明していきます。(順次公開していきます)

第一章 ~環境構築~

今回第一章では開発環境を作っていきましょう

第一章の流れ

  1. node.jsをインストールしよう
  2. パッケージマネージャーの確認
  3. creat-react-appをインストールしよう
  4. VSCodeをインストールしよう
  5. reactを起動しよう
  6. Hello React!!

1.node.jsをインストールしよう

node.jsはReactを使う際に必須なのでインストールします。
まず、https://nodejs.org/ja/ に行きLTS版をダウンロード
image.png

ダウンロード・インストール共に終わったらインストールの確認をします。
バージョンが出てればOK!!

node -v
v12.16.3

2.パッケージマネージャーの確認

パッケージ管理をしてくれるシステムです。
npmやyarn等種類はあるのですが今回はnode.jsと一緒にインストールされているはずのnpmを利用していきます。

npmが入っているかの確認
バージョンが出ればOK!!

npm -v
6.13.4

どうしてもyarnが使いたいという人は
下のコマンドでインストールとインストール確認をしてください。(上記のようにnpmを使う人は飛ばしてください)
バージョンが出ればOKです

npm install -g yarn
yarn -v

3.creat-react-appをインストールしよう

creat-react-appとはReactで使う基本的なパッケージをまとめて簡単にインストールしてくれるものです。
(Webpack, Babel ,ESLint等を個別でインストールしなくていいよって感じです。)

下記のコマンドでインストールしてください。

npm install -g create-react-app

4.VSCodeをインストールしよう

VSCodeはエディターです。
エディターに強いこだわりがある人以外はVSCodeをインストールしてください。
Reactの開発にはVSCodeが本当に使いやすいです。

下記リンクからダウンロードしてください。
https://code.visualstudio.com/

image.png

5.reactを起動しよう

まずは下記のコマンドでアプリケーションを作りましょう。
create-react-appの後ろはプロジェクト名を書きます。今回はhello_reactにします。

コンソールの最後にHappy hacking!が出れば成功です

create-react-app hello_react

次に作成したプロジェクトに移動し、起動します。

cd hello_react
npm start

ブラウザに下記の画面がでればOK!!
image.png

6.Hello React!!

では第一章の最後
実際にReactを見て書いていこう

まず、VSCodeを開いてください。
Open Folderをクリックし先ほど作成したプロジェクトを開いてください。
image.png

開いたらsrcフォルダー内のApp.jsxを開いてください。
少し解説するとreturn()内部に記述されている箇所が現在ブラウザに表示されているコードです。

src/App.jsx
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

このreturn()中のコードを変えてHello React!!を表示させます。
下記のようにApp.jsxを変えてください。

src/App.jsx
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <p>Hello React!!</p>
    </div>
  );
}

export default App;

下記のように表示されたらOK!!
image.png

お疲れ様でした!!
これで第一章 ~環境構築 Hello React!!~は終了です。

次回は第二章 ~Reactを使ってみよう~です。

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

create-react-app で .eslintignore を有効にする

WHY?

.eslintignore に定義されたファイルとディレクトリはESLintの検査対象外となります。
通常はrootディレクトリに配置するだけでOKですが、
create-react-appで作成した場合はrootディレクトリに配置しただけでは有効になりません。

手順

package.jsonと同じところに.envファイルを用意して以下を定義ます。

.env
EXTEND_ESLINT=true

以上です。

こちらに載ってました。
Enable .eslintignore with extend flag (#7562)

おまけ

create-react-appの中はこんな感じです。

packages/react-scripts/config/webpack.config.js
{
  ignore: process.env.EXTEND_ESLINT === 'true',
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

create-react-appで.eslintignore を有効にする

WHY?

.eslintignore に定義されたファイルとディレクトリはESLintの検査対象外となります。
通常はrootディレクトリに配置するだけでOKですが、
create-react-appで作成した場合はrootディレクトリに配置しただけでは有効になりません。

手順

package.jsonと同じところに.envファイルを用意して以下を定義ます。

.env
EXTEND_ESLINT=true

以上です。

こちらに載ってました。
Enable .eslintignore with extend flag (#7562)

おまけ

create-react-appの中はこんな感じです。

packages/react-scripts/config/webpack.config.js
{
  ignore: process.env.EXTEND_ESLINT === 'true',
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Typescript (.tsx)で 郵便番号フォームを作る。

はじめに

こんにちは

最近は、もっぱらGo Nuxtで作ったポートフォリオサイトをブラッシュアップし続けてそこら辺もだいぶ収集がついてきたので、
新しいことをやろうと以前から目をつけていたReact(Typescript込み)をやろうと思い、実際に公式のチュートリアルをやってみたところ楽しくてハマってしまい、こりゃなんか作ろうか、ということで郵便番号を受け取って自治体情報を返すアプリを作っておりました。

しかし、なんか無料公開されている郵便番号APIがしっくり来るものが見当たらなかったため(そんなに探してない)来たるべき時代までGithubに冷凍保存しておこうかと、そして、そのついでにコードの鎮魂のために郵便番号を受け取るフォーム部分を記事に書いておこうかなということで投稿します。

ちなみに、構築はローカルにcreate-react-app [アプリ名] --typescriptコマンドです。

※この記事の作者?React=初心者&typescript=仕事でちょっとだけ通り過ぎただけ
※郵便番号を受け取るフォームコンポーネント部分だけを書いていきます。

本編

まず、郵便番号を受け取るフォームに必要な要素がなにかを考えました。

[課題]
1. 前後の空白を削除する。
2. 全角で入力されたら、そのまま全角で受け取りたく無い->半角にする。
3. 3文字目と4文字目の間にハイフンを入れてくる人もいる。->ハイフンを消す?
4. 7桁の数字を文字列で受け取る。-> 7桁以外ならボタンをdisableにする?

おまけ

  • 日本語表記と英語表記を切り替えたい

まずコンポーネントのファイルをForm.tsxとかで作成します。
App.tsx ------> Home.tsx ------> Form.tsx の流れでpropsを渡します。
API叩く処理は割愛します。

一個上のHome.tsxはこんな感じです。

Home.tsx
  import React, { FC } from 'react'
  import Form from '../components/Form'
  import * as w from '../words.json'

  // App から flag を受け取っています。
  type Props = {
    flag: boolean
  }
  const postForm = (value: string): void => {
    // axiosなどでAPI叩く?
  }

  const Home: FC<Props> = props => {
    // 日本語か英語科の分岐
    const btnstr: string = props.flag ? w.btnstr_en : w.btnstr
    const placeholder: string = props.flag ? w.placeholder_en : w.placeholder
    return (
      <React.Fragment>
        <Form postForm={postForm} btntext={btnstr} placeholder={placeholder} />
        <style>{`
        `}</style>
      </React.Fragment>
    )
  }
  export default Home  

おまけ要素の英語との切り替えのためにAppからflagをもらってきてる以外は普通です。
flagによって英語と日本語を切り替えます。(おまけクリアー)

Formコンポーネントにpropsとしてbtntext(ボタンに表示するテキスト)とplaceholder(input内に表示するテキスト)とボタンを押したときの関数postFormを渡しているだけです。

ちなみにwords.jsonからテキストをひっぱってきているので載せておきます。

words.json
  {
    "btnstr": "検索",
    "placeholder": "郵便番号入力",
    "unsuitable": "文字数が適切ではありません",
    "isnumber": "値には数値を入力してください",

    "btnstr_en": "Click Me",
    "placeholder_en": "Please, input zipcode",
    "unsuitable_en": "The number of characters is incorrect",
    "isnumber_en": "Please, input a number"
  }

そんで、本題のForm.tsxです。

Form.tsx
  ...

  const [value, setValue] = useState('')

  ...

  <input
    type="text"
    ...
    // setValueで更新されたvalueが入る。
    value={ value }
    onBlur={(e) => {
      let newVal: string = ConvertToHS(e.target.value)
      newVal = OptStr(newVal)
      // React Hooksでstate更新
      setValue(newVal)
    }}
  />

inputからいきます。受け取った値をチェックしたいです。
onBlurをみてください。

  • [課題]1,3: 前後の空白カット。, ハイフンの除去 -> OptStr
  • [課題]2: 全角を半角にする。 -> ConvertToHs

という外部関数で解消します。

1の方はハイフンを取り去って前後の空白を除去、
2の方は単に正規表現で全角->半角の置き換えをしているだけです。

  export const OptStr = (str: string): string => {
    let returnStr: string = ''
    const slice: Array<string> = str.split('-')
    returnStr = slice.join('')
    return returnStr.trim()
  }

  export const ConvertToHS = (str: string): string => {
    return str.replace(/[A-Za-z0-9]/g, function (s) {
      return String.fromCharCode(s.charCodeAt(0) - 0xfee0)
    })
  }

これらの受けとった値を整形する1~3の課題はフォーカスが外れたときなどに実行したいのでOnBlurにしました。

[課題]4のdisableもやっちゃいましょう。
しかも、なんならついでにvalidationもクライアントの方である程度したい。

Form.tsx
  const unsuitable: string = props.flag ? w.unsuitable_en : w.unsuitable
  const isnumber: string = props.flag ? w.isnumber_en : w.isnumber
  const [value, setValue] = useState('')
  const [validatemessage, setValidatemessage] = useState('')  
  ...

  <input
    ...
    value={value}
    placeholder={props.placeholder}
    autoComplete="postal-code"
    onChange={(e) => {
      setValue(e.target.value)
      !(
        (e.target.value.indexOf('-') === -1 &&
          e.target.value.length === 7) ||
        (e.target.value.indexOf('-') !== -1 &&
          e.target.value.length === 8)
      )
        ? setValidatemessage(unsuitable)
        : setValidatemessage('')
    }}
    onBlur={(e) => {
      let newVal: string = ConvertToHS(e.target.value)
      newVal = OptStr(newVal)
      setValue(newVal)
      newVal && newVal.length !== 7
        ? setValidatemessage(unsuitable)
        : setValidatemessage('')
    }}
  />

  <button
    disabled={
      !(
        (value.indexOf('-') === -1 && value.length === 7) ||
        (value.indexOf('-') !== -1 && value.length === 8)
      )
    }
    onClick={async () => {
      if (!isNaN(Number(value))) {
        value.length === 7
          ? await props.postForm(value)
          : await setValidatemessage(unsuitable)
      } else {
        await setValidatemessage(isnumber)
        await setValue("")
      }
    }}
  >
    {props.btntext}
  </button>

onChangeでReactHooksを使ってvalidationの更新を行っていきます。
そのときにハイフンが入っているときはハイフン含めて8文字になりますが、受け取れるようにしています。

buttonの中のdisabledにもハイフンが入っている場合は8文字を受け取る処理を書いて、
クリックしたときに
if (isNan(...)) {}
で数字かどうかを判定して入力が不適切ならvalidationにいれます。([課題]4クリアー)

煩雑になりましたが、以上です。

最後に完成版載せておきます。

Form.tsx
  import React, { FC, useState } from 'react'
  import { OptStr, ConvertToHS } from 'react-app-env.d'
  import * as w from '../words.json'

  type Props = {
    flag: boolean
    btntext: string
    placeholder: string
    postForm: (value: string) => void
  }

  const Form: FC<Props> = (props) => {
    // 日本語と英語の分岐
    const unsuitable: string = props.flag ? w.unsuitable_en : w.unsuitable
    const isnumber: string = props.flag ? w.isnumber_en : w.isnumber
    // React Hooks でstateを更新するかー
    const [value, setValue] = useState('')
    const [validatemessage, setValidatemessage] = useState('')
    return (
      <React.Fragment>
        <div className="form">
          <div className="form__input__wrapper">
            <p className="validate">{validatemessage}</p>
            <input
              type="text"
              value={value}
              className="form__input"
              placeholder={props.placeholder}
              autoComplete="postal-code"
              onChange={(e) => {
                setValue(e.target.value)
                !(
                  (e.target.value.indexOf('-') === -1 &&
                    e.target.value.length === 7) ||
                  (e.target.value.indexOf('-') !== -1 &&
                    e.target.value.length === 8)
                )
                  ? setValidatemessage(unsuitable)
                  : setValidatemessage('')
              }}
              onBlur={(e) => {
                let newVal: string = ConvertToHS(e.target.value)
                newVal = OptStr(newVal)
                setValue(newVal)
                newVal && newVal.length !== 7
                  ? setValidatemessage(unsuitable)
                  : setValidatemessage('')
              }}
            />
          </div>
          <button
            className={
              !(
                (value.indexOf('-') === -1 && value.length === 7) ||
                (value.indexOf('-') !== -1 && value.length === 8)
              )
                ? 'disable form__btn'
                : 'form__btn'
            }
            disabled={
              !(
                (value.indexOf('-') === -1 && value.length === 7) ||
                (value.indexOf('-') !== -1 && value.length === 8)
              )
            }
            onClick={async () => {
              if (!isNaN(Number(value))) {
                value.length === 7
                  ? await props.postForm(value)
                  : await setValidatemessage(unsuitable)
              } else {
                await setValidatemessage(isnumber)
                await setValue("")
              }
            }}
          >
            {props.btntext}
          </button>
        </div>
        <style>{`
          .validate {
            color: red;
            font-size: 10px;
            position: absolute;
            top: 0;
            left: 15px;
          }
          .sentence {
            text-align: center;
          }
          .form {
            display: flex;
            justify-content: center;
            height: 80vh;
            padding: 34vh;
          }
          .form__input__wrapper {
            position: relative;
          }
          .form__input {
            width: 300px;
            height: 40px;
            outline: none;
            border-radius: 5px;
            border: solid 3px black;
            padding: 0 10px;
            margin: 15px;
          }
          .form__btn {
            cursor: pointer;
            padding: 0 10px;
            border: solid 3px black;
            width: 74px;
            height: 40px;
            margin: 15px;
            border-radius: 5px;
            outline: none;
            background-color: black;
            color: white;
            font-weight: bold;
          }
          .disable {
            opacity: 0.6;
          }
        `}</style>
      </React.Fragment>
    )
  }

  export default Form

最後に

見ていただいた方ありがとうございます。
Qiitaも2記事目なので、うまいこと要点をまとめられませんでしたが、これで件のコードもおとなしく眠ってくれると思います。

最後に開発途中のスクショ貼っておきます。

image.png
上記のコードでこんな感じになってくれます。

では、また!!

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