- 投稿日:2020-09-26T22:50:17+09:00
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
- 投稿日:2020-09-26T22:23:55+09:00
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を選択します。登録は流れのままやってください。
次にこんな画面になると思いますのでImport Git Repositoryを選択。
そしたら以下の画面になりますのでAll repositories か Only select repositoryを選択。
ALL reporitoriesを選択しても、Only select repositoryで今回デプロイするリポジトリだけを選択してもどっちでも大丈夫です。後から変更できますので。
次に以下の画面になります。ここのproject nameというところがURL名になるので、ここで好きに変更できます。
あとはビルドコマンドやディレクトリを選択するのですが、すでに良い感じになっていますので、特に変更せずにDeploy
以上です!あとはデプロイ完了まで待つだけです。
最後に
すごく簡単にできるのでぜひ!
- 投稿日:2020-09-26T22:22:45+09:00
ReactとPython flaskを使ってWebアプリを作りたい
はじめに
仕事でReactによるフロント開発に携わり始めたので、アウトプットの練習も兼ねて簡易webアプリを作成しました。
どんなアプリにするかアイデアはまったく思い浮かばなかったので、手元にあったmecabを使った分かち書きスクリプトを使って、フロントで受け取った入力テキストをサーバー側で分かち書きをし、その結果をフロントで表示するという非常にシンプルなアプリです。
(主目的はreactとflaskをつなぐ部分を勉強することだったため、アプリの見た目や機能は全然作り込んでいませんのであしからず。)表題の通り、フロント側はReact、サーバー側はpython flaskで実装しています。
今回実装したスクリプトはこちらで公開しています。
完成品
実装環境
OS: Ubuntu 18.04.2 LTS Python: 3.6 flask==1.0.2 npm: 6.14.7reactの環境構築については今回触れませんが、公式チュートリアルが日本語でも充実していて非常に参考になりました。
- https://ja.reactjs.org/こちらもすごくおすすめです。
- https://mae.chab.in/archives/2529実装する
構成図
今回実装したアプリの構成は以下のようになっています(主要部分のみ)。
サーバー側
サーバー側は以下のような構成になっています。
backend/ ├─ requirements.txt ├─ server.py └─ utils.py
server.py
はflaskサーバーを立ち上げるコードです。アドレスやポートは一番下、
app.run(host='127.0.0.1', port=5000)
で指定します。server.pyfrom 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.jsimport 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/wakati
にthis.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アプリを実装できるので素晴らしいですね。フロント修行中の身なので、見た目や機能についてはまだまだなのでご意見、アドバイス等いただければ幸いです。
最後まで読んでいただきありがとうございました!
- 投稿日:2020-09-26T18:42:52+09:00
react (face api) 簡単なアプリケーション
はじめに
今回はazureのface apiを叩いて、結果をviewに表示するという簡単なプログラムをつくったのでここでアウトプットしておきたいと思います。
完成形
ご覧の通り、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として返ってくるのが、こちらになります。
recharts pieグラフ
emotionは割合で返ってきていたので、rechartsをつかってグラフにしてみました。
こちらがグラフになります。
まずこの中にセットするため受け取った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はこちらです。
- 投稿日:2020-09-26T18:09:57+09:00
CodePenでReactを動かして学ぶ
はじめに
実現したいこと
- CodePenでReactを書く
モチベーション
- 普段はバックエンドなので、フロントを勉強するに当たってCodePenを使ってみたかった。
- 誰かに簡単にコードを共有できるのが魅力的
- React楽しそう
- 備忘録として
想定読者
- CodePenを初めて触る人
- (React入門者)
やってみる
CodePenにログイン
新しくPenエディターを立ち上げる
Settingsを押す
JavaScript PreprocessorにTypeScriptを設定
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
Save&Close
書く!
お疲れ様でした。
- 投稿日:2020-09-26T17:37:53+09:00
React HooksのuseEffectで関数コンポーネントにライフサイクルを持たせる
目次
はじめに
この記事では、React 16.8で導入されたHooksの機能の一つで、関数コンポーネントにライフサイクルメソッドを記述することのできる
useEffect
についてまとめています。現在ReactやHooksについて学習中の方の一助となれば幸いです。前提条件
環境
導入 version react 16.13.1 useEffectとは
useEffect
は簡単にいうと、関数コンポーネントにライフサイクルメソッドを持たせるための機能です。
Hooksが導入される以前のReactでは、コンポーネントにライフサイクルメソッドを持たせるためにはクラスコンポーネントで記述する必要がありました。そのためはじめは関数コンポーネントで記述していたコンポーネントにライフサイクルイベントを追加する必要が出てきたときにわざわざクラスコンポーネントに変換しなければなりませんでした。このような問題を解決する手段としてuseEffect
が利用できます。
useEffect
では、クラスコンポーネントで利用していたライフサイクルメソッドであるcomponentDidMount
、componentDidUpdate
、componentWillUnmount
を1種類の関数で扱うことができます。またHooksがないときにはクラスコンポーネントと関数コンポーネントが混在していましたが、
useEffect
やuseState
などの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.jsimport React, { useEffect } from 'react'; const App = () => { useEffect(() => { // 処理 }); // 第二引数を省略 return ( // JSX ); };※ ただし第二引数を省略し、レンダリング時に毎回実行するようにすると何らかの原因で無限ループに陥る可能性があるので極力このような記述はしない方がいいでしょう。
初回レンダリング時のみ実行する
クラスコンポーネントのライフサイクルメソッドの
componentDidMount
のように初回レンダリング時にのみ処理を実行したいときは、第二引数の配列を空にして記述します。App.jsimport React, { useEffect } from 'react'; const App = () => { useEffect(() => { // 処理 }, []); // 第二引数の配列を空にする return ( // JSX ); };任意の変数が変化したときのみ実行する
関数コンポーネント内で使用している変数に変化が生じたときに、
useEffect
に渡したコールバック関数を実行したいときには、第二引数の配列にその変数を指定します。
少し具体的な例として、ボタンを押すと数値が加算されるカウンターを作成してみます。Counter.jsimport 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
の使い方としてはまだまだたくさんありますが、基本中の基本はこの記事にまとめてあることなのでしっかり抑えていただければと思います。
今後アプリケーションを作成する際に、適した使い方をしていきましょう。また参考資料に記載しているリンク先ではさらに詳しい使い方や解説をしてくれていますので、是非みて見てください。
参考資料
- 投稿日:2020-09-26T14:13:56+09:00
[初学者の備忘録]ReactとDjango Rest Frameworkの画像アップロード実装まで(プレビュー機能付き)
はじめに
現在
React
とDjango REST framework
を用いて成果物を制作しています。
画像アップロード機能を実装するまでに、多少苦労をしたので、他の学習者の方がより簡単に実装できるように一連の流れをまとめておきたいと思って執筆させていただいております。画像アップロード機能は詰まることが多いと聞いたことがあるので、この記事が少しでもお役に立てれば幸いです。
参考
参考にした記事は以下のものです。
バックエンドのdjango
側の画像アップロード機能実装のためと、フロントエンドの画像プレピュー機能実装のために参照をしました。はじめてのDjango (7) 画像データの管理やページへの表示,アップロードの方法などについて知ろう
Reactで超簡単な画像ビューアを作る - FileReaderバックエンド
まず、バックエンドから簡潔に説明していきたいと思います。
画像を扱うための、
Pillow
というパッケージを仮想環境にダウンロードをしなければいけません。
なので、まずはじめに仮想環境をアクティベートした後に以下のコードを打ってダウンロードしてください。pip install pillowmodels.py
続いて、画像を扱うモデルを作成していきます。今回は私が実際に使ったモデルを用いて説明を進めていきます。
画像を扱うためには、ImageField
を設定しなければいけません。models.pyclass 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.pyfrom 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.pyurlpatterns = [ 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.pyclass Item_ImageSerializer(serializers.ModelSerializer): class Meta: model = Item_Image fields = "__all__"views.pyclass CommentViewSet(viewsets.ModelViewSet): queryset = Comment.objects.all() permission_classes = [ permissions.AllowAny ] serializer_class = CommentSerializerこれにてバックエンドの設定は完了です。
フロントエンドに移ります。フロントエンド
input
フォームの作成画像をアップロードしてもらうことになるので、
input
を作成する必要があります。
ここで大切なのはform
タグを使用することです。
button
でonSubmit
に関数を代入した方が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.jsxreadImageUrl = () => { 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
で全ファイルが代入されるまで待っていますが、この非同期処理への対応は必須ではないのでお任せします。(後にわかったことですがsetState
をasync/await
に付けても特に効果はないようです。)重要なことは、選択されたファイルをスプレッド構文を用いて、配列にまとめて代入するということです。
2. ファイルをインラインで埋め込む
data:URL
に変更する
(この表現が正しいかはわかりませんが)inputから得られるFile
はBlob
を継承しているため中のデータに直接アクセスすることはできません。File
に格納されたデータにアクセスするための一つの方法がFile
を`data:URL"として読み込むことであるので、この関数を使って配列内のFile
達を変換しているということになります。3.
FileReader
を使って画像のURLを取得しstate
に入れる
このプレビュー機能の実装方法において、肝となるのはFileReader
というオブジェクトです。今回は複数投稿での実装ということで、input
から得たfiles
arrayを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
モデルでは可能なaxios
のdata
部分に値を入れてPOSTリクエストを送ってもエラーが返ってきます。ImageField
はFileField
を継承しているからか理由は定かではありませんが、少なくとも画像アップロードにはFormData
オブジェクトとしてリクエストを送らないとモデルを作成できないのは間違い無いと思います。
FormData
を使うことさえわかれば実装は至極単純です。.append(name, value)
を用いてFormData()
にリクエストを送りたい値を入れるだけです。私の場合、複数投稿された画像一枚ごとにモデルを新規作成したかったので
input
から得たfile
を格納した配列をmap
しています。豆知識となりますが、
FormData
にちゃんと値が入っているか確認したい場合は以下の方法を使えば可能です(上記のコードからlet data = new FormData()
として代入されている前提です)。console.log(...data)まとめ
上記が私が実装した方法です。
input
から得られるfile
はBlob
形式であるBlob
形式のファイルをdata:URL
等に変換するのにFileReader()
が有効である- フロントからバックエンドへの
ImageField
を持つモデルを作る場合、FormData
として送信しなければならない以上がまとめです。
他にもより良い実装方法があると思いますが、あくまでも一つの方法として参考にしていただければ幸いです。アドバイスや間違っている点含めコメントをいただければとても嬉しいです。
拙く読み辛い文章だったとは思いますが最後までご覧いただき誠にありがとうございました。
- 投稿日:2020-09-26T12:24:33+09:00
Python+Reactでレンズ検索データベースを構築した時の技術的な話
概要
この度、交換用レンズの情報について、条件を指定して検索できるツールを開発・公開しました。
自作のマイクロフォーサーズ用レンズデータベース検索Webアプリを、Firebaseでデプロイしました。
— YSR@あいミス10章クリア (@YSRKEN) August 30, 2020
追加した検索条件は、同種の条件を追加すると上書きされ、条件自体をクリックすると削除されます。
また、詳細ボタンからレンズの詳細データを確認できます。https://t.co/TRta0pX3H1 pic.twitter.com/5hQpjaOBiM【お知らせ】レンズを検索できるデータベースを更新しました。検索条件をシェアしたり、クリップボードにコピーしたりできるように!
— YSR@あいミス10章クリア (@YSRKEN) September 16, 2020
レンズデータベース(マイクロフォーサーズ, ライカL マウント向け。スマホ対応!) https://t.co/TRta0pX3H1 pic.twitter.com/l5ZA6q9XET今回は、その際に工夫したことについてのまとめです。
スクレイピング用ライブラリは適宜ラップした
今回の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_lengthdataclassesは積極的に活用した
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> );
- 投稿日:2020-09-26T11:31:20+09:00
【技術書まとめ】『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の各タグ
div
やp
など- ノード
- 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
- オブジェクトは正しく保管できない時がある
- Reducerの書き方を覚える
- Storeの使い方を覚える
- 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でデータベースを使おう!
firebase
のon
は非同期
- 「終わったらこれを実行しておくように」という処理
- 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} />
- 投稿日:2020-09-26T00:08:08+09:00
styled-componentsでレスポンシブを楽に書く
はじめに
今回はstyled-componentsを使ってレスポンシブデザインをできるだけ楽にする方法を紹介します。
他のstyled-componentsの記事はこちら
* styled-componentsを使ってみる
* styled-componentsでJavascriptの値を使う
* styled-componentsのThemeを使ってみる実際に描いてみる
今回のは一回書いてしまえばつかいまわせるので簡単に書きます。
src直下にmedia.tsを作成し、以下のコードを書きます。Javascriptで書く場合はtypeの部分は全部消します。
src/media.tsimport { 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.tsximport 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)を中心に勉強しているのでそちらの方の記事を多く投稿していくと思います。感想やリクエストなどどんどん送ってくれると嬉しいです!
参考記事
- 投稿日:2020-09-26T00:06:07+09:00
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.tsximport 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.tsximport 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に変わります。
使ってみた感想
今回は簡単な物を作ったのであまり恩賜を受けられませんでしたが、グローバルステートと組み合わせて、サイト全体のテーマを変えたりするのに使えそうな感じがしました。
ここまで読んでいただきありがとうございます!今後もいろいろな記事を書いていきたいと思っているので感想や要望などいただけたら、モチベーションにもつながります。
参考記事
- 投稿日:2020-09-26T00:03:46+09:00
styled-componentsでJavascriptの値を使う
はじめに
一応、下の記事の続きとして書いているのでインストールなどはお手数ですが下の記事をご覧ください。
他のstyled-componentsの記事
* styled-componentsのThemeを使ってみる
* styled-componentsでレスポンシブを楽に書く今回はstyled-componentsでJavascriptの値を使う方法を試していきます。
実際にやってみる
連想配列で定義した値を使う
まず、srcディレクトリ直下にstyle.tsファイルを作り、以下のコードを書きます。
src/style.tsexport 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.tsximport 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.tsximport 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.tsximport 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について書きたいと思います。
ここまで読んでいただきありがとうございます!少しでもお役に立てれば幸いです!
参考記事
- 投稿日:2020-09-26T00:01:37+09:00
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.tsximport React from 'react'; export const App = () => { return <h1>Hello World!</h1>; };ちなみに
index.tsx
は以下のようにしています。src/index.tsximport React from 'react'; import ReactDOM from 'react-dom'; import { App } from './App'; ReactDOM.render(<App/>, document.getElementById('app'));次にstyled-componentsを使って、スタイルを当ててみましょう。
以下のような感じで使います。VSCodeの拡張機能の
vscode-styled-components
を入れるとシンタックスハイライトが効いてみやすくなります。const [コンポーネントとして使う名前] = styled.[タグ名]` //style `;
App.tsx
にstyled-componentsをインポートして、h1にスタイルを当てています。src/App.tsximport 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.tsximport 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.tsximport 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.tsximport 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などにも使う事ができて便利です。ここまで読んでいただきありがとうございます!少しでもお役に立てれば幸いです!
参考記事