20200926のReactに関する記事は13件です。

React with typescript入門-Reactの型定義-

はじめに

どうもシュータです。
React.jsを使用する際、より堅牢なプログラムの作成やパフォーマンス、リーダブル性などの観点から型定義が行われることがしばしばありますが
型定義の選択肢の一つとしてaltjsであるtypescriptの採用が挙げられます。

今回はtypescriptでReact.jsのコンポーネントを書く方法を簡単にまとめていきます。

Functional Component

まず、typescriptを使用せずにReactの関数コンポーネントを定義します。

import React from 'react'; 

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

//アロー関数バージョン

const Welcome = (props) => <h1>Hello, {props.name}</h1>;

これをtypescriptで書くとこうなります。
Propsに型定義を加えました。

import React from 'react'; 

type Props = {
  name : string
}

function Welcome(props : Props) {
  return <h1>Hello, {props.name}</h1>;
}

//アロー関数バージョン

const Welcome = (props : Props) => <h1>Hello, {props.name}</h1>;

さらにtypescriptのGenerics(ジェネリクス)を使用して次のように書くこともできます。

import React, { FunctionComponent } from 'react';

type Props = {
  name : string
}

const Welcome: FunctionComponent<Props> = (props) => <h1>Hello, {props.name}</h1>;

//下記も同様です。

const Welcome: React.FC<Props> = (props) => <h1>Hello, {props.name}</h1>;

ちなみにReact.SFCというのもあるのですが
現在はdeprecatedのようです。

https://github.com/DefinitelyTyped/DefinitelyTyped/pull/30364

class component

こちらもまず、typescriptを使用せずにクラスコンポーネントを定義します。

import React from 'react'; 

class Welcome extends React.Component {
  render(){
    return <h1>Hello, {this.props.name}</h1>;
  }
}

上記をtypescriptを使用して書くと次のようになります。

import React from 'react'; 

interface Props {
  name: string,
}
interface State {}

class Welcome extends React.Component<Props,State> {
  render(){
    return <h1>Hello, {this.props.name}</h1>;
  }
}

stateに値を入れる場合はinterface State {}で設定し、constructorにも型を加えます。

import React from 'react'; 

interface State {
  name : string
}

class Welcome extends React.Component<{},State> {
 constructor(props: State) {
    super(props);
    this.state = {
      name : "hoge"
    };
  }

  render(){
    return <h1>Hello, {this.state.name}</h1>;
  }
}

終わりに

ざっくりとReact.jsの関数コンポーネントとクラスコンポーネントを
typescriptで記述する方法をまとめました。

propsにデフォルトの値を設定する書き方や
optionalのpropsを設定する書き方もありますが
状況によってカスタマイズして使っていくと良いと思います。

reference

https://reactjs.org/docs/components-and-props.html
https://fettblog.eu/typescript-react/components/
https://fettblog.eu/typescript-react-component-patterns/
https://tech.pjin.jp/blog/2017/09/20/react-primer-component/
https://qiita.com/alfe_below/items/1cb81a6a03d8d6d73b27

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

Reactで作ったアプリを一瞬でVercelへデプロイする

はじめに

Reactで作ったTodoアプリをVercelへデプロイしたので記しておきます。

URL: https://le0tk0k-react-todo.vercel.app/

Vercelとは

Vercel (旧Zeit) は、静的サイトとサーバレス機能のホスティングを提供するPaasです。
詳しくは↓

VercelとNetlifyの違いが分からなかったので実際に比べてみた。
ZEIT/Vercelの料金形態と内容について調べてみた

とりあえずやってみる

https://vercel.com/ へいってSign upを選択します。
そして、GitHubを選択します。登録は流れのままやってください。
スクリーンショット 2020-09-26 22.08.42.png

次にこんな画面になると思いますのでImport Git Repositoryを選択。
スクリーンショット 2020-09-26 21.50.33.png

次にこの画面でリポジトリのURLを入力します。
スクリーンショット 2020-09-26 21.50.40.png

そしたら以下の画面になりますのでAll repositories か Only select repositoryを選択。
ALL reporitoriesを選択しても、Only select repositoryで今回デプロイするリポジトリだけを選択してもどっちでも大丈夫です。後から変更できますので。
スクリーンショット 2020-09-26 21.51.11.png

次に以下の画面になります。ここのproject nameというところがURL名になるので、ここで好きに変更できます。
あとはビルドコマンドやディレクトリを選択するのですが、すでに良い感じになっていますので、特に変更せずにDeploy
スクリーンショット 2020-09-26 22.20.12.png

以上です!あとはデプロイ完了まで待つだけです。

最後に

すごく簡単にできるのでぜひ!

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

ReactとPython flaskを使ってWebアプリを作りたい

はじめに

仕事でReactによるフロント開発に携わり始めたので、アウトプットの練習も兼ねて簡易webアプリを作成しました。

どんなアプリにするかアイデアはまったく思い浮かばなかったので、手元にあったmecabを使った分かち書きスクリプトを使って、フロントで受け取った入力テキストをサーバー側で分かち書きをし、その結果をフロントで表示するという非常にシンプルなアプリです。
(主目的はreactとflaskをつなぐ部分を勉強することだったため、アプリの見た目や機能は全然作り込んでいませんのであしからず。)

表題の通り、フロント側はReact、サーバー側はpython flaskで実装しています。

今回実装したスクリプトはこちらで公開しています。

完成品

画面
app_top.png

分かち書きしてみる
app_res.png

実装環境

OS: Ubuntu 18.04.2 LTS
Python: 3.6
flask==1.0.2

npm: 6.14.7

reactの環境構築については今回触れませんが、公式チュートリアルが日本語でも充実していて非常に参考になりました。
- https://ja.reactjs.org/

こちらもすごくおすすめです。
- https://mae.chab.in/archives/2529

実装する

構成図

今回実装したアプリの構成は以下のようになっています(主要部分のみ)。

app_architect.png

サーバー側

サーバー側は以下のような構成になっています。

backend/
   ├─ requirements.txt
   ├─ server.py
   └─ utils.py

server.pyはflaskサーバーを立ち上げるコードです。

アドレスやポートは一番下、app.run(host='127.0.0.1', port=5000)で指定します。

server.py
from flask import Flask
from flask import request, make_response, jsonify
from flask_cors import CORS
from utils import wakati

app = Flask(__name__, static_folder="./build/static", template_folder="./build")
CORS(app) #Cross Origin Resource Sharing

@app.route("/", methods=['GET'])
def index():
    return "text parser:)"

@app.route("/wakati", methods=['GET','POST'])
def parse():
    #print(request.get_json()) # -> {'post_text': 'テストテストテスト'}
    data = request.get_json()
    text = data['post_text']

    res = wakati(text)
    response = {'result': res}
    #print(response)
    return make_response(jsonify(response))

if __name__ == "__main__":
    app.debug = True
    app.run(host='127.0.0.1', port=5000)

@app.route("/wakati", methods=['GET','POST')部分でフロントからテキストを受け取り、分かち書き処理した後、フロントへ返す処理をしています。
data = request.get_json()によってフロントからポストされてきた内容をjson形式で取得します。
ここから必要なデータを取り出して、何らかの処理(関数にかけたり、DBに入れたりし)をし、response = {'result': res}のようにjson形式にしてフロントに返します。

(補足:CORSとは)
別リソースへアクセス(=クロスサイトHTTPリクエスト)できるようにするために必要なルールです。これがないとフロント側から立ち上げたflaskサーバへアクセスできません。
- 参考:https://aloerina01.github.io/blog/2016-10-13-1

フロント側

今回はcreate-react-appの雛形を用いました。
(create-react-appの設定および使い方はこちらが非常にわかりやすいです!)

フロント側は以下のような構成になっています(主要ファイルのみ掲載)。

frontend/app/
   ├─ node_modules/
   ├─ public/
   ├─ src/
   |   ├─ App.css
   |   ├─ App.js
   |   ├─ index.js
   |   └─ ...
   └─ ...

自動生成された雛形の中のApp.jsを以下のように書き換えました。

App.js
import React from 'react';
import './App.css';
import Axios from 'axios';

//function App() {
export class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <h1>text parser</h1>
          <form onSubmit={this.handleSubmit}>
            <label>
              <textarea name="text" cols="80" rows="4" value={this.state.value} onChange={this.handleChange} />
            </label>
            <br/>
            <input type="submit" value="Parse" />
          </form>
        </header>
      </div>
    );
  }


  wakati = text => {
    //console.log("input text >>"+text)
    Axios.post('http://127.0.0.1:5000/wakati', {
      post_text: text
    }).then(function(res) {
      alert(res.data.result);
    })
  };

  handleSubmit = event => {
    this.wakati(this.state.value)
    event.preventDefault();
  };

  handleChange = event => {
    this.setState({ value: event.target.value });
  };
}

