- 投稿日:2019-12-04T22:40:36+09:00
React x Typescriptで@emotion/coreを使用するために。
cssが認識されずにハマったので共有。
こちらの記事で解決。
cssを認識させるために、jsxに解釈させる必要があるらしい。
Emotion v10 unusable with pure TypeScript · Issue #1046 · emotion-js/emotion · GitHubtsconfig.jsonにjsxFactoryを追加
"jsxFactory": "jsx",コンポーネントファイルごとにpragmaを宣言する
/** @jsx jsx */ import { css, jsx } from '@emotion/core'; const theme = css` width: 100vw; height: 100vh; background-color: #000; `; const App: React.FC = () => { return ( <div css={theme}> <p>hello</p> </div> ); } export default App;pragmaとは?
https://www.gatsbyjs.org/blog/2019-08-02-what-is-jsx-pragma/
A pragma is a compiler directive. It tells the compiler how it should handle the contents of a file.
プラグマはコンパイラ指令です。ファイルの内容を処理する方法をコンパイラに指示します。jsx pragmaはコンパイラにjsxと解釈させるために必要なもの。
- 投稿日:2019-12-04T22:23:44+09:00
ReactとReduxで複数のReducerを使う
簡単な機能を実装する場合にはreducerは一つだけでも問題はないですが、
機能が増えるとreducerをモノリスにしておくわけにもいかなくなります。複数のreducerを機能別に分けて使うにはreduxのCombineReducersを使う方法があります。
使い方のまとめとして投稿します。CombineReducersのインポート例
rootReducer
にreducerを注ぎ込む。
このrootReducerの部分はuseSelectorで取得することができるstateになる。index.tsimport { combineReducers } from 'redux' import { FirstReducer, SecondReducer, } from './reducer' const rootReducer = combineReducers({ first: FirstReducer, second: SecondReducer, }) export default rootReducer各reducerの例
actionのpayloadは対象のreducerが担当するstateの分だけ用意すれば良い。
CombineReducersがそれらを結合してくれる。reducer.tsconst initialFirst = { hoge: '', foo: '', } const initialSecond = { bar: '', baz: '', } export const FirstReducer = ( state: typeof initialFirst, action: { type: string, payload: typeof initialFirst } ): typeof initialFirst => { switch(action.type) { case 'HOGE': return { ...action.payload, hoge: action.payload, } case 'FOO': return { ...action.payload, foo: action.payload, } default: return state } } export const SecondReducer = ( state: typeof initialSecond, action: { type: string, payload: typeof initialSecond } ): typeof initialSecond => { switch(action.type) { case 'BAR': return { ...action.payload, bar: action.payload, } case 'BAZ': return { ...action.payload, baz: action.payload, } default: return state } }
- 投稿日:2019-12-04T22:17:56+09:00
[React] Windows 10上でGatsbyJSを利用して開発用サーバでデフォルトの静的サイトを立ち上げる
目次
- 初めに
- GatsbyJSとは
- 前提条件
- Gatsby CLIのインストール
- GatsbyJS Quick Start
- 終わりに
初めに
応用情報の試験が終わった約一か月前からReactの勉強を始めて、最近では
create-react-app
でひな型を生成してそこから色々書いていました。
ですが最近この記事を読んでGatsbyJS
というReactのフレームワークの存在を知って、興味がわいたのでWindows 10上でGatsbyJSを触ってみることにしました。Reactの最強フレームワークGatsby.jsの良さを伝えたい!! - Qiita
この記事では以下のGatsbyJSのデフォルトのページを立ち上げるところまでやります。
GatsbyJSとは
公式→GatsbyJS
私もこの記事を書いてる段階でアーキテクチャ等の理解が追い付いていないので、冒頭で紹介した記事を引用します。
Gatsby.jsはReactで作られた静的サイトジェネレーターです。内部的にGraphQLを用いてデータを取得し、markdownからHTMLを生成、などの処理を簡単に行うことができます。
静的サイトジェネレーターが何かと言うと、何かしらの言語で書かれたソースから、静的なHTML/CCC & JavaScriptを生成するツールのことを言います。
私はこちらの方のブログも参考にさせて頂いてます。
「Gatsby JS」を導入してみた - masalibの日記
セキュリティ面や通信速度面などの様々なメリットが挙げられていますが、Web技術絶賛勉強中の私はこのGatsbyJSを勉強するとHTTP2とかGraphQLとか最近流行りだけど一体なんのこっちゃって感じの技術も一緒に勉強できる点が大変魅力に感じました。
人によってはGatsbyJS以外にもGatsby.jsだったり単にGatsbyだったり呼ばれていますが、本記事では引用部分以外は公式に沿ってGatsbyJSで統一します。
次節で実際にGatsbyJSを触っていきます。
前提条件
以下私が触った時の前提条件になります。
- Windows 10
- node v12.13.1
- npm 6.12.1
- Gatsby CLI version 2.8.15※以降ではnpmは既にWindows 10上にインストールしてある前提で書いてしまいました。余裕があれば後々追記します。
Gatsby CLIのインストール
公式によるとWindows上でGatsbyJSのCLIをインストールする前に
windows-build-tools
が必要らしい?(Gatsby on Windows | GatsbyJS)
実際私はいきなりnpm install -g gatsby-cli
を叩いたら上手くいきませんでした。公式にはこの
windows-build-tools
がコマンドラインで上手くインストールできなかった場合のケアも記載されていますが結論として私は問題なかったので飛ばします。ということで私はコマンドプロンプトを開いて
windows-build-tools
をインストールを試みました。> npm install windows-build-tools -gすると怒られました。
Starting installation... Please restart this script from an administrative PowerShell! The build tools cannot be installed without administrative rights. To fix, right-click on PowerShell and run "as Administrator". npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! windows-build-tools@5.2.2 postinstall: `node ./dist/index.js` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the windows-build-tools@5.2.2 postinstall script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\<username>\AppData\Roaming\npm-cache\_logs\2019-12-03T23_19_04_021Z-debug.log要約すると2-3行目に「管理者権限のPowerShellでやり直せ!」って書いてますね。
公式にもちゃんと管理者権限のPowerShellでやれって書いてました。完全に見落としてました。> npm install windows-build-tools -g次にGatsby CLIのインストールに移ります。
> npm install -g gatsby-cli一応バージョン確認。
> gatsby --version Gatsby CLI version: 2.8.15GatsbyJS Quick Start
Gatsby CLIがインストールできたので公式のQuick Startに従って、GatsbyJSで作った静的サイトのデフォルトページを表示してみたいと思います。
Quick Start | GatsbyJS
https://www.gatsbyjs.org/docs/quick-start/まずは静的サイトのひな型を作成します。
> gatsby new gatsby-site次にディレクトリを移動して開発サーバの立ち上げ。
> cd gatsby-site > gatsby develop開発サーバの立ち上げに成功すると途中で以下のようなメッセージが表示されます。
You can now view gatsby-starter-default in the browser. ⠀ http://localhost:8000/言われたとおりに
http://localhost:8000/
にアクセス。冒頭と同じ画面が表示されました。
終わりに
React以外の技術もたくさん学べそうなのでGatsbyJSをこれから触っていこうと思います。
- 投稿日:2019-12-04T19:02:00+09:00
react-nativeでロード画面を表示させる
はじめに
前回の記事からの続きです。
前回はstrageに画像をアップロードするまでをやりました。strageに画像をアップしてからURLを入手するまで少し時間がかかります。そこで、待ち時間にロード画面を表示するようにします。(concurrent modeは使いません)コード
Camera.tsximport React, { useRef, useState } from 'react'; import { NavigationStackScreenComponent } from 'react-navigation-stack'; import { View, StyleSheet, ActivityIndicator } from 'react-native'; import { Button, Container, Icon, Text, } from 'native-base'; import { Camera as ExpoCamera } from 'expo-camera'; import { upLoadImg } from '../../../utils/upLoadImg'; import usePermission from '../../../utils/usePermission'; const styles = StyleSheet.create({ button: { position: 'absolute', bottom: 100, zIndex: 1, alignSelf: 'center', height: 80, width: 80, flex: 1, justifyContent: 'center', }, icon: { fontSize: 50, }, flexOne: { flex: 1, }, activityIndicator: { flex: 1, alignItems: 'center', justifyContent: 'center', }, }); const Camera: NavigationStackScreenComponent = () => { const { cameraPermission } = usePermission(); const [isLoading, setIsLoading] = useState(false); const cameraRef = useRef(null); const snap = async () => { if (cameraRef) { const { uri } = await cameraRef.current.takePictureAsync(); // uriはローカルイメージURIで一時的にローカルに保存される const response = await fetch(uri); const blob = await response.blob(); const imgName = blob.data.name; // console.log(blob.data.name); setIsLoading(true); upLoadImg(imgName, blob) .then((url) => { console.log(url); setIsLoading(false); }); // .catch((error) => console.log(error)); } }; const renderCamera = (): React.ReactElement => { // console.log(cameraPermission); if (cameraPermission === null) { return <View />; } if (cameraPermission === false) { return <Text>No access to camera</Text>; } return ( <Container style={styles.flexOne}> <ExpoCamera style={styles.flexOne} ref={cameraRef}> <View style={styles.flexOne}> <Button rounded icon onPress={() => snap()} style={styles.button} > <Icon name="camera" style={styles.icon} /> </Button> </View> </ExpoCamera> </Container> ); }; const renderActivityIndicator = () => ( <View style={styles.activityIndicator}> <ActivityIndicator /> </View> ); return ( <> { isLoading ? renderActivityIndicator() : renderCamera() } </> ); }; export default Camera;isLoadingというステートを用意しておいて、ロードが始まったらisLoadingをtrueに、終わったらfalseにするというようにして、isLoadingによってロード画面を表示しています。
そんなに大変な作業ではないですね。そうなんです、記事を分ける必要はなかったんです( ´•д•`; )
この記事の本題はここまでですが、僕はこれから尺を稼ぎます。
よかったら読んでください、、、フックについて
今回はreactのhooksやカスタムフックを使いましたが、hooksについてまとめます。
フックとは
フックはreactの機能の一つで、ステートやライフサイクルなどの機能をクラスを書かずに使えるようにします。react-nativeでは、React Native0.59リリース以降でフックをサポートしています。
一部のコンポーネントだけでフックを使うこともでき、クラスコンポーネントと共存させることができます。(クラスコンポーネント内ではフックを利用することはできません)フックのルール
- 通常のjavascript関数内から呼び出してはいけない(呼び出すのはfunctional componentのみ)
- hooksは条件分岐やネストされた関数内から呼び出すことはできない(react関数内のトップレベルでのみ呼び出す)
一つ目のルールについては二つ目のルールを違反しないためのルールだと思います。
二つ目の理由についてはこの記事がとてもわかりやすかったです。
とにかくreactはフックが呼ばれる順番に依存しているということを覚えておけば良さそうです。カスタムフックを作る時の注意点
カスタムフックとは、名前がuseで始まり、ほかのフックを呼び出すJavaScriptの関数のことです。(javaScriptの関数内から呼び出せるんかいって思いましたが、他のフックを呼び出した時点でそれはもうJavaScript関数ではなくてカスタムフックになります。)
関数名をuseで始める理由は、その関数にフックのルールが適用されるということを知らせるためです。これによってカスタムフックを利用する時に明示的にトップレベルで呼び出すだけで良くなります。(useから始めなくても普通に動きます。)
フックにはルールがあるからフックを使っているロジックをコンポーネント間で共有するときは気をつけようということだと思います。functional components VS stateless components
ちなみに、こんな関数?は以前までstateless componentsと呼ばれていましたが、hooksが登場した時点でこれらはfunctional componentsと呼ぶようになりました。
import React from 'react'; const Hoge = (props) => ( <h1>{props.name}</h1> ); function Hoge(props) { return ( <h1>{props.name}</h1> ); };あと、functional componentで”import React from 'react';”するのなんでなのか、ずっと気になっていました。
functional componentでreactをインポートする理由は、インポートしないと”React.createElement()”が使えないからです。上記のコードではh1タグのようなものが出てきましたが、これはJSXと呼ばれるもので、このJSXを使うとreactは自動的にReact.createElement()によって"react要素"と呼ばれるものに変換しています。babelによってこんな感じ?に変換されるのですが、その時にReact.createElement()が必要になります。reactを書いているときはbabelによって変換される前のコードしか見ていないため分かりずらいですね。ぜひbabelのアレで試してみてください。
// 変換前 function hoge() { return <div className="container">hello world</div>; } // 変換後 function hoge() { return React.createElement("div", { className: "container" }, "hello world"); }途中で出てきた誰やねんって感じの"react要素"ですが、react要素と”ルートDOMノード”をReactDOM.render()に渡すことで画面が表示されます。
// .html // ルートDOMノード <div id="root"></div> // .js .jsx import React from 'react'; import ReactDOM from 'react-dom'; // JSX const element = <h1>Hello, world</h1>; ReactDOM.render(element, document.getElementById('root'));reactの環境をずっとcreate-react-appで整えてきたためこの辺りはあまり知りませんでした。babelやらwebpackやらは名前かっこいいくらいにしか思っていなかったため、自力でreactの環境を整えながら勉強しようと思いました。
最後に
最後まで読んで頂きありがとうございます。締めの一言的なものが何も思い浮かばないため、世界一どうでもいい情報を発表しときます。
最近ボクサーからトランクスに乗り換えました!!
- 投稿日:2019-12-04T18:53:19+09:00
react-firebase-hooksを使ってみた(Firestore Hooks編)
はじめに
前回の記事でreact-firebase-hooksのAuth Hooksを使ってみたのですが、今回はFirestore Hooksに挑戦したいと思います。
react-firebase-hooks
リポジトリはこちらです。
https://github.com/CSFrequency/react-firebase-hooks
このライブラリは、4種類のAPIを提供しています。
- Auth Hooks
- Cloud Firestore Hooks
- Cloud Storage Hooks
- Realtime Database Hooks
今回対象とするのは2つ目のFirestore Hooksです。
テストデータの作成
最初に、Firestoreのコンソールでコレクションといくつかのドキュメントを作成します。コレクション名はtodosとしました。
コーディング
モジュールのimport
今回使うモジュールをimportします。
import React, { useState } from "react"; import ReactDOM from "react-dom"; import firebase from "firebase"; import { useCollectionData } from "react-firebase-hooks/firestore";TodoListコンポーネント
todosコレクションを表示するコンポーネントを作ります。
const TodoList = () => { const [values, loading, error] = useCollectionData( firebase.firestore().collection("todos"), { idField: "id" } ); if (loading) { return <div>Loading...</div>; } if (error) { return <div>{`Error: ${error.message}`}</div>; } return ( <ul> {values.map(value => ( <li key={value.id}>{value.title}</li> ))} </ul> ); };
idField
でidを取得するところがポイントです。NewTodoコンポーネント
todosコレクションに新たなドキュメントを追加するためのコンポーネントを作ります。
const NewTodo = () => { const [title, setTitle] = useState(""); const [pending, setPending] = useState(false); const add = async () => { setTitle(""); setPending(true); try { await firebase .firestore() .collection("todos") .add({ title }); } finally { setPending(false); } }; return ( <div> <input value={title} onChange={e => setTitle(e.target.value)} /> <button type="button" onClick={add}> Add </button> {pending && "Pending..."} </div> ); };エラー処理は省略しています。
Appコンポーネント
最後に、全体をつなげるAppコンポーネントとReactDOMのrenderです。
const App = () => { return ( <div> <TodoList /> <NewTodo /> </div> ); }; const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);CodeSandbox
https://codesandbox.io/s/wonderful-browser-zh0wc
完成したものがこちらです、実際に動作させるためにはforkしてfirebaseConfigを置き換える必要がありますのでご注意ください。
おわりに
Firestoreのリアルタイム更新とReact Hooksはとても相性がいいと感じました。ドキュメントを追加したら、すぐに更新されます。Firestoreのコンソールから追加しても同様です。
今回は、useCollectionDataを使いましたが、用意されているhooksはさらにあります。
- useCollection
- useCollectionOnce
- useCollectionData
- useCollectionDataOnce
- useDocument
- useDocumentOnce
- useDocumentData
- useDocumentDataOnce
Once系は一度だけの取得なのですが、その場合はhookがどれだけ役立つかは微妙です。callbackから使うことになることが多い気がします。また、Data系のhookはTypeScriptの型が付けられますが、ソースコード上は単にアサーションしているだけなので、予期せぬランタイムエラーが発生する可能性がありそうです。結局、独自の拡張をしようと思うとcustom hooksを作ることになりそうですが、その先に本ライブラリのhooksから合成できるかはやってみないと分からないといった感じになりそうです。
- 投稿日:2019-12-04T18:37:59+09:00
ゆ、useEffectちゃん!初回に動かないで!
TL;DR
よいしょ……よいしょ……
useEffect便利ですよね。
stateの変化を監視し、そのstateの変化に伴うべき処理の流れを一元管理できます。import React, { useState, useEffect } from 'react' import ReactDOM from 'react-dom' function Counter(props) { const [count, setCount] = useState(0) const [lastUpdatedAt, setLastUpdatedAt] = useState(null) useEffect(() => {// 『count』 が更新された際に、それに伴い必ず実行される setLastUpdatedAt(new Date().toString()) }, [count]) return ( <div> <p>カウント {count} 回目</p> {/* 変な要件の機能だなぁ…? */} <p>?最終カウントアップ日時? {lastUpdatedAt || ''} </p> <p> <button onClick={() => setCount(count + 1)}>カウントアップ</button> </p> </div> ) } ReactDOM.render( <Counter />, document.getElementById('root') )かなしいところ
(上記例の様に)何も考えずそのまま使うと、対象のstateが変更されているか否かに関わらず、初回レンダー時『にも』必ず動いてしまう。
カウントまだ1回もしてないのに「最終カウントアップ日時」出てんのおかしいダルルォン!?
(うるさいですね・・・)かいけつ
useRefを使う。
import React, { useState, useEffect, useRef } from 'react' import ReactDOM from 'react-dom' function Counter(props) { const [count, setCount] = useState(0) const [lastUpdatedAt, setLastUpdatedAt] = useState(null) const isFirstRender = useRef(false) useEffect(() => { // このeffectは初回レンダー時のみ呼ばれるeffect isFirstRender.current = true }, []) useEffect(() => {// 『count』 が更新された場合『と』初回レンダー時に動くeffect if(isFirstRender.current) { // 初回レンダー判定 isFirstRender.current = false // もう初回レンダーじゃないよ代入 } else { setLastUpdatedAt(new Date().toString()) } }, [count]) return ( <div> <p>カウント {count} 回目</p> <p>?最終カウントアップ日時? {lastUpdatedAt || ''} </p> <p> <button onClick={() => setCount(count + 1)}>カウントアップ</button> </p> </div> ) } ReactDOM.render( <Counter />, document.getElementById('root') )かつてのクラスコンポーネントで言う所の「componentDidMount」と似た働きが期待出来ます。
けつろん
結果オーライ! 終わりやっぱりReactはたのしい。以上。
- 投稿日:2019-12-04T18:16:25+09:00
Nerves と GraphQLsever の組み合わせを考える「ポエム」
この記事は、「NervesJP Advent Calendar 2019」の6日目になります。
「NervesJP Advent Calendar 2019」5日目は、zacky1972さんの「CPU Info や Pelemay を開発している時にわかった Nerves 対応のコツを書きます。(CPU Info 編)」。
そしてこの記事は、「fukuoka.ex Elixir/Phoenix Advent Calendar 2019」の1日目の続きになります。
東京だけど fukuoka.ex の YOSUKENAKAO.me です。普段は合同会社The Waggleで「教育」に関わるサービス作りのお仕事と学習教材の開発や
研修講座の企画開発をしています。この記事の構成
このカレンダーでは、以下3つの Advent Calendar に投稿する予定の3部構成の第2弾となります。
そして、Advent Calender で扱うテーマは「GraphQL と Elixir で始めるプロトタイプ開発の未来」
という名のポエムです。3部構成の大まかな予定は以下です。
Advent Calendar fukuoka.ex 1日目
https://qiita.com/advent-calendar/2019/fukuokaex
GraphCMS から Absinthe を利用して作る Elixir で体験的に GraphQLSever を作る「ポエム」Advent Calendar NervesJP 6日目
https://qiita.com/advent-calendar/2019/nervesjp
Nerves と GraphQLsever の組み合わせを考える「ポエム」Advent Calendar Elixir 24日目
https://qiita.com/advent-calendar/2019/elixir
GraphCMS から入り、Absintheを利用して作って動かす「チュートリアル」となっています。
Nerves と GraphQLsever の組み合わせを考える「ポエム」
前回の実装の続きを書く前に、Nervesについてのポエムを先に書きたいと思います。内容は薄目ですみません!
さて、もともとはフィジカルプログラミングというテーマでプログラミングの学習を5年前にやり始めたのが僕のここ最近のルーツだったりします。当時はArduiunoのLチカから、信号機の歴史を振り返りながらArduiunoの勉強ができる歴史の教材を作っていました。プログラミングではなく、歴史の教材です。ここがポイント。
それが、当時PETSという子供のプログラミング学習ロボットを作っていたメンバーの一人、今はホロラボのファウンダーの1人である方にお声がけ頂いたのをきっかけに、プログラミング学習教材「PETS」という教材を作る事になったきっかけでもあります。PETSは、学校の先生でも45分の授業の中で完結でき、子供の学習意欲を引き出すゲーミフィケーションを取り入れたカリキュラム設計となっています。
学習教材をデジタル化する上で、こだわった事はアナログとの融合です。
そもそも学習教材をデジタル化する上で検討しなければならない大事な観点として以下2つを考える必要があると僕は考えています。
1.それデジタルでやる意味ある?
2.アナログとのコストと比べて学習価値が出せているか?
PETSは、その点を融合して作られたプロトタイプの教材です。
ただし、製品化はされて現在も販売して、いまでこそ大がかりなPRをしてなくても学校に選ばれるようになってきています。
それも先生が欲しがる教材です。
そのようなプロダクトでも、デジタルの持つ特徴の1つで実現したかったけど、当時断念したことが一つあります。
これは今後の課題です。
この問題が解決する事ができる事で、教材として別の次元や違うサービスを構築する事が可能になります。
その課題は、ファームウェアのアップデートです。
そして、教材ですからできれば学習データを取得したいです。しかし、学習データとして何を取得するのか?
これは闇雲に取得しても使えるものにはならないので、柔軟にアップデートとデータの変更ができる仕組みも欲しい所です。
という事で、これらの課題に応えられそうな技術として、NervesとGraphQL Serverの組み合わせに関心があります。
まだ、Nervesについては、Nerves-hubを通じて遠隔のデバイス内のソフトウェアのアップデートをネットを通じてするという
体験くらいしか触れていないのですが、NervesとGraphQLの組み合わせの事例を探して、どんな利用ができるのか?というのを調べてみると、全然見つからない状態で、やっと1件だけみつけたのですが、遠い、、、。
https://elixirconf.com/2019/training-classes/8という事でNervesとGraphQLの組み合わせは今後のテーマとして、できたらシェアしていこうかなと思います。
という事で、今回のポエム部分はこれくらいにして、NervesとGraphQLの組み合わせのGraphQLの方の続きという事で以下よりお届けします。
GraphQLのクエリを書く
query { authors{ id name bibliography } }クエリを書いて、無事に成功していれば下記のようなデータが返ってきます。
React のreact-apollo-blog のAbout.jsに上記のクエリを上書きする
src/components/About.jsexport const authors = gql` query authors { authors{ id name bibliography } } `エンドポイントを書き換える
src/index.jsconst GRAPHCMS_API = http://localhost:4000/api/これで、
yarn start
してlocalhost:3000/about
ページにアクセスすると、、、見れません。
エラーを確認するとクロスサイトスクリプティングの問題でデータが取得できてないです。そこで、GraphQL server側に機能を追加します。Cros_plugを追加する
https://hex.pm/packages/cors_plug
mix.exsdefp deps do [ # ~省略 {:absinthe, "~> 1.4.2"}, {:absinthe_plug, "~> 1.4.0"}, {:absinthe_phoenix, "~> 1.4.0"}, {:cors_plug, "~> 2.0"}, #<- 追加 ] end機能を追加する。
$ mix deps.getrouter.exのパイプラインにプラグを追加
lib/sampleBlog_web/router.expipeline :api do plug CORSPlug, origin: "http://localhost:3000" #<-追加 plug :accepts, ["json"] endこれで、
http://localhost:3000/about
にアクセスで以下のようにデータが取得できたら成功です。
- 投稿日:2019-12-04T18:13:37+09:00
君はVue,Reactの次に来るSvelteを知っているか?
はじめに
この記事はAteam Brides Inc. Advent Calendar 2019 5日目の記事です。
はじめまして、エイチームブライズ新卒1年目の@oekazumaです。最近僕がハマっているSvelteに関して書きたいと思います!
Svelteとは?
SvelteはRich Harris氏によって開発されたコンパイラーでVueやReactのようにブラウザー上でコンポーネント化をするフレームワークではなく*.svelteファイルをhtml, js, cssに変換します。
「すらりとした」という意味を持つ名の通り軽量で高速。
ベンチマークでReactの35倍、Vueの50倍速いです。Svelteの3つの魅力
公式にも書かれている下記の3つを中心に説明していきます!
1. Write less code (より少ないコードを書く)
2. No Virtual DOM (仮想DOMがない)
3. Truly reactive (本当に反応的)Write less code(記述量が少ない)
入力フォームで変数aとbに値を入力し、足して表示するプログラムを例にしてみると
React 442文字
import React, { useState } from 'react'; export default () => { const [a, setA] = useState(1); const [b, setB] = useState(2); function handleChangeA(event) { setA(+event.target.value); } function handleChangeB(event) { setB(+event.target.value); } return ( <div> <input type="number" value={a} onChange={handleChangeA}/> <input type="number" value={b} onChange={handleChangeB}/> <p>{a} + {b} = {a + b}</p> </div> ); };Vue 263文字
<template> <div> <input type="number" v-model.number="a"> <input type="number" v-model.number="b"> <p>{{a}} + {{b}} = {{a + b}}</p> </div> </template> <script> export default { data: function() { return { a: 1, b: 2 }; } }; </script>Svelte 145文字
<script> let a = 1; let b = 2; </script> <input type="number" bind:value={a}> <input type="number" bind:value={b}> <p>{a} + {b} = {a + b}</p>すごく記述量が少ないことがわかると思います。
書き方自体はVueに似ている部分もあるので既にVueを書いている方だとそんなに違和感なく開発できそうです。No Virtual DOM(仮想DOMがない)
仮想DOMはオーバーヘッドであると言っています。大きな理由としては「実DOMとの差分を計算するのって無料じゃないしオーバーヘッドだよね」というところにあります。
Svelteは仮想DOMを使用せずに同様のプログラミングモデルで十分なパフォーマンスで、状態遷移を考慮することなくアプリを構築できます。
以下の流れでいうとSvelteは1と4だけで済むということです。仮想DOMでHTMLが書き換わるまでの流れ
1. 現在の状態(state)が変わる
2.再レンダリング(仮想DOMの再構成)を実行する
3.実DOMとの差分を計算する
4.実際にHTML(=実DOM)を書き換えるTruly reactive(本当に反応的)
ReactおよびVueは、状態変数が変更されたときに更新する場所を追跡できず、その結果、状態変数が存在するコンポーネント全体とそのすべての子を更新します。
一方、Svelteはアプリケーションを介してデータを追跡し、更新された変数に依存する変数のみを更新できます。さいごに
日本では正直全然話題になっていませんが、海外のフロントエンド界隈では盛り上がっているようでこれから日本でも流行っていくのではないかなと勝手に思っています。
数年後にはVue,Reactと肩を並べて語られている気がする...(^ω^)
今は日本語文献がかなり少ないので盛り上げていってもっと身近にSvelteを感じられるようになれば嬉しいなと思います!
この記事では実践的な部分がなかったのですが、明日に@mkinがsvelte3でToDoリストをチュートリアルと照らし合わせて作るぞ! 【入門編】を書いてくれるので楽しみにしていてください!私たちのチームで働きませんか?
エイチームは、インターネットを使った多様な技術を駆使し、幅広いビジネスの領域に挑戦し続ける名古屋の総合IT企業です。
そのグループ会社である株式会社エイチームブライズでは、一緒に働く仲間を募集しています!上記求人をご覧いただき、少しでも興味を持っていただけた方は、まずはチャットでざっくばらんに話をしましょう。
技術的な話だけでなく、私たちが大切にしていることや、お任せしたいお仕事についてなどを詳しくお伝えいたします!Qiita Jobsよりメッセージお待ちしております!
- 投稿日:2019-12-04T17:25:38+09:00
react-firebase-hooksを使ってみた(Auth Hooks編)
はじめに
FirebaseとFirestoreをReact Hooksで使いたいと以前から思っていましたが、react-firebase-hooks v1はあまり納得がいかず、自作のcustom hooksを使っていました。その後v2が出たので、調べなければと思いつつ、半年くらい経ってしまいましたが、とうとう重い腰をあげることにします。
react-firebase-hooks
リポジトリはこちらです。
https://github.com/CSFrequency/react-firebase-hooks
今回はAuth Hooksを試してみようと思います。
コーディング
モジュールのimport
最初に必要なモジュールをimportします。
import React, { useState, useRef, useEffect } from "react"; import ReactDOM from "react-dom"; import firebase from "firebase"; import { useAuthState } from "react-firebase-hooks/auth";firebaseの初期化
次に、firebaseの初期化をします。
const firebaseConfig = { apiKey: "...", authDomain: "...", databaseURL: "...", projectId: "...", storageBucket: "...", messagingSenderId: "...", appId: "..." }; firebase.initializeApp(firebaseConfig);Loginコンポーネント
ログイン用のコンポーネントを作ります。
const Login = () => { const [email, setEmail] = useState(""); const [pass, setPass] = useState(""); const [error, setError] = useState(null); const [pending, setPending] = useState(false); const mounted = useRef(true); useEffect(() => { const cleanup = () => { mounted.current = false; }; return cleanup; }, []); const onSubmit = async e => { e.preventDefault(); setError(null); setPending(true); try { await firebase.auth().signInWithEmailAndPassword(email, pass); } catch (e) { console.log(e.message, mounted); if (mounted.current) setError(e); } finally { if (mounted.current) setPending(false); } }; return ( <div> <form onSubmit={onSubmit}> <input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="Email..." /> <input type="password" value={pass} onChange={e => setPass(e.target.value)} placeholder="Password..." /> <button type="submit">Login</button> {pending && "Pending..."} {error && `Error: ${error.message}`} </form> </div> ); };ちょっと複雑になりましたが、やっていることは単純です。本来は、テキストフィールドを更新したところで、エラーメッセージをクリアすべきですが、そこは省略。
Logoutコンポーネント
ログアウト用のコンポーネントを作ります。
const Logout = () => { const [pending, setPending] = useState(false); const mounted = useRef(true); useEffect(() => { const cleanup = () => { mounted.current = false; }; return cleanup; }, []); const logout = async () => { setPending(true); await firebase.auth().signOut(); if (mounted.current) setPending(false); }; return ( <div> <button type="button" onClick={logout}> Logout </button> {pending && "Pending..."} </div> ); };Pending表示が短い場合はChrome Dev ToolsのNetwork TabでThrottlingをしましょう。
Appコンポーネント
最後に、全体をつなげるAppコンポーネントとReactDOMのrenderです。
const App = () => { const [user, initialising, error] = useAuthState(firebase.auth()); if (initialising) { return <div>Initialising...</div>; } if (error) { return <div>Error: {error}</div>; } if (!user) { return <Login />; } return ( <div> User: {user.email} <Logout /> </div> ); }; const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);今回は、ログインしたらユーザのemailを表示するだけのシンプルなものです。
CodeSandbox
https://codesandbox.io/s/upbeat-chaum-vzpjg
完成したものがこちらです、実際に動作させるためにはforkして
firebaseConfig
を置き換える必要がありますのでご注意ください。おわりに
今までは、onAuthStateChangedをラップした独自custom hooksを使ってましたが、それがライブラリ化されることで、多少見通しはよくなったような気はします。しかし、loginやlogoutの機能を内包するcustom hooksは提供されていないため、今回のように長いコードになってしまいました。結局、そこには独自custom hooksが必要になりそうです。
- 投稿日:2019-12-04T16:56:38+09:00
Reactで作る汎用的なシンプルアコーディオン
背景
クリックしたら開いて閉じるというただ単純な実装が、色々なコンポーネントに散乱しだしていたので、汎用的にその機能を使えるコンポーネントを実装してみました。
実装
import React, { useState } from 'react' const SimpleAccordion = ({ defaultShow = false, onOpen, onClose, ...props }) => { const [show, setShow] = useState(defaultShow) const toggle = () => { const toggled = !show setShow(toggled) toggled ? onOpen && onOpen() : onClose && onClose() } const display = show ? 'block' : 'none' return ( <React.Fragment> {props.children.map((child, idx) => child.props.name === 'SimpleAccordionSummary' ? ( <div key={idx} onClick={toggle}> {child} </div> ) : child.props.name === 'SimpleAccordionDetails' ? ( <div key={idx} style={{ display }}> {child} </div> ) : null )} </React.Fragment> ) } const SimpleAccordionSummary = ({ children }) => ( <React.Fragment>{children}</React.Fragment> ) const SimpleAccordionDetails = ({ children }) => ( <React.Fragment>{children}</React.Fragment> ) SimpleAccordionSummary.defaultProps = { name: 'SimpleAccordionSummary' } SimpleAccordionDetails.defaultProps = { name: 'SimpleAccordionDetails' } export { SimpleAccordion, SimpleAccordionSummary, SimpleAccordionDetails }実用例
const Sample = () => ( <React.Fragment> <SimpleAccordion> <SimpleAccordionSummary> <p>概要</p> {/* ここをクリックすると開いたり閉じたりする */} </SimpleAccordionSummary> <SimpleAccordionDetails> <p>詳細</p> </SimpleAccordionDetails> </SimpleAccordion> </React.Fragment> )
- 投稿日:2019-12-04T16:47:23+09:00
【React】マウント時に自動でfocusあてるhooks
useAutoFocus.tsimport * as React from 'react'; export default function useAutoFocus<RefType extends HTMLElement>() { const inputRef = React.useRef<RefType>(null); React.useEffect(() => { const node = inputRef.current; if (node) { node.focus(); } }, []); return inputRef; }使う側
function Hoge(props: Props) { const [code, setCode] = React.useState(''); const inputRef = useAutoFocus<HTMLInputElement>(); return ( <TextInput value={code} onChange={(e) => setCode(e.target.value)} ref={inputRef} /> ); }
- 投稿日:2019-12-04T14:59:35+09:00
"react-beautiful-dnd" で実装する React の Drag&Drop with TypeScript, 関数コンポーネント
はじめに
React で自前の Drag&Drop (DnD) を実装するのは、なかなか大変だと思います。
ライブラリを探していたところ、Atlassian 製の react-beautiful-dnd というライブラリがよさそうだったので使ってみての所感を書きます。公式サンプル
縦方向リストの DnD
横方向リストの DnD
関数コンポーネントで実装
2カラム間の DnDヌルヌル動いて気持ち〜〜〜
手元で実装してみる
バージョン
"typescript": "^3.7.3",
"react": "^16.12.0",
"react-beautiful-dnd": "^12.2.0",
"styled-components": "^4.4.1"作ったのはこれ
(見た目はほぼサンプルと同じだねとか、、聞こえてますよ、、ええ)↓ディレクトリ構成など分かりやすいようにアレンジしてあるので参考にしてみてください↓
https://github.com/kk-web/react-beautiful-dnd_sampleインストール
※ React まわりの構築は割愛します。
まずは、モジュールをインストール!
npm install --save react-beautiful-dnd元になるリストを作る
src/App.tsximport React from "react"; import { ItemType } from "./types"; import List from "./List"; const App = () => { const initial: ItemType[] = Array.from({ length: 10 }, (v, k) => k).map(k => { return { id: `id-${k}`, content: `Item ${k}` }; }); return <List items={initial} />; }; export default App;src/types.tsexport type ItemType = { id: string; content: string; };src/List.tsximport React from "react"; import Item from "./Item"; const List = ({ items }) => ( <> {items.map(item => ( <Item item={item} key={item.id} /> ))} </> ); export default List;src/Item.tsximport React from "react"; import styled from "styled-components"; const StyledItem = styled.div` width: 200px; border: 1px solid grey; margin-bottom: 8px; background-color: lightblue; padding: 8px; `; const Item = ({ item }) => { return <StyledItem>{item.content}</StyledItem>; }; export default Item;型定義
Typescript
If you are using TypeScript you can use the community maintained DefinitelyTyped type definitions. Installation instructions.とあります。型定義モジュールもインストールしておきましょう。
npm install --save @types/react-beautiful-dndHere is an example written in typescript.
サンプルも用意されています!
DnD 実装
src/App.tsximport React, { useState } from "react"; import { DragDropContext, Droppable } from "react-beautiful-dnd"; import { ItemType } from "./types"; import List from "./List"; const reorder = ( list: ItemType[], startIndex: number, endIndex: number ): ItemType[] => { const result = Array.from(list); const [removed] = result.splice(startIndex, 1); result.splice(endIndex, 0, removed); return result; }; const App = () => { const initial: ItemType[] = Array.from({ length: 10 }, (v, k) => k).map(k => { return { id: `id-${k}`, content: `Item ${k}` }; }); const [state, setState] = useState({ items: initial }); const onDragEnd = result => { if (!result.destination) { return; } if (result.destination.index === result.source.index) { return; } const items = reorder( state.items, result.source.index, result.destination.index ); setState({ items }); }; return ( <DragDropContext onDragEnd={onDragEnd}> <Droppable droppableId="list"> {provided => ( <div ref={provided.innerRef} {...provided.droppableProps}> <List items={state.items} /> {provided.placeholder} </div> )} </Droppable> </DragDropContext> ); }; export default App;↓変更ありません
src/types.tsexport type ItemType = { id: string; content: string; };src/List.tsximport React from "react"; import Item from "./Item"; const List = React.memo<{ items }>(({ items }) => ( <> {items.map((item, index: number) => ( <Item item={item} index={index} key={item.id} /> ))} </> )); export default List;src/Item.tsximport React from "react"; import styled from "styled-components"; import { Draggable } from "react-beautiful-dnd"; const StyledItem = styled.div` width: 200px; border: 1px solid grey; margin-bottom: 8px; background-color: lightblue; padding: 8px; `; const Item = ({ item, index }) => { return ( <Draggable draggableId={item.id} index={index}> {provided => ( <StyledItem ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} > {item.content} </StyledItem> )} </Draggable> ); }; export default Item;Server Side Rendering
Next.js で実装したときにつまづきました、、
Drag できなくて、console を見てみると
react-beautiful-dnd A setup problem was encountered.
> Invariant failed: Draggable[id: id-9]: Unable to find drag handle? This is a development only message. It will be removed in production builds.とエラーが出ていました。
ドキュメントを見てみるとAPI ?
(中略)
resetServerContext() - Utility for server side rendering (SSR)と記載が!
SSR の場合は、任意の場所でこの関数を実行しましょう!おわりに
DnD を自作で実装しようとすると手間がかかりますが、react-beautiful-dnd を使えば数行の実装用関数と state や props を設定するだけで、ヌルヌル動くものができるのはありがたいですね!
公式サンプルではコンポーネントが1つのファイルにまとめられていますが、今回の実装のようにリストやアイテムで分解すれば、既存のリストにも適用しやすいのではないかと思います。
- 投稿日:2019-12-04T14:11:11+09:00
【React】 二種類のコンポーネント class component と functional component について
class component
class App extends Component
にて、AppクラスがComponentクラスを継承している。このAppのことをクラスコンポーネントと呼ぶ。/src/App.jsimport React, {Component} from 'react'; class App extends Component{ render(){ return( <React.Fragment> <label htmlFor="bar">bar </label> <input type="tect" onChange={() => {console.log("i am clicked")}} /> </React.Fragment> ) } } export default App;functional component
関数のみをエクスポートしているから関数コンポーネントと呼ばれる
/src/App.jsimport React, {Component} from 'react'; const App = () => { return <div>Hi</div> } export default App;
import React, {Component} from 'react';
としているが、ここでは次のワーニングメッセージが表示される。Compiled with warnings. ./src/App.js Line 1:16: 'Component' is defined but never used no-unused-vars Search for the keywords to learn more about each warning. To ignore, add // eslint-disable-next-line to the line before.クラスコンポーネントと異なり、Componentを継承する必要がないため。
故にimport React from 'react';
としてよい。
- 投稿日:2019-12-04T13:31:15+09:00
最速のフレームワーク(というのは存在しない)
何日か前にTwitterでこの投稿が話題になりました。
This is why i don't trust frameworks claiming to be fast b/c they score ? benchmarks. This is React in concurrent mode, facing an impossible(!) amount of load (2000 state-connected comps getting re-rendered with fresh props 60 frames/sec). The scheduler makes that a stable 60fps pic.twitter.com/PcuVGheRWX
— Paul Henschel (@0xca0a) November 28, 2019ReactのConcurrent Modeでは、ステートを持つ2000個のコンポーネントを安定した60fpsで再レンダーさせられるようです。一方で、ReactのいわゆるLegacy Modeでは全然60fpsにならない。何人かが、Svelteで同じデモできないかとツイートし、Svelteの創始者Rich Harris氏もデモ作ってくれました。
On the left, we have the React version, running in prod mode. On the right, Svelte (in dev mode, because I forgot to build it. Oh well.) Notice the huge gap in the React version whenever you change the count. With Svelte? Honey badger don't care. pic.twitter.com/lBB1cQ3kSE
— Rich Harris (@Rich_Harris) November 30, 2019dev環境なので60fpsまでは出ないが、React版と違って、コンポーネントの個数を選ぶ度に遅延がないとのことです。(数に関わらず)
しかし、フレームワークの比較はそもそもこれでいいのか?
Here's the only framework performance advice you'll ever need, in 2 parts:
— Mike Sherov (he/him) ? (@mikesherov) December 2, 2019
1. If you find yourself rendering a purple 3D ball of polygons, you probably shouldn't use a FW for that.
2. Instrument your app, find bottlenecks, then fix them. 99/100 times it's not the FW being slow.そもそも上のような数千ポリゴンの3Dボールをレンダーしたい時に、わざわざReactなどのFWを使うのはおかしくないか?実際のアプリと上のデモはかなり違うし、仮に同じものをページに置きたいとしても、Three.jsなどのライブラリですでにできることだから、それをFWで抽象化する意味は何か?ということを考えないといけないです。
何が本当に遅いかというと、ユーザーのコードです。FWのコントリビュータはパフォーマンスの最適化にベストを尽くしているが、任意のユーザーのコードはもちろん最適化できない(が、Svelteの斬新なアプローチで、ステートの更新など、期待できるパターンも最適化の対象にはなっているようです)。あまり例としては現実的ではないのですが、ユーザー(開発者)が無限ループを書いたらFWどころではなくなります。
さらに、実際のアプリで不可欠なIO処理は、ベンチマークでは測られていないです。データの取得とレンダリングはどう設計されているか?という点ではFWは多少違います。たとえば、ReactのConcurrent Modeでは、ネットワークのIO処理を待っている間に、アイドリングしているCPUを有効に使って、一部の要素をプリレンダーできます。IO処理が終わってからレンダリングを始める場合は、必然的に表示が遅くなります。
他にも指摘されたのは、測る過程。マイナーな原因だと思いますが、JSのJITコンパイラ仕様上、上のようなデモでは、1回目のレンダリング(初期ロード・マウント)を測っているか、時間が経ってから測っているかによって差が出るようです。なぜかというと、JITコンパイラが
cold
な(まだ実行されていない)コードをインタプリターとして解釈するけど、よく実行されるhot
なコードを、コンパイルかつ最適化してくれるからです。なので複数回レンダリングされたコンポーネントのコードがコンパイルされ速くなっている可能性もあります。それと比べて、新規マウントが多い実際のアプリでは、cold
なコードが多いです。
- 投稿日:2019-12-04T12:01:54+09:00
react
https://www.youtube.com/watch?v=-edmQKcOW8s&t=12s
のsrc/context.jsについて
import React, { Component } from "react"; import { storeProducts, detailProduct } from "./data"; const ProductContext = React.createContext(); class ProductProvider extends Component { state = { products: [], detailProduct: detailProduct, cart: [], modalOpen: false, modalProduct: detailProduct, cartSubTotal: 0, cartTax: 0, cartTotal: 0 }; componentDidMount() { this.setProducts(); } setProducts = () => { let products = []; storeProducts.forEach(item => { const singleItem = { ...item }; products = [...products, singleItem]; }); this.setState(() => { return { products }; }, this.checkCartItems); }; getItem = id => { const product = this.state.products.find(item => item.id === id); return product; }; handleDetail = id => { const product = this.getItem(id); this.setState(() => { return { detailProduct: product }; }); }; addToCart = id => { let tempProducts = [...this.state.products]; const index = tempProducts.indexOf(this.getItem(id)); const product = tempProducts[index]; product.inCart = true; product.count = 1; const price = product.price; product.total = price; this.setState(() => { return { products: [...tempProducts], cart: [...this.state.cart, product], detailProduct: { ...product } }; }, this.addTotals); }; openModal = id => { const product = this.getItem(id); this.setState(() => { return { modalProduct: product, modalOpen: true }; }); }; closeModal = () => { this.setState(() => { return { modalOpen: false }; }); }; increment = id => { let tempCart = [...this.state.cart]; const selectedProduct = tempCart.find(item => { return item.id === id; }); const index = tempCart.indexOf(selectedProduct); const product = tempCart[index]; product.count = product.count + 1; product.total = product.count * product.price; this.setState(() => { return { cart: [...tempCart] }; }, this.addTotals); }; decrement = id => { let tempCart = [...this.state.cart]; const selectedProduct = tempCart.find(item => { return item.id === id; }); const index = tempCart.indexOf(selectedProduct); const product = tempCart[index]; product.count = product.count - 1; if (product.count === 0) { this.removeItem(id); } else { product.total = product.count * product.price; this.setState(() => { return { cart: [...tempCart] }; }, this.addTotals); } }; getTotals = () => { // const subTotal = this.state.cart // .map(item => item.total) // .reduce((acc, curr) => { // acc = acc + curr; // return acc; // }, 0); let subTotal = 0; this.state.cart.map(item => (subTotal += item.total)); const tempTax = subTotal * 0.1; const tax = parseFloat(tempTax.toFixed(2)); const total = subTotal + tax; return { subTotal, tax, total }; }; addTotals = () => { const totals = this.getTotals(); this.setState( () => { return { cartSubTotal: totals.subTotal, cartTax: totals.tax, cartTotal: totals.total }; }, () => { // console.log(this.state); } ); }; removeItem = id => { let tempProducts = [...this.state.products]; let tempCart = [...this.state.cart]; const index = tempProducts.indexOf(this.getItem(id)); let removedProduct = tempProducts[index]; removedProduct.inCart = false; removedProduct.count = 0; removedProduct.total = 0; tempCart = tempCart.filter(item => { return item.id !== id; }); this.setState(() => { return { cart: [...tempCart], products: [...tempProducts] }; }, this.addTotals); }; clearCart = () => { this.setState( () => { return { cart: [] }; }, () => { this.setProducts(); this.addTotals(); } ); }; render() { return ( <ProductContext.Provider value={{ ...this.state, handleDetail: this.handleDetail, addToCart: this.addToCart, openModal: this.openModal, closeModal: this.closeModal, increment: this.increment, decrement: this.decrement, removeItem: this.removeItem, clearCart: this.clearCart }} > {this.props.children} </ProductContext.Provider> ); } } const ProductConsumer = ProductContext.Consumer; export { ProductProvider, ProductConsumer }; import React, { Component } from "react"; import { storeProducts, detailProduct } from "./data"; const ProductContext = React.createContext(); class ProductProvider extends Component { state = { products: [], detailProduct: detailProduct, cart: [], modalOpen: false, modalProduct: detailProduct, cartSubTotal: 0, cartTax: 0, cartTotal: 0 }; componentDidMount() { this.setProducts(); } setProducts = () => { let products = []; storeProducts.forEach(item => { const singleItem = { ...item }; products = [...products, singleItem]; }); this.setState(() => { return { products }; }, this.checkCartItems); }; getItem = id => { const product = this.state.products.find(item => item.id === id); return product; }; handleDetail = id => { const product = this.getItem(id); this.setState(() => { return { detailProduct: product }; }); }; addToCart = id => { let tempProducts = [...this.state.products]; const index = tempProducts.indexOf(this.getItem(id)); const product = tempProducts[index]; product.inCart = true; product.count = 1; const price = product.price; product.total = price; this.setState(() => { return { products: [...tempProducts], cart: [...this.state.cart, product], detailProduct: { ...product } }; }, this.addTotals); }; openModal = id => { const product = this.getItem(id); this.setState(() => { return { modalProduct: product, modalOpen: true }; }); }; closeModal = () => { this.setState(() => { return { modalOpen: false }; }); }; increment = id => { let tempCart = [...this.state.cart]; const selectedProduct = tempCart.find(item => { return item.id === id; }); const index = tempCart.indexOf(selectedProduct); const product = tempCart[index]; product.count = product.count + 1; product.total = product.count * product.price; this.setState(() => { return { cart: [...tempCart] }; }, this.addTotals); }; decrement = id => { let tempCart = [...this.state.cart]; const selectedProduct = tempCart.find(item => { return item.id === id; }); const index = tempCart.indexOf(selectedProduct); const product = tempCart[index]; product.count = product.count - 1; if (product.count === 0) { this.removeItem(id); } else { product.total = product.count * product.price; this.setState(() => { return { cart: [...tempCart] }; }, this.addTotals); } }; getTotals = () => { // const subTotal = this.state.cart // .map(item => item.total) // .reduce((acc, curr) => { // acc = acc + curr; // return acc; // }, 0); let subTotal = 0; this.state.cart.map(item => (subTotal += item.total)); const tempTax = subTotal * 0.1; const tax = parseFloat(tempTax.toFixed(2)); const total = subTotal + tax; return { subTotal, tax, total }; }; addTotals = () => { const totals = this.getTotals(); this.setState( () => { return { cartSubTotal: totals.subTotal, cartTax: totals.tax, cartTotal: totals.total }; }, () => { // console.log(this.state); } ); }; removeItem = id => { let tempProducts = [...this.state.products]; let tempCart = [...this.state.cart]; const index = tempProducts.indexOf(this.getItem(id)); let removedProduct = tempProducts[index]; removedProduct.inCart = false; removedProduct.count = 0; removedProduct.total = 0; tempCart = tempCart.filter(item => { return item.id !== id; }); this.setState(() => { return { cart: [...tempCart], products: [...tempProducts] }; }, this.addTotals); }; clearCart = () => { this.setState( () => { return { cart: [] }; }, () => { this.setProducts(); this.addTotals(); } ); }; render() { return ( <ProductContext.Provider value={{ ...this.state, handleDetail: this.handleDetail, addToCart: this.addToCart, openModal: this.openModal, closeModal: this.closeModal, increment: this.increment, decrement: this.decrement, removeItem: this.removeItem, clearCart: this.clearCart }} > {this.props.children} </ProductContext.Provider> ); } } const ProductConsumer = ProductContext.Consumer; export { ProductProvider, ProductConsumer };addToCart = id => { let tempProducts = [...this.state.products]; const index = tempProducts.indexOf(this.getItem(id)); const product = tempProducts[index]; product.inCart = true; product.count = 1; const price = product.price; product.total = price; this.setState(() => { return { products: [...tempProducts], cart: [...this.state.cart, product], detailProduct: { ...product } }; }, this.addTotals); };tempProductsにstateのデータ、indexにid情報を、productに商品データを、商品データがカートに入ってるという情報をtrueにし、priceに商品の値段を当てはめます。
this.setState(() => { でproducts、cart、detailProductを使って合計金額を求めています。他の関数についてはaddToCartを転用したような作りとなっています。
- 投稿日:2019-12-04T11:49:35+09:00
create-react-app Advanced Configuration まとめ 2019年末版
VISITS Technologies Advent Calendar 2019 5日目は @overgoro56 が担当します。
create-react-appは
eject
すればカスタマイズ可能ですがしなくても設定でいろいろできます。
詳細な情報は公式ドキュメントを見れば書いてあるので、ここでは簡単な使い方を紹介します。
一部、勉強のためにcreate-react-appのソースも紹介します。Advanced Configurationの公式ドキュメント
設定方法
shellで設定する場合には
package.json"scripts": { "start": "CI=true react-scripts start",のようにするか、
.env
ファイルで.env.developmentCI=trueのように環境ごとに設定します。
Advanced Configuration
BROWSER
ブラウザを指定することにより
react-scripts start
した時に開くブラウザを指定できます。
package.json
のscripts
にstart:safari
のように用意して、普段はchromeで実行するけど、ブラウザ検証のためSafariで実行したい場合に用意しておくとか。設定例.BROWSER=/Applications/Safari.app/Contents/MacOS/SafariBROWSER_ARGS
BROWSER
を設定しているときに渡す引数を設定。HOST
デフォルトだとdevice上の全てのhostnamesでアクセス可能にするが明示的に指定も可能。
社内の安全なネットワーク以外の環境で開発する場合に
HOST=localhost
を設定してネットワーク上に公開しないようにしたり。
カフェや外のワーキングスペースで仕事する場合には重要な設定。PORT
ポートを指定可能。
複数アプリケーションを同時に起動して開発する時に指定したり。HTTPS
ネットワークに公開する時にHTTPSにしたい時。
PUBLIC_URL
アセットの参照先URLを変えたい時など。
CI
ビルド中の警告をエラーとして扱う。
CIで使うための設定。REACT_EDITOR
エラーが発生した場合、ブラウザ上のエラーリンクから指定したエディタにジャンプできる。
例えばVisual Studio Codeで開きたい場合には、shellでcode
でVisual Studio Codeを起動できるように設定した上でREACT_EDITOR=code
と設定しておく。CHOKIDAR_USEPOLLING
VM上で動作させる時にソースの変更を検出してくれない時に設定。
GENERATE_SOURCEMAP
本番環境でユーザーにソースを見られないようにmapファイル生成したくない時に使用。
NODE_PATH
absolute pathでimportする時に使用していた。
最近はjsconfig.json
またはtsconfig.json
にbaseUrl
を設定することで対応が可能。
baseUrl
を設定してない場合には、NODE_PATH
を使うようになってるっぽい。以下のソースで確認できる。
INLINE_RUNTIME_CHUNK
デフォルトだと
index.html
にruntime script
がインラインで埋め込まれる。
CSPを厳格に行う場合にはこの設定をfalse
にすることにより他のスクリプト同様インポートする。IMAGE_INLINE_SIZE_LIMIT
デフォルト値は10KB。
この値よりも小さいサイズの画像をビルド時にbase64に変換してインラインで埋め込むことにより画像取得のリクエストを削減。
0を設定すると無効化。ビルド後の結果を比較すると挙動がわかりやすいので確認してみるといい。
EXTEND_ESLINT
デフォルトだと
react-scripts
実行時は、.eslintignore
は使わない、baseConfig
はeslint-config-react-app
を使いますが、この設定をtrue
にすると.eslintignore
、.eslintrc
を参照するように設定できます。
最近追加された設定です。追加後に修正・改善が行われているので使う場合には
react-scripts
のバージョンを3.2.0
以降にするのが良さそう。やってることは以下のソースで確認できる。
TSC_COMPILE_ON_ERROR
TypeScriptエラーが発生していてもコンパイルできるようにする。
プロトタイプの時はいいかもしれないが、製品開発では使わない方がよいと思う。まとめ
自分が使っている設定は
HOST
、PORT
、CI
、REACT_EDITOR
、GENERATE_SOURCEMAP
、EXTEND_ESLINT
です。
他は必要が出た時に導入予定です。いろいろ設定がありますが、その目的を確認したりソースを実際に見てどんなことしてるか理解すると安心して使えると思います。
使ってない設定で便利そうだなと思ったものがあれば是非導入してみてください。また、create-react-appのソースを見ると
eject
すると消されてしまうソースもがあることもわかるので直接ソースを見た方が勉強になります。
ここまで確認できれば、あとは必要な設定をcreate-react-appに対してPR出してコミッターの仲間入りもできそうですね!
- 投稿日:2019-12-04T11:48:01+09:00
React アプリケーションのボイラープレート CLI を作って使っている話
この記事は ミクシィグループ Advent Calendar 2019 の5日目の記事です。
React で CLI というと create-react-app が有名です。
格好良いベースを作ってくれるのですが個人的には依存 package が多いので、自分用の CLI を作ってそちらを使っています。@yami-beta/create-ts-app
TypeScript を使ったアプリケーションのベースを作る対話型のインターフェースを持った CLI ツールです。
https://www.npmjs.com/package/@yami-beta/create-ts-app
意外と色々な package を用意する必要がある ESLint + Prettier の設定を含めていたり、author や LICENSE を設定できます。
(あくまで個人用なので自分の好みによせたボイラープレートになっています)現在は React のシンプルなボイラープレートしかありませんが
- React, React Router, Redux 等が含まれた Single Page Application
- express によるサーバアプリケーション
のボイラープレートを追加していく予定です。
仕組み
この CLI ですが SAO というライブラリを使って実装しています。
(create-nuxt-app も SAO を利用していたりします)以下のようなコードを書くことで対話型のインターフェースを用意したり、テンプレートからファイルをコピーやリネームといったことが出来ます。
module.exports = { prompts() { return [ { name: 'name', message: 'What is the name of the new project', default: this.outFolder, filter: val => val.toLowerCase() } ] }, actions: [ { type: 'add', files: '**' }, { type: "move", patterns: { "LICENSE_*": "LICENSE" } } ], async completed() { this.gitInit() await this.npmInstall() this.showProjectTips() } }
@yami-beta/create-ts-app
では このような実装 になっています。
一部を抜粋すると、以下のようにコマンド実行時の回答に応じて package.json に記載する依存関係を編集することも可能です。const config = { actions() { const { answers } = this; return [ // 略 { type: "modify", files: "package.json", handler(data: any, filepath: string) { return { name: answers.name || data.name, version: answers.version || data.version, main: data.main, author: answers.author, license: answers.license || data.license, scripts: data.scripts, dependencies: { ...data.dependencies }, devDependencies: { ...data.devDependencies, "@typescript-eslint/eslint-plugin": answers.features.includes( "eslint" ) ? data.devDependencies["@typescript-eslint/eslint-plugin"] : undefined, "@typescript-eslint/parser": answers.features.includes("eslint") ? data.devDependencies["@typescript-eslint/parser"] : undefined, eslint: answers.features.includes("eslint") ? data.devDependencies["eslint"] : undefined, "eslint-config-prettier": answers.features.includes("eslint") && answers.features.includes("prettier") ? data.devDependencies["eslint-config-prettier"] : undefined, "eslint-plugin-prettier": answers.features.includes("eslint") && answers.features.includes("prettier") ? data.devDependencies["eslint-plugin-prettier"] : undefined, prettier: answers.features.includes("prettier") ? data.devDependencies["prettier"] : undefined } }; } }, // 略 ].filter(Boolean); } };CLI を作るほどでもない場合
ボイラープレートは欲しいけれども CLI を作るほどでは無い、という場合もあるかと思います。
そういう場合は GitHub のテンプレートリポジトリでボイラープレートを活用する方法があります。
- https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-template-repository
- https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template
詳細は上記のドキュメントを参照してください。
まとめ
- React アプリケーションのボイラープレートを生成する CLI を作っている
- テンプレートからファイルをコピー、リネーム、編集することが出来るので複数のボイラープレート生成が可能
- 手軽にボイラープレートを作る場合は GitHub のテンプレートリポジトリが活用出来そう
備考
- 投稿日:2019-12-04T11:40:23+09:00
妥協しないTypescript
テックタッチアドベントカレンダー5日目を担当する@takakobemです。
4日目は @terunuma による 4Kモニタ環境で1年間Web開発してみた所感 でした。
4Kモニタいいですよね。ただ、以前私は43型の4Kモニタを使っていたのですが、正直でかすぎて画面の端が見づらかったです。。30インチぐらいがベストかもしれませんね。まえがき
本記事はTypescriptを触ったことがある人を対象にしています。
「Typescriptは使っているけど、ちゃんと使いこなせているかわからない」という人に一番しっくりくる内容だと思います。
私が以前C++を触っていたこともあり、結構C++を引き合いに出しています。
また、私がReactやReduxを触っていることもあり、オブジェクト指向よりも関数型プログラミングを意識したものになっています。はじめに
Typescriptは非常に便利なツールです。
Javascriptそれ自体は静的な型チェックができないため、コーディング中に実装ミスに気づきにくいのですが、Typescriptを導入することで型チェックが効くようになり、VSCodeなどのエディターを使えば補完も利用できるようになります。
ただ、TypescriptはC++やJavaなどといった言語とは違い、型の定義が中途半端でも動いてしまいます。
一見型をしっかりつけているつもりで書いていても、ちょっとしたことで信頼性がないコードができあがってしまいます。
今回は型を妥協してしまっている書き方をBadケースとして例に上げ、どうすればよいかをGoodケースとして紹介していきます。※本記事はTypescript3.7時点のものです。
1. anyは使わない
Typescriptを触ったことがある人であれば、
any
は知っているかと思います。
極端に言ってしまうと、any
は型を付けるのを放棄したのと同義です。
Typescript は Javascript に型を付けるための言語なのに、型を付けるのを放棄してしまっては元も子もありません。
Typescript で型を付けることを選んだのであれば、any
を使うことは避けましょう。Bad
![]()
number
を引数に取りたいのに、any
にしてしまうとstring
型でも通ってしまいます。declare function hoge(num: any): void hoge(1) // ok hoge("hoge") // これもokになってしまうGood
![]()
ちゃんと型を定義してあげましょう。
declare function fuga(num: number): void fuga(1) // ok fuga("fuga") // error2.
!
は使わない
!
はnon-null assertion operatorと呼ばれています。
これをつけることで、null
かもしれないようなものを強制的にnull
じゃないものとしてみなすことができます。
「ここはnull
はありえないはずだから〜」という理由でよく使いがちなのですが、ありえないと思ってしまっているだけかもしれませんし、今後null
が入ってしまっても気づけなくなりますよね。
なので、そもそもnull
をとらないようにしてしまうか、ちゃんと実行時にチェックをするべきです。
Typescript3.7からの機能であるOptional Chainingを使うのもありです。Bad
function hoge(str: string | null) { str.toUpperCase() // Object is possibly 'null'. str!.toUpperCase() // ok }Good
![]()
ちゃんと実行時にチェックしてあげましょう。
もしくはnullをとらないようにしてあげましょう。function hoge(str: string | null) { if (str !== null) { str.toUpperCase() // ok } } // or function fuga(str: string | null) { str?.toUpperCase() // ok } // or function fuga(str: string) { str.toUpperCase() // ok }3.
{}
を使う時は注意例えばkeyに名前を持ち、valueに年齢を持つようなオブジェクトを定義したいとします。
keyは名前だからstring
, valueは年齢だからnumber
と安直にやってしまうと、型安全が崩れてしまいます。Bad
![]()
安直に上記の通りに定義してみます。
const ageData: Record<string, number> = { yamada: 10, tanaka: 20 } console.log("山田さんの年齢=", ageData.yamada) // 10 console.log("田中さんの年齢=", ageData.tanaka) // 20 console.log("鈴木さんの年齢=", ageData.suzuki) // undefinedこの時、山田さんのデータは入っていますが、鈴木さんのデータは入っていないため、鈴木さんの年齢は
undefined
として返ってきます。
しかし、Typescript的にはエラーと認識してくれません。Good
![]()
keyが予め予測可能なものであれば、keyを定義しておきましょう。
const ageData: Record<"yamada" | "tanaka", number> = { yamada: 10, tanaka: 20 } console.log("鈴木さんの年齢=", ageData.suzuki) // Property 'suzuki' does not exist on type 'Record<"yamada" | "tanaka", number>'.(2339)idなど、keyが事前に定義不能な場合は、valueに
undefined
も定義しておきましょう。const ageData: Record<string, number | undefined> = { yamada_id: 10, tanaka_id: 20 } console.log("鈴木さんの年齢=", ageData.suzuki + 1) // Object is possibly 'undefined'.4.
as
は使わない以下のような定義があったとしましょう。
type Base = { x: number } type Derived = Base & { y: number }これは、オブジェクト指向でいう基底クラスと派生クラスの関係に近いです。
Typescript では、このように Intersection Types を使うことで継承のようなものを表現することができます。
ここで問題なのが、as
を使うことでこれら2つの型が双方向にキャストできてしまうということです。Bad
![]()
例えば、以下のようなアップキャストは問題なく行なえます。
const derived: Derived = { x: 1, y: 2 } const base = derived as Base console.log(base.x) // 1元の
derived
にはx
が含まれているので何も問題ありませんね。
次はダウンキャストです。const base: Base = { x: 1, } const derived = base as Derived // ダウンキャストができてしまう console.log(derived.y) // undefined
base
にはy
が無いのに、エラーなく実行されてしまいます。
as
を使うとダウンキャストがすんなりできてしまいます。Good
![]()
ではどうすればよいのか。
いえ、どうもしなくていいんです。
そもそもTypescriptが提供するものはただの型です。
継承といっても、カプセル化や関数のオーバーライドなどはありません。
そもそもキャストする必要がないのです。ただ、引数に基底クラスを受け、条件に応じて派生クラスに変換し結果を返したいようなケースはありますよね。それを次に説明します。
5. Union Types + String Literalを使いこなす
以下のような、CircleとSquareという型があるとします。
type Circle = { radius: number } type Square = { height: number width: number }これはどちらも図形ですね。Typescriptでは以下のようにUnion Typesを使うことで、異なる2つの型を1つの型として扱うことができます。
type Figure = Circle | Squareこれは、派生クラスと派生クラスから基底クラスを作るようなものです。
C++から入った僕は非常に理解に苦しみました。
Typescript恐ろしい。。Bad
![]()
このようにすることで、例えば以下のように図形を引数にその面積を返すような関数が定義できます。
function area(figure: Figure): number { if ("height" in figure) { return figure.width * figure.height } if ("radius" in figure) { return figure.radius * figure.radius * Math.PI } throw new Error("no such figure") }あれ?なんか奇妙ですよね。
そう、上記の例では、図形に特定のプロパティがあるかどうかで図形を判定し、面積を計算しています。
でもこれって気持ち悪いですよね。例えばTriangle
という型が追加になると、Triangle
もheight
を持つので、条件式を見直さなければならなくなります。
「クラスを使ってメンバ関数で計算しろよ」って言われそうですが、クラスにしなくてもオブジェクトにちょこっと細工することで解決できちゃうんです。
(なぜクラスを頑なに使わないのか、React/Redux使いの人なら知ってるかと思いますが、その理由はまたいつか機会があれば記事書きます。)Good
![]()
ではどうするかというと、こういう時は その型が何か を string literal で定義しておけばよいです。
type Circle = { kind: "circle" radius: number } type Square = { kind: "square" height: number width: number }こうすることで、プロパティの存在によってif文を分けなくても、
kind
というkeyによってswitch文で処理を分けることができるようになります。
また、例えばcase "circle"
内でfigure.width
にアクセスしようとするとエラーになります。function area(figure: Figure): number { switch (figure.kind) { case "circle": // figure.width // Property 'width' does not exist on type 'Circle'. return figure.radius * figure.radius * Math.PI case "square": return figure.height * figure.width } }Typescript賢すぎる!
最後に
いかがでしたでしょうか。
Typescriptは非常に便利なツールですが、使いこなすにはいくつかコツがいりますし、正直難しいです。
今回挙げた内容もTypescriptができることのほんの一部に過ぎません。
しかし、使いこなせれば非常に便利なツールです。こんなことできるかな?と思ったことが大抵できますので、是非いろいろ試してみてください。6日目は @ihiroky による「JSXとvirtual-domで遊ぶ」です。
- 投稿日:2019-12-04T10:40:38+09:00
個人開発したサイトを見直し、リニューアルすることで技術を得る
本記事は個人開発 Advent Calendar 2019 4日目の記事です。
概要
普段は企業でビッグデータ×マーケティングでAWSメインにフルスタック寄りのエンジニアをしています。ですので、本記事で言う個人開発は趣味の日曜開発といった度合が強いのですが、業務外の開発によってこそやりやすいインプットとアウトプットがある、というポエムとなります。
背景
昨年書かせていただいた 個人ファンサイトは、無事安定して稼働しています。応援先グループも稼働しています(これは幸せな事です)。
しかしながら
・インフラ構築が2017年当初なので見直したい
・フロントエンドが簡素なので見直したい
・応援先グループの新たな取り組みに最適化したサイトにしたい
といった、自分の作ったものに対する課題感と解決モチベーションが沸いてくる訳です。インフラの見直し
リージョンの見直し
2017年初頭に構築した基盤に使っている Amazon Lightsailがバージニアリージョンで稼働していました。これは当時東京リージョンがまだ無かったためです。当時としては割と新しいサービスを試していたのですね。
ネットワークの見直し
ふと見ていると「静的IPを作成」というボタンが。
なんと静的IPじゃありませんでした。。。SSL対応
httpでした
2017年に作ったんなら当時の時点でやれよという話は分かりつつ先延ばしにしてしましまいたが、何かを応援しているサイトがhttpsで無いというのは、応援先にも悪影響を及ぼしかねません。直さねば!応援先グループの公式サイトはSSL非対応ですが。フロントエンドの見直し
利用技術とデザイン面
必要最低限で軽快に動く、ことを目指してはいたものの、ある意味でデザイン面からの逃げ(私はBootstrapすら使えない子)でもありました。それとjQuery。
また、いわゆる今風のUI/UXにチャレンジしてみたいと考えました。
丁度、社内で若手がReactを使って素敵UIを生み出しているのを見ておりました。私は広めに業務に関わっていますが、フロントエンドが一番離れている場所でした。ここらでキャッチアップしたい。
そうだ、Reactしよう。
※Vueと迷いましたが、社内でReact活用が進む流れもあり、そこは乗っかりました。
やるぞ
昨年の状態
まずは構成を思い出すところから始めました。思い出し工数というやつです。
インフラ見直し
Amazon Lightsail を東京リージョンに変更
・・・ボタン1つでは出来ないですよね。はい。
あとUbuntuを使っていたのですが、やっぱAmazon Linuxの方が好き、、、となったので、この機会に東京リージョン&Amazon Linuxで作成し直すこととしました。移植作業
ディストリビューションが違うもので作成し直すということは、色々動かない可能性もあります、というか動きません。API連携も結構やってます。
図の通り部分を整頓していくとGoogle スプレッドシートへの書き込み処理のみ気を付ければ、むしろ別インスタンスを作成するのであれば、移行時のテスト実行にリスクが無いことが分かります。まさか昨年、図に描いておいたことがここで役に立つとは。
そのほか、RDBMSなども無くリアルタイムで更新されるトランザクションデータが無いことも大きいですね。
新たに稼働させたインスタンスに、S3に控えておいた主なリソースを移し、主にShellScriptのあたりが雑だったので書き換え、ひたすら適用していくだけです。といった感じで、思っていたよりもサクサク移行できました。
サクサクと言えば、東京リージョンで立ち上げてSSH接続時の操作もサクサクになりました。
普段本業ではEC2等は東京リージョンしか使っていないのですが、あれが海外リージョンのレイテンシだったのでしょうか。静的IP付与
EC2の場合だと、Elastic IPを発行して、、、と簡単ですが、Lightsailの場合は更に簡単で、管理画面からポチポチと数クリックで発行されました。ようやく恩恵に預かりました。
発行されたIPアドレスをRoute53へ反映。しかしよく今まで大丈夫だったな・・・
SSL対応
ZeroSSLを用いてSSL対応をしました。
マジでブラウザだけで証明書の発行が完了してびっくりしました。
このあたりも、業務では中々使えていないので良い体験になりました。
(なお、この記事を書いている時点で期間が3ヵ月なので処置が必要なことに気付く。)フロントエンド見直し
要件:応援先グループの活動の変化
・メンバーがそれぞれ個人チャンネルを持った
・ゲストメンバーという概念が出現
・動画が変わらぬペースで増え続けている(余裕の1,000件突破)ようは情報量が1年前に比べて更に増えて、単純に表示、だけでは物足りなくなった。
やりたいこと(ざっくり)
・メンバーに紐づく情報が公式サイトプロフィール、個人チャンネル、Twitterアカウントと複数存在したため、プロフィール的なものを画面の邪魔にならない形で表示させたい
・とにかく今風にしたいやってみた(ざっくり)
・全くReact分からん
・とりあえずひたすらReact入門記事を読む。
・直感でMaterial-UIに絞った
・そのうちサンプルを扱えるようになる
・あれもこれも出来るようになる結果として、以下のComponentsを利用して
- Popover
- Avatar
- Paper
- Expansion Panel
- Table(material-table)以下のようなことを実現できた
・出演メンバーの可視化
→ Avatarを用いて、アイコンで誰が出演しているかを賑やかな感じで可視化
・増えた情報の出し入れ
→ 動画の収集元が7チャンネルになったのでそれぞれのExpansion Panelで表示
→ メンバーが4人+ゲスト3人構成になったのでExpansion Panelで見やすく表示
・動画サムネイル表示
→ マウスカーソルをあてることでサムネイルを表示することで解決。ちゃんとカーソルにあたったタイミングで読み込みに行く。この期に及んでスクリーンショットすらも載せませんが、Material-UIを使っているので綺麗です。
フレームワークなのだからそうやろという話ではありますが、サンプルをもとにデザイン+機能が簡単確実に実装出来た体験は、かなりの衝撃でした。
まとめ、得られたものなど
サクサクなSSH環境
リージョンでここまで違うとは。日曜開発の環境がサクサクになるのは良い事です。
フロントエンド事情に少し追いつけた
実際触ってみるまで、Reactがどういったものか分かりませんでした。今のフロントエンド開発はこういうことになっているんだ、と手遅れになる前に追いつけた感じが良かったというか、やらねばヤバかったなと思いました。
やらねばヤバかったなと思う一方、通常の業務をやっているとこのキャッチアップは出来なかったなぁ、、、というのが今回の個人開発サイトリニューアルで感じた一番のポイントです。もちろん業務においてもフロントエンジニアとの会話がしやすくなる、といったメリットもあるのですが自身の技術レベルを少しアップ出来たのが何より大きかったです。
業務と個人開発の違いとモチベーション
自社のお仕事の場合、どうしてもコスト、スピード、信頼性を考慮して最適解というものを選んでしまいます。新しいものが歓迎される文化もある一方で、費用対効果が明確だったら私は費用対効果が明確な手段を優先してしまいます。
では個人開発をしまくれば良いかと言うと、業務も普通に楽しい中で更にインプットとアウトプットを増やすのは私には結構困難です。
と考えた時に「趣味を利用した強烈なモチベーションを軸に、使ってみたい技術領域を試そう」といった考えで今回リニューアルをやってみました。結果、良いインプットとアウトプットが得られたと感じています。今後の展望
個人開発サイトを、よりサーバーレスで完結するような世界にしてゆきたいです。
- AWS Amplify
- Lambda(RPA部分)
とか。re:Inventにも期待。
- 投稿日:2019-12-04T10:40:38+09:00
個人開発しているサイトをリニューアルした話と得られたもの
本記事は個人開発 Advent Calendar 2019 4日目の記事です。
概要
普段は企業でビッグデータ×マーケティングでAWSメインにフルスタック寄りのエンジニアをしています。ですので、本記事で言う個人開発は趣味の日曜開発といった度合が強いのですが、業務外の開発によってこそやりやすいインプットとアウトプットがある、というポエムとなります。
背景
昨年書かせていただいた 個人ファンサイトは、無事安定して稼働しています。応援先グループも稼働しています(これは幸せな事です)。
しかしながら
・インフラ構築が2017年当初なので見直したい
・フロントエンドが簡素なので見直したい
・応援先グループの新たな取り組みに最適化したサイトにしたい
といった、自分の作ったものに対する課題感と解決モチベーションが沸いてくる訳です。インフラの見直し
リージョンの見直し
2017年初頭に構築した基盤に使っている Amazon Lightsailがバージニアリージョンで稼働していました。これは当時東京リージョンがまだ無かったためです。当時としては割と新しいサービスを試していたのですね。
ネットワークの見直し
ふと見ていると「静的IPを作成」というボタンが。
なんと静的IPじゃありませんでした。。。SSL対応
httpでした
2017年に作ったんなら当時の時点でやれよという話は分かりつつ先延ばしにしてしましまいたが、何かを応援しているサイトがhttpsで無いというのは、応援先にも悪影響を及ぼしかねません。直さねば!応援先グループの公式サイトはSSL非対応ですが。フロントエンドの見直し
利用技術とデザイン面
必要最低限で軽快に動く、ことを目指してはいたものの、ある意味でデザイン面からの逃げ(私はBootstrapすら使えない子)でもありました。それとjQuery。
また、いわゆる今風のUI/UXにチャレンジしてみたいと考えました。
丁度、社内で若手がReactを使って素敵UIを生み出しているのを見ておりました。私は広めに業務に関わっていますが、フロントエンドが一番離れている場所でした。ここらでキャッチアップしたい。
そうだ、Reactしよう。
※Vueと迷いましたが、社内でReact活用が進む流れもあり、そこは乗っかりました。
やるぞ
昨年の状態
まずは構成を思い出すところから始めました。思い出し工数というやつです。
インフラ見直し
Amazon Lightsail を東京リージョンに変更
・・・ボタン1つでは出来ないですよね。はい。
あとUbuntuを使っていたのですが、やっぱAmazon Linuxの方が好き、、、となったので、この機会に東京リージョン&Amazon Linuxで作成し直すこととしました。移植作業
ディストリビューションが違うもので作成し直すということは、色々動かない可能性もあります、というか動きません。API連携も結構やってます。
図の通り部分を整頓していくとGoogle スプレッドシートへの書き込み処理のみ気を付ければ、むしろ別インスタンスを作成するのであれば、移行時のテスト実行にリスクが無いことが分かります。まさか昨年、図に描いておいたことがここで役に立つとは。
そのほか、RDBMSなども無くリアルタイムで更新されるトランザクションデータが無いことも大きいですね。
新たに稼働させたインスタンスに、S3に控えておいた主なリソースを移し、主にShellScriptのあたりが雑だったので書き換え、ひたすら適用していくだけです。といった感じで、思っていたよりもサクサク移行できました。
サクサクと言えば、東京リージョンで立ち上げてSSH接続時の操作もサクサクになりました。
普段本業ではEC2等は東京リージョンしか使っていないのですが、あれが海外リージョンのレイテンシだったのでしょうか。静的IP付与
EC2の場合だと、Elastic IPを発行して、、、と簡単ですが、Lightsailの場合は更に簡単で、管理画面からポチポチと数クリックで発行されました。ようやく恩恵に預かりました。
発行されたIPアドレスをRoute53へ反映。しかしよく今まで大丈夫だったな・・・
SSL対応
ZeroSSLを用いてSSL対応をしました。
マジでブラウザだけで証明書の発行が完了してびっくりしました。
このあたりも、業務では中々使えていないので良い体験になりました。
(なお、この記事を書いている時点で期間が3ヵ月なので処置が必要なことに気付く。)フロントエンド見直し
要件:応援先グループの活動の変化
・メンバーがそれぞれ個人チャンネルを持った
・ゲストメンバーという概念が出現
・動画が変わらぬペースで増え続けている(余裕の1,000件突破)ようは情報量が1年前に比べて更に増えて、単純に表示、だけでは物足りなくなった。
やりたいこと(ざっくり)
・メンバーに紐づく情報が公式サイトプロフィール、個人チャンネル、Twitterアカウントと複数存在したため、プロフィール的なものを画面の邪魔にならない形で表示させたい
・とにかく今風にしたいやってみた(ざっくり)
・全くReact分からん
・とりあえずひたすらReact入門記事を読む。
・直感でMaterial-UIに絞った
・そのうちサンプルを扱えるようになる
・あれもこれも出来るようになる結果として、以下のComponentsを利用して
- Popover
- Avatar
- Paper
- Expansion Panel
- Table(material-table)以下のようなことを実現できた
・出演メンバーの可視化
→ Avatarを用いて、アイコンで誰が出演しているかを賑やかな感じで可視化
・増えた情報の出し入れ
→ 動画の収集元が7チャンネルになったのでそれぞれのExpansion Panelで表示
→ メンバーが4人+ゲスト3人構成になったのでExpansion Panelで見やすく表示
・動画サムネイル表示
→ マウスカーソルをあてることでサムネイルを表示することで解決。ちゃんとカーソルにあたったタイミングで読み込みに行く。この期に及んでスクリーンショットすらも載せませんが、Material-UIを使っているので綺麗です。
フレームワークなのだからそうやろという話ではありますが、サンプルをもとにデザイン+機能が簡単確実に実装出来た体験は、かなりの衝撃でした。
まとめ、得られたものなど
サクサクなSSH環境
リージョンでここまで違うとは。日曜開発の環境がサクサクになるのは良い事です。
フロントエンド事情に少し追いつけた
実際触ってみるまで、Reactがどういったものか分かりませんでした。今のフロントエンド開発はこういうことになっているんだ、と手遅れになる前に追いつけた感じが良かったというか、やらねばヤバかったなと思いました。
やらねばヤバかったなと思う一方、通常の業務をやっているとこのキャッチアップは出来なかったなぁ、、、というのが今回の個人開発サイトリニューアルで感じた一番のポイントです。もちろん業務においてもフロントエンジニアとの会話がしやすくなる、といったメリットもあるのですが自身の技術レベルを少しアップ出来たのが何より大きかったです。
業務と個人開発の違いとモチベーション
自社のお仕事の場合、どうしてもコスト、スピード、信頼性を考慮して最適解というものを選んでしまいます。新しいものが歓迎される文化もある一方で、費用対効果が明確だったら私は費用対効果が明確な手段を優先してしまいます。
では個人開発をしまくれば良いかと言うと、業務も普通に楽しい中で更にインプットとアウトプットを増やすのは私には結構困難です。
と考えた時に「趣味を利用した強烈なモチベーションを軸に、使ってみたい技術領域を試そう」といった考えで今回リニューアルをやってみました。結果、良いインプットとアウトプットが得られたと感じています。今後の展望
個人開発サイトを、よりサーバーレスで完結するような世界にしてゆきたいです。
- AWS Amplify
- Lambda(RPA部分)
とか。re:Inventにも期待。
- 投稿日:2019-12-04T09:27:02+09:00
react-hooks-workerの紹介
はじめに
React Hooksを使うと、非同期処理を比較的簡単に書くことができます。つまり、async/awaitをhooks内に隠蔽することができます。Web Workerを手軽に利用する有名なライブラリとしてcomlinkがありますが、WebWorkerとの通信は非同期であるためawaitをつけなければいけません。そこで、より手軽に扱えるReact Hooks専用のWeb Workerラッパーを紹介します。
react-hooks-worker
リポジトリはこちらです。
https://github.com/dai-shi/react-hooks-worker
このライブラリでは、Web Workerとの通信をstateを介して行うため、async/awaitをあまり意識する必要がないことが特徴です。
Web Workerを手軽に使うには、bundlerのサポートが重要です。各種プラグイン等がありますが、今回使用したのはwebpackのworker-pluginです。これにより、workerでも外部ライブラリが使えるようになります(本記事での使用例の紹介は無し)。
使用例
フィボナッチ数を計算する例を紹介します。まずは、workerの実装です。
// slow_fib.worker.js: import { exposeWorker } from 'react-hooks-worker'; const fib = i => (i <= 1 ? i : fib(i - 1) + fib(i - 2)); exposeWorker(fib);次に、これを利用するReactコンポーネントの実装です。
// app.js: import React from 'react'; import { useWorker } from 'react-hooks-worker'; const createWorker = () => new Worker('./slow_fib.worker', { type: 'module' }); const CalcFib = ({ count }) => { const { result, error } = useWorker(createWorker, count); if (error) return <div>Error: {error}</div>; return <div>Result: {result}</div>; }; const App = () => ( <div> <CalcFib count={5} /> </div> );これだけで、workerが使えるようになります。重い計算処理(かつ、結果が小さい場合)は、どんどんworkerにoffloadしましょう。
発展的な使い方
上記の例ではworkerの実装は単純な関数でしたが、実は、非同期関数やgeneratorでも動きます。列挙すると使えるパターンは下記になります。
- sync function
- async function
- sync generator function
- async generator function
参考までに、async generatorでフィボナッチ数の計算過程をゆっくりと出力するworker関数を載せます。
// fib-steps.worker.js import { exposeWorker } from 'react-hooks-worker'; async function* fib(x) { let x1 = 0; let x2 = 1; let i = 0; while (i < x) { yield `(calculating...) ${x1}`; await new Promise(r => setTimeout(r, 100)); [x1, x2] = [x2, x1 + x2]; i += 1; } yield x1; } exposeWorker(fib);workerの処理をプログレッシブに表示する場合などにこのパターンが使えるのではないでしょうか。
おわりに
本ライブラリではworkerは関数として表現されますが、comlinkは様々なオブジェクトをサポートしています。よく紹介されるのはworkerをclassとして実装する例ですが、このパターンを好む人がいることを知りました。react-hooks-workerでも様々なパターンをサポートすることが今後の改題の一つになりそうです。
- 投稿日:2019-12-04T08:34:46+09:00
useStateとuseReducerの関係(どっちが強力?同じ?わずかな違い?決定的な違い?)
はじめに
React HooksのuseStateとuseReducerに関する小ネタです。
useStateはuseReducerで実装されている
内部実装ではuseStateはuseReducerで実装されていると、どこかに書いてありました。こちらのブログ記事にuserlandでの実装例が載っています。
const stateReducer = (prevState, newState) => typeof newState === 'function' ? newState(prevState) : newState; const stateInitializer = initialValue => typeof initialValue === 'function' ? initialValue() : initialValue; const useState = initialValue => useReducer(stateReducer, initialValue, stateInitializer);こんな感じになります。
つまり、useStateでできることは全てuseReducerでもできるということです。では、useReducerだけでしかできないことはあるのでしょうか。
useReducerはuseStateでも実装できる
実は、useReducerはuseStateでも実装できます。こちらのブログ記事にuserlandでの実装例があります。
const useReducer = (reducer, initialArg, init) => { const [state, setState] = useState( init ? () => init(initialArg) : initialArg, ); const dispatch = useCallback( action => setState(prev => reducer(prev, action)), [reducer], ); return useMemo(() => [state, dispatch], [state, dispatch]); };こんな感じになります。つまり、機能的には同等ということになります。
実はわずかな違いがある
I just noticed, if we were to use lazy initialization using props with useState, it would be better to do it with useReducer which has initArg, because that could allow #JavaScript engine to optimize the inline function without closure.#ReactJS #React #ReactHooks pic.twitter.com/M0VsdhLXNd
— Daishi Kato (@dai_shi) 2019年11月13日こちらのツイートにあるように、useReducerではinitArgとinitが分離されているため、initをpropsに依存しないように書くことができます。これにより、hookの外側で関数定義することもできますし、inline関数で書いたとしてもJavaScriptのランタイムエンジンで最適化される可能性が高いです。
決定的な違いもあるにはある
お勧めしませんが、useReducerには非常に特殊な利用法もあります。それはreducerをコンポーネント内で定義できることです。reducerをコンポーネント内で定義すると未来のpropsを読み込めちゃいます。"cheat mode"と呼ばれたりします。
詳細は、こちらのブログ記事のセクションをご参照ください。
追記(12/4)
このcheat modeは上記のuserland実装では再現できていないですね。
おわりに
小ネタにしようかと思っていましたが、書き始めたらマニアックなネタになってしまいました。楽しんでくれた方がいらしたら幸いです。
- 投稿日:2019-12-04T03:20:04+09:00
Next.jsを使用してGoogle Chromeの拡張機能を作るとレイアウトとルーティングができるので結構楽に拡張の開発ができる。
はじめに
@yushimatenjinです。Next.jsアドベントカレンダー4日目ですね。
Next.jsを使うと簡単にGoogle Chromeの拡張機能が作れるんじゃないかなと思ったら簡単に作ることが出来たのでその紹介を今回しようかなと思います。このときに得た知見をもとに書いています。
Google Chrome 78にアップデートされたところ、今まであった、現在開いているタブ以外を閉じる機能がなくなっていて作業がはかどらなかったため、Chrome Extensionsを作ってみました。
— はが (@Mxcn3) November 2, 2019
アイコンを押すとすべてのタブを閉じるだけです ↓ダウンロードhttps://t.co/oBt2S62m9F pic.twitter.com/cqKiW1l3ZwChrome ExtensionをNext.jsで作ってみた
こちらが先日作成した、Next.jsで作ったChrome拡張を作るためのリポジトリです。
https://github.com/yushimatenjin/next-js-chrome-extension-starterkitNext.jsのはじめ方
Next.jsを始めるには、
React
の使える環境がある方でしたら簡単に始められます。1. 必要なパッケージをインストール
小さい構成で始めるにはこれだけで大丈夫です。
yarn init yarn add react react-dom next2.Pagesディレクトリを作る
mkdir pages
pages/index.jsを作成 & 書き換える
pages/index.jsconst React from 'react' export default () => <div>Hello, World!</div>これだけ書くだけで
/
にアクセスしたときにHello, World
を表示するページを作ることが出来ます。. ├── package.json ├── pages │ └── index.js └── yarn.lock起動をするためのスクリプトをpackage.jsonに追加する
package.json... scripts{ "dev": "next", "start": "next start", "build": "next build", "export": "next build && next export" } ...起動する
index.jsの内容を表示するためには先程設定した
yarn dev
のコマンドを打つことでlocalhost:3000
にアクセスできるようになります。
yarn devこれがNext.jsで最小限に起動をする方法になります。開発環境で起動する場合には
yarn dev
、ビルド&起動する場合にはyarn build && yarn start
でサーバーが立ち上がります。ChromeのExtensionを作る場合には
export
を使用して静的なページとして出力をします。Chrome Extensionを作る
ChromeのExtensionを作るためにいくつのファイルを追加&変更します。
追加するフォルダ & 追加するファイル
- extensions // フォルダ
- extensions/manifest.json // 拡張機能の詳細を記述するファイル
manifest.jsonはこちらを参考にして記述をしていきます。
例
manifest.json{ "name": "extension", "description": "extension", "version": "1.0.0", "browser_action": { "default_popup": "./dist/index.html" }, "permissions": [ "bookmarks", "tabs", "activeTab" ], "manifest_version": 2 }変更するファイル
- package.json
変更前
package.json.... "scripts": { ... "export": "next build && next export" }, ...変更後
export
に-o
オプションを付けて拡張機能の出力先を変更します。package.json.... "scripts": { ... "export": "next build && next export -o extensions/dist" }, ...この3つの新しく変更を加えた状態の現在のディレクトリ構成はこのような状態となっております。
. ├── README.md ├── extensions │ ├── dist │ └── manifest.json ├── package.json ├── pages │ └── index.js └── yarn.lockちなみに上記の設定を行ったものがこのリポジトリとなりますので、このリポジトリをクローンをするとすぐに開発をし始めることが出来ます。
next-js-chrome-extension-starterkit
こちらのリポジトリは
tsx
ファイルを追加したため少しファイルの構成が増えています。Next.js
ではTypeScriptを扱おうとするとある程度必要なファイルを自動的にファイルを追加してくれます。Chromeの拡張としてエクスポート
この状態で起動をすると、白い背景にHello, World!と表示されていると思います。この状態のまま拡張にしてみましょう。
yarn export
出力先をextension/distにpackage.jsonで設定しているので、静的なファイルが生成されます。
エクスポートされた拡張を読み込む
- エクスポートされたChromeの拡張機能を読み込むためにはGoogle Chromeの
設定
→その他ツール
→拡張機能
を選択
- パッケージ化されていない拡張を読み込む
プロジェクト
→ extensions ファイルを選択しますこれで拡張機能を読み込むことが出来ます。
Chrome拡張を作ることが出来た(やったー!)
ルーティングを追加する
Next.jsの特徴として静的サイトのルーティングを簡単に作れるので、新しいページを追加してみます。
Next.jsの
Link
は可能ですが、URLの指定を.htmlまで記述する必要があります。開発環境では必要ないのでページのパスを取得するための関数を追加します。constants/page.jsconst Pages = () => { const suffix = process.env.NODE_ENV === "development" ? "" : ".html" const data = { index: `index${suffix}`, about: `about${suffix}` } return data } export default Pagesトップページから飛ぶ先のページ名
about
を追加します。pages/about.jsimport React from "react"; import Link from "next/link"; import Page from "../constants/page"; const About = () => { return ( <div> このExtensionは@mxcn3が作成しました。 <Link href={Page().index}> <a>トップページへ</a> </Link> </div> ); }; export default About;トップページからページ名
about
に飛べるようにします。pages/index.jsimport React from "react"; import Link from "next/link"; import Page from "../constants/page"; const Index = () => { return ( <div> Hello World <Link href={Page().about}> <a>この拡張について</a> </Link> </div> ); }; export default Index;yarn exportを再度実行することで拡張機能が更新されます。
ページのサイズを変更する
ページのサイズをインラインで変更します。
あまり推奨されていないやり方みたいですが便利上インラインでスタイを変更します。
https://reactjs.org/docs/dom-elements.html#stylepages/index.jsimport React from "react"; import Link from "next/link"; import Page from "../constants/page"; const Index = () => { return ( // ページの大きさを変更 <div style={{ width: 400, height: 400 }} > Hello World <Link href={Page().about}> <a>この拡張について</a> </Link> {/* iframeを追加 */} <iframe style={{ width: "100%", height: "100%", margin: 0, border: "none" }} src="https://playcanv.as/p/8NZ92jAY/" /> </div> ); }; export default Index;このような形で表示されます。
まとめ
Next.jsはReactを使用して記述ができるので、Next.jsでGoogle Chromeの拡張機能を書くことができれば比較的作りやすいのではないでしょうか。Next.jsを使用したChrome拡張の記事などをあまり見なかったので記事にさせていただきました。
- 投稿日:2019-12-04T03:20:04+09:00
Next.jsを使用してGoogel Chromeの拡張機能を作るとレイアウトとルーティングができるので結構楽に拡張の開発ができる。
はじめに
@yushimatenjinです。Next.jsアドベントカレンダー4日目ですね。
Next.jsを使うと簡単にGoogle Chromeの拡張機能が作れるんじゃないかなと思ったら簡単に作ることが出来たのでその紹介を今回しようかなと思います。このときに得た知見をもとに書いています。
Google Chrome 78にアップデートされたところ、今まであった、現在開いているタブ以外を閉じる機能がなくなっていて作業がはかどらなかったため、Chrome Extensionsを作ってみました。
— はが (@Mxcn3) November 2, 2019
アイコンを押すとすべてのタブを閉じるだけです ↓ダウンロードhttps://t.co/oBt2S62m9F pic.twitter.com/cqKiW1l3ZwChrome ExtensionをNext.jsで作ってみた
こちらが先日作成した、Next.jsで作ったChrome拡張を作るためのリポジトリです。
https://github.com/yushimatenjin/next-js-chrome-extension-starterkitNext.jsのはじめ方
Next.jsを始めるには、
React
の使える環境がある方でしたら簡単に始められます。1. 必要なパッケージをインストール
小さい構成で始めるにはこれだけで大丈夫です。
yarn init yarn add react react-dom next2.Pagesディレクトリを作る
mkdir pages
pages/index.jsを作成 & 書き換える
pages/index.jsconst React from 'react' export default () => <div>Hello, World!</div>これだけ書くだけで
/
にアクセスしたときにHello, World
を表示するページを作ることが出来ます。. ├── package.json ├── pages │ └── index.js └── yarn.lock起動をするためのスクリプトをpackage.jsonに追加する
package.json... scripts{ "dev": "next", "start": "next start", "build": "next build", "export": "next build && next export" } ...起動する
index.jsの内容を表示するためには先程設定した
yarn dev
のコマンドを打つことでlocalhost:3000
にアクセスできるようになります。
yarn devこれがNext.jsで最小限に起動をする方法になります。開発環境で起動する場合には
yarn dev
、ビルド&起動する場合にはyarn build && yarn start
でサーバーが立ち上がります。ChromeのExtensionを作る場合には
export
を使用して静的なページとして出力をします。Chrome Extensionを作る
ChromeのExtensionを作るためにいくつのファイルを追加&変更します。
追加するフォルダ & 追加するファイル
- extensions // フォルダ
- extensions/manifest.json // 拡張機能の詳細を記述するファイル
manifest.jsonはこちらを参考にして記述をしていきます。
例
manifest.json{ "name": "extension", "description": "extension", "version": "1.0.0", "browser_action": { "default_popup": "./dist/index.html" }, "permissions": [ "bookmarks", "tabs", "activeTab" ], "manifest_version": 2 }変更するファイル
- package.json
変更前
package.json.... "scripts": { ... "export": "next build && next export" }, ...変更後
export
に-o
オプションを付けて拡張機能の出力先を変更します。package.json.... "scripts": { ... "export": "next build && next export -o extensions/dist" }, ...この3つの新しく変更を加えた状態の現在のディレクトリ構成はこのような状態となっております。
. ├── README.md ├── extensions │ ├── dist │ └── manifest.json ├── package.json ├── pages │ └── index.js └── yarn.lockちなみに上記の設定を行ったものがこのリポジトリとなりますので、このリポジトリをクローンをするとすぐに開発をし始めることが出来ます。
next-js-chrome-extension-starterkit
こちらのリポジトリは
tsx
ファイルを追加したため少しファイルの構成が増えています。Next.js
ではTypeScriptを扱おうとするとある程度必要なファイルを自動的にファイルを追加してくれます。Chromeの拡張としてエクスポート
この状態で起動をすると、白い背景にHello, World!と表示されていると思います。この状態のまま拡張にしてみましょう。
yarn export
出力先をextension/distにpackage.jsonで設定しているので、静的なファイルが生成されます。
エクスポートされた拡張を読み込む
- エクスポートされたChromeの拡張機能を読み込むためにはGoogle Chromeの
設定
→その他ツール
→拡張機能
を選択
- パッケージ化されていない拡張を読み込む
プロジェクト
→ extensions ファイルを選択しますこれで拡張機能を読み込むことが出来ます。
Chrome拡張を作ることが出来た(やったー!)
ルーティングを追加する
Next.jsの特徴として静的サイトのルーティングを簡単に作れるので、新しいページを追加してみます。
Next.jsの
Link
は可能ですが、URLの指定を.htmlまで記述する必要があります。開発環境では必要ないのでページのパスを取得するための関数を追加します。constants/page.jsconst Pages = () => { const suffix = process.env.NODE_ENV === "development" ? "" : ".html" const data = { index: `index${suffix}`, about: `about${suffix}` } return data } export default Pagesトップページから飛ぶ先のページ名
about
を追加します。pages/about.jsimport React from "react"; import Link from "next/link"; import Page from "../constants/page"; const About = () => { return ( <div> このExtensionは@mxcn3が作成しました。 <Link href={Page().index}> <a>トップページへ</a> </Link> </div> ); }; export default About;トップページからページ名
about
に飛べるようにします。pages/index.jsimport React from "react"; import Link from "next/link"; import Page from "../constants/page"; const Index = () => { return ( <div> Hello World <Link href={Page().about}> <a>この拡張について</a> </Link> </div> ); }; export default Index;yarn exportを再度実行することで拡張機能が更新されます。
ページのサイズを変更する
ページのサイズをインラインで変更します。
あまり推奨されていないやり方みたいですが便利上インラインでスタイを変更します。
https://reactjs.org/docs/dom-elements.html#stylepages/index.jsimport React from "react"; import Link from "next/link"; import Page from "../constants/page"; const Index = () => { return ( // ページの大きさを変更 <div style={{ width: 400, height: 400 }} > Hello World <Link href={Page().about}> <a>この拡張について</a> </Link> {/* iframeを追加 */} <iframe style={{ width: "100%", height: "100%", margin: 0, border: "none" }} src="https://playcanv.as/p/8NZ92jAY/" /> </div> ); }; export default Index;このような形で表示されます。
まとめ
Next.jsはReactを使用して記述ができるので、Next.jsでGoogle Chromeの拡張機能を書くことができれば比較的作りやすいのではないでしょうか。Next.jsを使用したChrome拡張の記事などをあまり見なかったので記事にさせていただきました。
- 投稿日:2019-12-04T00:15:19+09:00
DenoでReact Server Side Renderingした話
概要
最近何かと注目が集まってるDenoですが、なんとDenoでjsxが動くみたいなのでDenoでReactが動くかやってみました。
Denoってなんぞや
これについては、今Deno Advent Calendarにて@kt3kさんが Deno ってなんだっけ?と言う記事をあげているので、そちらを参考にしてみてください。
Denoのインストール
何はともあれ、Denoを導入してみましょう!
brew install denoインストールが完了したら、
deno -v
で以下のような画面が出てきたらインストール完了です。
コードを書いてみよう
早速コードを書いていきたいわけですが、今回二つファイルを作ります。
- index.tsx
- App.tsx
App.tsx
こちらはいつも通りのreactコンポーネントです。一つ違うのはDenoはnode_modulesではなく直接ダウンロードする方式ですね。後のコードはいつも通りです。
Apptsximport React from 'https://dev.jspm.io/react'; const App = () => <div>Hello Deno React</div>; export default App;index.tsx
次にindexファイルですが、
index.tsximport { createRouter } from 'https://denopkg.com/keroxp/servest/router.ts'; import React from 'https://dev.jspm.io/react'; import ReactDOMServer from 'https://dev.jspm.io/react-dom/server'; import App from './app.tsx'; const router = createRouter(); router.handle('/', async req => { await req.respond({ headers: new Headers({ 'content-type': 'text/html; charset=UTF-8', status: 200, }), body: ReactDOMServer.renderToString( <html> <head> <title>deno react ssr</title> </head> <body> <App /> </body> </html> ) }) }); router.listen(':8000');ファイルの準備は以上になります。
ここで、以下コマンドを実行した上でhttp:/localhost:8000/にアクセスしてみましょう!deno index.tsx --allow-net // denoの起動これで画面上に
Hello Deno React
が出れば成功です!
Deno、未来ありますよね!では!