- 投稿日:2020-03-20T23:57:54+09:00
Next.js(React)でCSSとSASS(SCSS)、Bootstrapを使う
Next.jsでCSSとSASS(SCSS)、Bootstrapを使う
next.jsでreactのSPAを作り始めたのですが、css, sass, bootstrapの設定周りですこしつまづいたのでメモ。
Next.jsのプロジェクト作成
プロジェクト作成
yarn craete next-appこれでNext.jsのプロジェクトが作成されます。
yarn dev
で起動できます。CSS, SASSの設定
css, sass(scss)ファイルをimportできるようにします。
まずはパッケージインストールします。
yarn add @zeit/next-css yarn add @zeit/next-sass node-sassnext.config.jsの作成
プロジェクトのルートに
next.config.js
ファイルを作成します。これはnext.jsの設定ファイルです。以下はcssとsassどちらもimportできるような設定です。
next.config.jsconst withSass = require('@zeit/next-sass'); const withCSS = require('@zeit/next-css'); module.exports = withCSS(withSass());Bootstrapの設定
Bootstrapを入れます。
yarn add bootstrap yarn add react-bootstrapBootstrapを適用します。
アプリ全体に適用したいので、グローバルに設定したいです。まず、
/pages/_app.js
を作成します。
next.jsでは、全体設定のためのインターフェースとして_app.js
で設定できるようになっています。
_app.js
でbootstrapをimportすることでアプリ全体に適用していくのです。/pages/_app.jsimport 'bootstrap/dist/css/bootstrap.min.css'; function MyApp ({ Component, pageProps }) { return <Component {...pageProps} /> }; export default MyApp;ページを作ってみる
では正しく設定できているか実際にページを作って確認してみましょう。
/pages/login.jsimport {Container, Row, Col, Form, Button} from 'react-bootstrap'; const Login = () => { return ( <Container className='login'> <Row> <Col className='text-center'> <h2>ログイン</h2> </Col> </Row> <Row> <Col> <Form className='col-6 offset-3'> <Form.Group> <Form.Control type='text' name='id' /> </Form.Group> <Form.Group> <Form.Control type='password' name='password' /> </Form.Group> <Form.Group> <Form.Control type='submit' name='submit' value='ログイン' className='btn btn-primary' /> </Form.Group> </Form> </Col> </Row> </Container> ); }; export default Login;確認してみる
起動します
yarn dev
localhost:3000/login
にアクセス。わあい
- 投稿日:2020-03-20T23:57:54+09:00
Next.jsでCSSとSASS(SCSS)、Bootstrapを使う
Next.jsでCSSとSASS(SCSS)、Bootstrapを使う
next.jsでreactのSPAを作り始めたのですが、css, sass, bootstrapの設定周りですこしつまづいたのでメモ。
Next.jsのプロジェクト作成
プロジェクト作成
yarn craete next-appこれでNext.jsのプロジェクトが作成されます。
yarn dev
で起動できます。CSS, SASSの設定
css, sass(scss)ファイルをimportできるようにします。
まずはパッケージインストールします。
yarn add @zeit/next-css yarn add @zeit/next-sass node-sassnext.config.jsの作成
プロジェクトのルートに
next.config.js
ファイルを作成します。これはnext.jsの設定ファイルです。以下はcssとsassどちらもimportできるような設定です。
next.config.jsconst withSass = require('@zeit/next-sass'); const withCSS = require('@zeit/next-css'); module.exports = withCSS(withSass());Bootstrapの設定
Bootstrapを入れます。
yarn add bootstrap yarn add react-bootstrapBootstrapを適用します。
アプリ全体に適用したいので、グローバルに設定したいです。まず、
/pages/_app.js
を作成します。
next.jsでは、全体設定のためのインターフェースとして_app.js
で設定できるようになっています。
_app.js
でbootstrapをimportすることでアプリ全体に適用していくのです。/pages/_app.jsimport 'bootstrap/dist/css/bootstrap.min.css'; function MyApp ({ Component, pageProps }) { return <Component {...pageProps} /> }; export default MyApp;ページを作ってみる
では正しく設定できているか実際にページを作って確認してみましょう。
/pages/login.jsimport {Container, Row, Col, Form, Button} from 'react-bootstrap'; const Login = () => { return ( <Container className='login'> <Row> <Col className='text-center'> <h2>ログイン</h2> </Col> </Row> <Row> <Col> <Form className='col-6 offset-3'> <Form.Group> <Form.Control type='text' name='id' /> </Form.Group> <Form.Group> <Form.Control type='password' name='password' /> </Form.Group> <Form.Group> <Form.Control type='submit' name='submit' value='ログイン' className='btn btn-primary' /> </Form.Group> </Form> </Col> </Row> </Container> ); }; export default Login;確認してみる
起動します
yarn dev
localhost:3000/login
にアクセス。わあい
- 投稿日:2020-03-20T16:56:42+09:00
【REACT】stateを学びましょう!
概要
stateを理解し、使って見ましょう!
state?
REACTでstateは、Component内部で変えることができる値
REACTには、二種類のstateがあります。
- class型Componentが持っているstate
- 関数型Componentで
useState
という関数で使うstateclass型Component
簡単な数字カウンター
count.jsimport React, { Component } from "react"; class Count extends Component { constructor(props) { super(props); // stateの初期値を設定 this.state = { number: 0 }; } render() { // stateを呼ぶとき使う const { number } = this.state; return ( <div> <h1>{number}</h1> <button onClick={() => { // setStateを使って、stateに新しいvalueを設定 this.setState({ number: number + 1 }); }} > +1 </button> </div> ); } } export default Count;関数型Component
簡単な数字カウンター
Say.jsimport React, { useState } from "react"; const say = () => { const [message, setMessage] = useState(""); const onClickEnter = () => setMessage("Hello"); const onClickLeave = () => setMessage("Good Bye"); return ( <div> <button onClick={onClickEnter}>Enter</button> <button onClick={onClickLeave}>Leave</button> <h1>{message}</h1> </div> ); }; export default say;問題
【Red】、【Blue】ボタンを追加して、ボタンをクリックしたら、
<h1>{message}</h1>
の messageの色が変わるようにしましょう!hint
style
を使いましょう!
- 投稿日:2020-03-20T15:21:27+09:00
[React]コンポーネントの再帰処理でディレクトリ構造を表示する
コンポーネントの再帰処理をやってみた。
サンプルはこちら
ディレクトリ構造を表すようなイメージで作成してみたけど、サイドバーとかドロップダウンメニューとか、いろいろと使えそう。
import React, { FC, Children } from 'react'; // ディレクトリ構造としてのサンプルデータ const fileNames = [ { name: 'file A' }, { name: 'file B' }, { name: 'file C', items: [ { name: 'file C-1' }, { name: 'file C-2' } ], }, { name: 'file D' }, { name: 'file E', items: [ { name: 'file E-1' }, { name: 'file E-2' }, { name: 'file E-3' }, { name: 'file E-4', items: [ { name: 'file E-4-1' }, { name: 'file E-4-2' } ], }, ], }, ]; // items で files を受ける。 depth は階層を表す。 const RecursionComponent: FC<{ items: any; depth: number }> = ({ items, depth, }) => { return ( <> {Children.toArray( items.map((item: any) => ( <> <div style={{ marginLeft: depth * 20 }}> - {item.name} </div> {item.items && ( <RecursionComponent items={item.items} depth={depth + 1} /> )} </> )), )} </> ); }; const Recursion: FC = () => (<RecursionComponent items={fileNames} depth={0} />); export default Recursion;
- 投稿日:2020-03-20T14:36:11+09:00
【React】 Driver.jsでチュートリアル風ハイライト
概要
初めて起動したアプリなどでよく見るチュートリアルで利用されている、ハイライトをReactを使って再現したいと思います!
(巷では、「コーチマーク」と呼ばれているようでしたが、言葉知らないとなかなか検索ヒットしないよね。)その後、ちょっと自分好みにUIを変えてみました。
iOS版は、こちらの記事で詳細されてました。
https://qiita.com/tkt989/items/2981497580beae09875dDriver.js
今回は、JavaScriptライブラリで、
Driver.js
というものを使ってみます。Light-weight, no-dependency, vanilla JavaScript engine to drive user's focus across the page
と、説明されています!公式サイトはこちら
Reactで開発
では、早速作っていきます!
React環境構築 Driver.jsの導入
npmにもありましたので、こちらを使っていきます!
React×Material-UI環境に
Driver.js
をインストールして、簡単ではありますが、
ボタンがクリックされたら、ハイライトが起動するように準備していきます。import React from "react"; import Button from "@material-ui/core/Button"; import Driver from "driver.js"; class App extends React.Component { handleClick = props => { // ハイライト発火の処理記述 }; render() { return ( <React.Fragment> <div> <h1>Hello, Driver.js</h1> <Button onClick={this.handleClick} variant="outlined" color="secondary" > click </Button> </div> </React.Fragment> ); } } export default App;Driver.js エレメントを指定する
ここまでで、
handleClick
メソッドに処理を記入すればハイライトできるようにできました。公式サイトのサンプルコードを見ると、めちゃめちゃ簡単に実装できそうですね。
const driver = new Driver(); driver.highlight('#create-post');
hightlight
メソッドにエレメントをしてするだけで簡単にいけそう!
(本当に!?)Buttonコンポーネントに
id
を指定しました。styleファイルをimportする
ダメだった...
公式のソースに設定した
id
を設定してみましたが、なぜか、ボタンが消えた...
全然ダメじゃん...と思いながら、
一度、公式サイトに戻ってよく見ると...Or grab the code from dist directory and include it directly.
<link rel="stylesheet" href="/dist/driver.min.css"> <script src="/dist/driver.min.js"></script>スタイルファイルが必要なのか...
ちゃんと公式は読みましょうね!
改めて、css
ファイルをimportしてトライするとうまくいきました!
コーチマークの表示
ハイライトは実装できたので、コーチマークを表示させます。
hightLight
メソッドはオブジェクト型で値を渡すことができます。const driver = new Driver(); driver.highlight({ element: "#block", // ↓コーチマークの記述 popover: { title: "クリック", description: "ここをクリックする事で〇〇できます。", position: "right" } })さらに、連結したコーチマークの表示なども可能です。
詳細は、公式サイトでご確認ください!自分好みにカスタマイズ
透明度やハイライト範囲の余白などの変更が可能です。
ちょっと、四角で囲っているのが、クールではないので、ぼかしを入れてみました。
CodeSandboxのリンク置いてますので、ソース確認してみてください!
後付け感があって、ちょっと使い物になりませんね。
ちょっと工夫の必要がありそうです。。さいごに
今回は、
Driver.js
を使って、
ハイライト、コーチマークのチュートリアル風インタラクションをReactで再現してみました。
ライブラリによって、得意不得意あると思いますし、
Reactとの相性がいいものがあればいいなぁ。知っている方いましたら是非教えていただきたい。。
- 投稿日:2020-03-20T14:02:37+09:00
[React]uniq key がないときの `Each child in a list should have a unique "key" prop.` の回避方法
サンプル作ってたり、データにユニークな key がないときに出る
Each child in a list should have a unique "key" prop.
のエラーが面倒だなと思ってたんですが、React.Children.toArray
で回避できる。<> {React.Children.toArray(items.map(item => <div>{item.title}</div>))} </>データ構造が非公開の children を平坦な配列として返し、それぞれの要素に key を割り当てます。
https://ja.reactjs.org/docs/react-api.html#reactchildrentoarray
便利!
- 投稿日:2020-03-20T12:35:31+09:00
Noodl react library ガイド(和訳)
この記事は、Noodl公式のSlackコミュニティー上でMathias L氏が2019年9月に公開した「Noodl react library guide」の和訳になります。
※注:和訳は意訳+注釈を追加しています。
Noodl Reactライブラリガイド(和訳)
このガイドではReact LibraryをNoodlワークスペースに追加する方法を紹介します
ワークスペースフォルダが適切に設定されていることを確認してください
まずはNoodl CLI toolがインストールされていることを確認してください。
ツールはnpmを使ってインストールできます。$ npm install -g noodl-lab-cli次に、空のワークスペースフォルダを作成します。
このフォルダにすべてのカスタムコンポーネントとモジュールを含めます。$ mkdir my-workspace-folder $ cd my-workspace-folderすべてをゼロから開始する必要はありません。
Noodlスターターラボのテンプレートが使⽤できます。$ curl -O https://s3.amazonaws.com/updates.noodlcloud.com/lab s/noodl-starter-lab.zip $ unzip noodl-starter-lab.zipワークスペースフォルダーではじめ、このワークスペースをNoodlクラウドワークスペースに接続します。
initコマンドを実⾏できます。下記のXXXをあなたが取得したアクセスキーに置き換え、提供されているワークスペース名に置換えるてください。$ noodl-lab init --accessKey "XXX" --name "your-workspace-nam e"アクセスキーはワークスペースの管理ページにあります(labとワークスペースどちらも使用でき、同じものを参照します)。
※訳注: このアクセスキーはNoodlワークスペースの管理者にしか公開されていません。残念ながら、ベータテスタ権限ではアクセスキーを確認することができません。
⻭⾞をクリックして管理ページに⼊ります。ラボ名がユーザ名の下に表⽰されます
アクセスキーは管理ページの下部にあります
ワークスペースのコンテンツを変更するたびにpushする必要があります。
また、pushすることでコンテンツフォルダが適切に設定されていることをテストできます。$ noodl-lab push上記のコマンドが正常に実⾏されると、ワークスペースフォルダーが正しく設定されます。Noodlモジュールの共同作業ができるので、このフォルダをバージョン管理下に置いておくとよいでしょう。
訳注:Noodl管理者ではないので、このガイドで紹介されている「noodl-lab」コマンドを使いNoodlクラウドワークスペースへ追加することはできません。
ですが、下記のReactモジュールを作成しNoodlプロジェクトに追加することはできます。新しいReactライブラリモジュールを作成する
ここでワークスペースフォルダタイプに新しいライブラリモジュールを追加します。
$ cd library $ curl -O https://s3.amazonaws.com/updates.noodlcloud.com/lab s/react-module-template.zip $ unzip react-module-template.zipこれでワークスペースライブラリフォルダに2つのフォルダーと1つのアイコンファイルが追加されます。
feed library/ newmodule-project newmodule newmodule-icon.png作成しているライブラリモジュールに合わせて、2つのフォルダとアイコン名を変更します。
例えば「mymodule-project」と「mymodule」のように。$ mv newmodule mymodule $ mv newmodule-project mymodule-project $ mv newmodule-icon.png mymodule-icon.pngまずは「mymodule」フォルダを⾒てみましょう。
ここにはモジュールのソースコードを含めます。
たとえばすべてのReactコードとNoodlを結合するコード、webpackを使⽤するビルドスクリプトなど。
まずpackage.jsonファイルのモジュール名を変更します。{ "name"︓ "com-your-domain-module-name" ... }名前をモジュールの代表的な名前に変更しビルドを進めると。
すべての依存関係あるモジュールがインストールされます。
訳注:「mymodule」フォルダでnpm install
を実行すると、「node_modules」フォルダが作成され、package.jsonに書かれている依存関係のあるモジュールが自動でダウンロードされます。$ npm installテンプレートモジュールにはサンプルコードが含まれているため、そのままでビルドができます。
$ npm run buildこのステップではReactモジュールをビルドし、別フォルダのmymodule-projectにコピーします。このフォルダーはNoodlで開いて編集、テストできるNoodlプロジェクトが含まれています。
訳注:npm run bulid
によってwebpackがproductionモードで実行されます。これで自作モジュールに必要なファイルをindex.jsにまとめます。
さらに、mymodule-projectフォルダにNoodlに必要な「noodl_modules」フォルダを作成しますNoodlを起動し、[Project]タブに移動して、ページ下部の[Import existing project]を選択します。
プロジェクトページの下部にある[Import existing project]ボタンをクリックします
重要 [Save local]チェックボックスがオンになっていることを確認してください。インポートするプロジェクトがローカルのコンピュータに保存され、
インポート元の場所で編集されます。例ではワークスペースフォルダ内のプロジェクトを編集します。「ローカルに保存」がチェックされていることを確認してください︕
プロジェクトに名前を付けて(任意の名前でかまいません)、Pick project folderボタンを押してください。
プロジェクトに名前を付けて、「Pick project folder」をクリックします
ワークスペースディレクトリ(上記のテンプレートを解凍し作成されたディレクトリ)「mymodule-project」フォルダを探し、[開く]をクリックします。
開いたプロジェクトには、サードパーティのReactコンポーネントのようないくつかのカスタムReactコンポーネントが表⽰されます。このコンポーネントを使用したりプロパティを調べることができます。ではモジュールのコードを詳しく⾒てみましょう。
「mymodule」フォルダに戻ると、srcフォルダの下位にindex.jsが含まれているのがわかります
mymodule/ assets/ node_modules/ src/ index.js package.json package-lock.json webpack.config.jsonこれはReactコンポーネントをNoodlにインポートする、すべての結合用コードを含むファイルです。
もしモジュールに新しいコンポーネントを追加するなら、npmを介してパッケージをインストールするか、コードをモジュールフォルダーにコピーし結合用コードをindex.jsファイルに追加してください。MyCustomReactComponentとサードパーティパーティコンポーネントFieldRangeの2つの例をご覧になり、これがどのように⾏われているか確認することをお勧めします。
訳注:サンプルプログラムもこの記事の下部に掲載しています。モジュールのソースコードを変更した場合は再構築する必要があります。
開発モードでビルドステップを起動することができます。
$ npm run dev
これで変更が監視され、新しいモジュールが⾃動的にビルドされてNoodlプロジェクトにコピーされます。
変更を加えたら、Ctrl/Cmd-Rを使ってNoodlビューアを更新するだけです。モジュールをライブラリに追加する
自作モジュールに満足したら、自分のワークスペースライブラリにモジュールを追加できるだけでなく、ほかのNoodlユーザープロジェクトにも含めることができます。
そのためには、ライブラリフォルダー内のindex.jsonファイルをモジュールに合わせて編集してください。
[ { "label":"My React Module", "desc":"I made this myself!", "thumb":"mymodule-icon.png", "project":"mymodule-project" } ]
- label:ライブラリモジュールのラベル
- desc:簡単な説明
- thumb:ライブラリパネルにアイコンとして表⽰される画像
- project:プロジェクトフォルダの名前。プロジェクトの名前でなければなりません。モジュールフォルダではなくプロジェクトフォルダです。
これが完了したら、ワークスペースフォルダのルートに戻り、
新しいコンテンツをNoodlクラウドワークスペースにプッシュします。$ noodl-lab pushプッシュ後、Noodlを再起動する必要があります。再起動するとエディター内のLIBRARYタブに新しいモジュールが表示されます
訳注:ここでもnoodl-lab
コマンドを使えないため、手動で追加していきます。
「noodl_module」フォルダごとNoodlプロジェクトのリソースフォルダ(左のバーからProjectSetting>Open Project folderボタンをクリックして表示されるフォルダ。Windowsでは「C:\Users\user\Documents\Noodl」にある各プロジェクトフォルダでした。)にコピーすることで、Noodlに自作モジュールを追加します。左のバーからLIBRARYタブを選択すると、新しいモジュールが表示されます。
これで、ユーザーは[ADD]ボタンをクリックしてプロジェクトにモジュールを追加できます。
(追加)サンプルコード
最後に上記例で説明されているカスタムReactコンポーネントのサンプルコードがISCライセンス(GNU GPLと両立する自由ライセンス)で提供されているので和訳化して掲載します
index.jsimport FieldRange from '@atlaskit/range'; // https://atlaskit.atlassian.com/packages/core/range //サードパーティReactコンポーネントの例 //Third party react component example const AtlasKitRange = { //ノード名、Noodlで呼ばれるもの。必須 name: 'Atlas Range', //name of the node, this is what it'll be called in Noodl. Mandatory //カテゴリも必須 category: 'Atlas', //A category is also mandatory. //ここでReactコンポーネントを返す必要があります //Here we need to return the React Component. getReactComponent() { return FieldRange; }, //Reactコンポーネントに転送されるすべての入力リスト //A list of all inputs that will be forwarded to the react component inputProps: { isDisabled: {type: 'boolean', default: false}, max: {type: 'number', default: 100}, min: {type: 'number', default: 0}, value: {type: 'number'} }, //出力プロパティはNoodlノードの出力で生成される以外、inputPropsと非常に似ています //これらは通常Reactのコールバックで、Noodlは値をキャッチして出力として送信します //these props are very similar to the inputProps, except that they will generate outputs on the Noodl node //these are typically callbacks in React, and Noodl will catch the value and and send it as an output outputProps: { onChange: { type: 'number', displayName: 'New Value', // Reactコンポーネントに送信されるカスタムコールバックを定義できます。Noodlはここで出力で送信する値が返ってくることを期待しています // この例は非常に単純なので、カスタムの「getValue」関数を定義する必要はありません。 // Noodlが自動的に判断しgetValu関数を補完します // You can define a custom callback that's sent to the React component. Noodl expect you to return a value here // that Noodl will send on the output. In this case its so simple that we don't need to define a custom 'getValue' function. // Noodl will automatically figure it out // getValue: sliderValue => sliderValue } } } //カスタムReactコンポーネントの例 //Custom react component example function MyCustomReactComponent(props) { const style = { color: props.textColor, backgroundColor: props.backgroundColor, borderRadius: '10px', padding: '20px', marginBottom: props.marginBottom }; return <div style={style} onClick={props.onClick} >{props.children}</div> } const MyCustomReactComponentNode = { name: 'Custom React Component', category: 'Tutorial', getReactComponent() { return MyCustomReactComponent; }, inputProps: { backgroundColor: {type: 'color', default: 'white'}, marginBottom: {type: {name: 'number', units: ['px'], defaultUnit: 'px'}, default: 10} }, outputProps: { onClick: {type: 'signal', displayName: 'Click'} } } Noodl.defineModule({ reactNodes: [ AtlasKitRange, MyCustomReactComponentNode ], nodes:[ ], setup() { //起動時に一度呼び出されます //this is called once on startup } });まとめ
Noodl向けにReactコンポーネントを自作し、Noodlノードとして登録したかったので、一連の流れを調べてみました。
自作や外部のReactコンポーネントをNoodノード化できると、Noodlの可能性がまた広がりますね。
- 投稿日:2020-03-20T10:51:04+09:00
第7回 2020年版 React+Firebaseで画像のアップロード(その2)
1. 概要
前回の記事でFirebaseの環境を構築が完了しましたが、今回は実際に画像をアップロードするプログラムを開発します。
2. 前提条件
作業日時
- 2020/3/20
環境
- MacBook Pro
- macOS Catalina
ソフトウェアのバージョン
分類 ソフトウェア バージョン フレームワーク React 16.13.0 フレームワーク React-dom 16.13.0 静的型付け TypeScript 3.7.5 Firebase CLI firebase-tools 7.14.0 ライブラリ Material UI v4.9.4 @material-ui/core 4.9.4 @material-ui/icons 4.9.1 ライブラリ react-dropzone 10.2.1 3. 追加のライブラリのインストール
Material UIに加えて、画像をドラッグアンドで追加可能とするため
react-dropzone
をインストールする。$ yarn add @material-ui/core @material-ui/icons $ yarn add react-dropzone4. Firebaseのコンフィグファイルの作成
Firebaseのアカウント情報を保持するconfigファイルを作成します。 コンフィグファイルのAPI keyなどの入力項目はFirebaseのWebコンソールから確認できます。
「プロジェクトの設定」 > 「全般」 > 「マイアプリ」 > 「ウェブアプリ」 > 「マイアプリ」 > 「Firebase SDK snippet」 > 「構成」に記載されています。以下では直接値を記載していますが、環境変数で指定して
databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL
といった形で値を指定する方が望ましいです。/src/firebase_config.tsimport * as firebase from 'firebase/app'; //必要なモジュールごとにimport // import 'firebase/auth'; // import 'firebase/firestore'; // import 'firebase/database'; import 'firebase/storage'; // import 'firebase/function'; // import 'firebase/analytics'; // インスタンスの初期化 const firebaseConfig = { apiKey: "hoeghogehogehoeghogehogehoeghogehoge", authDomain: "hoge", databaseURL: "https://hoge.firebaseio.com", projectId: "hoge", storageBucket: "hoge.appspot.com", messagingSenderId: "9999999999", appId: "1:1000000000000:web:hogehogehogehoge" }; export const firebaseApp = firebase.initializeApp(config); export default firebaseApp;Storageのアクセス権限修正
今回はサンプルのため、未認証でもファイルの書き込みが可能なように、Storageのアクセス権限を付与します。
storage.rulesrules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { // allow read, write: if request.auth!=null; allow read, write; } } }`firebase deploy'で設定を反映します。
5. プログラムの作成
App.tsx
最初に
App.tsx
にFileUpload
のコンポーネントを表示するように修正します。src/App.tsximport React from "react"; import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; import CssBaseline from "@material-ui/core/CssBaseline"; import green from '@material-ui/core/colors/green'; import FileUpload from "./FileUpload"; // 独自のテーマを作成する const theme = createMuiTheme({ palette: { //type: 'dark', // ダークテーマ primary: green, }, typography: { fontFamily: [ 'Noto Sans', 'sans-serif', ].join(','), fontSize: 12, h1: { fontSize: "1.75rem" }, h2: { fontSize: "1.5rem" }, h3: { fontSize: "1.25rem" }, h4: { fontSize: "1.125rem" }, h5: { fontSize: "1rem" }, h6: { fontSize: "1rem" }, } }); function App() { return ( <MuiThemeProvider theme={theme}> <CssBaseline /> <FileUpload /> </MuiThemeProvider > ); } export default App;FilreUpload.tsx
次に、
FileUpload.tsx
のコンポーネントを作成します。
useDropzone
でDropzoneの設定と、ファイルがドロップされた時に呼びされる関数onDrop
を指定します。onDrop
の中ではドロップされたファイルをfiles
に保存してます。この
files
に保存された画像ファイルはサムネイルとして、下部に表示しています。Masonryっぽく全てのタイルが埋まるようにしています。ちゃんと実装するなら以下のようなライブラリを利用してください。Firebase storageへのアップロードは、アップロードボタンが押下された時に呼び出される
onUpload
関数の中で行っています。
const storageRef = firebaseApp.storage().ref().child('images/' + file_name);
でアップロード先を指定し、
var task = storageRef.put(file);
でアップロードを行っています。task
はアップロード処理の途中経過・完了の把握や、処理の停止をするためのものです。
Promise.all
で各ファイルを並列にアップロードを行い、全てアップロードが完了したら、ローディングの中止とアラートの表示を行っています。src/FileUpload.tsximport React, { useState, useEffect, useCallback } from "react"; import * as firebase from 'firebase/app'; import { firebaseApp } from './firebase_config'; import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; import { Typography, Grid, GridList, GridListTile, GridListTileBar, Button, IconButton, Paper, CircularProgress } from '@material-ui/core/'; import CloudUploadIcon from '@material-ui/icons/CloudUpload'; import InfoIcon from '@material-ui/icons/Info'; import { useDropzone } from 'react-dropzone' // スタイルを適用する const useStyles = makeStyles((theme: Theme) => createStyles({ root: { display: 'flex', }, paper: { padding: theme.spacing(2), textAlign: 'center', '& > *': { margin: theme.spacing(3), }, }, dropzone: { width: "100%", height: 200, boxSizing: "border-box", borderWidth: 2, borderColor: "#666666", borderStyle: "dashed", borderRadius: 5, verticalAlign: "top", marginRight: "2%", }, thumbsContainer: { marginTop: 16, }, gridList: { width: "100%", height: 450, // Promote the list into his own layer on Chrome. This cost memory but helps keeping high FPS. transform: 'translateZ(0)', }, titleBar: { background: 'linear-gradient(to bottom, rgba(0,0,0,0.7) 0%, ' + 'rgba(0,0,0,0.3) 70%, rgba(0,0,0,0) 100%)', }, icon: { color: 'white', }, upButton: { color: "secondary", margin: theme.spacing(3), }, circular: { textAlign: 'center', } }), ); // propsは無し type Props = {}; // Dropzoneの設定 const acceptFile = 'image/*'; const maxFileSize = 1048576; // previewを追加 type MyFile = File & { preview: string; }; export default function FileUpload(props: Props) { console.log("FileUpload page start."); // State const [files, setFiles] = useState<MyFile[]>([]); const [uploading, setUploading] = useState(false); const [progress, setProgress] = useState(0); const classes = useStyles(props); /* ドロップした時の処理 */ const onDrop = useCallback((acceptedFiles: File[]) => { console.log('onDrop'); // previewの追加 setFiles(acceptedFiles.map( file => Object.assign(file, { preview: URL.createObjectURL(file) }))); }, []) // Dropzone const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: acceptFile, minSize: 0, maxSize: maxFileSize }) const onUpload = async () => { console.log('onUpload start'); // ローディングをOn。progressを初期化 setUploading(true); setProgress(0); function uploadImageAsPromise(file) { console.log('uploadImageAsPromise start'); // アップロード先のファイルパスの作成 const file_name = file.name; const storageRef = firebaseApp.storage().ref().child('images/' + file_name); return new Promise(function (resolve, reject) { //Upload file var task = storageRef.put(file); //Update progress bar task.on(firebase.storage.TaskEvent.STATE_CHANGED, function progress(snapshot) { var percent = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; console.log(percent + "% done"); }, function error(err) { // 失敗時 console.log("upload error"); reject(err); }, function complete() { // 成功時 console.log('upload complete.'); task.then(function (snapshot: firebase.storage.UploadTaskSnapshot) { resolve(snapshot.ref.getDownloadURL()); }) } ); }).then(function (downloadURL) { console.log("Finished uploading file: " + file_name); // progressを更新する setProgress(oldProgress => (oldProgress + 1)); return downloadURL; }).catch(function () { console.log("Error:uploadImageAsPromise"); }); } // 複数のファイルアップロードをPromise.allで並列に実行する const result = await Promise.all(files.map((file) => { return uploadImageAsPromise(file); })); console.log("Upload result"); console.log(result); // ローディングを終了し、リストを空に setUploading(false); setProgress(0); setFiles([]); alert("送信されました"); } // アップロード中はCircularを表示する if (uploading === true) { const percent = Math.round((progress / files.length) * 100) console.log("Loadingの表示。Progreass:" + progress + " Percent:" + percent); return ( <Grid container className={classes.root} spacing={3} justify="center"> <Grid item xs={6}> <Paper variant="outlined" elevation={3} className={classes.paper}> <CircularProgress className={classes.circular} variant="determinate" value={percent} /> </Paper> </Grid> </Grid> ) } else { // タイルを敷き詰められるように、一部画像のサイズは大きくする const tile_cols = 3; let tile_featured = []; switch (files.length % tile_cols) { case 0: tile_featured = []; break; case 1: tile_featured = [0, files.length - 1]; break; case 2: tile_featured = [0]; break; } // サムネイルの作成 const thumbs = files.map((file, index) => ( <GridListTile key={file.preview} cols={tile_featured.indexOf(index) >= 0 ? 2 : 1} rows={1}> <img src={file.preview} alt={file.name} /> <GridListTileBar title={file.name} subtitle={file.size} actionIcon={ <IconButton aria-label={`star ${file.name}`} className={classes.icon}> <InfoIcon /> </IconButton> } actionPosition="left" className={classes.titleBar} /> </GridListTile> )); const diabled_button = (files.length === 0); return ( <Grid container className={classes.root} spacing={3} justify="center"> <Grid item xs={6}> <Paper variant="outlined" elevation={3} className={classes.paper}> <Typography variant="h4">Upload image files to GCS</Typography> <div> <Paper className={classes.dropzone} {...getRootProps()}> <input {...getInputProps()} /> { isDragActive ? <p>Drop the files here ...</p> : <p>Drag 'n' drop some files here, or click to select files</p> } </Paper> <Button onClick={onUpload} variant="outlined" color="primary" disabled={diabled_button} className={classes.upButton} startIcon={<CloudUploadIcon />} >Upload</Button> <aside className={classes.thumbsContainer}> <GridList cellHeight={200} className={classes.gridList} cols={tile_cols}> {thumbs} </GridList> </aside> </div> </Paper> </Grid> </Grid> ); } }6. 動作確認
yarn start
でサーバーを起動し、ブラウザで表示します。FirebaseのWebコンソールでファイルがアップロードされていることを確認します。
7. 最後に
次回はアップロードした画像をCloud Functionsでリサイズする方法について説明します。
8. 関連記事
Reactに関する記事です。
- 投稿日:2020-03-20T10:40:06+09:00
React+Typescript+SWRでアプリをつくる
SWR という面白そうなライブラリに遭遇し、気になったので簡単にアプリを作ってみようと思います。
構成はReact+Typescript+SWRです。
今回はReact+Typescriptの環境構築の仕方などは書きません。
create-react-app
を使うなり、webpack
を使うなり自分の好きな方法で構築して下さい。APIはGitHubさんが公開しているAPIがあるのでそちらを使っていきたいと思います。
API(GitHub)
ライブラリ公式(英語のみ)
環境
MacBookPro v10.14.6 node v10.15.3 yarn v1.15.2 React v16.13.0 Typescript v3.8.3 SWR v0.1.18インストール
皆さんそれぞれReact Appを作成したとして、今回のメインであるSWRをインストールします。
# 今回、僕はyarnを使っていますがnpmでも問題ないです # SWRはtypescriptに対応しているので@typesをインストールする必要はないです。 yarn add swr使用するAPI
今回、GitHub APIの中でもユーザーネームを入れたらそのユーザーのpublicリポジトリを返すAPIを使いたいと思います。
# usernameのリポジトリを返す https://api.github.com/users/:username/repos** GitHub APIは1日にリクエストを送れる回数が決まっているので、気をつけて下さい。
実装
ここからアプリを実装していきたいと思います。
まず、今回必要なモノをimportするのと雛形を作成していきます。App.tsximport React from 'react'; // インストールしたSWR import useSWR from 'swr'; const App: React.FC = () => {} export default App;ここまできたら、GitHub APIを使ってデータを取得する為のコードを追加していきます。
App.tsximport React from 'react'; import useSWR from 'swr'; const App: React.FC = () => { // 追加 const fetcher = ( url: string ) => fetch(url) .then(res => res.json) .catch(error => error) // facebookのリポジトリを取得します const { data, error } = useSWR( "https://api.github.com/users/facebook/repos", fetcher ) } export default AppこれだけでAPIへのリクエスト、レスポンスの取得ができます。
めちゃくちゃシンプルじゃないですか???ここまで来たら完成したようなもの、後はViewに表示させたいと思います。
App.tsxconst App: React.FC = () => { const fetcher = ( url: string ) => fetch(url) .then(res => res.json) .catch(error => error) const { data, error } = useSWR( "https://api.github.com/users/facebook/repos", fetcher ) // 追加 // errorとloadingの部分は公式のホームページに載っているものです if (error) return <div>failed to load</div> if (!data) return <div>loading...</div> return ( <div> // 今回は、anyにしています。 {data.map((d: any) => ( <a href={`https://github.com/${d.full_name}`}> // d.full_nameで facebook/:repo_name みたいな値が取れます。 {d.full_name} </a> ))} </div> ) } export default Appここまでやるとこうなります。
僕の場合styleをつけているので少し違うと思いますが、facebookのリポジトリを取得することができました!
完成形
完成形がこちらです。
App.tsximport React from 'react'; import useSWR from 'swr'; const App: React.FC = () => { const fetcher = ( url: string ) => fetch(url) .then(res => res.json) .catch(error => error) const { data, error } = useSWR( "https://api.github.com/users/facebook/repos", fetcher ) if (error) return <div>failed to load</div> if (!data) return <div>loading...</div> return ( <div> {data.map((d: any) => ( <a href={`https://github.com/${d.full_name}`}> {d.full_name} </a> ))} </div> ) } export default App今回、触ってみて思ったのは本当にシンプルで読みやすい。
個人的にかなりオススメなライブラリなので、皆さんもお試しあれ。使用したモノ
- 投稿日:2020-03-20T07:18:59+09:00
React Native × React Navigation v5 で Deep linking
はじめに
またしても React Navigation v5 の記事です。
今回は Deep linking を実装していきます。下準備
iOS 用の設定をします。
URL Types
の設定をおこないます。次に、
AppDelegate.m
を修正します。コードはこちらです。
#import "AppDelegate.h" // 元からある #import <React/RCTLinkingManager.h> ... - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { return [RCTLinkingManager application:application openURL:url options:options]; } - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler { return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; }これだけで、 Safari などで
<URL Schemas>://xxxx
のように入力すれば、アプリを開くことができます。
これでアプリへのアクセスはできるようになりました。
各ページへの遷移は React Navigation と組み合わせて使うと便利です。
以下ではその方法を実装していきます。React Navigation v5 で Deep linking
公式ドキュメントを参考に、
useLinking
を使用します。const App: React.FunctionComponent = () => { const ref = useRef(); const { getInitialState } = useLinking(ref, { prefixes: ['srn5://'], config: { Details: 'details', }, }); const [isReady, setIsReady] = useState(false); const [initialState, setInitialState] = useState(); useEffect(() => { Promise.race([ getInitialState(), new Promise((resolve) => setTimeout(resolve, 150)), ]) .catch((e) => { console.error(e); }) .then((state) => { if (state !== undefined) { setInitialState(state); } setIsReady(true); }); }, [getInitialState]); if (!isReady) { return null; } return ( <AuthProvider> <NavigationContainer initialState={initialState} ref={ref}> <HomeNavigator /> </NavigationContainer> </AuthProvider> ); }
ref
を定義して、NavigationContainer
とuseLinking
に渡しています。
useLinking
のconfig
でパスと画面を紐づけています。
HomeNavigator
は下記の形で定義しています。import { createStackNavigator } from '@react-navigation/stack'; import React from 'react'; const Stack = createStackNavigator(); const HomeNavigator = () => ( <Stack.Navigator> <Stack.Screen name="Home" component={Home} /> <Stack.Screen name="Details" component={Details} /> </Stack.Navigator> )通常のアクセスだと
Home
が表示されますが、srn5://details
にアクセスするとDetails
が直接開きます。おわりに
今回は、 Deep linking の実装について紹介しました。
実際に利用する場合、ユニバーサルリンク(
https://~~~
)という形で利用するのが現実的かと思いますが、まずはこの方法で簡易的なアプリへのリンクを設定することができます。参考
https://reactnative.dev/docs/linking#handling-deep-links
https://reactnavigation.org/docs/deep-linking/
- 投稿日:2020-03-20T03:21:40+09:00
create-react-appで作成したアプリのデプロイにおける環境変数の扱いについて
環境
- create-react-app 3.4.0
- gh-pages 2.2.0
create-react-appでの環境変数について
ReactやNodeなどで環境変数を使いたい場合には、dotenvをインストールする必要がありますが、create-react-appを使用した場合だと、デフォルトでdotenvが組み込まれています。
このため、追加のインストールをせずとも環境変数が設定できます。
ただし、create-react-appの場合はREACT_APP_
から始まる変数名をつけなくてはなりません。
.env
ファイルの例は以下のようになります。.envREACT_APP_API_KEY="YOURKEY"環境変数にアクセスするには次のようになります。
App.jsrender () { return ( <div> {process.env.REACT_APP_API_KEY} </div> ) }デプロイする際の注意点
環境変数の読み込みにはいくつかモードがあり、
ローカル実行時には、.env.development
、
テスト実行時には、.env.test
、
ビルド時には、.env.production
というふうになっています。このため
.env
しか用意せず、ビルドを行い、デプロイをすると、環境変数が読み込まれない場合があります。解決法
この解決法として、
.env
ファイルを複数モード用に分ける方法があります。
私は以下のように分けました。
- 開発用
.env.developmentREACT_APP_API_KEY="YOURKEY"
- 本番用
.env.productionREACT_APP_API_KEY="YOURKEY"このように分けることで、
npm run build
やgh-pages -d build
を行った際に、生成したjsファイルに環境変数が埋め込まれるようになります。参考文献
- 投稿日:2020-03-20T00:00:21+09:00
CSS Modules の問題点をミックスインで解決する
はじめまして、よこけんです。(2回目)
今日は、CSS Modules の問題点を解決する方法について検討したのでそれを共有してみます。CSS Modules の問題点
CSS Modules ではクラスセレクタによるスタイル適用が基本です。
そしてクラス名は一つのコンポーネントに複数指定することができます。
しかし、複数指定した場合の適用順序は保証されません。クラス名の指定順序ではなく、クラスセレクタが読み込まれた順序に依存します。例えば下記の場合、背景色は赤色ではなく青色になります。
a.styl.a background-color: redb.styl.b background-color: blueHoge.tsximport * as aStyles from "./a.styl"; import * as bStyles from "./b.styl"; const Hoge = () => <div className={`${bStyles.b} ${aStyles.a}`}>Hoge</div>;サンプルコードでは
Hoge.tsx
がインポート順序をb.styl
->a.styl
に変えれば意図した結果を得ることができますが、例えばHoge.tsx
から参照される別のコンポーネントでb.styl
をインポートしている場合にはやはり.b
が優先されて青色になってしまいます。
この問題は非常に厄介な上に、直接的な解決方法というものはありません。ミックスインによる解決
本記事では、この問題に対する比較的扱いやすい解決方法として、Stylus や Sass のミックスイン機能を利用した解決方法を紹介します。
ミックスインはクラス継承と違い、適用位置にプロパティを全てコピーします。全てのプロパティが集約されることにより、単純にミックスインの指定順序に従ってプロパティが適用されます。ミックスイン本体の読み込み順序は関係ありません。次のサンプルコードでは、
mixHoge()
がmixB()
の後にmixA()
を呼んでいるため、背景色は必ず赤色になります。a.stylmixA() background-color: red .a mixA()b.stylmixB() background-color: blue .b mixB()Hoge.styl@import "a.styl" @import "b.styl" mixHoge() mixB() mixA() .hoge mixHoge()Hoge.tsximport * as styles from "./Hoge.styl"; const Hoge = () => <div className={styles.hoge}>Hoge</div>;シンプルなルール
前述のコードをルール化すると次のようになります。
- 一つのコンポーネントに複数のクラスを指定してはいけない
- クラスセレクタを用意する場合、対になる単一のミックスインを必ず用意する
- クラスセレクタでは常に、対になる単一のミックスインの適用のみを行い、スタイル記述はミックスイン内で行う
- クラス継承 (
@extends
) は一律禁止とし、代わりにミックスインを使用するクラス継承 (
@extends
) は一律禁止ということに気を付けてください。
単一継承であれば大丈夫のように思うかもしれませんが、クラス継承を使ってしまうと、そこから先をミックスインで派生させても全てのプロパティが一箇所に集約されなくなってしまい、読み込み順序に再び依存するようになってしまいます。この方式の欠点は、トランスパイルされた CSS ファイルのサイズが肥大化するリスクです。
前述の通り、ミックスインはクラス継承と違い、適用位置にプロパティを全てコピーします。だから読み込み順序に一切依存しなくなるわけですが、これはトランスパイル後の CSS ファイルのサイズに影響します。
スタイルの継承を多用するようなプロジェクトではリスクが顕在化するかもしれません。
リスクが顕在化してきた場合には、プロパティ数が多く継承も多く行われる特定のスタイルに対してのみクラス継承 (@extends
) を許可し、それらのクラスセレクタだけは読み込み順序を慎重に管理します。追加ルールでリスクを軽減
先ほどはクラス継承を使用すると問題に繋がるとしていましたが、厳密には、派生を許可しないクラスからであればクラス継承を使用しても問題には繋がりません。
合法的にクラス継承を使用できるケースが発生すると、ファイルサイズを抑える効果が期待できます。次のサンプルコードは、
.hoge
クラスの派生を禁止することで安全を確保できます。a.stylmixA() background-color: red .a mixA()b.stylmixB() background-color: blue .b mixB()Hoge.styl@import "a.styl" @import "b.styl" .hoge @extends .b mixA()Hoge.tsximport * as styles from "./Hoge.styl"; const Hoge = () => <div className={styles.hoge}>Hoge</div>;これをルール化すると次のようになります。
- 一つのコンポーネントに複数のクラスを指定してはいけない
- 派生を許可するクラスセレクタを用意する場合、対になる単一のミックスインを必ず用意する
- 派生を許可するクラスセレクタでは常に対になる単一のミックスインの適用のみを行い、スタイル記述はミックスイン内で行う
- ミックスイン内でのクラス継承 (
@extends
) は一律禁止とし、代わりにミックスインを使用する- 派生を許可しないクラスセレクタではスタイルを直接記述して良い
- 派生を許可しないクラスセレクタではクラスを一つだけ継承 (
@extends
) して良い (2つ以上のクラスを継承したい場合はミックスインを併用する)この方式の欠点は二つあります。
一つはルールが少し複雑になるために混乱を招いたりルール違反が発生しやすくなることです。
ただし、派生を許可するクラスセレクタがあまり多くない (整理されていて見通しが良い) プロジェクトなら、追加ルールを適用しても混乱やルール違反は最低限に抑えられると思います。もう一つは、リスクの低減はできても完全に回避することはできないということです。
リスクが顕在化してきたら最初の解決方法と同様、特定のスタイルに対してクラス継承 (@extends
) を許可し、それらのクラスセレクタだけは読み込み順序を慎重に管理します。
しかし、元々少し複雑なルールにこの例外措置が加わることになりますので、混乱やルール違反をより招きやすくなる恐れがあります。結論
- リスクが顕在化する可能性が低そうであればシンプルなルールを採用する
- 追加ルールを採用する場合、混乱やルール違反を招かないよう工夫する
なお、そもそも複数クラスの継承をしようとしなければ問題は起きません。ただし、そのためのルールは結局必要になります。 (そして恐らく、そのルールによって新たなリスクも発生します。)
根本的には、クラスセレクタの読み込み順序ではなくクラス指定順序で結果が決まってくれれば良いんですが、CSS の仕様のようなので。