XXとしてる
2019年4月16日火曜日
ProxyとMutationObserver
Reactでサイトを作り変えているところ、こんな感じな画面になった。
下に余白がある
フッターが中途半端な部分にあって、ちょっとダサい感じ。 なのでbodyタグの高さが画面より低かったら画面下部に固定するようにしたかったが、一筋縄ではいかなかったので、メモを残していく。 ## とりあえずReactのrender関数で 手っ取り早いのでReact.Coponentのrender関数内でフッターのスタイルを動的に切り替えようと試みてみた。 ```js export class Footer extends React.Component
{ render() { const body = document.body let STYLE = { position: undefined, bottom: undefined } as React.CSSProperties if (body.scrollHeight < window.innerHeight) { STYLE.width = '100vw' STYLE.position = 'absolute' STYLE.bottom = 0 } return // ... } } ```
へへ、bodyの高さが0のままだぜ
そりゃ、Reactで今から描画しようとしているんだから、書きかけな状態ではまだ全体の高さはわからないよ。 というわけで、次に移行する ## Reactのライフサイクル関数をフックする それじゃ描画後に高さを計算したらいいんじゃないと言うわけで`componentDidMount()`内でスタイルの切り替えを行うようにしてみた。 [State and Lifecycle](https://reactjs.org/docs/state-and-lifecycle.html) ```js export interface State { isFixedBottom: boolean } export class Footer extends React.Component
{ componentDidMount() { // 描画後に呼び出される const body = document.body this.setState({isFixedBottom: body.scrollHeight < window.innerHeight}) } render() { // this.state.isFixedBottomを使ってスタイルを切り替えるように変更 } } ```
やった!高さがちゃんと取得できたぞ!
ちなみにページ内容をタブで切り替えられるようにしていたので`componentDidMount()`だけでは不十分で`componentDidUpdate()`で更新したときにも処理をはさまないといけない。 ```js export interface State { isFixedBottom: boolean } export class Footer extends React.Component
{ componentDidUpdate() { //なにか更新があったときに呼び出される const body = document.body this.setState({isFixedBottom: body.scrollHeight < window.innerHeight}) } } ```
いや〜、出来た出来・・うん?
'componentDidUpdate()'が呼びされない・・だと・・・
なぜだぁ・・・なぜなんだ・・・
フッターそのものは更新されないぞ
その通りである、実際に更新されるのはページ内の他のコンポーネントなので、フッターそのものは更新されないのである。 困った。これは困った。 フッターではなくてページ単位でフックするようにしたらいいのだと思うけど、コンポーネント間のやり取りが出来てしまって管理が大変になるし、 毎回ページにそれ関係のコードを追加するのがめんどくさいぞ。 いったいどうしたらいいんだ・・・ ## ProxyをつかってダイレクトにBodyの高さを監視する じゃあさぁ・・・bodyタグの高さの変更をフッターコンポーネント内で監視するようにしたらいいんじゃない? というわけで早速調べてみると、JavaScriptにProxyというものがあってそれがぴったり今回の問題に使えそうであった。 [Proxy](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy) ```js export class Footer extends React.Component
{ constructor(props: Props) { super(props) const bodyProxy = new Proxy(document.body, { set: (target: HTMLElement, name: string | number | symbol, value: any): boolean => { if ('scrollHeight' !== name) { return true } // 現在の状態と異なっていたときに変更するようにしたほうがいいけど簡略のためにこうしている this.setState({isFixedBottom: document.body.scrollHeight < window.innerHeight}) return true } }) document.body = bodyProxy // エラー } } ```
が、ダメ・・・
Proxy経由で値を変更しないと設定したハンドラーは実行されないので
、なんとかしてdocumentにあるbodyをそれと差し替えないといけない。 上のコードのようにReactでは直接値を書き換えることは禁止されているので、どうしたらいいんだ・・・。 ## DOMのイベントハンドラーに都合のいいイベントありますか?
ないです!
`Document.onvisibilitychange`はウィンドウやタブ単位の可視性が変わったときに発行されるイベントで今回とは異なる。 `Window:afterprintイベント`もドキュメントが描画されから終わった後に発行されるので、これも異なる。 JSではうまく検知できない問題? そんなことない! ####
MutationObserver!!
jsにはMutationObserverというDomツリーの変更を監視できるインターフェースが用意されている。 [MutationObserver ドキュメント](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) Proxyでは生成したインスタンス経由で値を変更しないとコールバックが呼び出されなかったので断念したが、これを使えばできる! それで以下のようなコードを書くと、 ```js export class Footer extends React.Component
{ constructor(props: Props) { super(props) this.state = { isFixedBottom: false } let config = { childList: true, subtree: true, } as MutationObserverInit const callback = (_mutationList: MutationRecord[], _observer: MutationObserver): void => { const lowBodyHeight = document.body.scrollHeight < window.innerHeight if (this.state.isFixedBottom !== lowBodyHeight) { this.setState({ isFixedBottom: lowBodyHeight }) } } let observer = new MutationObserver(callback) observer.observe(document.body, config) } // ... ``` 無事、目的を達成することが出来る。 `observer.observe(document.body, config)`の部分で監視を始めている。 第一引数に監視対象を、第二引数に監視内容を渡す。 今回は子要素も含めたノードで子の追加/削除があったときにコールバックを呼び出すように設定してある。 これで他のコンポーネントの更新があったかが擬似的にわかるようになったので、 あとはそれに合わせてフッターを画面下部に固定するかしないかを決めるだけであっさりと出来上がり。
終わり
(画像は以下のものを使用させていただきました。)
2019年4月12日金曜日
JS内でCSSをインポートしたいときはcss-loaderのmodulesをtrueにしよう。
# タイトル通りです。
## 解決例 webpackを使っているときの話で、以下のような設定にしましょう! ```js export.modules = { module: { rules: [ { test: /\.css?$/, use: [ "style-loader", { loader: "css-loader", options: { modules: true // 必要! } } ] }, // SASS/SCSSのとき { test: /\.scss?$/, use: [ "style-loader", { loader: "css-loader", options:{ modules: true // 必要! } }, "sass-loader" ] }, ] } } ``` ## この機能のことをCSS Modulesって言うんだって! [CSS Modules](https://github.com/css-modules/css-modules)から > CSS Moduleは全てのクラス名とアニメーション名がデフォルトでローカルにスコープされるCSSファイルである。 > 全てのURLs(`url(...)`)と`@imports`は要求したフォーマットでモジュールの中にある(`./xxx`と`../xxx`は相対パスを意味し、`xxx`と`xxx/yyy`は例えば`node_modules`の中といったモジュールフォルダーを意味する)。 CSSはあまり詳しくないのだけど、色々発展しているそうでCSS Modulesもその機能の一つだそうだ。 これを使うことで次のようなコードが書くことが出来る。 ファイルの位置関係については同じディレクトリにあるものとしてほしい。 ```js import * as React from 'react' import * as ReactDOM from 'react-dom' import MyStyle from './my-style.css' ReactDOM.render(
見出しだよ!
, document.getElementById('root')) ``` CSSファイルはこんな感じ。 ```css /*my-style.css*/ .Title { font-size: 72px; color: red; } ``` この機能によってCSSがJSコード内で使いやすくなる恩恵があるとかないとか。 CSSのクラス名を変更したらJS側も変更しないと何らかのエラーが起きやすくなっていると思うので、是非使いたい機能だ。 (使う際はJS側で扱いやすいように名前をキャメルケースで書いたほうが捗ると思う。) ## TypeScriptを使っているときは型定義も一緒に作ってね! [こちらを参考にしています](https://qiita.com/jerrywdlee/items/3c525001f8029312d5fa) これで名前とか変更してもコンパイル時にわかるようになるし、IDEとかでコード補間が効いてコーディングしやすくなる。 ```js // my-style.d.ts declare module '*.scss' { const content: { Title: string; } export default content } ``` 記事に書いてあるが、CSSファイルを作るたびに型定義を書くのは面倒なので一つのファイルにまとめて書くのがおすすめ。 ```js declare module 'foo.css' { const content: { bar: string; baz: string; }; export = content; } declare module 'bar.scss' { const content: { baz: string; }; export = content; } ``` ## 終わり
フロントエンドって依存パッケージがたくさんあって大変だね!
開発環境を整備するのが一番大変かもしれない…
(画像は以下のものを使用させていただきました。)
2019年4月11日木曜日
Webpackで開発中に複数のページを確認したいとき
Reactを触ってみて、`create-react-app`コマンドでお手軽にかつService Workerとかのテンプレートも含まれて簡単に開発できるなぁと思っていたところ、 複数ページを作ったらときはどのように確認したらいいのかわからないことに気がついた。
ちょっとわからないです・・・
`create-react-app`で作ると中でWebpack的なことが行われているから、拡張できない? そもそもReactってSingle Page Application向けのライブラリなんだから、ページを複数作ることを考えていない感じなのか? React Routerもあるしなぁ・・・。 けどな〜一つのページで作っていくと後々困りそうだし、検索してみたらMultiple Page Applicationの中にSPAを組み込むことができるってあるから、 出来るならそうしたいな〜。 てな感じで結構な時間悩んでいたので、とりあえずWebpackを使って複数のページの開発を試してみようと思ったら、`webpack-dev-server`が一つのページにしか対応していないことにわかった。
これは困ったぞ!
これを使うんだ!
'''webpack-dev-middleware'''
## 問題解決!
Webpackの公式ガイドのwebpack-dev-serverの下に書いてあったwebpack-dev-middleware
を使えば複数ページでも確認が出来るぞ! 使うときは自前のサーバーを建てる必要があるけど、nodejsならexpressがあるし、なによりドキュメントにサンプルコードがあるのであっさりと準備が終わる。 ```js // 開発時の確認用サーバーを起動するためのプログラム const express = require('express'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); const app = express(); const config = require('./webpack.config.js'); const compiler = webpack(config); // Tell express to use the webpack-dev-middleware and use the webpack.config.js // configuration file as a base. app.use(webpackDevMiddleware(compiler, { publicPath: config.output.publicPath })); // Serve the files on port 3000. app.listen(3000, function () { console.log('Example app listening on port 3000!\n'); }); ``` 必要になるパッケージのインストールこんな感じ。 ```bash yarn add -D express webpack webpack-cli webpack-dev-middleware ``` あと、webpackの設定もHTMLを複数出力するように修正すればOK! `webpack-dev-middleware`も内部で`webpack-dev-server`を使っているのでその設定も必要になる。 後、HtmlWebpackPluginの`chunks`オプションに使いたいパックされたものを指定すれば、ページに必要なリソースだけを読み込むことが出来る。 ```js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports = { mode: 'development', entry: { //必要な分だけ書く。 index: './src/index.js', about: './src/about.js' }, devtool: 'inline-source-map', devServer: { contentBase: './dist' }, plugins: [ new HtmlWebpackPlugin({ chunks: ['index'], //使用するチャンク名を指定する。 template: './src/index.html', filename: './index.html' }), new HtmlWebpackPlugin({ chunks: ['about'], template: './src/about.html', filename: './about.html' }) ], output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist'), publicPath: '/' } }; ``` ## 余談 より本番環境に近づけるために (URLから'.html'を省く) 少し使ってみて、ページ遷移をするときに'.html'を付けないといけないのってよくよく考えてみると本番環境と違うよねと気がついたので、省略する方法を考えてみた。 あくまで開発用でページ遷移ができるだけでいいので、細かな動作の違いは考えないことにする。 ```js // 開発時の確認用サーバーを起動するためのプログラム const express = require('express'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); const path = require('path') const app = express(); const config = require('./webpack.config.js'); const compiler = webpack(config); //開発用にhtmlファイルにリダイレクトするコード app.all('*', function (req, res, next) { if (path.extname(req.url) === '' && req.url !== '/') { const redirectURL = req.url+'.html' console.log(`!!Development Mode!! Redirect to ${redirectURL} from ${req.url}`) res.status(303).redirect(redirectURL) } else { next() } }) // Tell express to use the webpack-dev-middleware and use the webpack.config.js // configuration file as a base. app.use(webpackDevMiddleware(compiler, { publicPath: config.output.publicPath })); // Serve the files on port 3000. app.listen(3000, function () { console.log('Example app listening on port 3000!\n'); }); ``` 拡張子がないURLに対して`.html`を付けたものでリダイレクトするだけのコードを追加した。 動作確認のためなので、これで十分だと思う。
(画像は以下のものを使用させていただきました。)
2019年4月10日水曜日
How to Build a Simple Web App with React, Graphql, and Goを読んだ
GolangとReactで作ったサイトを作り直すのでこちらのサイトを読んでみた。 [How to Build a Simple Web App with React, Graphql, and Go](https://medium.com/@chrischuck35/how-to-build-a-simple-web-app-in-react-graphql-go-e71c79beb1d) 以下、サイトの内容の流れとメモ書き ## はじめに Golangのバージョンは1.11に上げたので書き直したよ。 Gloangはサイコーでパワフルな言語だから、次のプロダクトではNode.jsではなくてGoを使って作ってみるよ。 ここではReactとGraphql、Golangを使ってnot-todoリストサイトを作ってみる。 Githubにリポジトリにあげているので何かあったら参考にしてね! [React-Go-Graphql-Example](https://github.com/Chrischuck/React-Go-Graphql-Example)
※訳注 MongoDBも使ってます。
## 前準備 nodejsとGolang、depをインストールしておくこと。 データーベースにMongoDBクラウドサービスのMLabを使っているけど、好きなものを使ってね。 一応、このサイトの内容は無料プラン内で収まるから安心だね! プロジェクトのディレクトリ構造はこんな感じにするよ。 ```js |--client |--server | |--src | | |--app | | | |--main.go ``` GOPATHもプロジェクトに合わせておくことを忘れないように。 ## サーバーサイド ### 初期化 次のコマンドでサーバーサイドの初期化を行う。 `dep`はgolangのプロジェクト単位のパッケージ管理ツールになる。 `go get`とは違って他のプロジェクトに影響を与えないから便利。 ```bash cd server/src/app dep init ``` ### シンプルなRESTサーバー まずは簡単なサーバーを作ってみよう! Golangには標準ライブラリにHTTPサーバーを建てるパッケージがあるのでそれを使おう! ```js // main.go package main import ( "log" "net/http" ) func main() { http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println("Hello Mars!") })) log.Println("Now server is running on port 3000") http.ListenAndServe(":3000", nil) } ``` `go run main.go`でサーバーを起動しよう。 `localhost:3000`にリクエストを送るとサーバーの出力に'Hello Mars!'が書き込まれるよ。 ## データベース 次にデータベースを建てよう! GolangのMongoDBドライバーがあるからそれをプロジェクトにインストールしよう。
※記事が書かれてからMongoDBで更新があったみたいなのでMongoDB周りは次のパッケージを使用しています
[MongoDB Go Driver](https://github.com/mongodb/mongo-go-driver) ```bash dep ensure -add "go.mongodb.org/mongo-driver/mongo@~1.0.0" ``` 追加したら、`server/src/app/data/mongo.go`を新しく追加しよう。 ```js //server/src/app/data/mongo.go package mongo import ( "context" "time" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) var Client, err = mongo.NewClient(options.Client().ApplyURI("Insert your MongoDB URI here!")) var ctx, _ = context.WithTimeout(context.Background(), 10*time.Second) var errConnect = Client.Connect(ctx) ``` Client変数は頭文字が大文字になっているからエクスポートされる。 ## Graphql お次はGraphqlの準備だ! こちらは`main.go`を修正していく。 ```js package main import ( "log" "net/http" "github.com/graphql-go/graphql" "github.com/graphql-go/handler" "app/queries" "app/mutations" ) var schema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: queries.RootQuery, Mutation: mutations.RootMutation, }) func main() { h := handler.New(&handler.Config{ Schema: &schema, Pretty: true, }) http.Handle("/graphql", disableCors(h)) log.Println("Now server is running on port 3000") http.ListenAndServe(":3000", nil) } func disableCors(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, Accept-Encoding") if r.Method == "OPTIONS" { w.Header().Set("Access-Control-Max-Age", "86400") w.WriteHeader(http.StatusOK) return } h.ServeHTTP(w, r) }) } ``` 修正したので、`/graphql`にリクエストを送るとなにか反応を返してくれるようになった。 `/graphql`で行う処理は`disableCors`関数が返すハンドルに書かれている。 そのハンドルの内容はCross-Origin Resource Sharing(CORs, オリジン間リソース共有)を無効にしている。 [こちらのIssueを参考にしている](https://github.com/graphql-go/graphql/issues/290) `graphql-go/graphql`と`graphql-go/handler`パッケージが必要に成るの`dep`に登録しよう。 `app/queries`と`app/mutations`は後で作るので覚えておいてね。 #### 訳注 CORsについて追記 CORsについては以下のサイトを参考になる。 Web開発者はみんな知っておくべき内容であるそうだ。 ブラウザ側が良きに働いてくれているが、新しい標準仕様はサーバー側で管理しないといけないので必須知識である。 [オリジン間リソース共有 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS) [サーバーサイドアクセス制御 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/Server-Side_Access_Control) [Access-Control-Allow-Origin](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) [Access-Control-Allow-Methods](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Methods) [Access-Control-Allow-Headers](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) [Access-Control-Max-Age](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Max-Age) `disableCors`関数を見たとき一見無効にしてなさそうに見える。 #### Access-Control-Allow-Origin が、`Access-Control-Allow-Origin`を`*`に指定することで、資格情報があるときエラーを返すようになる。 (CORSリクエストの認証フラグをオフにすればエラーは起きない) `null`も設定できるがこちらは使用しないよう推奨されている。 詳しい説明が上のAccess-Control-Allow-Originリンクから見ることができる。 #### Access-Control-Allow-MethodsとAccess-Control-Allow-Headers `Access-Control-Allow-Methods`に指定したHTTPメソッド(POST, GET, OPTIONS, PUT, DELETE)はCORsを禁止される そして、`Access-Control-Allow-Headers`で指定したHTTPヘッダーもCORsで使うことができなくなる。 #### Access-Control-Max-Age `Access-Control-Max-Age`はプリフライトリクエストの結果をキャッシュできる時間を示す。 プリフライトリクエストとは実際にリクエストを送る前にOPTIONSメソッドによるリクエストを他のドメインに送り、 リクエストを送っても大丈夫か確認するリクエストのことである。 ブラウザごとにキャッシュ可能な時間の上限が異なるみたいなので、あくまで最長時間と見るべき。 #### CORsの通信の流れ 以上レスポンス側の設定項目について書いてきたが、実際にはリクエスト側にもCORs関係のヘッダー項目もあるので、上だけをみて理解は出来ないと思う。 CORsには以下の3つのシナリオが存在している。 - 単純リクエスト: プリフライトリクエストを引き起こさないリクエスト - プリフライトリクエスト: プリフライトリクエストを始めに行うリクエスト - 資格情報を含むリクエスト: 認証情報付きのリクエスト どれになるかはリクエストの内容次第で[オリジン間リソース共有 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)に書かれている。
CORsについて追記 終わり
## depを使ったパッケージ管理 `graphql-go/graphql`と`graphql-go/handler`のインストールは下のコマンドを実行するだけでいい。 ```bash dep ensure ``` 簡単でしょう! インストールするパッケージ名を何も書いてないじゃないかと思うかもしれないが、`dep`はソースコードにあるのを見てうまいことしてくれる。 便利! ## main.goの分割 さて、main.goもごちゃごちゃしてきたから、機能ごとに分割したい。 なので次のようなディレクトリ構造にしてほしい。 ``` |--client |--server | |--src | | |--app | | | |--data | | | |--mutations | | | |--queries | | | |--types | | | |--main.go ``` `server/src/app`の`mutations`と`queries`、`types`にGraphqlのコンポーネントを分けていきたい。 ### Types Graphqlのための型定義は`types`ディレクトリに入れていく。 それじゃ、さっそく`types/notTodo.go`を作っていこう。 `types/notTodo.go`にはnot-todoオブジェクトをモデリングしていく。 ```js //types/notTodo.go package types import ( "github.com/graphql-go/graphql" ) var NotTodo = graphql.NewObject(graphql.ObjectConfig { Name: "NotTodo", Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, }, "description": &graphql.Field{ Type: graphql.String, }, }, }) ``` ### Queries 次にクエリを用意する。これは`Graphql-go`で要求されているから作る。 Graphqlではデータを受け取ることにクエリを使っている。 クエリには使いたいパラメータをはっきり書く必要があって、Graphqlはクエリにあるものだけを送る。 ```js //queries/queries.go package queries import ( "github.com/graphql-go/graphql" fields "app/queries/fields" ) var RootQuery = graphql.NewObject(graphql.ObjectConfig{ Name: "RootQuery", Fields: graphql.Fields{ "getNotTodos": fields.GetNotTodos, }, }) ``` `queries/queries.go`は`queries/fields`にあるパッケージを使っている。 それじゃ、実際に使っている`fields.GetNotTodos`を作ろう! その内容は`queries/fields/getNotTodos.go`に書いていく ```js //queries/fields/getNotTodos.go package queries import ( "context" "github.com/graphql-go/graphql" "github.com/mongodb/mongo-go-driver/bson" "app/data" types "app/types" ) type todoStruct struct { NAME string `json:"name"` DESCRIPTION string `json:"description"` } var GetNotTodos = &graphql.Field { Type: graphql.NewList(types.NotTodo), Description: "Get all not todos", Resolve: func(params graphql.ResolveParams) (interface{}, error) { notTodoCollection := mongo.Client.Database("medium-app").Collection("Not_Todos") ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) todos, err := notTodoCollection.Find(ctx, bson.D{}) if err != nil { fmt.Println(err) panic(err) } defer todos.Close(ctx) var todosList []todoStruct for todos.Next(ctx) { doc := todoStruct{} err := todos.Decode(&doc) if err != nil { fmt.Println(err) panic(err) } todosList = append(todosList, doc) } return todosList, nil }, } ``` MongoDBからデータを全てのドキュメントを取ってきて、BSON形式からGolangで使うデータに変換している。 ### Mutations 最後に新しいnot-todoを作るためにミューテーションを作るよ。 ミューテーションは変化するデータに使われる。 今回はクエリを使ってデータを変更しているけど、いい設計とは言えないことを覚えておいてね。 ```js //mutations/mutations.go package mutations import ( "github.com/graphql-go/graphql" fields "app/mutations/fields" ) var RootMutation = graphql.NewObject(graphql.ObjectConfig{ Name: "RootMutation", Fields: graphql.Fields{ "createNotTodo": fields.CreateNotTodo, }, }) ``` MutationsもQueriesと同じく`mutations/fields`ディレクトリにあるパッケージを使っている。 なので、早速`createNotTodo.go`を作ろう。 ```js //mutations/fields/createNotTodo.go package mutations import ( "github.com/graphql-go/graphql" "context" "app/data" types "app/types" ) type todoStruct struct { NAME string `json:"name"` DESCRIPTION string `json:"description"` } var CreateNotTodo = &graphql.Field { Type: types.NotTodo, Description: "Create a not Todo", Args: graphql.FieldConfigArgument { "name": &graphql.ArgumentConfig { Type: graphql.String, }, "description": &graphql.ArgumentConfig { Type: graphql.String, }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { // get our params name, _ := params.Args["name"].(string) description, _ := params.Args["description"].(string) notTodoCollection := mongo.Client.Database("medium-app").Collection("Not_Todos") ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) _, err := notTodoCollection.InsertOne(ctx, map[string]string{"name": name, "description": description }) if err != nil { panic(err) } return todoStruct{name, description}, nil }, } ``` これにてGolangによるGraphqlのバックエンドを作ることが出来た! さぁ、`go run main.go`を実行してサーバーが動くかどうか確認しよう! ## React 次はフロントエンドを作っていこう。 ### 初期化 次のコマンドでフロントエンド側の開発環境を構築する。 ```bash cd ../../../client npm init -f ``` 開発時に使う依存パッケージは次のものだ。 BabelとWebpack関係のものをインストールする。 ```bash npm install --save-dev @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-syntax-dynamic-import @babel/preset-env @babel/preset-react @babel/polyfill "babel-loader@^8.0.0-beta" html-webpack-plugin webpack webpack-cli webpack-dev-server ``` リリース時にも使う依存パッケージは次になる。 ReactとGraphqlクライアントのようにApolloを使う。(Asyncルーティングには後で入れる) ```bash npm install --save apollo-boost graphql react react-apollo react-dom react-loadable react-router-dom ``` 必要なパッケージをインストールしたら、`package.json`にスクリプトを定義しよう。 ```json "scripts": { "dev": "webpack-dev-server --mode development", "build": "webpack --mode production" } ``` ファイル階層は次のようになる。 ``` |--client | |--node_modules | |--src | | |--components | | |--routes | | |--index.html | | |--index.js | |--.babelrc | |--.package.json | |--.webpack.config.js |--server ``` 確認できたら次に進もう! `.babelrc`は次の内容にする。 ``` { "presets": ["@babel/preset-env", "@babel/preset-react"], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", {"loose": true}], "@babel/plugin-syntax-dynamic-import" ] } ``` `webpack.config.js`は次のように。 ```js const HtmlWebPackPlugin = require('html-webpack-plugin'); const htmlWebpackPlugin = new HtmlWebPackPlugin({ template: './src/index.html', filename: './index.html' }); module.exports = { entry: [ '@babel/polyfill', './src/index.js', ], output: { filename: 'app.js', path: __dirname + '/dist' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader" } }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true, importLoaders: 1, localIdentName: "[name]_[local]_[hash:base64]", sourceMap: true, minimize: true } } ] } ] }, devtool: 'eval-source-map', // ← 注釈参考 devServer: { historyApiFallback: true, }, plugins: [htmlWebpackPlugin] }; ``` ※注釈 webpack.config.js絡みの警告 ブラウザで表示すると`TypeError: ’xxx’ is not a valid URL. ...`と大量の警告が出てきたので以下のページを参考に修正しています。 [How to avoid “TypeError: popper.js.map is not a valid URL” message](https://stackoverflow.com/questions/54774650/how-to-avoid-typeerror-popper-js-map-is-not-a-valid-url-message)
注釈終わり
さぁ、Reactを使ってフロントエンド側を作っていこう! ### React `src/index.html`は次のコードをペーストしてね。 ```html
``` そして、`src/index.js`は次の内容でしよう。 ```js import React from "react"; import ReactDOM from "react-dom"; ReactDOM.render(
Hello World!
, document.getElementById('app') ); ``` これで基本的なReactアプリが完成! `npm run dev`を実行して、`localhost:8080`にアクセスしてみよう。 "Hello World!"って画面に出るはずだ。 ### Routing お次はReact Routingを使ってアプリのルーティングを追加しよう。 React Routetを使っているからすぐにでも非同期ルーティングを実装することが簡単にできるんだ。 React RouterはReact Loadableを作り直したものなんだって。 それじゃまず先に`loading.js`を`src/components`ディレクトリに作ろ! このReactコンポーネントはページ切り替えのとき、まだ読み込み待ちのときに表示されるものになるよ。 ```js //src/components/loading.js import React from 'react' const Loading = () =>
loading
export default Loading ``` よし、実際にルーティングを作っていこう。 `src/routes/home`ディレクトリを作って、その中に`index.js`と`home/home.js`を追加しよう。 ```js // src/routes/home/home.js import React from 'react' const Home = () => (
Home!
) export default Home ``` ```js //src/routes/home/index.js import React from 'react' import Loadable from 'react-loadable' import Loading from '../../components/loading' const LoadableComponent = Loadable({ loader: () => import('./home'), loading: Loading, }) const LoadableHome = () =>
export default LoadableHome ``` `index.js`で`react-loadable`パッケージの`Loadable`を使って`loading.js`と`home.js`を組み合わせているよ。 これで読み込み中に"loading"って画面に表示されるんだ。 #### 404ルーティング よし、次は404ルーティングを作ろか。 作り方は同じだよ。 `src/routes/notFound`ディレクトリを作って、その中に`index.js`と`notFound.js`を作るだけ。 ```js //src/routes/notFound/notFound.js import React from 'react' const NotFound = () =>
404 Not Found :(
export default NotFound ``` ```js //src/routes/notFound/indexjs import React from 'react' import Loadable from 'react-loadable' import Loading from '../../components/loading' const LoadableComponent = Loadable({ loader: () => import('./notFound'), loading: Loading, }) const LoadableNotFound = () =>
export default LoadableNotFound ``` #### 仕上げ よし、これで必要なルーティングは揃ったから仕上げとして、`src/routes/index.js`を作ろう。 ```js //src/routes/index.js import React from 'react' import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' import Home from './home' import NotFound from './notFound' class AppRouter extends React.Component { render() { return (
) } } export default AppRouter; ``` React Routerの`Route`コンポーネントのプロパティに私達が作ったルートを渡しているのがわかるかな? Homeルートは`localhost:8080/`でアクセスすることができるようにしているよ。 簡単だね! けどNotFoundルートはどうやってアクセスしたいいんだろ? じつはNotFoundルートはワイルドカードルートを使っていて、 例えば`localhost:8080/not-home`みたいに何も設定されていないルートにアクセスすることで出来るんだ。 よーし、最後の作業だ!`src/index.js`を次のように編集しよう! ```js //src/index.js import React from "react"; import ReactDOM from "react-dom"; import AppRouter from './routes' ReactDOM.render(
, document.getElementById('app') ); ``` 素晴らしい!!これでルーティングは完成だね。 早速`localhost:8080/`にアクセスしてみて出来ているか確認しよう! 404ページも忘れずにね。 ### Apollo ApolloはReactアプリでGraphqlを使うためのGraphqlクライアントだ。 FacebookからRelayという他のGraphqlクライアントもあるけど、僕はApolloが簡単で柔軟に使えると思っているよ。 それじゃ、Apolloを使うように`src/index.js`を修正しよう。 ```js //src/index.js import React from "react"; import ReactDOM from "react-dom"; import ApolloClient from 'apollo-boost'; import { ApolloProvider } from 'react-apollo'; import AppRouter from './routes' const client = new ApolloClient({ uri: 'http://localhost:3000/graphql' }); ReactDOM.render(
, document.getElementById('app') ); ``` 簡単でしょ?これでApolloの追加は終わったよ。 それじゃ、前に作ったサーバーがとコミュニケーションを取ろうか。 `src/routes/home/home.js`を次のように変更してね。 ```js //src/routes/home/home.js import React from 'react' import { gql } from 'apollo-boost'; import { graphql, compose } from 'react-apollo'; // example of a graphql query const query = gql` query GetNotTodos{ getNotTodos { name description } } ` // example of a graphql mutation const mutation = gql` mutation CreateNotTodo($name: String, $description: String) { createNotTodo(name: $name, description: $description) { name description } } ` @compose( graphql(query), graphql(mutation) ) class Home extends React.Component { render() { console.log(this.props) return (
Home still!
) } } export default Home ``` 上のコードは何してるんだろうね? まずHomeコンポーネントを関数形式から標準的なコンポーネントに変えたよ。 次に、これが魔法みたいなんだけど、`graphql`ラッパーを使ってHomeコンポーネントをラップしているんだ。 これで自動的にgraphqlのクエリを実行するようになるんだ。 (嘘はついていないよ、あまりに一瞬なことだからわからないだけで、ログを確認してみてね) 確認したら、Homeコンポーネントを次のように変更してね。 サーバーにあるnot-todoのリストを取得して表示するようにしているよ。 ```js class Home extends React.Component { render() { const { getNotTodos = [] } = this.props.data return (
name
description
{ getNotTodos.map(notTodo => (
{notTodo.name}
{notTodo.description}
)) }
) } } ``` OK!これで簡単なテーブルで名前と説明を表示できるようになったね。 ### ※注釈 Reactでリストの子要素にはKeyプロパティを与えないと警告がでる。 その際は添字ではなくユニークなキーに成るようにしたほうがアニメーションとかで不具合がでないので推奨される。 参考サイト [Why using an index as Key in React is probably a bad idea?](https://medium.com/@vraa/why-using-an-index-as-key-in-react-is-probably-a-bad-idea-7543de68b17c)
注釈終わり
次はnot-todoを追加できるようにしよう。 ```js class Home extends React.Component { constructor(props) { super(props) this.state = { name: '', description: '' } } onChange = event => { this.setState({ [event.target.name]: event.target.value}) } save = () => { this.props.mutate({ variables: { name: this.state.name, description: this.state.description }, refetchQueries: ['GetNotTodos'] }) } render() { const { getNotTodos = [] } = this.props.data return (
Add something to not do!
Save
name
description
{ getNotTodos.map(notTodo => (
{notTodo.name}
{notTodo.description}
)) }
) } } ``` 上のコードのコンストラクタでアプリに状態を持ち込んで、その状態を保存するためのボタンと機能を追加したよ。 保存する中でgraphqlのミューテーションを利用しているのがわかるかな? とても簡単に作れるでしょ? ## 終わり (実際に手を動かしながらいくつかのバグや警告を発見したので元の記事からコードを修正しています。)
2019年4月8日月曜日
空間参照系
ちょっとOpenLayersを触っていて、地図の単位でもある空間参照系というものに初めて触れたのでメモを残しておく ## 空間参照系の種類 ### EPSG:3857 WebにおいてはGoogleMapが初めて採用したEPSG:3857という空間参照系が広く使われている。 地図の画像データを全て正方形で表現できる利点があるため、コンピュータにとって都合がいいので地図アプリでは広く使われている。 ただし、欠点として高緯度付近は歪んでいるのだが、あまり問題にならないだろうという考えの下で生まれた空間参照系なのでそうゆうものである。 ### EPSG:4326 一方、EPSG:4326というアメリカ国防省が策定したものもあり、こちらはメルカトル図法とよく似ているので、地図といえばこちらを思い浮かべると思う。 EPSG:4326は別名WGS84(World Geodetic System 84)とも呼ばれている。 EPSG:4326は緯度経度の値がわかりやすいので、作業を行う際はこちらで行い、OpenLayersで表示するときは上のEPSG:3857を使用する形にするつもりである。 ## 空間参照系の変換 OpenLayersではEPSG:3857とEPSG:4326の間の変換しか対応していない。 空間参照系には用途に合わせてかなりの数の種類があるため、もしかしたらデフォルト機能だけでは満足できないかもしれない。 そのときは[proj4.js](http://proj4js.org/)というパッケージと[epsg.io](https://epsg.io/)というサイトを利用することで対応できるようになるそうだ。 [参考サイト](https://qiita.com/baikichiz/items/8205deb0bdbae48b0b17) ## GeoJSON 地図のある地点を表現するファイルフォーマットもいくつかあり、GeoJSONはJSON形式で表現されたものである。 [geojson.io](http://geojson.io)というサイトが位置情報を編集する上で手軽で、GeoJSON形式以外にも出力できるので便利である。 ちなみにgeojson.ioはEPSG:4326を採用しているので、EPSG:3857を使用するときはなんらかの変換が必要なので注意すること。 OpenLayersでGeoJSONを使うときはファイル内容をそのまま読み込むことが出来るが、座標系の変換や見た目の情報などは反映してくれないので自前でパーサーを作ってなんやこらする必要が出てくると思う。 ## 最後に 空間座標系多すぎ!!
2019年4月7日日曜日
はじめてのCron
試験的にWebサイトの問題をTwitterでツィートしている。 が、
twitterに問題をツィートするの面倒だな〜
それに毎日しないといけないってのも意外と大変。
あ〜ぁ、ツィートの予約機能があったら楽できるのになぁ〜。
ほら、これを使え!
・
・
・
よそ見しているんじゃねぇ!
## Twitter APIを使う(node-twitter) というわけでTwitter APIでツィートが出来るとのことなので、cronと組み合わせて決まった時間に問題をツィートする機能を作ってみた。 Twitter APIはnode-twitterを使うことで簡単に目的を果たすことができた。 しかし、cronの扱いに困った困った困っちゃんになってしまったので、メモを残しておく。 ## Cron設定ってどうするの? なんか、/etc/cron***的なところにcron用のファイルを置いておくとcronデーモンが勝手に仕事をしてくれるそうっすよ。 - cron.d 汎用的なタスクを行うときにファイルを置く - cron.daily 一日一回実行したいときにファイルを置く - cron.hourly 一時間に一回実行したいときにファイルを置く - cron.weekly 一週間に一回実行したいときにファイルを置く - cron.month 一月に一回実行したいときにファイルを置く - crontab cron.daily,cron.hourly cron.weekly cron.month内にあるcronファイルを実行する内容になっている ちなみにcrontabというコマンドも存在していて、今回はそれを使った。 ```bash crontab template-file #Gitで記録するわけには行かないので後からAPIキーを書き込む crontab -e ``` ## Twiiter APIを使うには環境変数を設定しないといけないぞ! 今回の困った部分。 環境変数の指定の仕方だが、なんのことないcronファイルにそのまま書ける。 一度知ってしまえば簡単である。(数十回ぐらい設定をいじっては直していました・・・) が、環境変数の展開はできないそうなのでbashなど既存の環境を使用するのではなく、cron用に一から環境を設定してあげる意識で使うといいと思う ``` USE_ENV='use variable' NEW_PATH='' # 実際にcron内部に設定されている環境変数を確認したいときは以下のコマンドを使うといい 10 * * * * env > /tmp/env.log ``` ## おしまい 最近Linux関係の調べものが多くなってきた感がある。 あとはsystemctlも扱えるようになればいいなぁと思ったり思わなかったり。
(画像は以下のものを使用させていただきました。)
2019年4月5日金曜日
Golang環境構築
簡単なメモとしてGolangの環境構築で使ったものを書いていく。 ## goenv はじめは公式ホームページからバイナリをインストールしていたが結構面倒。 で、ちょっと調べんてみると
goenv
というものがいろんなバージョンを一括管理出来て便利とのことだったので使ってみた。 Githubに書かれているインストールの手順はちょっと面倒だけど、Macだとパッケージ管理ツールからインストールできるとかないとか。 ```bash git clone https://github.com/syndbg/goenv.git ~/.goenv #環境変数の設定 echo 'export GOENV_ROOT="$HOME/.goenv"' >> ~/.bash_profile echo 'export PATH="$GOENV_ROOT/bin:$PATH"' >> ~/.bash_profile #バッシュ環境にgoenvを登録する echo 'eval "$(goenv init -)"' >> ~/.bash_profile #(オプション)Golangの環境変数もgoenvで管理したいときは以下のも追加する # ただし、上のeval "$(goenv init -)"を実行してからでないといけない echo 'export PATH="$GOROOT/bin:$PATH"' >> ~/.bash_profile echo 'export PATH="$GOPATH/bin:$PATH"' >> ~/.bash_profile ``` 上のコマンドで初期設定は完了する。 あとはターミナルを再起動するなり、なんなりすると使う準備は完了する。 次のコマンドでインストールしたいバージョンを指定してGolangをインストールする。 ```bash goenv install 1.12.1 #細かいバージョンまでわからないときは途中まで書くとあとに続く候補を教えてくれる。 goenv install 1.7 ``` ## direnv GolangではGOPATHなど専用の環境変数を設定することで作業ディレクトリをしていする。 なのでgolangを使うときはあるディレクトリにソースコードやらを入れる必要があるが、ちょっと違うディレクトリでも使いたいとなると毎回環境変数を設定し直す必要が出てくる。 これは面倒なのでdirenvというコマンドを使用し環境変数の設定を自動的に行ってくれるようにする こちらもgolang製のコマンドだが、OSのパッケージ管理ツールからインストールできる。 `eval "$(direnv hook bash)"`でdirenvの初期化を行っているので実行忘れをすると当然動かない。 ```bash apt-get install direnv ## 次のコマンドを忘れない。 # ※2019/4/18 ~/.bash_profileから~/.bashrcに修正 echo 'eval "$(direnv hook bash)"' >> ~/.bashrc ``` 使うときは、環境変数を変更したいディレクトリに.envrcファイルを作る。 あとはその中に設定を書いていけばいい。 ```bash export GOROOT=$(go env GOROOT) export GOPATH=$(pwd) ``` ## あとは便利パッケージをインストールをして終わり。以上!
こちらのページにあるものをインストールするといいみたい
- goimports - Golint などなど。
2019年4月3日水曜日
時間がずれている!!
Amazon lightssailで自作のWebページを公開したところ、データベースの時刻がどうもずれてしまっていることに気がつく。
やばい、このままだとデータが正しく取得できないぞ!
## なんか9時間きっかりずれているなぁー 9時間といえばUTC時刻の基準と成っているイギリスと日本の時差になる。 9時間といえばUTC時刻の基準と成っているイギリスと日本の時差になる。 9時間といえばUTC時刻の基準と成っているイギリスと日本の時差になる。
ハッ、わかったぞ!
原因はタイムゾーンが開発PCと異なっているんだ! そうとわかれば早速タイムゾーンを合わせるぞ===っ!! ```bash timedatectl set-timezone Asia/Tokyo ``` あとは、データベースにデータを入れ直して確認すると・・・
やったぜ!!
## 終わりに
公開する前に確認しろ!!
すいませーん!!
公開して15分足らずで、サーバー管理をしている人の心理を追体験できました!
(画像は以下のものを使用させていただきました。)
新しい投稿
前の投稿
ホーム
登録:
投稿 (Atom)