export default App;

この中の以下の部分でサーバー側とのやり取りを行なっています。

  wakati = text => {
    //console.log("input text >>"+text)
    Axios.post('http://127.0.0.1:5000/wakati', {
      post_text: text
    }).then(function(res) {
      alert(res.data.result);
    })
  };

server.pyで立てたhttp://127.0.0.1:5000/wakatithis.state.valueの値をポストします。
サーバー側で処理された後、返ってきたresultの値がalert(res.data.result);によってブラウザに表示されます。

動かす

フロントエンド/バックエンド用にそれぞれターミナルを立ち上げて以下のコマンドを実行します。

サーバー側

$ cd backend
$ python server.py

フロント側

$ cd frontend/app
$ yarn start

ブラウザからlocalhost:3000にアクセスすることでアプリを利用できます(yarn startで自動で立ち上がります)。

おわりに

今回はReactとPython flaskを用いて簡易的なWebアプリを実装しました。
簡易的とはいえ、短時間で楽にWebアプリを実装できるので素晴らしいですね。

フロント修行中の身なので、見た目や機能についてはまだまだなのでご意見、アドバイス等いただければ幸いです。
最後まで読んでいただきありがとうございました!

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

react (face api) 簡単なアプリケーション

はじめに

今回はazureのface apiを叩いて、結果をviewに表示するという簡単なプログラムをつくったのでここでアウトプットしておきたいと思います。

完成形

スクリーンショット 2020-09-22 21.08.06.png
ご覧の通り、viewに関してはまだまだ改善点があり、私自身まだまだcssの勉強不足ですので暖かくみてください!!笑笑

使ったパッケージ

主に使ったものは、

recharts
axios
@material-ui/coreとicons

になります。環境構築はもちろんcreate-react-appです。

api通信処理

blobへの変換

export const inputImage = (e, setFace, setIsFetched, setImage ) => {
    e.preventDefault();
    const file = e.target.files[0]
    const reader = new FileReader()
    reader.onload = () => {
        fetchFace(reader.result)
    }
    reader.readAsDataURL(file)
}

FileReaderでは、Fileオブジェクトのファイルを実際に読み込みます。プレビューとして表示するというような動作はFileReaderを利用して行います

FileReaderの読み込みメソッドであるreadAsDataURL()を使いfileをDataURLとして読み込みます。
そしてその結果であるreader.resultを実際にapiを叩くfetchFaceに渡します。

    const bin = atob(image.split(',')[1]);
    const buffer = new Uint8Array(bin.length)
    for(let i = 0; i < bin.length; i++){
        buffer[i] = bin.charCodeAt(i);
    }
    const blob = new Blob([buffer.buffer], {type: 'application/octet-stream'});

ここでは先ほどのreader.resultをimageという引数として使っています。
このコードはblobの変換方法としてネットからそのまま引っ張ってきたコードになりますため、詳しい説明はできません。すいません。ちゃんと勉強します!!

今回face apiはurlのapplication/json以外のリクエストだとこのapplication/octet-streamですかリクエストできないようなのでこちらのtypeで行いました。。

axios api

変換したblobを使い実際にapiを叩く処理はこちらになります。

import axios from 'axios'

const apiKey = "apiKey"
const url = "https://yuuki.cognitiveservices.azure.com/face/v1.0/detect";
var params = {
    "returnFaceId": "true",
    "returnFaceLandmarks": "false",
    "returnFaceAttributes":
        "age,gender,headPose,smile,facialHair,glasses,emotion," +
        "hair,makeup,occlusion,accessories,blur,exposure,noise"
};
export const fetchFace = (blob) => {
    return axios({
        method: 'POST',
        url: `${url}?`,
        params: params,
        headers:{
            'Content-Type': 'application/octet-stream',
            'Ocp-Apim-Subscription-Key': apiKey,
        },
        data: blob
    })
    .then(response => response.data})
    .catch(error => console.log(error.response))
}

今回はjavascirptライブラリであるaxiosを使いました。
通常通りmethod,url,paramsを設定して、apiを叩きました。
このresponseとして返ってくるのが、

スクリーンショット 2020-09-26 18.32.05.png

こちらになります。

recharts pieグラフ

emotionは割合で返ってきていたので、rechartsをつかってグラフにしてみました。
スクリーンショット 2020-09-22 22.05.29.png

こちらがグラフになります。
まずこの中にセットするため受け取ったdataを配列にして管理します

const data = [
        { emotion: '怒り', value: emotion.anger * 100, color: "#996633"},
        { emotion: '混乱', value: emotion.contempt * 100, color: "#9933FF"},
        { emotion: '嫌悪', value: emotion.disgust * 100, color: "#9900FF"},
        { emotion: '無感情', value: emotion.neutral * 100, color: "#99FFFF"},
        { emotion: '恐怖', value: emotion.fear * 100, color: "#999966"},
        { emotion: '幸福', value: emotion.happiness * 100, color: "#99FF00"},
        { emotion: '悲しみ', value: emotion.sadness * 100, color: "#99CCFF" },
        { emotion: '驚き', value: emotion.surprise * 100, color: "#990033"},
    ];

    const newData = data.filter(item => item.value !== 0)

dataのvalueが受け取った数値を100倍してパーセント表示できるようにしています。
そしてnewDataでは、0%の値を表示することがないようにfilterにかけて新しく配列作っています。
このnewDataをPieに渡して表示してもらいます。

            <ResponsiveContainer width="99%" height={370}>
                <PieChart textAncor="center" className="pie__chart">
                    <Pie
                        data={newData}
                        cy={180}
                        label={renderCustomizedLabel}
                        outerRadius={180}
                        fillOpacity={0.7}
                        dataKey="value"
                        paddingAngle={1}
                    >
                        { data.map((data, index) => 
                            <Cell key={index} fill={data.color}/>
                        )}
                    </Pie>
                </PieChart>
            </ResponsiveContainer>

ここで実際にrechartsを使っています。
renderCustomizedLabelは、

    const RADIAN = Math.PI / 180;
    const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, emotion, value}) => {
         const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
         const sin = Math.sin(-RADIAN * midAngle);
         const cos = Math.cos(-RADIAN * midAngle)
         const mx = cx + (outerRadius + 30) * cos;
         const ex = mx + (cos >= 0 ? 1 : -1) * 22;
         const my = cy + (outerRadius + 90) * sin;
         const ey = my;
         const val = value.toFixed(1)
        return (
            <text textAnchor="middle" fill="black" x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} fontSize="12px" fontWeight={400}>{emotion}:{val}%</text>
        )
    };

ここでは円グラフの外のそれぞれの項目パーセンテージの設定を行っています。(幸福:81.4%)ここに関しても詳しくは理解できておりません。
ほとんどrechartsのドキュメント通りに書いたので深くは理解できていませんが簡単に実装できたので便利だなと感じました。

まとめ

ローカルファイル(写真)をblob(バイナリーデータ)に変換することに時間がかかりました。。
私自身まだまだプログミングを学習し初めて日が浅いので、blobとは、何かから調べることになりました。

