- 投稿日:2020-09-06T23:57:56+09:00
gatsby入門 チュートリアルをこなす 8. 公開するサイトの準備
gatsbyの作業履歴
gatsby入門 チュートリアルをこなす 0.開発環境をセットアップする
gatsby入門 チュートリアルをこなす 1. ギャツビービルディングブロックについて知る(1)
gatsby入門 チュートリアルをこなす 1. ギャツビービルディングブロックについて知る(2)
gatsby入門 チュートリアルをこなす 2. ギャツビーのスタイリングの概要
gatsby入門 チュートリアルをこなす 3. ネストされたレイアウトコンポーネントの作成
gatsby入門 チュートリアルをこなす 4. ギャツビーのデータ
gatsby入門 チュートリアルをこなす 5. ソースプラグインとクエリされたデータのレンダリング
gatsby入門 チュートリアルをこなす 6. 変圧器プラグイン※Transformer pluginsのgoogle翻訳
gatsby入門 チュートリアルをこなす 7. プログラムでデータからページを作成する
今回:gatsby入門 チュートリアルをこなす 8. 公開するサイトの準備
gatsby入門 ブログ作ってサーバーにアップしてみるチュートリアルをこなす!
今回実施するgatsbyのチュートリアルはこちら
https://www.gatsbyjs.com/tutorial/part-eight/
基本はこれで最後かな?
チュートリアルの冒頭にこう書かれています。この最後のセクションでは、Lighthouseと呼ばれる強力なサイト診断ツールを導入して、サイトを稼働させるための一般的な手順をいくつか説明します。途中で、Gatsbyサイトでよく使用するプラグインをいくつか紹介します。
※google翻訳です。
早速やっていきましょう。
ソースは前回作ったやつを使用します。Preparing a Site to Go Live
Audit with Lighthouse
何やら監査をするそうな。
まずサイトの本番ビルドが必要のようです。Create a production build
まず、すでに開発サーバが起動している場合は停止する。
そして、以下コマンドを実行する。
gatsby build
そして
gatsby serve
そして以下にアクセス
http://localhost:9000
OK!Run a Lighthouse audit
続いてLighthouseテストを実行します。
手順は以下とチュートリアルに記載1.Chromeシークレットモードでサイトを開いて、拡張機能がテストに干渉しないようにします。次に、Chrome DevToolsを開きます。
なんか出てきた。
Generate reportを押します。
またなんか出てきた。
なんか黄色がいっぱい。。。Add a manifest file
LighthouseのProgressive Web App不足を見直すみたい。
そしてなんだかmanifestを作れば良いらしい。
そしてGatsby’s manifest pluginを使うみたいUsing gatsby-plugin-manifest
プラグインを以下コマンドでインストール
npm install --save gatsby-plugin-manifest
※サイト直下のディレクトリで実行
とりあえずサーバ停止して実行しました。
何やらicon.pngがsrc/images配下にいるみたい。
適当にどっかから持ってくる。
gatsby-config.jsを修正gatsby-config.jsmodule.exports = { siteMetadata: { title: `Pandas Eating Lots`, }, plugins: [ { resolve: `gatsby-source-filesystem`, options: { name: `src`, path: `${__dirname}/src/`, }, }, `gatsby-plugin-emotion`, `gatsby-transformer-remark`, { resolve: `gatsby-plugin-typography`, options: { pathToConfigModule: `src/utils/typography`, }, }, ↓ここから { resolve: `gatsby-plugin-manifest`, options: { name: `GatsbyJS`, short_name: `GatsbyJS`, start_url: `/`, background_color: `#6b37bf`, theme_color: `#6b37bf`, // Enables "Add to Homescreen" prompt and disables browser UI (including back button) // see https://developers.google.com/web/fundamentals/web-app-manifest/#display display: `standalone`, icon: `src/images/icon.png`, // This path is relative to the root of the site. }, }, ↑ここまで追記 ], }※過去チュートリアルの場合です。
Add offline support
なんかpwaにservice workerがいるみたい。
Gatsby’s offline pluginをインストールします。
プラグインを以下コマンドでインストール
npm install --save gatsby-plugin-offline
gatsby-config.jsを修正gatsby-config.jsmodule.exports = { siteMetadata: { title: `Pandas Eating Lots`, }, plugins: [ { resolve: `gatsby-source-filesystem`, options: { name: `src`, path: `${__dirname}/src/`, }, }, `gatsby-plugin-emotion`, `gatsby-transformer-remark`, { resolve: `gatsby-plugin-typography`, options: { pathToConfigModule: `src/utils/typography`, }, }, { resolve: `gatsby-plugin-manifest`, options: { name: `GatsbyJS`, short_name: `GatsbyJS`, start_url: `/`, background_color: `#6b37bf`, theme_color: `#6b37bf`, // Enables "Add to Homescreen" prompt and disables browser UI (including back button) // see https://developers.google.com/web/fundamentals/web-app-manifest/#display display: `standalone`, icon: `src/images/icon.png`, // This path is relative to the root of the site. }, }, `gatsby-plugin-offline`,←これ追記 ], }Add page metadata
ページにメタデータを追加します。
またまたプラグインをインストールUsing React Helmet and gatsby-plugin-react-helmet
以下コマンドを実行
npm install --save gatsby-plugin-react-helmet react-helmet
gatsby-config.jsを修正gatsby-config.jsmodule.exports = { siteMetadata: { title: `Pandas Eating Lots`, description: `A simple description about pandas eating lots...`,←これ追記 author: `gatsbyjs`,←これ追記 }, plugins: [ { resolve: `gatsby-source-filesystem`, options: { name: `src`, path: `${__dirname}/src/`, }, }, `gatsby-plugin-emotion`, `gatsby-transformer-remark`, { resolve: `gatsby-plugin-typography`, options: { pathToConfigModule: `src/utils/typography`, }, }, { resolve: `gatsby-plugin-manifest`, options: { name: `GatsbyJS`, short_name: `GatsbyJS`, start_url: `/`, background_color: `#6b37bf`, theme_color: `#6b37bf`, // Enables "Add to Homescreen" prompt and disables browser UI (including back button) // see https://developers.google.com/web/fundamentals/web-app-manifest/#display display: `standalone`, icon: `src/images/icon.png`, // This path is relative to the root of the site. }, }, `gatsby-plugin-offline`, `gatsby-plugin-react-helmet`,←これ追記 ], }src/components/seo.jsを追加し以下を記述
src/components/seo.jsimport React from "react" import PropTypes from "prop-types" import { Helmet } from "react-helmet" import { useStaticQuery, graphql } from "gatsby" function SEO({ description, lang, meta, title }) { const { site } = useStaticQuery( graphql` query { site { siteMetadata { title description author } } } ` ) const metaDescription = description || site.siteMetadata.description return ( <Helmet htmlAttributes={{ lang, }} title={title} titleTemplate={`%s | ${site.siteMetadata.title}`} meta={[ { name: `description`, content: metaDescription, }, { property: `og:title`, content: title, }, { property: `og:description`, content: metaDescription, }, { property: `og:type`, content: `website`, }, { name: `twitter:card`, content: `summary`, }, { name: `twitter:creator`, content: site.siteMetadata.author, }, { name: `twitter:title`, content: title, }, { name: `twitter:description`, content: metaDescription, }, ].concat(meta)} /> ) } SEO.defaultProps = { lang: `en`, meta: [], description: ``, } SEO.propTypes = { description: PropTypes.string, lang: PropTypes.string, meta: PropTypes.arrayOf(PropTypes.object), title: PropTypes.string.isRequired, } export default SEOsrc/templates/blog-post.jsを修正
src/templates/blog-post.jsimport React from "react" import { graphql } from "gatsby" import Layout from "../components/layout" import SEO from "../components/seo"←これ追記 export default function BlogPost({ data }) { const post = data.markdownRemark return ( <Layout> <SEO title={post.frontmatter.title} description={post.excerpt} />←これ追記 <div> <h1>{post.frontmatter.title}</h1> <div dangerouslySetInnerHTML={{ __html: post.html }} /> </div> </Layout> ) } export const query = graphql` query($slug: String!) { markdownRemark(fields: { slug: { eq: $slug } }) { html frontmatter { title } excerpt←これ追記 } } `これでもう一回Lighthouseを実行か
もう一回ビルドして動かすか。。。
なんかあんまり向上はしてねぇな。
まぁでもちょっと向上したからいいか。HTTPSにするとか今のところ無理だし!Keep making it better
Lighthouseを見ながら良いもの作っていってくださいねー!
だって。
基本は以上であとはまだチュートリアルがあるから見て作っていってね!
だって。う~ん。。。どうしよう。
とりあえず今回はここまで。ありがとうございました。
- 投稿日:2020-09-06T23:21:59+09:00
流行りのReactでFlexboxを利用して、検索フォーム内にアイコンを入れてみる。
はじめに
ソースコード https://github.com/TokyoProgramming/search__bar
React + Flexbox + Material-UIを利用して、よくあるこのような検索フォームデザインを作りたいと思います。
このQiitaもそうですが、注意してみてみるとこのデザインの検索フォームって、本当に多いですよね。1.React構造
Componentsディレクトリー内にSearch.jsファイルとSearch.cssファイルを作りました。
2.Search.jsファイル
Search.jsimport React from 'react'; import './Search.css'; import SearchIcon from '@material-ui/icons/Search'; function Search() { return ( <div className="search"> <div className='search__bar'> <SearchIcon /> <input className="search__bar__input" placeholder="Search" /> </div> </div> ) } export default Search;スタイリングしないと...
きっとこれでは、だれも検索してくれませんよね。(笑)では、cssファイルで徐々にスタイリングしていきます。
2.CSS スタイリング
いきなり結果を表示するのでなく、徐々に仕上げていきます。
(1)
Search.css.search{ display: flex; justify-content: center; } .search__bar{ display: flex; padding: 10px; border: 1px solid lightgray; border-radius: 999px; background-color: #f0f2f5; }まだ、醜いですね。。。
border-radius: 999pxは、このようなフォームを作るときのテンプレートですので、覚えておくと便利です。
border-radius: 1pxから、遊んでみると徐々に角が完璧な丸になっていく様子がよくわかります。(2)inputのボーダーをなくす。
さて、こちらの付け加えると、
Search.css.search__bar > input { border: none !important; background-color: #f0f2f5; }ちょっといい感じになりましたが、選択すると、右側のように
borderがくっきり出てきてしまいます。
(3)選択した際の
borderを消すSearch.css.search__bar > input:focus { outline-width: 0; }
input::focusでoutline-width: 0;設定することにより、 以下のように検索フォームを作成することができます。(4)placeholderとMaterial-iconsを指定する方法。
Search.css.search__bar > input::placeholder{ text-align: left; font-size: 15px; } .search__bar > .MuiSvgIcon-root { margin-right: 5px; }
input::placeholderで、placehoderの文字をSearchを指定して、スタイリングできます。
.MuiSvgIcon-rootは、Material-iconsのアイコンを指定できます。そして完成した、検索フォームがこちらです。
ちなみに背景色はFacebookの検索フォームと同じ色を使用しています。Search.css.search{ display: flex; justify-content: center; } .search__bar{ display: flex; padding: 10px; border: 1px solid lightgray; border-radius: 999px; background-color: #f0f2f5; } .search__bar > input { border: none !important; background-color: #f0f2f5; } .search__bar > input:focus { outline-width: 0; } .search__bar > input::placeholder{ text-align: left; font-size: 15px; } .search__bar > .MuiSvgIcon-root { margin-right: 5px; }お疲れさまでした。
- 投稿日:2020-09-06T20:08:37+09:00
ReduxとReact (Native)Hooksとの共存
Reactは「自由にカスタマイズできる」かつ「日進月歩」というのがメリットの反面、
ネット上には新旧の情報が入り乱れていてベストプラクティスが見つけづらいというのがデメリットになっているのではないでしょうか。その最たる例が
Hooks、Reduxをどういった構成にするかだと思います。
巷ではHooksはReduxの機能を補えるから、Reduxはもはや必要ないといった意見もでているようです。本当に不必要なのでしょうか?
初学者の方にもなるべくわかりやすいように
Reduxの概要・非同期処理について根本的なところから改めて振り返って整理したのち、Hooks(useReducer・useContext)との設計概念・機能比較を行いました。
「もうそんなこと知ってるよ」という方は、Redux概要の箇所は飛ばしていただけるとありがたいです。【Redux概要】 MVC・Flux・Reduxアーキテクチャ比較
Redux自体MVC → Flux → Reduxと既存のアーキテクチャの欠点を補うために生まれてきたアーキテクチャのため、遠回りなように思えますが、理解を深めるためにはこれらの差異について知ることが重要になってきます。MVCアーキテクチャ
アプリケーション開発の学習を始めると一番最初に覚えるアーキテクチャではないでしょうか? 初心者にもシンプルでわかりやすいです。私自身もプラグラミングスクールで
Ruby on Railsを使い開発を行っていたのでお馴染みのアーキテクチャです。単純なアーキテクチャであればシンプルに以下のようなフローになります。
従来型のフルサーバサイドレンダリングアプリケーションであれば、Model・View間のデータフローは単方向ですが、ReactなどのSPAアプリ開発においては、Model・View双方向にデータの受け渡しがあるところがポイントになります。
複雑なアーキテクチャになればなるほど、Model・View間のフローのパターンは指数関数的に増大してしまいメンテナンスが難しくなってしまいます。(以下図は概念を理解するために若干大げさな表記になっています。)
Fluxアーキテクチャ
Fluxアーキテクチャは上記MVCモデルの煩雑なデータフローを解消するために生まれました。
つまり、シンプルなアプリであればMVCモデルでもなんら問題はないということになりますね。
MVCにたとえるならDispatcherがControllerでStoreがModelといったところです。
ただし以下の点で異なっています。
Storeは一枚岩で複数のオブジェクトの状態を管理している(Modelのようにオブジェクトごとに個別に存在しない)Storeの変更は、Actionから派生するDispatcherによってのみ行われる。Viewから直接操作することはない。Storeの値が変更されると、直ちにViewに反映される。上記のようなデータフローにより、
Store・Viewはどれだけアプリケーションのボリュームが大きくなっても、単方向のデータフローに集約することができるようになります。つまり「どこのModelがどこのViewを更新して〜」、「どこのViewがどこのModelを更新して〜」、などを考えなくても単純に「Viewから『何をしたいか』のActionを発行し、その変更をStoreが受けつけ、Storeの状態が即時にViewに反映される」といったシンプルなデータフローになります。Reduxアーキテクチャ
ReduxはFluxアーキテクチャに更に制限を追加して、アプリケーションの状態管理をスムーズにするフレームワークです。
Fluxのデータフローに加えて以下のような制限が加わります。
Storeはアプリケーションに1つのみ存在する。つまり状態の更新先・参照先は常に同じStore(シングルトン)Storeの状態(State)を変更できるのは、ReducerのみReducerはStore内のStateを、Viewより発行された「一意のActionType」により振り分け、新しいStateに更新するReducerは常に同じ結果を返す(非同期処理やランダムな計算を行ってはいけない)【Redux概要】 Reduxで非同期処理を扱う
本格的なアプリケーション開発において、「非同期処理(外部APIとの連携)」を行わないケースは少ないと思います。
そのため非同期処理の実装は重要になってくるのですが、ReduxにおいてもMiddlewareを導入することで、非同期処理を実現することができます。
今回はMiddlewareの中でも人気のRedux-thunkとRedux-sagaについて、特徴を整理してみます。Redux Thunk
非常にシンプルな
Middlewareで、Thunkを導入するとActionCreater内部のロジックで非同期処理が実行できるようになります。
React同様、いい意味でも、悪い意味でもコード設計は自由なため、構成をうまく考えないとファットActionになってしまうリスクもあります。
Redux Saga
Redux-sagaについては、非同期処理はSagaに完全に切り分けてReduxとは別で処理を回すという考え方なので、ファイル数は多くなりますがredux-thunkよりこちらのほうが各機能の役割がはっきりして処理の流れが掴みやすくなります。
ただし、Sagaの内部処理ではあまり他では見かけないジェネレータ関数を利用しているため、Thunkよりも学習コストがかかるというデメリットがあります。Hooksが導入されてできるようになったこと
useReducer
Reactのみでも、「Actionを発行 →Reducer内部でActionTypeによりStateの値を更新 →Viewの表示が変更」といったReduxのような外部フレームワークでしか実現できなかったFluxベースのアーキテクチャが組めるようになりました。
ただし、Storeの概念はなく、状態の管理は各オブジェクトごとに分散して管理する必要があります。(Reduxっぽく、すべてのオブジェクトのReducerをCombineできなくはないようですが、それならreduxを使ったほうが効率的です。)useContext
コンポーネントのトップレベルで定義した
stateをその配下のコンポーネント(子・孫・ひ孫・・・)から、propsでのバケツリレーなしで値を取得することができます。useReducer と useContextを組み合わせてReduxフローを実現
useReducerとuseContextを組み合わせることにより、Reduxのフローを再現してみました。
下記コードは親と子の単純な例ですが、もっとコンポーネントの階層が深くなった場合でもRootコンポーネントで定義したStateにアクセス・ActionをDispatchすることが可能になり、Reduxよりも簡単かつ軽量にデータフローを実現することができます。App.tsx(root)import React, { FC, useReducer, createContext, } from 'react'; import AppScreen from './AppScreen'; const initialState = { count: 0 }; interface StateProps { count: number; } interface ActionProps { type: string; } const reducer = (state: StateProps, action: ActionProps) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } }; interface StoreContextProps { state: StateProps; dispatch: ({ type }: ActionProps) => void; } export const StoreContext = createContext({} as StoreContextProps); const App: FC = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <StoreContext.Provider value={{ state, dispatch }}> <AppScreen /> </StoreContext.Provider> ); }; export default App;AppScreen.tsximport React, { FC, useContext } from 'react'; import { StoreContext } from './App'; const AppScreen: FC = () => { const { state, dispatch } = useContext(StoreContext); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }; export default AppScreen;useReducer で非同期処理をする
useReducerを使ったデータフローではMiddlewareの導入を前提としていないようですが、Middlewareなしでも非同期処理は行うことができます。
色々と調べていたのですが、Reduxのようにアーキテクチャのベストプラクティスのようなものは定まっていないように感じました。結局ReduxのようなアーキテクチャにするならReduxを最初から導入すればいいし、違ったやり方をするのであればグローバルの状態管理項目が増えた場合にカオスになりそうな気がするし、Reactの放任主義はいつものことですが、ここも悩ましいところですね...。App.tsx(root)import React, { FC, useReducer, createContext, } from 'react'; import axios, { AxiosError, AxiosResponse } from 'axios'; import AppScreen from './AppScreen'; axios.defaults.withCredentials = true; axios.defaults.baseURL = 'http://xxxxxxxxxxxxxxxx.com/api'; interface StateProps { result: AxiosResponse; isLoading: boolean; error?: AxiosError | null; } interface ActionProps { type: string; payload?: any; error?: boolean; } const initialState: StateProps = { result: {} as AxiosResponse, isLoading: false, }; const reducer = (state: StateProps = initialState, action: ActionProps) => { switch (action.type) { case 'start': return { ...state, isLoading: true, }; case 'succeed': return { ...state, result: action.payload?.result, isLoading: false, }; case 'error': return { ...state, result: action.payload?.error, isLoading: false, }; default: throw new Error(); } }; interface StoreContextProps { state: StateProps; dispatch: ({ type }: ActionProps) => void; } export const StoreContext = createContext({} as StoreContextProps); const App: FC = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <StoreContext.Provider value={{ state, dispatch }}> <AppScreen /> </StoreContext.Provider> ); }; export default App;AppScreen.tsximport React, { FC, useContext } from 'react'; import axios from 'axios'; import { StoreContext } from './App'; const AppScreen: FC = () => { const { state, dispatch } = useContext(StoreContext); const clickHandler = async () => { dispatch({ type: 'start' }); try { const result = await axios.get('/users'); if (result.status !== 200) { throw new Error('The request has failed'); } dispatch({ type: 'succeed', payload: { result }, error: true }); } catch (error) { dispatch({ type: 'error', payload: { error }, error: true }); } } return ( <> Result: {JSON.stringify(state.result)} <button onClick={clickHandler}>データ取得</button> </> ); }; export default AppScreen;Hooksの機能でもReduxと同じようなことはできる。それでもReduxを使うメリット
Reduxの設計原則は素晴らしい
ルールがありすぎるのは時には不自由に感じますが、コードが複雑になればなるほどその恩恵を受けることができます。その点、
Reduxにはアーキテクチャに関するドキュメントがしっかりとまとまっており、この原則にある程度従っておけばそこまで的はずれな構成にならないというのは大きいです。
Hooksを使うから、Reduxの原則よりも素晴らしいアーキテクチャが組めるようになるというなら話は別ですが、Hooksを使っても結局Reduxのアーキテクチャを模倣してコードを組むのであれば最初からReduxを使っておけば良いという見解です、
逆に原則やルールが必要がないほど、シンプルなアプリケーションであるならばReduxを導入する意味はあまりないので、Hooks(useReducer・useContext)を使ってサクッと作るのもありかと思います。異なるコンポーネント間でのやりとりが容易になる
原則部分とかぶる部分もありますが、グローバルの状態を
Storeが一手に引き受け、かつアプリ上のすべてのReducerをコンバインしてどのコンポーネントからでも「状態の取得」、「アクションを通しての状態の変更」ができるのは便利です。
useReducerにはStoreの概念がそもそもありません。Hooksを使ってもRootコンポーネントでContextを定義すればできないことはないですが、もともとそういったアーキテクチャ用のHookではないので開発者独自の記述方法になって後々メンテナンスに苦労するということになりかねません。
Storeの概念を使いたいなら、おとなしくReduxを導入して、Reduxで管理するまでもないところのみuseReducerで処理するというのが現状のベストではないでしょうか。
Middlewareにより非同期処理と状態管理を分離できる非同期通信を行うための
Middlewareを自由に選べるのもメリットの一つです。Redux-thunkは自由度が高く、技術者の裁量に任されてしまいますが、Redux-sagaを使用した場合、外部APIとの非同期処理はsagaにかき分けることを強制されるので、状態管理と非同期処理を明確に分離することができます。これによりテストが書きやすく、保守性の高いプロダクトを保持することができます。
Middlewareによりアクション発行履歴(トランザクション)を管理し、巻き戻しできるこれもまた
Middlewareの話になりますが、Redux devtoolsを導入することにより、グローバルStateの可視化、アクション発行履歴の管理、タイムトラベルデバッギングができるようになり、開発効率を高めることができます。
Redux with Hooks
Redux自体もv7.1.0以降Hooksの恩恵を受け随分とシンプルにかけるようになりました。
また、初期導入時にはRedux Tool Kitでコマンド一発でRedux & Hooks & Typescriptの開発環境が整うようです。
もっと早く欲しかった..。useSelector & useDispatch
mapStateToPropsとmapDispatchToPropsをコンポーネントにconnectせずにReduxのStoreにアクセスできるようになったので、記述量が減るだけでなくかなりフレキシブルにReduxを扱うことができます。従来までの書き方import React, { FC, useEffect } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { fetchAllItems, deleteItem } from '../actions/item'; const mapStateToProps = (state: AllState) => ({ items: state.item.items, isLoading: state.item.isLoading, }); const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( { fetchItemsStart: () => fetchAllItems.start(), deleteItemStart: (itemId: ItemId) => deleteItem.start(itemId), }, dispatch, ); interface HomeScreenProps extends ScreenNavigationProp { items: ItemModel[]; isLoading: boolean; fetchItemsStart: () => void; deleteItemStart: (itemId: ItemId) => { payload: { id: number } }; } const HomeScreen: FC<HomeScreenProps> = ({ items, isLoading, fetchItemsStart, deleteItemStart, navigation, route, }) => { useEffect(() => { (async () => { await fetchItemsStart(); })(); }, []); useEffect(() => { const unsubscribe = navigation.addListener('focus', () => { (async () => { await fetchItemsStart(); })(); }); return unsubscribe; }, [navigation]); const deleteItem = async (itemId: number) => { alert(`deleted ${itemId}`); await deleteItemStart({ id: itemId }); }; return ( ・・・ ); }; export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen);HooksAPIを使った書き方import React, { FC, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { fetchAllItems, deleteItem } from '../actions/item'; const HomeScreen: FC<ScreenNavigationProp> = ({ navigation, route }) => { const dispatch = useDispatch(); const itemState = useSelector((state: AllState) => state.item); useEffect(() => { (async () => { await dispatch(fetchAllItems.start()); })(); }, []); useEffect(() => { const unsubscribe = navigation.addListener('focus', () => { (async () => { await dispatch(fetchAllItems.start()); })(); }); return unsubscribe; }, [navigation]); const deleteButtonHandler = async (itemId: number) => { alert(`deleted ${itemId}`); await dispatch(deleteItem.start({ id: itemId })); }; return ( ・・・ ); }; export default HomeScreen;Redux Tool Kit
私自身使ったことがないため導入方法リンクのみ共有しておきます。
まとめ
- Hooksの登場により、Reduxは唯一の選択肢ではなくなった
- 複雑かつ大規模なプロダクトにはReduxの設計原則が最も力を発揮する
- Hooks useReducer + useContextはReduxと競合関係にあるわけではなく共存・使い分けできる
- Reduxの設計原則が必要ないほどシンプルなプロダクトならばHooksのみでグローバルの状態も管理できる
- Reduxの導入ハードルは年々下がってきている
- 投稿日:2020-09-06T17:52:17+09:00
React + TypeScriptプロジェクトを手動で作成する(webpack)
はじめに
この記事では、React+TypeScriptのプロジェクトを
create-react-appを使わずに一から手動で作成する方法の一例を紹介します。筆者はフロントエンド開発に興味を持ち始め、これからReact+TypeScriptを学習していくうえでwebpackを利用したプロジェクトを一から作成する手順も知っておきたいと思い、自身で調べて構築した環境を備忘録も兼ねて記事にします。
注意点として、この記事では各種の設定内容に関する詳細な解説はしません。
また、記事の内容は記事執筆時点で動作が確認できたものとなります。前提
- macOS Catalina 10.15.4
- Node.jsがインストール済みで、
nodeコマンドとnpmコマンドが使えるnode -v v12.16.1 npm -v 6.14.8作っていく開発環境について
今回作っていく開発環境の特徴は大まかに次の通りです。
- React + TypeScriptのプロジェクト(
create-react-appを使わずに)webpack-dev-serverで開発用サーバーを立ち上げれる- 開発用サーバー起動中は、ソースの変更を検知してブラウザ画面が自動で反映される
- TypeScriptの変更は
react-hot-loaderでStateを維持した反映- CSSの変更も自動で反映
- webpackの設定ファイル
(webpack.config)をTypeScript化する (任意)また、動作確認ができるように簡単なサンプルアプリも用意します。
GitHubにも上げているので参考にしていただければと思います。手順
プロジェクトのフォルダを作成する
任意の場所でReactプロジェクトのフォルダを作成します。
今回はreact-sample-appという名前にしました。cd PATH/TO/WORKING_DIR mkdir react-sample-app cd react-sample-apppackage.jsonを作成する
package.jsonを作成します。
npm init -y作成後、次のように書き換えます。
package.json{ "name": "react-sample-app", "version": "1.0.0", "description": "", "main": "webpack.config.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --mode=production", "start": "webpack-dev-server --hot --mode=development" }, "keywords": [], "author": "", "license": "ISC" }必要なパッケージをインストールする
必要なパッケージをnpmでインストールします。
- インストールするパッケージ
- webpack、webpack-cli
- typescript、ts-loader、source-map-loader
- react、react-dom、@types/react、@types/react-dom
- webpack-dev-server
- fork-ts-checker-webpack-plugin
- html-webpack-plugin
- react-hot-loader、@hot-loader/react-dom
- css-loader、mini-css-extract-plugin、@types/mini-css-extract-plugin
- optimize-css-assets-webpack-plugin、@types/optimize-css-assets-webpack-plugin
npm install --save-dev webpack webpack-cli typescript ts-loader source-map-loader react react-dom @types/react @types/react-dom webpack-dev-server fork-ts-checker-webpack-plugin html-webpack-plugin react-hot-loader @hot-loader/react-dom css-loader mini-css-extract-plugin @types/mini-css-extract-plugin optimize-css-assets-webpack-plugin @types/optimize-css-assets-webpack-plugintsconfig.jsonを作成する
tsconfig.jsonを作成します。
npx tsc --init作成後、次のように書き換えます。
tsconfig.json{ "compilerOptions": { "target": "es5", "module": "commonjs", "outDir": "./dist/", "strict": true, "esModuleInterop": true, "sourceMap": true, "forceConsistentCasingInFileNames": true, "allowJs": true, "checkJs": true, "jsx": "react", "lib": [ "ES2015", "DOM" ] } }webpack.config.jsを作成する
webpack.config.jsを作成します。
touch webpack.config.js作成後、次のように書き換えます。
webpack.config.jsconst path = require("path"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin") module.exports = { mode: "development", entry: ["react-hot-loader/patch", path.resolve(__dirname, "./src/index.tsx")], output: { path: path.resolve(__dirname, "dist/"), filename: "bundle.js" }, devtool: "source-map", resolve: { modules: ["node_modules"], alias: { "react-dom": "@hot-loader/react-dom" }, extensions: [".ts", ".tsx", ".js", ".jsx", ".json"] }, module: { rules: [ { test: /\.(j|t)s(x)?$/, exclude: /node_modules/, use: { loader: "ts-loader", } }, { enforce: "pre", test: /\.ts(x?)$/, loader: "source-map-loader" }, { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { hmr: true, reloadAll: true, } }, { loader: "css-loader", } ], } ] }, devServer: { host: "localhost", contentBase: path.resolve(__dirname, "dist/"), port: 8080, inline: true, open: true, hot: true, }, optimization: { minimizer: [new OptimizeCSSAssetsPlugin({})] }, plugins: [ new ForkTsCheckerWebpackPlugin(), new webpack.NamedModulesPlugin(), new HtmlWebpackPlugin({ template: "./src/index.html" }), new MiniCssExtractPlugin({ filename: "./css/style.css" }), ] };サンプルアプリを用意する
TypeScriptで書いたReactのアプリを用意していきます。
ここではビンゴゲームのようなサンプルアプリを作っていこうと思います。
(といっても作りは雑で、あくまで動作確認ができれば何でもよいかと思います)
サンプルはGitHubのsrcフォルダも参考にしてください。アプリの用意ができた時点でのフォルダ階層は次のようになります。
この階層図も参考にしながらhtml、css、tsxファイルを作成していってください。. ├── package-lock.json ├── package.json ├── src │ ├── components │ │ ├── Bingo.tsx │ │ ├── Card.tsx │ │ ├── Enums.tsx │ │ ├── Machine.tsx │ │ └── Square.tsx │ ├── css │ │ └── style.css │ ├── index.html │ └── index.tsx ├── tsconfig.json └── webpack.config.jsでは、まずフォルダを作成していきます。
mkdir src mkdir src/components src/css
src/index.htmlを作成します。src/index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <title>Sample App</title> </head> <body> <div id="app"></div> </body> </html>
src/css/style.cssを作成します。src/css/style.cssbody { background-color: #fff; } ul { width: 400px; margin: 20px auto; overflow: hidden; } li { text-align: center; line-height: 60px; display: inline-block; } .card ul li { width: 20%; } .square { box-sizing: border-box; background-color: #eee; border: solid 1px black; position: relative; font-size: 1.6rem; } .hit { background-color: #4caf50; } .machine { margin-top: 30px; text-align: center; } .btn { display: inline-block; color: #fefefe; background-color: #f06292; width: 200px; height: 60px; font-weight: bold; border: none; text-align: center; font-size: large; } .appearances { text-align: left; }tsxファイルを作成していきます。
src/index.tsximport * as React from "react"; import * as ReactDOM from "react-dom"; import { hot } from "react-hot-loader/root"; import "./css/style.css"; import Bingo from "./components/Bingo"; const App: React.FC = () => { return ( <div> <Bingo /> </div> ) } export default hot(App); //export default App; // react-hot-loaderを無効にする場合はこちらを使う ReactDOM.render(<App />, document.getElementById("app"));src/components/Bingo.tsximport React from "react"; import Machine from "./Machine"; import Card from "./Card"; const Bingo: React.FC = () => { const [appearanceNumbers, setAppearanceNumbers] = React.useState<number[]>([]); return ( <div> <Card appearanceNumbers={appearanceNumbers} /> <Machine appearanceNumbers={appearanceNumbers} setAppearanceNumbers={setAppearanceNumbers} /> </div> ) } export default Bingo;src/components/Card.tsximport React from "react"; import Square from "./Square"; import BingoCard from "./Enums"; type Props = { appearanceNumbers: number[] } const squareValues = Array.from(new Array(100)) .map((_, index) => index) .sort(() => Math.random() - 0.5) .slice(0, 25); squareValues[12] = BingoCard.Free; const Card: React.FC<Props> = ({ appearanceNumbers }) => { return ( <div className={"card"}> <ul> { squareValues.map(squareValue => ( <li> <Square value={squareValue} appearanceNumbers={appearanceNumbers} /> </li> )) } </ul> </div> ) } export default Card;src/components/Enums.tsxenum BingoCard { Free = -1 } export default BingoCard;src/components/Machine.tsximport React from "react"; type Props = { appearanceNumbers: number[] setAppearanceNumbers: React.Dispatch<React.SetStateAction<number[]>> } const remainNumbers = Array.from(new Array(100)) .map((_, index) => index) .sort(() => Math.random() - 0.5); const Machine: React.FC<Props> = ({ appearanceNumbers, setAppearanceNumbers }) => { const handleClick = () => { const appearanceNumber = remainNumbers.shift(); if (appearanceNumber === undefined) { return; } setAppearanceNumbers([appearanceNumber, ...appearanceNumbers]); } return ( <div className={"machine"}> <button className={"btn"} onClick={handleClick} >GO </button> <h2>{remainNumbers.length > 0 ? "ボタンを押してください" : "ビンゴは終了しました"}</h2> <div className={"appearances"}>{appearanceNumbers.join(", ")}</div> </div> ) } export default Machine;src/components/Square.tsximport React from "react"; import BingoCard from "./Enums"; type Props = { value: number, appearanceNumbers: number[] } const Square: React.FC<Props> = ({ value, appearanceNumbers }) => { return ( <div className={appearanceNumbers.includes(value) || value == BingoCard.Free ? "square hit" : "square"} > {value == BingoCard.Free ? "Free" : value} </div> ) } export default Square;これでサンプルアプリを用意できました。
webpack-dev-serverで開発用webサーバーを起動する
開発用webサーバーを起動します。
npm startブラウザが開き、ビンゴゲームのような画面が表示されるかと思います。
ボタンを押すと番号が排出され、当たればマスも埋まっていくと思います。
この状態でcssを変更してみましょう。
src/css/style.css/* (一部抜粋) */ body { background-color: #333; }次に、TypeScriptのコードを変更してみましょう。
src/components/Machine.tsxのh2タグの表示内容を適当に変更してみます。src/components/Machine.tsx// (省略) const Machine: React.FC<Props> = ({ appearanceNumbers, setAppearanceNumbers }) => { // (省略) return ( <div className={"machine"}> <button className={"btn"} onClick={handleClick} >GO </button> <h2>{remainNumbers.length > 0 ? "GOボタンを押してね" : "ビンゴは終了しました"}</h2> <div className={"appearances"}>{appearanceNumbers.join(", ")}</div> </div> ) } export default Machine;TypeScriptの変更も検知して自動で反映されました。
このとき、react-hot-loaderでStateを維持した反映となるため、ビンゴカードの埋まったマスはそのままとなっています。react-hot-loaderを使わない場合
せっかくなのでreact-hot-loaderを使わない場合の挙動も見てみましょう。
src/index.tsxを次のように変更して、開発用webサーバーを立ち上げます。
(hotを外しているだけです。また、cssやMachine.tsxは元に戻しました。)src/index.tsximport * as React from "react"; import * as ReactDOM from "react-dom"; import { hot } from "react-hot-loader/root"; import "./css/style.css"; import Bingo from "./components/Bingo"; const App: React.FC = () => { return ( <div> <Bingo /> </div> ) } //export default hot(App); export default App; // react-hot-loaderを無効にする場合はこちらを使う ReactDOM.render(<App />, document.getElementById("app"));npm start適当にマスを埋めておきます。
1ビンゴもらいました。
この状態で、先ほどと同様にsrc/components/Machine.tsxを変更してみます。src/components/Machine.tsx// (省略) const Machine: React.FC<Props> = ({ appearanceNumbers, setAppearanceNumbers }) => { // (省略) return ( <div className={"machine"}> <button className={"btn"} onClick={handleClick} >GO </button> <h2>{remainNumbers.length > 0 ? "GOボタンを押すといいよ!" : "ビンゴは終了しました"}</h2> <div className={"appearances"}>{appearanceNumbers.join(", ")}</div> </div> ) } export default Machine;変更を保存すると、
画面が「GOボタンを押すといいよ!」に変わりましたが、ブラウザのリロードによってビンゴカードは初期状態に戻されてしまいました。
react-hot-loaderを使わなかった場合はStateが維持されないことが確認できたと思います。
ビルドする
webpackでビルドしていきます。
npm run build
distフォルダが作成され、その配下に以下のファイルが出力されたかと思います。./dist/ ├── bundle.js ├── bundle.js.map ├── css │ └── style.css └── index.htmlこのとき、出力されたファイルは圧縮されていますが、圧縮したくない場合は
developmentモードでビルドすればよいです。npx webpack --mode=development以上が、React+TypeScriptのプロジェクト作成と開発用サーバーやビルドの動作確認でした。
以降はwebpackの設定をTypeScriptに書き換えます。ご興味ある方だけ次に進んでください。webpackの設定ファイルをTypeScriptにする (任意)
webpackの設定ファイル(
webpack.config.js)をTypeScriptで書き換えます。
書き換え作業の差分も参考にしてください。まずはTypeScriptで書いたwebpackの設定を読み込むために、
ts-nodeをインストールします。npm install --save-dev ts-node
webpack.config.jsをwebpack.config.tsにリネームし、内容を次のように変更します。webpack.config.tsimport * as path from "path"; import * as webpack from "webpack"; import HtmlWebpackPlugin from "html-webpack-plugin"; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import MiniCssExtractPlugin from "mini-css-extract-plugin"; import OptimizeCSSAssetsPlugin from "optimize-css-assets-webpack-plugin"; const config: webpack.ConfigurationFactory = (env, argv) => { const isDevelopmentMode = argv.mode === "development"; return { mode: "development", entry: ["react-hot-loader/patch", path.resolve(__dirname, "./src/index.tsx")], output: { path: path.resolve(__dirname, "dist/"), filename: "bundle.js" }, devtool: "source-map", resolve: { modules: ["node_modules"], alias: { "react-dom": "@hot-loader/react-dom" }, extensions: [".ts", ".tsx", ".js", ".jsx", ".json"] }, module: { rules: [ { test: /\.(j|t)s(x)?$/, exclude: /node_modules/, use: { loader: "ts-loader", } }, { enforce: "pre", test: /\.ts(x?)$/, loader: "source-map-loader" }, { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { hmr: isDevelopmentMode, reloadAll: true, } }, { loader: "css-loader", } ], } ] }, devServer: { host: "localhost", contentBase: path.resolve(__dirname, "dist/"), port: 8080, inline: true, open: true, hot: true, }, optimization: { minimizer: [new OptimizeCSSAssetsPlugin({})] }, plugins: [ new ForkTsCheckerWebpackPlugin(), new webpack.NamedModulesPlugin(), new HtmlWebpackPlugin({ template: "./src/index.html" }), new MiniCssExtractPlugin({ filename: "./css/style.css" }), ] }; } export default config;最後に、
package.jsonの"main"キーの値を"webpack.config.ts"に変更します。package.json{ // (省略) "main": "webpack.config.ts", // (省略) }これで、webpackの設定をTypeScriptに直すことができました。
先ほどと同様に、開発用サーバーの起動やビルドの実行ができることを確認します。
- 開発用webサーバーの起動
npm start
- ビルドの実行
npm run build参考
https://qiita.com/humi/items/49a7472e9a10558ea5c0
https://qiita.com/humi/items/72485614151fe564dceb
https://qiita.com/SoraKumo/items/5d92b15d06778458f5e1
https://numb86-tech.hatenablog.com/entry/2018/10/24/221130
https://webpack.js.org/plugins/mini-css-extract-plugin/
https://qiita.com/sathoshi-metal/items/b9ce118cca9b75b2e0a9
- 投稿日:2020-09-06T17:28:59+09:00
Reactで書いたWEBサイトをGithubにコミットして、netlifyにデプロイした。
- 投稿日:2020-09-06T17:25:48+09:00
VSCodeのExtension「Jest」の設定ファイルjest.configのパス
Githubからクローンしてきたソフトウェアで、yarn testとかは動くけど、VSCode上のJest拡張機能のテストが失敗してしまいました。このような場合は、package.jsonのscriptsにJestの設定ファイルのパスが指定されていないか確認してみましょう。
VSCodeのJest拡張機能で見に行くconfigファイルは
jest.config.jsなので、package.jsonでこれ以外のパスを設定していたりするとうまく動きません。https://github.com/jest-community/vscode-jest
we find Jest configuration files in the workspace: jest.config.js or jest.json
私の場合は、VSCode上のJestだけ以下のエラーが出ている状態でしたが、configのパスを修正したところエラーが解消しました。
Support for the experimental syntax 'jsx' isn't currently enabled
- 投稿日:2020-09-06T17:16:01+09:00
型チェックツールFlowはMaterial-UIのバージョンアップに対応していない?
型チェックツールのflowを走らせたら、@material-ui/coreのimportで失敗しました。
$ yarn run flow $ flow > Error > Cannot resolve module @material-ui/core. [cannot-resolve-module]Material-UI公式にflowの対応状況について説明が書かれていて、リンク先のプロジェクトで型定義情報がメンテナンスされているとあります。
https://material-ui.com/guides/flow/flow-typed
flow-typed is a repository of third-party library interface definitions for use with Flow. The community is maintaining the definitions under this project.
しかし、リンク先を見ると
core_v1.x.x/flow_v0.58.x-v0.88.xと書かれています。Material-UIのバージョンアップに追従していないようです。https://github.com/flow-typed/flow-typed/tree/master/definitions/npm/%40material-ui
少しググってみたら以下のページを発見して一応試してみましたが、@types/material-uiはTypeScriptの型情報なのでエラー解消しませんでした。
https://stackoverflow.com/questions/58941816/how-to-install-flow-types-for-material-ui-corev4-x-x
- 投稿日:2020-09-06T17:00:04+09:00
WindowsでReactの開発環境を整える
はじめに
WindowsでReactの開発環境を作ってみたところ、いくつかエラーが出たのでその手順と解決法を残します。
使用した環境は以下の通り
- Windows10
- Node.js v14.8.0
- NPM v6.14.7
- Windows パワーシェル(VScode経由)
手順
- Node.jsのインストール
- create-react-appのインストール
- プロジェクトを作成してHello worldしてみる
おまけ
- ESlintを設定する
- ディレクトリ構成のかんたんな説明
1.Node.jsのインストール
まずはNode.js公式サイトからダウンロードし、Node.jsをインストールします。
ターミナルで以下のコマンドを打ってVersion情報が表示されればインストール成功しています。Node.jsのVersion情報を確認するnode -vNode.jsがインストールされていればnpmも使うことが可能になります。
2.create-react-appのインストール
create-react-appをグローバルインストールします。
create-react-appをインストールするnpm install -g create-react-appここまでで準備は完了です。
3.プロジェクトを作成してHello worldしてみる
プロジェクトを作成したいディレクトリに移動し、プロジェクトを作成していきます。
多くのドキュメントではプロジェクトを作成するのに以下のコマンドを見かけると思います。Reactのプロジェクトを作成するcreate-react-app <プロジェクト名>Windowsでは、インストールしただけでは上記のコマンドにパスが通っておらず使う事ができないことがあるようです。
解決方法は以下
1. パスを通す
2. npxコマンドを使う今回はnpxコマンドを使う方法で作成していきます。
(以下サンプルのプロジェクト名をhelloworldとして作成します。)Reactのプロジェクトを作成するnpx create-react-app helloworldこれで、カレントディレクトリにhelloworldというディレクトリが作られ、プロジェクトの作成が完了します。
パスの通し方については現在調査中です。
プロジェクトが作成できたら、早速動かして行きます。
helloworldディレクトリに移動し、仮想サーバー上でWebアプリの動作を確認します。Webアプリをプレビューするcd helloworld npm start以上のコマンドを打つと、規定のブラウザが起動してWebアプリの動作を確認することができます。
他のブラウザで見たい場合はlocalhost:3000にアクセスすることで見ることができます。
終わる場合はターミナルでCtrl + cを押して終了します。以降、srcディレクトリ内のApp.jsを編集することでWebアプリを作っていきます。
おまけ
ESlintを設定する
create-react-appを使用してプロジェクトを作成すると、ESlintがモジュールに追加された状態になっています。
なので、設定を行うだけでESlintが使用可能になります。ESlintの設定ファイルを作成するnpm eslint --init最後の質問、「Would you like to install them now with npm?」でYesにするとうまく動かなかったので、Noにしています。
ここで改めてインストールしてしまうと、競合するのかもしれません。Compiles successfully!と表示されたら初期化が完了します。
画像では設定ファイルをJavaScriptにしているので.eslintrc.jsというファイルが作成されます。
環境に合わせて設定を追加してください。ディレクトリ構成のかんたんな説明
以下は自分のための備忘録としての、ディレクトリ構成のかんたんな説明です。
<プロジェクト>
L ./node_modules (インストールされているモジュールが入ってる場所。)
L ./public (テンプレートのHTMLや画像が入っています。)
L ./src (アプリのソース等、ここのファイルを編集してアプリを作成していきます。)
L .eslintrc.js (ESlintの設定ファイル)
L .gitignore (Gitで追跡対象にしないファイルを設定します。)
L package.json (インストールされているモジュールについての設定ファイル)
L README.md (Read meファイル)
L yarn.lock (環境によっては無いこともある)この他、ビルド後のデータを格納するディレクトリがあります。
最後に
create-react-appを使ってReactの開発環境を作ると、すでに必要なモジュールやファイルが用意されているため素早く開発を始められます。
最初はCDNを使ってみようと思ったのですが手こずってしまい、結局こちらのほうが早くReactの勉強を始めることができました。
- 投稿日:2020-09-06T15:04:42+09:00
React+Redux+Firebaseで読書管理アプリを作るまで
はじめに
初めてアプリを作ったので、その工程を記録に残したいと思い記事を書いています。
個人開発やポートフォリオの作成に役立つ内容があれば幸いです。作ったもの
「Flipper」という、シンプルな読書管理アプリです。
今まで読んだページ数がアニメーションで大きく表示されることで、自分の積み上げを視覚的に実感できることを目指しました。私について
プログラミングを仕事にしているわけではなく、普段は英語を教えたり、翻訳の仕事をしています。
約3〜4年前に趣味でコードを軽く触ったことはありましたが実務は未経験で、1ヶ月半前にJavaScriptとReactの勉強を始めました。Flipper
今回制作した「Flipper」の紹介です。
コンセプト
- シンプルで美しい
- 積み上げを実感できる
この2つがFlipperのコンセプトです。
読書管理アプリは選択肢が豊富ですが、記録をグラフにする、SNSで共有できるなど、個人的に不要だと感じる機能が搭載されているものが多いです。そこで、自分が本当に必要だと思う機能のみで構成されたシンプルなアプリを作ろうと思いました。
工夫したのは今まで自分が読んだページ数がアニメーションで表示されるところです。繰り返しになりますが、自分の積み上げを視覚的に実感できることでユーザーの読書へのモチベーションを高めることにフォーカスしました。
趣味ではなく、自己研鑽として読書をしているユーザーがターゲットです。
使用技術
- React
- redux
- redux-thunk
- react-router
- connected-react-router
- react-countup
- Firebase
- Firebase Authentication
- Cloud Firestore
- Firebase Hosting
- Material-UI
- Google Books API
機能
開発準備
コードを書く前の段階で行ったことです。
デザイン
かなり雑ですが、ノートに手書きすることでイメージを固めました。
タスク化
ノートへ書き出した画面からどんな機能が必要なのかを考え、何をすべきかを明確にします。
タスク管理にはTrelloを使用しました。開発をスムーズに進めるため、コードを書き始める前にある程度準備しておくことはとても大切だと感じました。
コードを書く
事前にタスク化を行っていたことで、ここからは「書く」「分からないことを調べる」を繰り返すだけでした。
開発期間は約2日です。主な処理を書いたコードを記載します。
Google Books APIからデータを取得、データを保存
operations.jsexport const searchAndSetBook = (isbn) => { return async (dispatch, getState) => { // 本のデータを取得 fetch( `https://www.googleapis.com/books/v1/volumes?q=${isbn}&maxResults=1`, {} ) .then((response) => { if (response.status === 200) { return response.json(); } else { throw new Error("データの取得に失敗しました"); } }) .then((data) => { // 必要なデータのオブジェクトを作成 return { title: data.items[0].volumeInfo.title, pages: data.items[0].volumeInfo.pageCount, image: data.items[0].volumeInfo.imageLinks["thumbnail"], }; }) .then((book) => { const uid = getState().users.uid; const pages = getState().users.pages; const books = getState().users.books; const newPages = pages + book["pages"]; books.unshift(book); // ストアを更新 dispatch( newBookAction({ pages: newPages, books: books }) ); // データベースを更新 db.collection("users").doc(uid).update({ pages: newPages, books: books }); }) .then(() => { alert("登録が完了しました"); }) .catch((error) => { alert(`エラー!: ${error}`); }); }; };読んだ本のリストを表示
BookCardList.jsxconst BookCardList = () => { const selector = useSelector(state => state) const books = getBooks(selector) return ( <div className="section-wrapper"> {books.length > 0 && ( books.map((book,index) => ( <BookCard key={index} book={book} title={book.title} pages={book.pages}/> )) )} </div> ); };本をリストから削除
operations.jsexport const deleteBook = (title, pages) => { return async (dispatch, getState) => { const uid = getState().users.uid; const oldPages = getState().users.pages; const oldBooks = getState().users.books; const newPages = oldPages - pages; const newBooks = oldBooks.filter((book) => book.title !== title); dispatch( newBookAction({ pages: newPages, books: newBooks, }) ); db.collection("users") .doc(uid) .update({ pages: newPages, books: newBooks, }) .then(() => { alert("削除しました。更新してください"); }) .catch((error) => { alert(`エラー!: ${error}`); }); }; };アプリを作ってみて
思ったことは以下の2点です。
- 何よりも楽しい
学習中にチュートリアルをいくつかやってみましたが、1から初めて10で終わる教科書的な学び方に飽きてしまうことがありました。一方で実際の開発は学んだことが即アウトプットに繋がるのでスピード感があって良いですし、自分の成長と共にアプリが完成に近づいていくので達成感があります。
- 興味関心の幅が広がる
今回の開発を通して、これから勉強したいことがどんどん出てきました。今回使用したRedux、Firebaseの理解に不十分な点があることにも気がつけましたし、レンダリングの速度が気になるのでSSRができるNext.jsにも興味があります。
また、Google Book APIのドキュメントを読んだ際、バックエンド側の知識不足を実感しました。フロントエンド以外の領域も学んで行きたいと思います。おわりに
ここまでお読みいただきありがとうございました。
FlipperのURLはこちらです。ゲストユーザーを用意しているので、よければ触ってみてください。
FlipperFlipperのコードはGitHubで見られます。
GitHubtwitterもやってます。Reactなど技術的なことを呟いているので、ぜひフォローしてください。
@indigo9alpha
- 投稿日:2020-09-06T11:04:14+09:00
react-beautiful-dndがわかりにくすぎたので最小サンプル
React でドラッグアンドドロップ(DnD)を実現するライブラリがいくつかあります。
今回は react-beautiful-dnd を使います。https://github.com/atlassian/react-beautiful-dnd
ですがドキュメントに、動作する最小サンプルが用意されてません。
代わりに用意しました。
Typescriptのものですが。Screen.tsximport React, { FC, useState } from 'react' import { DraggableProvided, DroppableProvided, DropResult, Draggable, Droppable, DragDropContext, } from 'react-beautiful-dnd' const droppableId = 'id-1' const list = ['Hello', 'World', 'nishisuke'] // List for dnd. Each item must be identified. const sort = <T,>(list: T[], before: number, after: number): T[] => { const copy = [...list] const [moving] = copy.splice(before, 1) copy.splice(after, 0, moving) return copy } export const Screen: FC = () => { const [uniqTexts, setUniqTexts] = useState(list) const dndHandler = (result: DropResult) => { if ( !result.destination || result.destination.index === result.source.index ) { return } const sorted = sort( uniqTexts, result.source.index, result.destination.index ) setUniqTexts(sorted) } return ( <DragDropContext onDragEnd={dndHandler}> <Droppable droppableId={droppableId}> {(provided: DroppableProvided) => ( <div ref={provided.innerRef} {...provided.droppableProps}> {uniqTexts.map((t, i) => ( <Draggable key={t} draggableId={t} index={i}> {(provided: DraggableProvided) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} > {t} </div> )} </Draggable> ))} {provided.placeholder} </div> )} </Droppable> </DragDropContext> ) }注意点
並び替えるアイテムは
idを持つ必要があります。
今回はユニークな string を並び替えたので、それ自身が id です。placeholder はアイテム達の後ろに置く必要があります。
- 投稿日:2020-09-06T06:04:50+09:00
React.js Springbootを導入する
React.js Springbootの公式を少し変えたものです。
Githubのレポジトリをご覧いただきたいです。
ReactSpringbootのレポジトリ
JPA
h2
Lombok
rest repository
tymeleaf
React.jsのGET部分は少し変えています。 axiosを使っています。
cmdからmvnw.cmd spring-bot:runで起動する。
IDEで起動するとコンパイル時に生成されるJavaScriptを利用しているため動かない。
localhost:8080で確認する
- 投稿日:2020-09-06T03:28:26+09:00
gatsby入門 チュートリアルをこなす 7. プログラムでデータからページを作成する
gatsbyの作業履歴
gatsby入門 チュートリアルをこなす 0.開発環境をセットアップする
gatsby入門 チュートリアルをこなす 1. ギャツビービルディングブロックについて知る(1)
gatsby入門 チュートリアルをこなす 1. ギャツビービルディングブロックについて知る(2)
gatsby入門 チュートリアルをこなす 2. ギャツビーのスタイリングの概要
gatsby入門 チュートリアルをこなす 3. ネストされたレイアウトコンポーネントの作成
gatsby入門 チュートリアルをこなす 4. ギャツビーのデータ
gatsby入門 チュートリアルをこなす 5. ソースプラグインとクエリされたデータのレンダリング
gatsby入門 チュートリアルをこなす 6. 変圧器プラグイン※Transformer pluginsのgoogle翻訳
今回:gatsby入門 チュートリアルをこなす 7. プログラムでデータからページを作成する
gatsby入門 チュートリアルをこなす 8. 公開するサイトの準備チュートリアル
今回実施するgatsbyのチュートリアルはこちら
https://www.gatsbyjs.com/tutorial/part-seven/
チュートリアルの冒頭にこう書かれています。Reactコンポーネントをsrc / pagesに配置することで、引き続きページを作成できます。ただし、プログラムからデータからページを作成する方法を学習します。
※google翻訳です。
早速やっていきましょう。
ソースは前回作ったやつを使用します。Programmatically create pages from data
Creating slugs for pages
マークダウンページを作成するには、onCreateNodeとcreatePagesの2つのGatsby APIを使用します。
gatsby-node.jsを作成し、以下を記述します。gatsby-node.jsexports.onCreateNode = ({ node }) => { console.log(`Node created of type "${node.internal.type}"`) }チュートリアルはこのように記載されています。
このonCreateNode関数は、新しいノードが作成(または更新)されるたびにGatsbyによって呼び出されます。
なるほど。
gatsby開発サーバを再起動します。
起動はgatsby developでできます。
なんかログがいっぱい出てその中にマークダウンの文字が。
gatsby-node.jsを修正します。gatsby-node.jsexports.onCreateNode = ({ node }) => { ↓ここから if (node.internal.type === `MarkdownRemark`) { console.log(node.internal.type) } ↑ここまで修正 }マークダウンだけログだしするのね。
再起動
出てる出てる
マークダウンファイルのファイル名からURLのパスを作っていきましょう。
。。gatsbyだとパスをslugっていうのか。
gatsby-node.jsを修正gatsby-node.jsexports.onCreateNode = ({ node, getNode }) => {←ここ修正 if (node.internal.type === `MarkdownRemark`) { const fileNode = getNode(node.parent)←ここ修正 console.log(`\n`, fileNode.relativePath)←ここ修正 } }再起動
これか!
ここからgatsby-source-filesystem pluginを使用
gatsby-node.jsを修正gatsby-node.jsconst { createFilePath } = require(`gatsby-source-filesystem`)←ここ追記 exports.onCreateNode = ({ node, getNode }) => { if (node.internal.type === `MarkdownRemark`) { console.log(createFilePath({ node, getNode, basePath: `pages` }))←ここ修正 } }再起動
出来てる!
ここからcreateNodeFieldを使用してslug(パス)を作ります。
gatsby-node.jsを修正gatsby-node.jsconst { createFilePath } = require(`gatsby-source-filesystem`) exports.onCreateNode = ({ node, getNode, actions }) => {←ここ修正 const { createNodeField } = actions←ここ修正 if (node.internal.type === `MarkdownRemark`) { ↓ここから const slug = createFilePath({ node, getNode, basePath: `pages` }) createNodeField({ node, name: `slug`, value: slug, }) ↑ここまで追記 } }再起動
GraphiQLで以下を実行query MyQuery { allMarkdownRemark { edges { node { fields { slug } } } } }Creating pages
ページ作りだ!
gatsby-node.jsを修正gatsby-node.jsconst { createFilePath } = require(`gatsby-source-filesystem`) exports.onCreateNode = ({ node, getNode, actions }) => { const { createNodeField } = actions if (node.internal.type === `MarkdownRemark`) { const slug = createFilePath({ node, getNode, basePath: `pages` }) createNodeField({ node, name: `slug`, value: slug, }) } } ↓ここから exports.createPages = async ({ graphql, actions }) => { // **Note:** The graphql function call returns a Promise // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise for more info const result = await graphql(` query { allMarkdownRemark { edges { node { fields { slug } } } } } `) console.log(JSON.stringify(result, null, 4)) } ↑ここまで追記この時点でどうなるのかさっぱりわからんが続けよう。
src/templatesディレクトリを作成
src/templates/blog-post.jsを作成し以下を記述blog-post.jsimport React from "react" import Layout from "../components/layout" export default function BlogPost() { return ( <Layout> <div>Hello blog post</div> </Layout> ) }gatsby-node.jsを修正
gatsby-node.jsconst path = require(`path`)←ここ追記 const { createFilePath } = require(`gatsby-source-filesystem`) exports.onCreateNode = ({ node, getNode, actions }) => { const { createNodeField } = actions if (node.internal.type === `MarkdownRemark`) { const slug = createFilePath({ node, getNode, basePath: `pages` }) createNodeField({ node, name: `slug`, value: slug, }) } } exports.createPages = async ({ graphql, actions }) => { const { createPage } = actions←ここ追記 const result = await graphql(` query { allMarkdownRemark { edges { node { fields { slug } } } } } `) ↓ここから result.data.allMarkdownRemark.edges.forEach(({ node }) => { createPage({ path: node.fields.slug, component: path.resolve(`./src/templates/blog-post.js`), context: { // Data passed to context is available // in page queries as GraphQL variables. slug: node.fields.slug, }, }) }) ↑ここまで追記 }再起動
以下でアクセス
http://localhost:8000/sdf
※sdfは何でもいい
あ、パスが出来てる
アクセス
マークダウンファイルはまだ出てこない。
なるほど、ちょっとわかったぞ。
gatsby-node.jsのonCreateNodeでマークダウンのファイル名を取得してslug(パス)を作成。
createPagesでそのslugを取得し、繰り返し処理の中でパスとsrc/templates/blog-post.jsを同期させるのね!
つづき!
src/templates/blog-post.jsの修正src/templates/blog-post.jsimport React from "react" import { graphql } from "gatsby" import Layout from "../components/layout" export default function BlogPost({ data }) { const post = data.markdownRemark return ( <Layout> <div> <h1>{post.frontmatter.title}</h1> <div dangerouslySetInnerHTML={{ __html: post.html }} /> </div> </Layout> ) } export const query = graphql` query($slug: String!) { markdownRemark(fields: { slug: { eq: $slug } }) { html frontmatter { title } } } `うんうん。graphqlでマークダウンのhtml情報を取得してsrc/templates/blog-post.jsに書き込む感じか。
出来た!
そしてsrc/pages/index.jsを修正src/pages/index.jsimport React from "react" import { css } from "@emotion/core" import { Link, graphql } from "gatsby"←ここ修正 import { rhythm } from "../utils/typography" import Layout from "../components/layout" export default function Home({ data }) { return ( <Layout> <div> <h1 css={css` display: inline-block; border-bottom: 1px solid; `} > Amazing Pandas Eating Things </h1> <h4>{data.allMarkdownRemark.totalCount} Posts</h4> {data.allMarkdownRemark.edges.map(({ node }) => ( <div key={node.id}> ↓ここから <Link to={node.fields.slug} css={css` text-decoration: none; color: inherit; `} > ↑ここまで修正 <h3 css={css` margin-bottom: ${rhythm(1 / 4)}; `} > {node.frontmatter.title}{" "} <span css={css` color: #555; `} > — {node.frontmatter.date} </span> </h3> <p>{node.excerpt}</p> </Link>←ここ修正 </div> ))} </div> </Layout> ) } export const query = graphql` query { allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) { totalCount edges { node { id frontmatter { title date(formatString: "DD MMMM, YYYY") } ↓ここから fields { slug } ↑ここまで修正 excerpt } } } } `再起動
index.jsからpostにアクセスできるようになった!
チュートリアルはここまでかな。これ、src/pages内にmdファイルあるのが気になるから移動できないかしら?
src/mdディレクトリ作成し、mdファイルを移動
再起動
うん、問題ないね!
今回はここまで。ありがとうございました。
- 投稿日:2020-09-06T00:34:17+09:00
①React + Firebase + Google-API を使用して、Googleクローンを作成してみる。
概要
React,Firebase, Google-APIを使って、Googleのクローンを作成します。
流行りのMarerial-UIも使用しています。
長くなるので、ReactのフロントエンドとGoogle-API,Firebaseを使用したバックエンドは分けて投稿する予定です。デプロイ先
URL: https://clone-6bd5b.firebaseapp.com/
Googleの検索結果と同じものが得られます。
Google-API(無料プラン)には、一日の検索数に制限があるので、スパムはやめてくださると幸いです。目次
1.App.jsファイル
2.Maretial-UI
3.Home.js
4.Search.js 検索バー
5.まとめ
6.最後に1. App.jsファイル
react-router-domを
npm install react-router-dom
でダウンロードしておきましょう。App.jsimport React from 'react'; import './App.css'; import Home from "./Pages/Home"; import { BrowserRouter as Router, Switch, Route} from "react-router-dom"; function App() { return ( <div className="app"> <Router> <Switch> <Route path="/"> <Home /> </Route> </Switch> </Router> </div> ); } export default App;2 Maretial-UI
使用するMaterial-UIのインポート
npm install @material-ui/coreこれだけではiconを使用できないので、
npm install @material-ui/icons
アイコンも忘れずにインストールしておきましょう。ここまですると、ここのMaterial Iconsから使いたいアイコンを検索して使用することができます
https://material-ui.com/components/material-icons/今回使用するアイコンの1つのAPPsを選択すると、
以下の画面が出てくるので、
import AppsIcon from '@material-ui/icons/Apps;'
をコピーして、Home.jsファイルに貼り付けます。3 Home.js
Home.jsimport React from 'react'; import './Home.css'; import { Link } from 'react-router-dom'; import AppsIcon from '@material-ui/icons/Apps'; import {Avatar} from "@material-ui/core"; function Home() { return ( <div className="home"> <div className="home__header"> <div className='home__headerLeft'> <Link to='/about'>About</Link> <Link to='/store'>Store</Link> </div> <div className='home__headerRight'> <Link to='/gmail'>Gmail</Link> <Link to='/images'>Images</Link> <AppsIcon /> <Avatar /> </div> </div> </div> ) } export default Homeコンポーネントを以下のように、home__header その中身を左側と右側に分けました。
cssファイル
Home.css.home { display: flex; flex-direction: column; height: 100vh; } .home__header{ display: flex; justify-content: space-between; padding: 20px 30px; align-items: center; } .home__header a{ margin-right: 20px; text-decoration: inherit; color: rgba(0,0,0,0.87); font-size: 15px; } .home__header a:hover{ text-decoration: underline; } .home__headerRight { display: flex; align-items: center; min-width: 13vw; justify-content: space-between; } .home__headerRight > .MuiSvgIcon-root { margin-right: 20px; }
<a href >はページがリフレッシュしてしまいますので、React では、<Link>使ったほうが良いです。Home.css.home__header a{ text-decoration: inherit; }Home.jsファイル内で使用している
<Link to=...> ... </Link>において、
Linkは aで参照してスタイルできます
text-decoration: inherit;は、でデフォルトで表示されるアンダーラインを表示させない設定です。Home.css.home__header a:hover{ text-decoration: underline; }
hover簡単に説明するとマウスが乗ったのみに起こるイベントです。
この場合、マウスが乗った時、アンダーラインを表示させます。Home.css.home__headerRight > .MuiSvgIcon-root { ... }
.MuiSvgIcon-rootは、Material-uiのiconをスタイリングできます。Home.css<div className="home"> <div className="home__header"> ... </div> {/*ここに追加します*/} <div className='home__body'> <img src="https://www.google.com/logos/doodles/2020/aya-kodas-116th-birthday-6753651837108515-l.png" alt="" /> <Search /> </div> </div>css: home__body
Home.css.home__body { flex: 1; display: flex; margin-top: 10%; flex-direction: column; } .home__body > img { object-fit: contain; height: 200px; }4 Search.js 検索バー
ここで、Search.jsコンポーネントを利用し、検索バーを作成していきます。
Search.jsimport React from 'react'; import './Search.css'; import SearchIcon from '@material-ui/icons/Search'; import MicIcon from '@material-ui/icons/Mic'; import {Button} from "@material-ui/core"; function Search() { return ( <form className="search"> <div className='search__input'> <SearchIcon className="search__inputIcon"/> <input /> <MicIcon /> </div> <div className='search__buttons'> <Button type="submit" variant="outlined">Google Search</Button> <Button variant="outlined">I'm Feeling Lucky</Button> </div> </form> ) } export default Search;Search.cssファイル
Search.css.search__input { display: flex; align-items: center; border: 1px solid lightgray; height: 30px; padding: 10px 20px; border-radius: 999px; width: 75vw; margin: 0 auto; margin-top: 40px; max-width: 560px; } .search__input > input { flex: 1; padding: 10px 20px; font-size: medium; border: none; } .search__input > input:focus{ outline-width: 0; } .search__inputIcon { color: gray; } .search__buttons { margin-top: 30px; display: flex; justify-content: center; } .search__buttons button { margin: 5px; padding: 7px 15px; background-color: #f8f8f8; border: 1px solid white; text-transform: inherit; color: #5f6368; } .search__buttons button:hover{ box-shadow: 0 1px 1px rgba(0,0,0,0.1); background-color: lightgray; }まとめ
ここまでやるとホームUIの完成です。
最後に
ホーム画面が完成してキリが良いので、今回はここで区切らせてもらいます。
第二回は、検索した際に表示される画面と,
javascriptの機能などを実装していきたいと考えています。第三回は(最終回を予定)、Google-APIとFirebaseを実装させていきます。
お疲れさまでした。
- 投稿日:2020-09-06T00:17:02+09:00
gatsby入門 チュートリアルをこなす 6. 変圧器プラグイン※Transformer pluginsのgoogle翻訳
gatsbyの作業履歴
gatsby入門 チュートリアルをこなす 0.開発環境をセットアップする
gatsby入門 チュートリアルをこなす 1. ギャツビービルディングブロックについて知る(1)
gatsby入門 チュートリアルをこなす 1. ギャツビービルディングブロックについて知る(2)
gatsby入門 チュートリアルをこなす 2. ギャツビーのスタイリングの概要
gatsby入門 チュートリアルをこなす 3. ネストされたレイアウトコンポーネントの作成
gatsby入門 チュートリアルをこなす 4. ギャツビーのデータ
gatsby入門 チュートリアルをこなす 5. ソースプラグインとクエリされたデータのレンダリング
今回:gatsby入門 チュートリアルをこなす 6. 変圧器プラグイン※Transformer pluginsのgoogle翻訳
gatsby入門 チュートリアルをこなす 7. プログラムでデータからページを作成する
gatsby入門 チュートリアルをこなす 8. 公開するサイトの準備チュートリアル
今回実施するgatsbyのチュートリアルはこちら
https://www.gatsbyjs.com/tutorial/part-six/
チュートリアルの冒頭にこう書かれています。これ重要前のチュートリアルでは、ソースプラグインがどのようにデータをギャツビーのデータシステムに取り込むかを示しました。このチュートリアルでは、変換プラグインがソースプラグインによってもたらされる生のコンテンツを変換する方法を学びます。ソースプラグインとトランスフォーマープラグインの組み合わせは、Gatsbyサイトの構築時に必要になる可能性のあるすべてのデータソースとデータ変換を処理できます。
※google翻訳
チュートリアルをざっと見た感じ、ブログの投稿機能が実装できるようになるのかしら。
早速やっていきましょう。
ソースは前回作ったやつを使用します。Transformer plugins
Transformer plugins
マークダウンファイルをサイトに追加し、変換プラグインとGraphQLを使用してHTMLに変換する方法を理解する。
マークダウンファイル(src/pages/sweet-pandas-eating-sweets.md)を作成します。src/pages/sweet-pandas-eating-sweets.md--- title: "Sweet Pandas Eating Sweets" date: "2017-08-10" --- Pandas are really sweet. Here's a video of a panda eating sweets. <iframe width="560" height="315" src="https://www.youtube.com/embed/4n0xNbfJLR8" frameborder="0" allowfullscreen></iframe>http://localhost:8000/my-files
出てきた!
これはgatsby-source-filesystemを常に、追加する新しいファイルをスキャンしているようです。
いいね。もうサーバ処理いらない。
続けてtransformer pluginをインストール
サーバ停止して以下を実行
npm install --save gatsby-transformer-remark
※tutorial-part-fourディレクトリ直下で実行
インストール後gatsby-config.jsを修正gatsby-config.jsmodule.exports = { siteMetadata: { title: `Pandas Eating Lots`, }, plugins: [ { resolve: `gatsby-source-filesystem`, options: { name: `src`, path: `${__dirname}/src/`, }, }, `gatsby-transformer-remark`,←これ追記 `gatsby-plugin-emotion`, { resolve: `gatsby-plugin-typography`, options: { pathToConfigModule: `src/utils/typography`, }, }, ], }再起動
お、追加されてる。
チュートリアル通りにクエリを実行してチュートリアル通りの結果。よし。Create a list of your site’s markdown files in
投稿リストをindexに表示するよう以下を記述
src/pages/index.jsimport React from "react" import { graphql } from "gatsby" import { css } from "@emotion/core" import { rhythm } from "../utils/typography" import Layout from "../components/layout" export default function Home({ data }) { console.log(data) return ( <Layout> <div> <h1 css={css` display: inline-block; border-bottom: 1px solid; `} > Amazing Pandas Eating Things </h1> <h4>{data.allMarkdownRemark.totalCount} Posts</h4> {data.allMarkdownRemark.edges.map(({ node }) => ( <div key={node.id}> <h3 css={css` margin-bottom: ${rhythm(1 / 4)}; `} > {node.frontmatter.title}{" "} <span css={css` color: #bbb; `} > — {node.frontmatter.date} </span> </h3> <p>{node.excerpt}</p> </div> ))} </div> </Layout> ) } export const query = graphql` query { allMarkdownRemark { totalCount edges { node { id frontmatter { title date(formatString: "DD MMMM, YYYY") } excerpt } } } } `queryでtitleと日付取得して表示する感じね。
いいね。
もう1件mdファイルを追加しちゃおう。src/pages/pandas-and-bananas.md--- title: "Pandas and Bananas" date: "2017-08-21" --- Do Pandas eat bananas? Check out this short video that shows that yes! pandas do seem to really enjoy bananas! <iframe width="560" height="315" src="https://www.youtube.com/embed/4SZl1r2O_bY" frameborder="0" allowfullscreen></iframe>
いいね!再起動だなんだが不要でソッコーで反映されるのが素晴らしい!
queryを以下にすると、日付の降順で取得できるsrc/pages/index.js(query部分)export const query = graphql` query { allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {←ここ修正 totalCount edges { node { id frontmatter { title date(formatString: "DD MMMM, YYYY") } excerpt } } } } `チュートリアルはここまでか。
たぶん、今後mdファイルを表示する処理があるのかな。今回はここまで。
ありがとうございました。























