今回はapiを叩いて取得したものただ羅列しただけのアプリケーションになるため、もっと何かのサービスに当てはめられるようにしたいと思っています。例えばazure face apiのfind similerでは顔の一致度などのapiもあるため、認証にも使えそうです。いずれは比較的大きめなサービスのごく一部としてもapiを利用できるようになりたいと感じています。

一応git hubにも公開しておりますので気になる方見ていただけると幸いです
こちらになります

azureのface apiはこちらです。

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

CodePenでReactを動かして学ぶ

はじめに

実現したいこと

  • CodePenでReactを書く

モチベーション

  • 普段はバックエンドなので、フロントを勉強するに当たってCodePenを使ってみたかった。
  • 誰かに簡単にコードを共有できるのが魅力的
  • React楽しそう
  • 備忘録として

想定読者

  • CodePenを初めて触る人
  • (React入門者)

やってみる

CodePenにログイン

https://codepen.io

新しくPenエディターを立ち上げる

スクリーンショット_2020-09-26_17_46_58.png

Settingsを押す

スクリーンショット_2020-09-26_17_47_11.png

JavaScript PreprocessorにTypeScriptを設定

スクリーンショット_2020-09-26_17_35_49.png

Add External Scripts/PensにCDNを2つ登録

https://unpkg.com/react@16/umd/react.development.js
https://unpkg.com/react-dom@16/umd/react-dom.development.js
スクリーンショット_2020-09-26_17_35_49_2.png

Save&Close

スクリーンショット_2020-09-26_17_35_49_3.png

書く!

スクリーンショット 2020-09-26 17.26.58.png


お疲れ様でした。

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

React HooksのuseEffectで関数コンポーネントにライフサイクルを持たせる

目次

はじめに

この記事では、React 16.8で導入されたHooksの機能の一つで、関数コンポーネントにライフサイクルメソッドを記述することのできるuseEffectについてまとめています。現在ReactやHooksについて学習中の方の一助となれば幸いです。

前提条件

  • Reactについて基本的な部分を理解している

環境

導入 version
react 16.13.1

useEffectとは

useEffectは簡単にいうと、関数コンポーネントにライフサイクルメソッドを持たせるための機能です。
Hooksが導入される以前のReactでは、コンポーネントにライフサイクルメソッドを持たせるためにはクラスコンポーネントで記述する必要がありました。そのためはじめは関数コンポーネントで記述していたコンポーネントにライフサイクルイベントを追加する必要が出てきたときにわざわざクラスコンポーネントに変換しなければなりませんでした。このような問題を解決する手段としてuseEffectが利用できます。
useEffectでは、クラスコンポーネントで利用していたライフサイクルメソッドであるcomponentDidMountcomponentDidUpdatecomponentWillUnmountを1種類の関数で扱うことができます。

useEffect.001.png

またHooksがないときにはクラスコンポーネントと関数コンポーネントが混在していましたが、useEffectuseStateなどのHooksの機能を利用することで全てを関数コンポーネントで記述できるため、コード全体の可読性を高めることもできるでしょう。

useEffectが呼び出されるタイミング

はじめに、関数コンポーネントがレンダリングおよび再レンダリングされるタイミングについて説明します。

初回レンダリングのタイミング

  • 関数コンポーネントがJSXとして読み込まれたとき

再レンダリングのタイミング

  • 親コンポーネントが再レンダリングされたとき
  • 受け取っているpropsに変化が生じたとき
  • 同コンポーネント内でuseStateを用いて定義している変数に変化が生じたとき

など

useEffectはこのようなレンダリングのタイミングで、自身で指定した任意の変数に変化が生じたときにコールバックとして記述した処理を実行します。

useEffectの使い方

では実際にuseEffectの使い方について見ていきましょう。useEffectの書き方の違いで呼び出される条件が変化するので、しっかり抑えておきましょう。

useEffectを利用するための基本

useEffectのインポート
何か別のライブラリを読み込む必要はなく、Reactからインポートして使用できます。

import React, { useEffect } from 'react';  // ここでuseEffectをインポートする

useEffectの基本構文
useEffectには第一引数としてコールバック関数、第二引数として任意の変数を格納する配列を記述します。
なお第二引数は省略可能です。

useEffect(() => {
  // 処理
}, [/* 任意の変数 */]);

以上がuseEffectを利用するための基本となります。

次に使用目的ごとに実際の記述方法について見ていきます。

レンダリングされるタイミングで毎回実行する

初回レンダリング時および再レンダリングに毎回useEffectに渡したコールバック関数を実行する方法について説明します。
レンダリング時に毎回実行する方法は、useEffectの第二引数を省略することです。

App.js
import React, { useEffect } from 'react';

const App = () => {
  useEffect(() => {
    // 処理
  });  // 第二引数を省略

  return (
    // JSX
  );
};

※ ただし第二引数を省略し、レンダリング時に毎回実行するようにすると何らかの原因で無限ループに陥る可能性があるので極力このような記述はしない方がいいでしょう。

初回レンダリング時のみ実行する

クラスコンポーネントのライフサイクルメソッドのcomponentDidMountのように初回レンダリング時にのみ処理を実行したいときは、第二引数の配列を空にして記述します。

App.js
import React, { useEffect } from 'react';

const App = () => {
  useEffect(() => {
    // 処理
  }, []);  // 第二引数の配列を空にする

  return (
    // JSX
  );
};

任意の変数が変化したときのみ実行する

関数コンポーネント内で使用している変数に変化が生じたときに、useEffectに渡したコールバック関数を実行したいときには、第二引数の配列にその変数を指定します。
少し具体的な例として、ボタンを押すと数値が加算されるカウンターを作成してみます。

Counter.js
import React, { useState, useEffect } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('useEffect!!')
  }, [count]);  // 第二引数の配列に任意の変数を指定(ここではcountが変化した時のみ実行されるようにしている)

  return (
    <div>
      {count}
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>
        ADD 1
      </button>
    </div>
  );
};

簡単な例ではありますが、このように記述することでcountが変化したときにのみuseEffectに渡しているコールバック関数が実行されます。

以上がuseEffectの基本的な使い方です。

useEffectで非同期処理をする

useEffect内の() => {}には非同期関数またはpromiseを返す関数を記述することができません。
ではどのようにuseEffectで非同期処理を行えばいいのでしょうか。
結論としては2通りの方法があります。

  • 方法1

 useEffectの外で別の関数を定義し、その関数をuseEffectで呼び出す

  • 方法2

 async, awaitを利用して特殊な記述をする

方法1については何となくイメージができると思いますので、ここでは方法2について詳しい記述方法を紹介します。

import React, { useEffect } from 'react';

const App = () => {

  useEffect(() => {
    (async () => {
      await // 処理
    })();
  }, [/* 任意の変数 */]);

  return (
    // JSX
  );
};

少し見慣れない書き方かもしれませんがこのように記述することで、useEffectで非同期処理を行うことができます。

誤って、

// BAD example!!
useEffect(async () => {
  await //  処理
}, [/* */])

としないように注意しましょう。

まとめ

簡単ではありますが、useEffectについての説明は以上になります。
まとめると、

  • useEffect関数コンポーネントでライフサイクルメソッドを持たせるための機能
  • クラスコンポーネントで利用していたライフサイクルメソッドを一つの関数で表現できる
  • 記述の仕方で実行タイミングが変わる
  • 非同期処理させるためには注意が必要

といった感じです。

実際にはuseEffectの使い方としてはまだまだたくさんありますが、基本中の基本はこの記事にまとめてあることなのでしっかり抑えていただければと思います。
今後アプリケーションを作成する際に、適した使い方をしていきましょう。

また参考資料に記載しているリンク先ではさらに詳しい使い方や解説をしてくれていますので、是非みて見てください。

参考資料

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

[初学者の備忘録]ReactとDjango Rest Frameworkの画像アップロード実装まで(プレビュー機能付き)

はじめに

現在ReactDjango REST frameworkを用いて成果物を制作しています。
画像アップロード機能を実装するまでに、多少苦労をしたので、他の学習者の方がより簡単に実装できるように一連の流れをまとめておきたいと思って執筆させていただいております。

画像アップロード機能は詰まることが多いと聞いたことがあるので、この記事が少しでもお役に立てれば幸いです。

参考

参考にした記事は以下のものです。
バックエンドのdjango側の画像アップロード機能実装のためと、フロントエンドの画像プレピュー機能実装のために参照をしました。

はじめてのDjango (7) 画像データの管理やページへの表示,アップロードの方法などについて知ろう
Reactで超簡単な画像ビューアを作る - FileReader

バックエンド

まず、バックエンドから簡潔に説明していきたいと思います。

画像を扱うための、Pillowというパッケージを仮想環境にダウンロードをしなければいけません。
なので、まずはじめに仮想環境をアクティベートした後に以下のコードを打ってダウンロードしてください。

pip install pillow

models.py

続いて、画像を扱うモデルを作成していきます。今回は私が実際に使ったモデルを用いて説明を進めていきます。
画像を扱うためには、ImageFieldを設定しなければいけません。

models.py
class Item_Image(models.Model):
    image = models.ImageField(upload_to="images/")
    item = models.ForeignKey(
        Give_Item, on_delete=models.CASCADE, related_name="item_image")

    def __str__(self):
        return self.item.parent_item.name

    class Meta:
        db_table = "item_images"

upload_toというのは、settings.pyで設定したMEDIA_ROOTからの相対パスを示しています。画像はデータベースで直接保存されるわけではなく、この指定したディレクトリにアップロードされているというのが実際のロジックです。

特にモデルでは書くことはないので、このままsettings.pyの説明に移ります。

settings.py

追記する内容は以下の通りです。

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

__file__は実行中のファイル(ここではsettings.py)を参照していて、os.path.dirnameは簡単に言うと一個上のディレクトリを参照するので、BASE_DIRはプロジェクトやアプリを全て格納しているフォルダ名を参照していることになります。
これによって、mediaディレクトリのパスを作成することができました。

続いて、プロジェクトの方のurls.pyに移ります。

urls.py

記述はとても簡単で、一種のおまじないみたいなものです。
importを忘れないようにしてください。

urls.py
from django.conf import settings # New
from django.contrib.staticfiles.urls import static # New
from django.contrib.staticfiles.urls import staticfiles_urlpatterns # New


urlpatterns += staticfiles_urlpatterns() # New
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # New

urlpatternsに関しては、他のエンドポイントを書いた後の下に書いて大丈夫です。具体的には以下のようになります。

urls.py
urlpatterns = [
    path("api/", include("app.urls")),
    path('admin/', admin.site.urls),
    url('rest-auth/', include('rest_auth.urls')),
    url('rest-auth/registration/', include('rest_auth.registration.urls'))
]

# これで準備完了です
urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

あとは、Reactを使うならDjango REST frameworkも必要となるのserializers.py, views.pyも設定しておきましょう。

serializers.py, views.py

ここは特に画像アップロードで加筆することはありません。
models.pyを作成しているなら、ModelSerializer,ModelViewSetを使えば楽にAPI実装ができます。

serialize.py
class Item_ImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = Item_Image
        fields = "__all__"
views.py
class CommentViewSet(viewsets.ModelViewSet):
    queryset = Comment.objects.all()
    permission_classes = [
        permissions.AllowAny
    ]
    serializer_class = CommentSerializer

これにてバックエンドの設定は完了です。
フロントエンドに移ります。

フロントエンド

inputフォームの作成

画像をアップロードしてもらうことになるので、inputを作成する必要があります。
ここで大切なのはformタグを使用することです。
buttononSubmitに関数を代入した方がe.preventDefault()を書かなくて楽だと思われるかもしれませんが、画像アップロードの際に必要になるので、必ずformタグで囲ってあげてください。

ちなみに、今回は複数投稿での実装となります。

form.jsx
<div className="imageForm">
            <form onSubmit={this.handleSubmit}>

             //
                   省略
             //              

              <label>商品画像</label>
          //
                // 複数アップロードする際は、multipleをつける必要があります
                //
               <input type="file" multiple onChange={this.handleImageSelect} />
                //
                // 下記はプレビュー機能のためのコードなので後で説明を加えます
                //
                {this.state.imgUrls.length === 0
                  ? null
                  : this.state.imgUrls.map((img, idx) => {
                   return <img key={idx} src={img}></img>;
                  })}

            <form/>

プレビュー機能の実装

先にコードを書きます。

form.jsx
  readImageUrl = () => {
    const files = Array.from(this.state.info.images);
    Promise.all(
      files.map((file) => {
       //
       //   3
       //
        return new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.addEventListener('load', (event) => {
            resolve(event.target.result);
          });
          reader.addEventListener('error', reject);
          reader.readAsDataURL(file);
        });
      })
    )
      .then((images) => {
        this.setState({ imgUrls: images });
      })
      .catch((err) => console.log(err));
  };


  handleImageSelect = async (e) => {
  // 
  //   1
  //
    await this.setState({
      info: { ...this.state.info, images: [...this.state.info.images, ...e.target.files] },
    });
    this.setState({
      message: {...this.state.message, images: this.validator("images", this.state.info.images )}
    })
    //
    //  2
    //
    this.readImageUrl();
  };

順番に説明をしていきます。
私のコードのValidationの実装方法に関連して、少し記述がごちゃごちゃになっております。申し訳ございません。

1.state内に選択されたファイルを格納する
一番はじめに行うことは極めて単純です。選択されたファイルをSubmitするためにstateに入れ込むだけです。私の場合、後のValidationの都合でasycn/awaitで全ファイルが代入されるまで待っていますが、この非同期処理への対応は必須ではないのでお任せします。(後にわかったことですがsetStateasync/awaitに付けても特に効果はないようです。)

重要なことは、選択されたファイルをスプレッド構文を用いて、配列にまとめて代入するということです。

2. ファイルをインラインで埋め込むdata:URLに変更する
(この表現が正しいかはわかりませんが)inputから得られるFileBlobを継承しているため中のデータに直接アクセスすることはできません。Fileに格納されたデータにアクセスするための一つの方法がFileを`data:URL"として読み込むことであるので、この関数を使って配列内のFile達を変換しているということになります。

3. FileReaderを使って画像のURLを取得しstateに入れる
このプレビュー機能の実装方法において、肝となるのはFileReaderというオブジェクトです。今回は複数投稿での実装ということで、inputから得たfilesarrayをmapしています。書かれてる順番が前後しますが、Promiseの中で行われているのは、まずreadAsDataURLメソッドを使って選択されたファイルを読み込むことです。名の通り読み込まれたファイルは上述のdata:URLに変換されます。そして、読み込みが終わったと同時に発火するのが、その上のloadイベントです。ちなみにこれはaddEventListenerを使う必要はなく、onloadというプロパティを使ってより簡潔に記述することもできます。書かれてる順がややこしくさせますが、このloadイベントは読み込みが完了され他あと、resultとして読み込まれたファイルを返してくれます。今回はプレビューとして画像を描画したいので、stateに入れます。

{
  this.state.imgUrls.length === 0
    ? null
    : this.state.imgUrls.map((img, idx) => {
        return <img key={idx} src={img} alc="アップロード写真" height="150px"></img>;
       })
}

今回は複数投稿なので、先ほど変換されたdata:URLが入ったファイルをmapします。後は、imgタグのsrcに受け取ったURLを入れるだけです。

画像アップロードの実装

this.state.info.images.map((image) => {
      let data = new FormData();
      data.append('image', image);
      data.append('item', giveItem_id);

      axios
        .post(this.props.axiosUrl + 'image/', data, authHeader)
        .then((res) => console.log(res.data))
        .catch((err) => console.log(err));
    });

画像アップロードに関連するコードだけ抜粋して書いていますが、説明に大きく影響はないのでそのまま使用させていただきます。

React, Django Rest Frameworkにおいて画像をアップロードする肝となるのはFormDataです。他のCharField,IntegerFieldモデルでは可能なaxiosdata部分に値を入れてPOSTリクエストを送ってもエラーが返ってきます。ImageFieldFileFieldを継承しているからか理由は定かではありませんが、少なくとも画像アップロードにはFormDataオブジェクトとしてリクエストを送らないとモデルを作成できないのは間違い無いと思います。

FormDataを使うことさえわかれば実装は至極単純です。.append(name, value)を用いてFormData()にリクエストを送りたい値を入れるだけです。

私の場合、複数投稿された画像一枚ごとにモデルを新規作成したかったのでinputから得たfileを格納した配列をmapしています。

豆知識となりますが、FormDataにちゃんと値が入っているか確認したい場合は以下の方法を使えば可能です(上記のコードからlet data = new FormData()として代入されている前提です)。

console.log(...data)

まとめ

上記が私が実装した方法です。

  1. inputから得られるfileBlob形式である
  2. Blob形式のファイルをdata:URL等に変換するのにFileReader()が有効である
  3. フロントからバックエンドへのImageFieldを持つモデルを作る場合、FormDataとして送信しなければならない

以上がまとめです。
他にもより良い実装方法があると思いますが、あくまでも一つの方法として参考にしていただければ幸いです。

アドバイスや間違っている点含めコメントをいただければとても嬉しいです。
拙く読み辛い文章だったとは思いますが最後までご覧いただき誠にありがとうございました。

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

Python+Reactでレンズ検索データベースを構築した時の技術的な話

概要

この度、交換用レンズの情報について、条件を指定して検索できるツールを開発・公開しました。

今回は、その際に工夫したことについてのまとめです。

スクレイピング用ライブラリは適宜ラップした

今回のWebアプリでは、レンズについての情報はJSONファイルとして運用していました。
ただ、各レンズの情報を全て手打ちしたわけではありません。
PythonでWebサイトをスクレイピングし、結果をJSONファイルに保存して、フロントエンド側で読み込ませていました。

……その際に使ったライブラリは、requests-HTMLです。と言っても、そのまま使うのではなく、別途クラスを作成してそちらに処理をまとめています。

from typing import List, MutableMapping, Optional
from requests_html import BaseParser, Element

class DomObject:
    """DOMオブジェクト"""

    def __init__(self, base_parser: BaseParser):
        self.base_parser = base_parser

    def find(self, query: str) -> Optional['DomObject']:
        temp = self.base_parser.find(query, first=True)
        if temp is None:
            return None
        return DomObject(temp)

    def find_all(self, query: str) -> List['DomObject']:
        return [DomObject(x) for x in self.base_parser.find(query)]

    @property
    def text(self) -> str:
        return self.base_parser.text

    @property
    def full_text(self) -> str:
        return self.base_parser.full_text

    # noinspection PyTypeChecker
    @property
    def attrs(self) -> MutableMapping:
        temp: Element = self.base_parser
        return temp.attrs

なぜかと言うと、素のままだと、PyCharm上で自動型推論がちゃんと効かないことがあったからです。
また、将来的にスクレイピング用ライブラリを差し替えたくなっても、ここだけ書き換えればOKという安心もあります。

さらに、Webサイトからデータを取得する部分についても、データベースと連携させてキャッシュする機構を組み込みました。
これにより、無駄なWebアクセスを避け、サーバーへの負荷を極限まで減らしています。
(IDataBaseServiceは自作クラス。詳細は書かないが、データベース操作をラップしたもの)

class ScrapingService:
    """スクレイピング用のラッパークラス"""

    def __init__(self, database: IDataBaseService):
        self.session = HTMLSession()
        self.database = database
        self.database.query('CREATE TABLE IF NOT EXISTS page_cache (url TEXT PRIMARY KEY, text TEXT)')

    def get_page(self, url: str) -> DomObject:
        cache_data = self.database.select('SELECT text from page_cache WHERE url=?', (url,))
        if len(cache_data) == 0:
            temp: HTML = self.session.get(url).html
            time.sleep(5)
            print(f'caching... [{url}]')
            self.database.query('INSERT INTO page_cache (url, text) VALUES (?, ?)',
                                (url, temp.raw_html.decode(temp.encoding)))
            return DomObject(temp)
        else:
            return DomObject(HTML(html=cache_data[0]['text']))

正規表現処理についてもラップした

プログラミング言語により、正規表現の有無・操作方法は様々です。Pythonについてもこの点は変わりません。
ただ、素の状態だとちょっと冗長になるなーってことがあるので、よくラップして運用しています。

def regex(text: str, pattern: str) -> List[str]:
    """グループ入り正規表現にマッチさせて、ヒットした場合はそれぞれの文字列の配列、そうでない場合は空配列を返す"""
    output: List[str] = []
    for m in re.finditer(pattern, text, re.MULTILINE):
        for x in m.groups():
            output.append(x)
    return output

これにより、例えば「regex('24~70mm', r'(\d+)mm~(\d+)mm')」と書いた場合、戻り値が「['24', '70']」となって扱いやすくなります。
また、「そのパターンとマッチしない=配列の要素数が0件である」ということなので、条件分岐も効率よく記述できます。

# 記述例
# ※Qiitaのソース埋め込みが壊れているので、「\d」と書くと自動色分けが正常に動作しない
# ※そのため意図的に「\\d」と記している。適宜読み替えること

# 35mm判換算焦点距離
result1 = regex(record['35mm判換算焦点距離'], r'(\\d+)mm~(\\d+)mm')
result2 = regex(record['35mm判換算焦点距離'], r'(\\d+)mm')
if len(result1) > 0:
    wide_focal_length = int(result1[0])
    telephoto_focal_length = int(result1[1])
else:
    wide_focal_length = int(result2[0])
    telephoto_focal_length = wide_focal_length

dataclassesは積極的に活用した

dataclassesとは、Python3.7から登場した、データクラスを手軽に作成できる仕組みのことです。今回も次のように、レンズ情報を記録するためのクラスとして活用しました。

@dataclass
class Lens:
    id: int = 0
    maker: str = ''
    name: str = ''
    product_number: str = ''
    wide_focal_length: int = 0
    telephoto_focal_length: int = 0
    wide_f_number: float = 0
    telephoto_f_number: float = 0
    wide_min_focus_distance: float = 0
    telephoto_min_focus_distance: float = 0
    max_photographing_magnification: float = 0
    filter_diameter: float = 0
    is_drip_proof: bool = False
    has_image_stabilization: bool = False
    is_inner_zoom: bool = False
    overall_diameter: float = 0
    overall_length: float = 0
    weight: float = 0
    price: int = 0
    mount: str = ''

また、dataclassesだけだとJSONデータにシリアライズする処理が面倒なので、dataclasses-jsonを追加導入して対処しています。

フィルター処理における抽象化

当Webアプリでは、検索条件を追加すると、即座に画面下のレンズ一覧が書き換わる仕組みです。
この際、レンズ情報を各種条件でフィルターする処理が挟まっているのですが、フィルター処理をどう記述しようか迷いました。

例えば、真っ先に思いつくのは次のようなコードでしょう。

// サンプルのフィルター設定
const filterList = [{'type': 'MaxWideFocalLength', 'value': 24, 'type': 'MinTelephotoFocalLength', 'value': 70}];

// フィルター処理
let temp = [...lensList];
for (const filter of filterList) {
  // switchで種類ごとに分岐
  switch (filter.type) {
    case 'MaxWideFocalLength':
      temp = temp.filter(lens => lens.wide_focal_length <= filter.value);
      break;
    case 'MinTelephotoFocalLength':
      temp = temp.filter(lens => lens.telephoto_focal_length >= filter.value);
      break;
  }
}

ただ、これだとフィルターの種類を増やすたびに、switch文がズラズラと連なることになります。可読性が悪い
そこで、「フィルター処理を行う機構」をクラスにラップすることで解決を見ました。
また、「フィルター処理を行う機構」と「フィルターのパラメーター」を分離することで、前者の複雑度を軽減しています。

※実際のコードでは、QueryType型は他にもプロパティを生やしています

// 「フィルター処理」を表現するための抽象クラス
abstract class QueryType {
  // フィルタ処理
  abstract filter(lensList: Lens[], value: number): Lens[];
}

// 個別のフィルター処理についての具象クラス
class MaxWideFocalLength implements QueryType {
  filter(lensList: Lens[], value: number): Lens[] {
    return lensList.filter(lens => lens.wide_focal_length <= value);
  }
}

class MinTelephotoFocalLength implements QueryType {
  filter(lensList: Lens[], value: number): Lens[] {
    return lensList.filter(lens => lens.telephoto_focal_length >= value);
  }
}

// 「1つのフィルター」を表現するためのインターフェース
interface Query {
  type: QueryType;
  value: number;
}

// サンプルのフィルター設定
const queryList: Query[] = [{'type': new MaxWideFocalLength(), 'value': 24, 'type': new MinTelephotoFocalLength(), 'value': 70}];

// フィルター処理
let temp = [...lensList];
for (const query of queryList) {
  temp = query.type.filter(temp, query.value);
}

抽象化の副次的作用

上記のQueryTypeですが、実際のコードではより多くのプロパティが生えています。

abstract class QueryType {
  // 型名
  abstract readonly name: string = '';
  // 数値部分の「手前」に表示するMessage
  abstract readonly prefixMessage: string;
  // 数値部分の「後」に表示するMessage
  abstract readonly suffixMessage: string;

  // フィルタ処理
  abstract filter(lensList: Lens[], value: number): Lens[];
}

これにより、例えばMaxWideFocalLengthは次のような定義になっています。

class MaxWideFocalLength implements QueryType {
  readonly name: string = 'MaxWideFocalLength';
  readonly prefixMessage: string = '広角端の換算焦点距離が';
  readonly suffixMessage: string = 'mm 以下';

  filter(lensList: Lens[], value: number): Lens[] {
    return lensList.filter(lens => lens.wide_focal_length <= value);
  }
}

こうした定義なのは、このアプリの性質上、「使用できるフィルターの一覧」を表示する需要があるからです。


<select>内に<option>を並べる場合、Reactだと次のように実装される方が多いと思います。

const queryTypeList = [
  {type: 'MaxWideFocalLength', prefixMessage: '広角端の換算焦点距離が'},
  {type: 'MaxWideFocalLength', prefixMessage: '望遠端の換算焦点距離が'}];

return (
  <select>
    {queryTypeList.map(q => <option key={q.type} value={q.type}>{q.prefixMessage}</option>)}
  <select>
);

何も間違ってはいないのですが、このまま実装すると、<select>された値から、MaxWideFocalLengthなどの(QueryTypeを継承した型)を生成する際にswitch文を使うことになってしまいます。これでは先ほど頑張って排除した意味がありません。

そこで、型ごとに使いたいプロパティを埋め込んでおきます。
すると、<select>された値をqueryTypeとした際、queryTypeList.filter(q => q.name === queryType)[0]とするだけで、所望の(QueryTypeを継承した)型のインスタンスを取得できます。switch文なんて要らんかったんや!

※この、「クエリの種類のインスタンス(フィルター処理を行う機構)を使い回せる」点が、「フィルター処理を行う機構」と「フィルターのパラメーター」を分離したご利益とも言えます

const queryTypeList = [
  new MaxWideFocalLength(),
  new MaxWideFocalLength()
];

return (
  <select>
    {queryTypeList.map(q => <option key={q.name} value={q.name}>{q.prefixMessage}</option>)}
  <select>
);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【技術書まとめ】『React.js&Next.js超入門』を読んだまとめ

読み終えて

Reactを本当に一から丁寧に教えてくれる本だった。1枚のペラページを改良しながら進んでいくやり方もいい。Reactの概念部分がコードとして具体的に見えて理解しやすかった。Amazonで低評価をつけている人もいるが、自分としてはReact入門者にとってすごくいい本だと思った。

「こんな本を読んだだけで、Reactをマスターできる人間なんて絶対にいない!」......わかりませんよ、一度読んだくらいでは。......この本は、......「入り口まで誘導する、導入書」です。ここから、Reactの本格的な学習が始まるのです。

最後にある言葉だが、これはとても真摯な言葉だ。

第1章 Reactを準備しよう!

いきなりReactの機能紹介に入らずに、htmlの1枚ファイルで仮想DOMなどの説明をしてくれる。とてもわかりやすい。

通常の技術書と違って話しことばに近く、かなりフレンドリーな印象を受ける。「覚えなくていいことですよ」といろんなところで強調して、覚えないと! と焦って途中で詰まってしまうのを防いでくれている。

表示を更新したければ、またエレメントを作ってレンダリングし直せばいい

なるほど。

$ npx create-react-app react_app # reactアプリを作る

$ yarn start # 起動
$ yarn build # ビルドする。これをプロダクションにあげるとそのまま動く

第2章 JSXをマスターしよう!

ここも1枚のhtmlで丁寧にReactの挙動を確認した。覚えることもまとめられていてとても親切。

  • エレメント
    • HTMLの各タグ
      • divpなど
  • ノード
    • HTMLのあらゆる要素を扱うオブジェクト
      • 「開始タグ」、「終了タグ」、「中のコンテンツ」など

「Reactでは、仮想DOMのエレメントを作って組み込むのだ」ということだけ頭に入れておきましょう。

なるほど。

「JSXは、あらゆるところで使うことができる」ということを頭によく入れておきましょう。そして、エレメントを利用する必要がある場合は、「ここでJSXが使えないか」を常に考えるようにしましょう。

ふむふむ。

  • リスト表示にはmapを使う

JSXは変数代入時にコンパイルされる

変数のみ更新してrenderしても表示は更新されない。

  • 覚えること
    • JSXはキャメルケース
      • onClickなど
    • タグは単発でも閉じる
      • <input />
    • 変数は{}に入れられる
    • ReactDOM.render(el, dom)でレンダリングする
      • 変数だけ変えても更新されない

第3章 コンポーネントをマスターしよう!

コンポーネントを作り、stateを管理したりつなげたり、Contextで共通の値を保持したりをしっかり確認できる。

  • コンポーネント名は大文字で始める
    • Welcome

setStateは、値を追加するだけで、削除はしません。この点は忘れないようにしましょう。

なるほど。

  • オブジェクトや配列を使うとき
    • プロパティとして値を用意(data = []
    • setStateで設定する
  • Context
    • 「すべてのコンポーネントで同じ値を設定し、一斉に変更できる」
    • テーマ切り替え

第4章 Reduxで値を管理しよう!

  • Reduxは状態管理ユーティリティ
    • 値の保管場所は1つだけ
      • 「ストア」
  • Reducer
    • 「どういうtypeの時にどんな値をreturnするか」
      • Reducer作成のポイント
    • Reducerで呼び出す処理は常に新しいステートをreturnする
  • connectで渡されれるステートはthis.propsに組み込まれる
    • 属性の値もいろいろ保管されている
      • 名前が被らないようにステートのマッピングで必要なものだけ取得するようにする
// ステートのマッピング
function mappingState(state) {
  return state;
}
  • dispatch
    • Reduxにアクションを送る
      • Reducerが呼び出される
        • 必ずtypeという値を用意する
this.props.dispatch({ type: 'DECREMENT' });

「ステートを用意する」→「レデューサーを用意する」→「ストアを用意する」という手順。「ディスパッチを呼び出す」→「レデューサーが呼び出される」→「アクションのtypeで処理を分岐」というアクションの流れ。これらをしっかりと頭に入れておきましょう。

なるほど。

アプリケーションは、極論するなら「用意されたデータ本体と、それを処理するための付け足し部分でできています。

データこそが本体。

  • export
    • 最後にエクスポートして使えるようにしておく
  • Redux Persist
    • オブジェクトは正しく保管できない時がある
  1. Reducerの書き方を覚える
  2. Storeの使い方を覚える
  3. ReduxPersistの組み込み方を覚える

第5章 Next.jsでReactをパワーアップしよう!

  • Reactは使っている間、常にどこかが書き換わっている
    • クライアントサイド・レンダリング
  • Next.js
    • 「HTMLファイルを使わずにWebアプリを開発する」
    • pagesフォルダが一番重要
      • ページ内容を用意する
  • レイアウトの基本構成
    • Layout
    • Header
    • Footer
    • Style
  • ReduxをNext.jsで使う
    • redux-store.js
    • app.js
    • を用意する

第6章 Firebaseでデータベースを使おう!

  • firebaseonは非同期
    • 「終わったらこれを実行しておくように」という処理
  • firebaseの項目はパスで指定できる
Next.jsアプリ作成
npm install --save react react-dom
npm install --save next
npm install --save redux react-redux redux-thunk
  • コンポーネントの属性をメソッドとして実行
    • コンポーネントを組み込んでいる側の処理を呼び出せる
<Account onLogined={this.logined}onLogouted={this.logouted} />
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

styled-componentsでレスポンシブを楽に書く

はじめに

今回はstyled-componentsを使ってレスポンシブデザインをできるだけ楽にする方法を紹介します。

他のstyled-componentsの記事はこちら
* styled-componentsを使ってみる
* styled-componentsでJavascriptの値を使う
* styled-componentsのThemeを使ってみる

実際に描いてみる

今回のは一回書いてしまえばつかいまわせるので簡単に書きます。

src直下にmedia.tsを作成し、以下のコードを書きます。Javascriptで書く場合はtypeの部分は全部消します。

src/media.ts
import {
    css,
    CSSObject,
    FlattenSimpleInterpolation,
    SimpleInterpolation,
} from 'styled-components';

export const sp = (
    first: CSSObject | TemplateStringsArray,
    ...interpolations: SimpleInterpolation[]
): FlattenSimpleInterpolation => css`
    @media (max-width: 560px) {
        ${css(first, ...interpolations)}
    }
`;

export const tab = (
    first: CSSObject | TemplateStringsArray,
    ...interpolations: SimpleInterpolation[]
): FlattenSimpleInterpolation => css`
    @media (min-width: 561px) and (max-width: 1024px) {
        ${css(first, ...interpolations)}
    }
`;
export const pc = (
    first: CSSObject | TemplateStringsArray,
    ...interpolations: SimpleInterpolation[]
): FlattenSimpleInterpolation => css`
    @media (min-width: 1025px) {
        ${css(first, ...interpolations)}
    }
`;

styled-componentsからインポートしたcssは、関数みたいにも使えます。sp(スマートフォン)、tab(タブレット)、pc(パソコン)という関数を作って、それぞれのサイズでスタイルを当てたい時に呼び出して使います。

引数のタイプはVSCodeの型推論と同じになるようにしただけです。

次にApp.tsxで以下のコードを書いて、使ってみましょう。

src/App.tsx
import React from 'react';
import styled from 'styled-components';
import { pc, sp, tab } from './media';

export const App = () => <Box>レスポンシブ</Box>;

const Box = styled.div`
    background-color: red;
    ${sp`
        width: 20px;
        height: 20px;
    `}
    ${tab`
        width: 50px;
        height: 50px;
    `}
    ${pc`
        width: 100px;
        height: 100px;
    `}
`;

ブラウザで確認するとそれぞれのサイズでちゃんと赤い正方形のサイズが変わると思います。
ブレイクポイントなど自由に変えて使ってみてください。

終わりに

ここまで読んで頂きありがとうございます!現在、PHP(Laravel)を中心に勉強しているのでそちらの方の記事を多く投稿していくと思います。感想やリクエストなどどんどん送ってくれると嬉しいです!

参考記事

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

styled-componentsのThemeを使ってみる

はじめに

今回はstyled-componentsのThemeに焦点を当てていきたいと思います。
themeに関しては実務で使った事がなく、あまり理解できていないところもあるので、詳しい方コメントで教えていただけると幸いです^_^

他のstyled-componentsの記事はこちら

themeとは?

ReduxみたいにProviderで囲ったコンポーネント内のどこからでもアクセスできる値みたいなイメージです。

styled-componentsドキュメント引用↓

// Define our button, but with the use of props.theme this time
const Button = styled.button`
 color: ${props => props.theme.fg};
 border: 2px solid ${props => props.theme.fg};
 background: ${props => props.theme.bg};
 font-size: 1em;
 margin: 1em;
 padding: 0.25em 1em;
 border-radius: 3px;
`;
// Define our `fg` and `bg` on the theme
const theme = {
 fg: "palevioletred",
 bg: "white"
};
// This theme swaps `fg` and `bg`
const invertTheme = ({ fg, bg }) => ({
 fg: bg,
 bg: fg
});
render(
 <ThemeProvider theme={theme}>
   <div>
     <Button>Default Theme</Button>
     <ThemeProvider theme={invertTheme}>
       <Button>Inverted Theme</Button>
     </ThemeProvider>
   </div>
 </ThemeProvider>
);

themeのメリット

themeというぐらいなので、色を全体的に変えたりなどスタイルの雰囲気をガラッと変えたりするのに使いやすそうです。

実際に使ってみる

ドキュメントの例でも十分ですが、せっかくなので使ってみましょう!
index.tsxにAppProviderコンポーネントを作って、ボタンを押したらthemeが切り替わる感じにします。

AppコンポーネントはThemeProviderで挟んで、themeを渡します。これでAppコンポーネント内ではt hemeが使えます。

src/index.tsx
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { ThemeProvider } from 'styled-components';
import { App } from './App';

const AppProvider = () => {
    const [is_theme_dark, set_is_theme_dark] = useState(false);

    const default_theme = {
        text_color: 'black',
        background_color: 'white',
    }

    const dark_theme = {
        text_color: 'white',
        background_color: 'black'
    }

    const toggle_theme = () => {
        set_is_theme_dark(!is_theme_dark);
    };

    return (
        <>
            <button onClick={toggle_theme}>Change!</button>
            <ThemeProvider theme={is_theme_dark ? dark_theme : default_theme}>
                <App />
            </ThemeProvider>
        </>
    );
};

ReactDOM.render(<AppProvider />, document.getElementById('app'));

次にAppコンポーネントことApp.tsxに以下のコードを書きます。

src/App.tsx
import React, { useContext } from 'react';
import styled, { ThemeContext } from 'styled-components';

export const App = () => {
    const theme = useContext(ThemeContext);

    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
            <Button>No Event</Button>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    width: 100vw;
    height: 100vh;
    background-color: ${(props) => props.theme.background_color};
    text-align: center;
    h1 {
        color: ${(props) => props.theme.text_color};
    }
`;

const Button = styled.button`
    color: ${(props) => props.theme.text_color};
    background-color: ${(props) => props.theme.background_color};
`;

themeはhooksのuseContextにstyled-componentsからインポートしたThemeContextを渡してあげるとindex.tsxで定義したthemeの値が入ります。そしてテーマの値をスタイルに使っています。

ブラウザを見てみると下の画像のようにdefault_themeが表示されると思います。

左上のボタンを押すとdark_themeに変わります。

使ってみた感想

今回は簡単な物を作ったのであまり恩賜を受けられませんでしたが、グローバルステートと組み合わせて、サイト全体のテーマを変えたりするのに使えそうな感じがしました。

ここまで読んでいただきありがとうございます!今後もいろいろな記事を書いていきたいと思っているので感想や要望などいただけたら、モチベーションにもつながります。

参考記事

styled-componentsドキュメント

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

styled-componentsでJavascriptの値を使う

はじめに

一応、下の記事の続きとして書いているのでインストールなどはお手数ですが下の記事をご覧ください。

他のstyled-componentsの記事
* styled-componentsのThemeを使ってみる
* styled-componentsでレスポンシブを楽に書く

今回はstyled-componentsでJavascriptの値を使う方法を試していきます。

実際にやってみる

連想配列で定義した値を使う

まず、srcディレクトリ直下にstyle.tsファイルを作り、以下のコードを書きます。

src/style.ts
export const COLOR = {
    RED: '#FF0000',
    ORANGE: '#FFA500',
    YELLOW: '#FFFF00',
    GREENYELLOW: '#ADFF2F',
    GREEN: '#008000',
    BLUE: '#0000FF',
    SKYBLUE: '#87CEEB',
    PURPLE: '#800080',
    PINK: '#FFC0CB',
    BROWN: '#A52A2A',
    WHITE: '#FFFFFF',
    GRAY: '#808080',
    BLACK: '#000000',
};

上のコードを見ると、色の名前とカラーコードの連想配列になっています。

App.tsxに以下のようなコードを書いてみましょう。App.tsxはsrc直下にあり、index.tsxにインポートする感じで書いています。

src/App.tsx
import React from 'react';
import styled from 'styled-components';
import { COLOR } from './style';

export const App = () => {
    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
            <Button>Click</Button>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    text-align: center;
    h1 {
        color: ${COLOR.RED};
    }
`;

const Button = styled.button`
    color: ${COLOR.WHITE};
    background-color: ${COLOR.BLUE};
    &:hover {
        background-color: ${COLOR.SKYBLUE};
    }
`;

先ほど作ったstyle.tsをインポートしてスタイルに使っています。このように、色や大きさの値をどこかのファイルにまとめて、インポートして使う感じにすると共同開発でもわかりやすくなります。

propsを渡して、その値を使う(例1)

App.tsxを以下のように書き換えてみましょう。Typescriptを使わない場合は、interfaceやの部分は消しましょう。

src/App.tsx
import React, { useState } from 'react';
import styled from 'styled-components';

export const App = () => {
    const [is_red, set_is_red] = useState(true);

    const handleClick = () => {
        set_is_red(!is_red);
    };

    return (
        <TitleWrapper is_red={is_red}>
            <h1>Hello World!</h1>
            <Button onClick={handleClick}>Click</Button>
        </TitleWrapper>
    );
};

interface ITitleWrapper {
    is_red: boolean;
}

const TitleWrapper = styled.div<ITitleWrapper>`
    text-align: center;
    h1 {
        color: ${({ is_red }) => is_red ? 'red' : 'blue'};
    }
`;

const Button = styled.button`
    color: white;
    background-color: blue;
    &:hover {
        background-color: skyblue;
    }
`;


※実際はクリックすると赤と青で切り替わります

上から見ていきましょう。まず、is_redという初期値がtrueのstateを作り、is_redはButtonをクリックするたびにtrue<->falseで切り替わる事がわかります。また、TitleWrapperにはis_redを渡している事がわかります。

そして、下の方のTitleWrapperを見てみるとis_redの値が使われています。ここでは三項演算子を使って、h1のテキストの色を変えています。

is_redの状態
true      赤     
false

propsを渡して、その値を使う(例2)

また、下のようにスタイルの連想配列をそのまま渡す方法もあります。

src/App.tsx
import React, { useState } from 'react';
import styled from 'styled-components';

export const App = () => {
    const [text_color, set_text_color] = useState({ color: 'blue' });

    const handleClick = () => {
        set_text_color({ color: 'red' });
    };

    return (
        <TitleWrapper text_color={text_color}>
            <h1>Hello World!</h1>
            <Button onClick={handleClick}>Click</Button>
        </TitleWrapper>
    );
};

interface ITitleWrapper {
    text_color: {
        color: string;
    };
}

const TitleWrapper = styled.div<ITitleWrapper>`
    text-align: center;
    h1 {
        ${({ text_color }) => text_color};
    }
`;

const Button = styled.button`
    color: white;
    background-color: blue;
    &:hover {
        background-color: skyblue;
    }
`;

※この場合はクリックしたら青から赤になり、その後にボタンを押しても色は変わりません

渡された連想配列がそのままスタイルとして適用されています。

うまく説明できたかわかりませんが。styled-componentsでは今回説明したJavascriptの値を使えるという部分が個人的にかなりいいなと思っています。次はthemeについて書きたいと思います。

ここまで読んでいただきありがとうございます!少しでもお役に立てれば幸いです!

参考記事

styled-componentsドキュメント

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

styled-componentsを使ってみる

はじめに

今回はstyled-componentsの簡単な使い方をやります。

他のstyled-componentsの記事
* styled-componentsでJavascriptの値を使う
* styled-componentsのThemeを使ってみる
* styled-componentsでレスポンシブを楽に書く

styled-componentsとは?

styled-componentsはReactにおけるCSSの当て方の一つで、Reactのコンポーネントのようにjsの値を渡したりでき、コンポーネントのようにスコープが作られるため、使いやすいです。

インストール

Reactの環境ができている方は下のコマンドはスルーしてください。
できていない方は以下のコマンドを打つか、記事を見ながら作ってみてください。
typescriptを使わない場合は下のコマンドの--typescriptは必要ありません。

ターミナル
npx create-react-app --typescript [アプリ名]

webpackでReact+Typescriptの環境構築をする

VSCodeで開き、ターミナルで以下のコマンドを打ちます。Typescriptを使わない場合は@types/styled-componentsは必要ありません。

VSCodeのターミナル
//npm
npm install --save styled-components
npm install --save-dev @types/styled-components

//yarn
yarn add styled-components
yarn add -D @types/styled-components

準備完了!

実際に使ってみる

早速使ってみましょう!

普通にスタイルを当てる

まずはApp.tsxに以下のコードを書いてブラウザで見てみましょう。

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

export const App = () => {
    return <h1>Hello World!</h1>;
};

ちなみにindex.tsxは以下のようにしています。

src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';

ReactDOM.render(<App/>, document.getElementById('app'));

ただの黒いHello World!が表示されるはずです。

次にstyled-componentsを使って、スタイルを当ててみましょう。
以下のような感じで使います。

VSCodeの拡張機能のvscode-styled-componentsを入れるとシンタックスハイライトが効いてみやすくなります。

const [コンポーネントとして使う名前] = styled.[タグ名]`
    //style
`;

App.tsxにstyled-componentsをインポートして、h1にスタイルを当てています。

src/App.tsx
import React from 'react';
import styled from 'styled-components'

export const App = () => {
    return <Title>Hello World!</Title>;
};

const Title = styled.h1`
    color: red;
`;

今度は赤く表示されるはずです。

補足:下のコードように連想配列で書く書き方もあるみたいです。詳しくはドキュメント

const Box = styled.div({
  background: 'palevioletred',
  height: '50px',
  width: '50px'
});

コンポーネント内の要素にスタイルを当てる

App.tsxを以下のように書き換えてみましょう。

src/App.tsx
import React from 'react';
import styled from 'styled-components';

export const App = () => {
    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    text-align: center;
    h1 {
        color: red;
    }
`;

中心に赤くHello World!が表示されるはずです。
上のようにある要素の中の要素にスタイルを当てるといった使い方もできます。

擬似要素を使う

App.tsxを以下のように書き換えてみましょう。

src/App.tsx
import React from 'react';
import styled from 'styled-components';

export const App = () => {
    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
            <Button>Hover</Button>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    text-align: center;
    h1 {
        color: red;
    }
`;

const Button = styled.button`
    color: white;
    background-color: blue;
    &:hover {
        background-color: skyblue;
    }
`;

先ほどに加えて、hoverで水色になるボタンが表示されます(hover前は青)。

&の後に擬似要素を書くことで使えます。

コンポーネントにスタイルを当てる

App.tsxを以下のように書き換えてみましょう。

src/App.tsx
import React from 'react';
import styled from 'styled-components';

export const App = () => {
    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
            <Button>Hover</Button>
            <StyledButton>Hover</StyledButton>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    text-align: center;
    h1 {
        color: red;
    }
`;

const Button = styled.button`
    color: white;
    background-color: blue;
    &:hover {
        background-color: skyblue;
    }
`;

const StyledButton = styled(Button)`
    color: black;
    background-color: white;
`;

上では色だけを変えたボタンを新しく作っています。このように、作ったコンポーネントに上書きする形でスタイルを当てる事ができます。
MaterialUIなどにも使う事ができて便利です。

ここまで読んでいただきありがとうございます!少しでもお役に立てれば幸いです!

参考記事

styled-componentsドキュメント

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