- 投稿日:2019-05-23T22:15:43+09:00
認証情報をreduxで管理してreact-routerのRedirectを学ぶ
目標
題名の通り、
redux
で管理した情報に基づきRedirect
を行います。
具体的に実装するのは、ログインした人じゃないと見れないページを作るというものです。実装していく
まずはトップのルーティングの部分
App.jsimport PrivateRoute from './PrivateRoute'; const App = () => { return ( <Switch> <PrivateRoute path="/" exact component={TopPage} /> <Route path="/login" component={Login} /> <Route component={NotFoundPage} /> </Switch> ); };ここで
PrivateRoute
というものを自分で作っています。
PrivateRoute
をみていきましょう。PrivateRoute.jsimport React from 'react'; import { connect } from 'react-redux'; import { Route, Redirect } from 'react-router-dom'; const PrivateRoute = ({user, component: Component, ...rest}) => { return ( <Route {...rest} render={props => user ? ( <Component {...props} /> ) : ( <Redirect to='/login' /> ) } /> ); } const mapStateToProps = state => { return { user: state.auth.user } } export default connect(mapStateToProps)(PrivateRoute);ここで
redux
で管理しているuser
の真偽により、偽であれば/login
にRedirect
します。reducers/authReducer.jsconst initialState = { user: false } export default (state = initialState, action) => { switch(action.type) { case 'LOG_IN': return { ...state, user: true }; case 'LOG_OUT': return { ...state, user: false }; default: return state; } }お試しに実装したので
reducer
では大したことはしていません。
あとでfirebase
でのGoogleログインにしようと思っているのでその時は少し考えなければいけないかも。最後にログインのコンポーネント
components/Login/index.jsimport React from 'react'; import { connect } from 'react-redux'; import { login } from '../../actions'; class Login extends React.Component { reduxLogin = () => { const { login, history } = this.props; login(); history.push('/'); } render() { return ( <div> <button onClick={this.reduxLogin}>login redux</button> </div> ); } } export default connect(null, { login })(Login);ログインのボタンを用意してログインするとトップページに飛ぶようにしています。
ログインしないでトップページに飛ぼうとしても/login
にRedirect
されます。まとめ
今回は
react-router
のRedirect
についてみました。
参考になれば幸いです。
- 投稿日:2019-05-23T19:39:27+09:00
【Heroku】ルートに無いディレクトリ上で、デプロイ時にビルドを実行する
問題
Herokuはデプロイ時にルートにcomposer.jsonやpackage.jsonがあった場合、
jsonファイルを元に依存パッケージのインストールやアップデート、ビルド等行ってくれますが、ルートから外れたものは行ってくれません。例えばこんな構成だと、package.json は無視されますしビルドは行われません。
ディレクトリ構成. ├── react │ ├── package.json ← 対象外 ・・・ └── composer.json ← 対象こういったルートから外れたjsonを対象にしたい場合、下記のように記述すればOK。
解決策
ルートにpackage.jsonのシンボリックリンクを作成
ディレクトリ構成. ├── react │ ├── package.json ・・・ ├── package.json(react/package.jsonのシンボリックリンク) └── composer.jsonpackage.jsonに追記。
react/package.json// package.jsonのscriptsに下記スクリプトを追加 "scripts": { ・・・ "heroku-postbuild": "cd ./ビルド対象のディレクトリ名 && npm run build" },scripts内でshell打てるんだから、対象ディレクトリに移動してビルドすればいいじゃない、
と答え見た時は愕然としました。
こういうのは頭柔らかくしないといけませんね。参考:
https://github.com/heroku/heroku-buildpack-nodejs/issues/323
- 投稿日:2019-05-23T16:31:43+09:00
ReactでGoogle mapをお届けする
はじめに
ReactでMapを使用し以下要件を満たす必要があった
・ マップスタイルの変更 ・ ピンスタイルの変更 ・ ピン間に線を描画する ・ ピンをクリックすると吹き出しが表示される要件を満たすためにReactで使用可能なMapをレンダリングできるライブラリをざっと調査した
ライブラリ
ライブラリ ピン変更 バルーン機能 ルーティング 備考 google-maps-react Markerコンポーネントを提供している infoWindowで変更可能 polylineで設定可能 weekly download 24,085 google-map-react 子コンポ-ネント内に画像やスタイルを適用したコンポーネントで対応 子コンポーネントで吹き出しコンポーネントを配置しonChildClickで表示する onGoogleApiLoaded時に描画可能 weekly download 69,369 react-google-maps componentのcoordinates内で変更可能 infoWindowをカスタム onLoaded時に描画可能 ・weekly download 2,414
UI componentを豊富に提供2gis-maps-react 細かいアイコンの設定が可能 popuupで設定可能 polylineで線を引ける ・no longer supported
・weekly download 15react-map-gl markerで設定可能 popuupで設定可能 overlay機能を仕様しcanvasに線を引ける ・mapboxを使用可能
・weekly download 34,397今回は単純にコミュニティがアクティブかどうか、公式サンプルが豊富かという視点でgoogle-map-reactを使用することにした
また、マップスタイルの変更としてsnazzymapsが豊富なサンプルを提供しているので今回使用することにした
実装
google-map-reactでピンを一個だけマップ上に表示したい場合はとても簡単に実装できる
import * as React from 'react' import styled from 'styled-components' import logo from './logo.svg' import GoogleMapReact from 'google-map-react' const App = () => ( <Wrapper> <Header> <Logo src={logo} /> <H1>Welcome to React</H1> </Header> <GoogleMapWrapper> <GoogleMapReact bootstrapURLKeys={{ key: {'ここにgoogle map apiのkeyをドウゾ'} }} defaultCenter={{ lat: 43.0582954, lng: 141.3466919 }} defaultZoom={15} > <Pin lat={43.0582954} lng={141.3466919} > おおおんどおり </Pin> </GoogleMapReact> </GoogleMapWrapper> </Wrapper> ) const Wrapper = styled.div` text-align: center; ` const Header = styled.header` background-color: ${Color.Primary}; height: 150px; padding: 20px; color: white; ` const Logo = styled.img` animation: App-logo-spin infinite 20s linear; height: 80px; @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ` const H1 = styled.h1` font-size: 1.5em; ` const Pin = styled.div<{ lat: number, lng: number }>` ` const GoogleMapWrapper = styled.div` height: 100vh; width: 100%; ` export default Appこれを適用すると大通公園に"おおおんどおり"と表示されてるはずだ
サンプルを参考にするだけでとても簡単にピンを配置できるマップスタイル変更
Googleマップのデザインを以下のように変更したいケースがあると思う
実はsnazzymapというサイトを使えば簡単に実装できるimport * as React from 'react' import styled from 'styled-components' import { Color } from './brand' import logo from './logo.svg' import GoogleMapReact, { MapOptions, Maps } from 'google-map-react' const App = () => { const createMapOptions = (maps: Maps): MapOptions => { return { mapTypeControlOptions: { position: maps.ControlPosition.TOP_RIGHT, }, mapTypeControl: false, zoomControl: false, scaleControl: false, streetViewControl: false, fullscreenControl: false, styles: [ { featureType: 'water', elementType: 'geometry', stylers: [ { color: '#e9e9e9', }, { lightness: 17, }, ], }, { featureType: 'landscape', elementType: 'geometry', stylers: [ { color: '#f5f5f5', }, { lightness: 20, }, ], }, { featureType: 'road.highway', elementType: 'geometry.fill', stylers: [ { color: '#ffffff', }, { lightness: 17, }, ], }, { featureType: 'road.highway', elementType: 'geometry.stroke', stylers: [ { color: '#ffffff', }, { lightness: 29, }, { weight: 0.2, }, ], }, { featureType: 'road.arterial', elementType: 'geometry', stylers: [ { color: '#ffffff', }, { lightness: 18, }, ], }, { featureType: 'road.local', elementType: 'geometry', stylers: [ { color: '#ffffff', }, { lightness: 16, }, ], }, { featureType: 'poi', elementType: 'geometry', stylers: [ { color: '#f5f5f5', }, { lightness: 21, }, ], }, { featureType: 'poi.park', elementType: 'geometry', stylers: [ { color: '#dedede', }, { lightness: 21, }, ], }, { elementType: 'labels.text.stroke', stylers: [ { visibility: 'on', }, { color: '#ffffff', }, { lightness: 16, }, ], }, { elementType: 'labels.text.fill', stylers: [ { saturation: 36, }, { color: '#333333', }, { lightness: 40, }, ], }, { elementType: 'labels.icon', stylers: [ { visibility: 'off', }, ], }, { featureType: 'transit', elementType: 'geometry', stylers: [ { color: '#f2f2f2', }, { lightness: 19, }, ], }, { featureType: 'administrative', elementType: 'geometry.fill', stylers: [ { color: '#fefefe', }, { lightness: 20, }, ], }, { featureType: 'administrative', elementType: 'geometry.stroke', stylers: [ { color: '#fefefe', }, { lightness: 17, }, { weight: 1.2, }, ], }, ], } } return ( <Wrapper> <Header> <Logo src={logo} /> <H1>Welcome to React</H1> </Header> <GoogleMapWrapper> <GoogleMapReact bootstrapURLKeys={{ key: '' }} defaultCenter={{ lat: 43.0582954, lng: 141.3466919 }} defaultZoom={15} options={createMapOptions} > <Pin lat={43.0582954} lng={141.3466919} > おおおんどおり </Pin> </GoogleMapReact> </GoogleMapWrapper> </Wrapper> ) } const Wrapper = styled.div` text-align: center; ` const Header = styled.header` background-color: ${Color.Primary}; height: 150px; padding: 20px; color: white; ` const Logo = styled.img` animation: App-logo-spin infinite 20s linear; height: 80px; @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ` const H1 = styled.h1` font-size: 1.5em; ` const Pin = styled.div<{ lat: number, lng: number }>` ` const GoogleMapWrapper = styled.div` height: 100vh; width: 100%; ` export default AppcreateMapOptionsのfullscreenControl optionの下にstyles optionがある
このstyles optionにsnazzymapで取得したソースコードをコピペするだけで実装できるちなみに今回はSubtle Grayscaleを適用した
ピンスタイルの変更
前述通りピンを1個だけ描画したい場合はとても簡単にできるが複数ピンのスタイルを変更し描画したい場合はどうすればいいのかともしかしたらなるかもしれない
これも簡単に実装できる複数ピンの描画位置に関しては、GoogleMapReactの1階層子コンポーネントでlatやlngを元に相対位置をよしなに算出してくれるので子コンポーネントにスタイルを適用するだけで実装ができる
import * as React from 'react' import styled from 'styled-components' import { Color } from './brand' import logo from './logo.svg' import GoogleMapReact, { MapOptions, Maps } from 'google-map-react' const App = () => { const pins: { lat: number, lng: number, name: string }[] = [ { lat: 43.0543412, lng: 141.355018, name: 'お姉さんレーベル' }, { lat: 43.0543451, lng: 141.3528293, name: '姉キャバ ジャックローズ' } ] const createMapOptions = (maps: Maps): MapOptions => { return { mapTypeControlOptions: { position: maps.ControlPosition.TOP_RIGHT, }, mapTypeControl: false, zoomControl: false, scaleControl: false, streetViewControl: false, fullscreenControl: false, styles: [ { featureType: 'water', elementType: 'geometry', stylers: [ { color: '#e9e9e9', }, { lightness: 17, }, ], }, { featureType: 'landscape', elementType: 'geometry', stylers: [ { color: '#f5f5f5', }, { lightness: 20, }, ], }, { featureType: 'road.highway', elementType: 'geometry.fill', stylers: [ { color: '#ffffff', }, { lightness: 17, }, ], }, { featureType: 'road.highway', elementType: 'geometry.stroke', stylers: [ { color: '#ffffff', }, { lightness: 29, }, { weight: 0.2, }, ], }, { featureType: 'road.arterial', elementType: 'geometry', stylers: [ { color: '#ffffff', }, { lightness: 18, }, ], }, { featureType: 'road.local', elementType: 'geometry', stylers: [ { color: '#ffffff', }, { lightness: 16, }, ], }, { featureType: 'poi', elementType: 'geometry', stylers: [ { color: '#f5f5f5', }, { lightness: 21, }, ], }, { featureType: 'poi.park', elementType: 'geometry', stylers: [ { color: '#dedede', }, { lightness: 21, }, ], }, { elementType: 'labels.text.stroke', stylers: [ { visibility: 'on', }, { color: '#ffffff', }, { lightness: 16, }, ], }, { elementType: 'labels.text.fill', stylers: [ { saturation: 36, }, { color: '#333333', }, { lightness: 40, }, ], }, { elementType: 'labels.icon', stylers: [ { visibility: 'off', }, ], }, { featureType: 'transit', elementType: 'geometry', stylers: [ { color: '#f2f2f2', }, { lightness: 19, }, ], }, { featureType: 'administrative', elementType: 'geometry.fill', stylers: [ { color: '#fefefe', }, { lightness: 20, }, ], }, { featureType: 'administrative', elementType: 'geometry.stroke', stylers: [ { color: '#fefefe', }, { lightness: 17, }, { weight: 1.2, }, ], }, ], } } return ( <Wrapper> <Header> <Logo src={logo} /> <H1>Welcome to React</H1> </Header> <GoogleMapWrapper> <GoogleMapReact bootstrapURLKeys={{ key: '' }} defaultCenter={{ lat: 43.0543451, lng: 141.3528293 }} defaultZoom={15} options={createMapOptions} > { pins.map((pin: { lat: number, lng: number, name: string }) => ( <Pin lat={pin.lat} lng={pin.lng} > {pin.name} </Pin> )) } </GoogleMapReact> </GoogleMapWrapper> </Wrapper> ) } const Wrapper = styled.div` text-align: center; ` const Header = styled.header` background-color: ${Color.Primary}; height: 150px; padding: 20px; color: white; ` const Logo = styled.img` animation: App-logo-spin infinite 20s linear; height: 80px; @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ` const H1 = styled.h1` font-size: 1.5em; ` const Pin = styled.div<{ lat: number, lng: number }>` ` const GoogleMapWrapper = styled.div` height: 100vh; width: 100%; ` export default Appお姉さんレーベルと姉キャバ ジャックローズが表示されてることが確認できると思う
あとは、Pinコンポーネントのstyleを変更すればデザインは簡単に変更可能だピン間に線を描画する
GoogleMapReactではGoogle map apiのloaded時に上乗せ描画できるapiを提供している
例えば線を描画するpolylineがある
今回はpolylineを使用しでピン間に線を引くimport * as React from 'react' import styled from 'styled-components' import { Color } from './brand' import logo from './logo.svg' import GoogleMapReact, { MapOptions, Maps } from 'google-map-react' interface PinProps { lat: number, lng: number, name: string } const App = () => { const pins: PinProps[] = [ { lat: 43.0543412, lng: 141.355018, name: 'お姉さんレーベル' }, { lat: 43.0543451, lng: 141.3528293, name: '姉キャバ ジャックローズ' } ] const apiLoaded = (map: any, maps: any, pins: any) => { const path = new maps.Polyline({ path: pins.map((p: PinProps) => ({ lat: p.lat, lng: p.lng })), geodesic: true, strokeColor: '#356859', strokeOpacity: 1, strokeWeight: 3, }) path.setMap(map) } const createMapOptions = (maps: Maps): MapOptions => { return { mapTypeControlOptions: { position: maps.ControlPosition.TOP_RIGHT, }, mapTypeControl: false, zoomControl: false, scaleControl: false, streetViewControl: false, fullscreenControl: false, styles: [ { featureType: 'water', elementType: 'geometry', stylers: [ { color: '#e9e9e9', }, { lightness: 17, }, ], }, { featureType: 'landscape', elementType: 'geometry', stylers: [ { color: '#f5f5f5', }, { lightness: 20, }, ], }, { featureType: 'road.highway', elementType: 'geometry.fill', stylers: [ { color: '#ffffff', }, { lightness: 17, }, ], }, { featureType: 'road.highway', elementType: 'geometry.stroke', stylers: [ { color: '#ffffff', }, { lightness: 29, }, { weight: 0.2, }, ], }, { featureType: 'road.arterial', elementType: 'geometry', stylers: [ { color: '#ffffff', }, { lightness: 18, }, ], }, { featureType: 'road.local', elementType: 'geometry', stylers: [ { color: '#ffffff', }, { lightness: 16, }, ], }, { featureType: 'poi', elementType: 'geometry', stylers: [ { color: '#f5f5f5', }, { lightness: 21, }, ], }, { featureType: 'poi.park', elementType: 'geometry', stylers: [ { color: '#dedede', }, { lightness: 21, }, ], }, { elementType: 'labels.text.stroke', stylers: [ { visibility: 'on', }, { color: '#ffffff', }, { lightness: 16, }, ], }, { elementType: 'labels.text.fill', stylers: [ { saturation: 36, }, { color: '#333333', }, { lightness: 40, }, ], }, { elementType: 'labels.icon', stylers: [ { visibility: 'off', }, ], }, { featureType: 'transit', elementType: 'geometry', stylers: [ { color: '#f2f2f2', }, { lightness: 19, }, ], }, { featureType: 'administrative', elementType: 'geometry.fill', stylers: [ { color: '#fefefe', }, { lightness: 20, }, ], }, { featureType: 'administrative', elementType: 'geometry.stroke', stylers: [ { color: '#fefefe', }, { lightness: 17, }, { weight: 1.2, }, ], }, ], } } return ( <Wrapper> <Header> <Logo src={logo} /> <H1>Welcome to React</H1> </Header> <GoogleMapWrapper> <GoogleMapReact bootstrapURLKeys={{ key: '' }} defaultCenter={{ lat: 43.0543451, lng: 141.3528293 }} defaultZoom={15} options={createMapOptions} onGoogleApiLoaded={({ map, maps }) => apiLoaded(map, maps, pins)} > { pins.map((pin: { lat: number, lng: number, name: string }) => ( <Pin lat={pin.lat} lng={pin.lng} > {pin.name} </Pin> )) } </GoogleMapReact> </GoogleMapWrapper> </Wrapper> ) } const Wrapper = styled.div` text-align: center; ` const Header = styled.header` background-color: ${Color.Primary}; height: 150px; padding: 20px; color: white; ` const Logo = styled.img` animation: App-logo-spin infinite 20s linear; height: 80px; @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ` const H1 = styled.h1` font-size: 1.5em; ` const Pin = styled.div<{ lat: number, lng: number }>` ` const GoogleMapWrapper = styled.div` height: 100vh; width: 100%; ` export default Appお姉さんレーベルと姉キャバ ジャックローズの間に緑色の線が引かれていることが確認できるだろう
ピンをクリックすると吹き出しが表示される
GoogleMapReactは子コンポーネントのクリック時にイベント取得するonChildClickというapiを提供している
onChildClickはイベント発生時に該当するコンポーネントのkeyと子コンポーネント自体の情報(childProps)を返す基本吹き出しを表示する際はクリックイベントのkeyとmapのindexが同じなら表示するで実装できるので、基本childPropsは使用しなくていいだろう
import * as React from 'react' import styled from 'styled-components' import { useState } from 'react' import { Color } from './brand' import logo from './logo.svg' import GoogleMapReact, { MapOptions, Maps } from 'google-map-react' interface PinProps { lat: number, lng: number, name: string } const App = () => { const [currentKey, setCurrentKey] = useState(-1) const pins: PinProps[] = [ { lat: 43.0543412, lng: 141.355018, name: 'お姉さんレーベル' }, { lat: 43.0543451, lng: 141.3528293, name: '姉キャバ ジャックローズ' } ] const apiLoaded = (map: any, maps: any, pins: any) => { const path = new maps.Polyline({ path: pins.map((p: PinProps) => ({ lat: p.lat, lng: p.lng })), geodesic: true, strokeColor: '#356859', strokeOpacity: 1, strokeWeight: 3, }) path.setMap(map) } const changeBalloon = (key: string) => { const keyNumber = Number(key) if (currentKey === keyNumber) { setCurrentKey(-1) } else { setCurrentKey(keyNumber) } } const createMapOptions = (maps: Maps): MapOptions => { return { mapTypeControlOptions: { position: maps.ControlPosition.TOP_RIGHT, }, mapTypeControl: false, zoomControl: false, scaleControl: false, streetViewControl: false, fullscreenControl: false, styles: [ { featureType: 'water', elementType: 'geometry', stylers: [ { color: '#e9e9e9', }, { lightness: 17, }, ], }, { featureType: 'landscape', elementType: 'geometry', stylers: [ { color: '#f5f5f5', }, { lightness: 20, }, ], }, { featureType: 'road.highway', elementType: 'geometry.fill', stylers: [ { color: '#ffffff', }, { lightness: 17, }, ], }, { featureType: 'road.highway', elementType: 'geometry.stroke', stylers: [ { color: '#ffffff', }, { lightness: 29, }, { weight: 0.2, }, ], }, { featureType: 'road.arterial', elementType: 'geometry', stylers: [ { color: '#ffffff', }, { lightness: 18, }, ], }, { featureType: 'road.local', elementType: 'geometry', stylers: [ { color: '#ffffff', }, { lightness: 16, }, ], }, { featureType: 'poi', elementType: 'geometry', stylers: [ { color: '#f5f5f5', }, { lightness: 21, }, ], }, { featureType: 'poi.park', elementType: 'geometry', stylers: [ { color: '#dedede', }, { lightness: 21, }, ], }, { elementType: 'labels.text.stroke', stylers: [ { visibility: 'on', }, { color: '#ffffff', }, { lightness: 16, }, ], }, { elementType: 'labels.text.fill', stylers: [ { saturation: 36, }, { color: '#333333', }, { lightness: 40, }, ], }, { elementType: 'labels.icon', stylers: [ { visibility: 'off', }, ], }, { featureType: 'transit', elementType: 'geometry', stylers: [ { color: '#f2f2f2', }, { lightness: 19, }, ], }, { featureType: 'administrative', elementType: 'geometry.fill', stylers: [ { color: '#fefefe', }, { lightness: 20, }, ], }, { featureType: 'administrative', elementType: 'geometry.stroke', stylers: [ { color: '#fefefe', }, { lightness: 17, }, { weight: 1.2, }, ], }, ], } } return ( <Wrapper> <Header> <Logo src={logo} /> <H1>Welcome to React</H1> </Header> <GoogleMapWrapper> <GoogleMapReact bootstrapURLKeys={{ key: '' }} defaultCenter={{ lat: 43.0543451, lng: 141.3528293 }} defaultZoom={15} options={createMapOptions} onGoogleApiLoaded={({ map, maps }) => apiLoaded(map, maps, pins)} onChildClick={(key: string) => changeBalloon(key)} > { pins.map(( pin: { lat: number, lng: number, name: string }, index: number) => ( <Pin lat={pin.lat} lng={pin.lng} > {pin.name} { index === currentKey && 'こんにちは' } </Pin> )) } </GoogleMapReact> </GoogleMapWrapper> </Wrapper> ) } const Wrapper = styled.div` text-align: center; ` const Header = styled.header` background-color: ${Color.Primary}; height: 150px; padding: 20px; color: white; ` const Logo = styled.img` animation: App-logo-spin infinite 20s linear; height: 80px; @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ` const H1 = styled.h1` font-size: 1.5em; ` const Pin = styled.div<{ lat: number, lng: number }>` ` const GoogleMapWrapper = styled.div` height: 100vh; width: 100%; ` export default Appそれぞれの子コンポーネントをクリックすると"こんにちは"が表示されるだろう
onGoogleApiLoadedのmapsからgoogle apiのdraw機能を扱うことができる
参照useStateがよくわからねえという方は自分で勉強してください
まとめ
今回使用したライブラリの使用事例の記事がなかなか見つからなかったので、今回記事にし公開することにした
これからReactでgoogle mapの使用を検討している方に少しでも参考になれば幸いだ今回作成したサンプルはこちら
- 投稿日:2019-05-23T14:00:16+09:00
React + Reduxでカウントアプリを作る
はじめに
この記事では、React+Reduxを使用してカウントアプリを作ってみたいと思います。
下記が完了している前提で話を進めていくので、インストールされてない方は実施してください。
- Node.jsのインストール
- パッケージマネージャー yarnのインストール
- creat-react-appのインストール
参考:https://qiita.com/rspmharada7645/items/25c496aee87973bcc7a5
1. プロジェクトを作成する
まず、任意のディレクトリに移動し、create-react-appコマンドでプロジェクトを作成します。
$ create-react-app countApp
実行後、下記のようなメッセージが表示されていればOKです。
Initialized a git repository. Success! Created countApp at /Users/******/countApp作成したプロジェクト配下に移動し、下記のstartコマンドを実行します。
$cd countApp $yarn start実行後、ブラウザが起動し、以下の画面が表示されていればOKです。
2. 必要なパッケージをインストールする
作成したプロジェクト配下に移動します。
今回はredux
、react-redux
の2つのパッケージを使用するので、yarnを使ってインストールします。$cd countApp $yarn add redux, react-redux3. Reduxによるカウントアプリを実装する
今回は以下のようなフォルダー構成でカウントアプリを実装していきます。
. ├── node_modules ├── public ├── src ├── actions └── index.js ├── components └── App.js ├── reducers ├── index.js └── count.js ├── index.js └── serviceWorker.js ├── index.js ├── yarn.lock ├── package.json └── README.mdまず、
actions/index.js
を作成していきます。
ここでは、アクションの定義とアクションクリエーターを作成していきます。今回は、カウントの値をstateで管理します。stateの状態を変更するアクションは、
カウントを増やす、減らすの2つしかないため、作成するアクションは2つになります。actions/index.js//Action定義 export const INCREMENT = "INCREMENT"; export const DECREMENT = "DECREMENT"; //Action Creater(Action Createrを呼び出すことで、stateの更新が行われる) export const increment = () =>({ type : INCREMENT }); export const decrement = () =>({ type : DECREMENT });次に
reducers/count.js
とreducers/index.js
を作成します。
count.js
の中で、実行したaction
に応じて、stateの情報を更新します。reducers/count.jsimport { INCREMENT, DECREMENT, } from '../actions'; const initialize = { value : 0 }; export default (count = initialize, action) => { switch(action.type){ case INCREMENT: return { value : count.value + 1 }; case DECREMENT: return { value : count.value - 1 }; default: return count; } }reducers/index.jsimport { combineReducers } from 'redux'; import count from './count'; export default combineReducers({count});次に、
index.js
でstoreの作成とReduxをcomponentに対して利用可能にするために、Provider
でCounter
をラップします。index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import Counter from './components/Counter'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; import reducer from './reducers'; import * as serviceWorker from './serviceWorker'; const store = createStore(reducer); //Provider(供給する)でラップすることで、stateの情報を共有することが可能 ReactDOM.render( <Provider store={store}> <Counter /> </Provider>, document.getElementById('root') ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();最後に、コンポーネントの作成をしていきます。
components/Counter.jsimport React, {Component} from 'react'; import { connect } from 'react-redux'; import { increment, decrement } from '../actions'; class Counter extends Component { render(){ return ( <React.Fragment> <div> カウント値 : {this.props.value} </div> <div> <button onClick={this.props.increment}>+</button> <button onClick={this.props.decrement}>-</button> </div> </React.Fragment> ); } } //reduxで管理しているState情報をPropsで扱えるようにする const mapStateToProps = state => ({ value : state.count.value}); //Action関数をPropsで扱えるようにする const mapDispatchToProps = ({increment, decrement}); //componentとRedux Storeを接続する export default connect(mapStateToProps, mapDispatchToProps)(Counter);4. カウントアプリを動かしてみよう
yarn startで動かしてみましょう!
$cd countApp $yarn start下記のような画面が表示されていればOKです。
+ボタンを押した時に、カウントの値が増えて、-ボタンを押した時にカウントの値が減っていればOKです。
- 投稿日:2019-05-23T12:17:14+09:00
ReactプロジェクトがWindowsだけ動かない場合の処方箋
React Static プロジェクトを Windows でビルドしようとすると、大変不可解なクラッシュが発生しました。
$ react-static build Bundling application for Production... Cleaning dist... [✓] Dist cleaned Cleaning artifacts... : : (中略) : [✓] Site Data Downloaded Fetching Route Data... [==========================================================] 104/104 100% 11556/s 0.0s [✓] Route Data Downloaded (0.3s) Exporting HTML across 4 threads... Error: Invariant Violation: Failed exporting HTML for URL / (../src/pages/index.tsx): Minified React error #130; visit https://reactjs.org/docs/error-decoder.html?invariant =130&args[]=undefined&args[]= for the full message or use the non-minified dev environ ment for full errors and additional helpful warnings. - react-dom-server.node.production.min.js:10 ba [e-sea]/[react-dom]/cjs/react-dom-server.node.production.min.js:10:312 - react-dom-server.node.production.min.js:11 r [e-sea]/[react-dom]/cjs/react-dom-server.node.production.min.js:11:166 - react-dom-server.node.production.min.js:44 a.render [e-sea]/[react-dom]/cjs/react-dom-server.node.production.min.js:44:191 : : (中略) : Error: Error: Invariant Violation: Failed exporting HTML for URL about (../src/pages/about.ts x): Minified React error #130; visit https://reactjs.org/docs/error-decoder.html?invar iant=130&args[]=undefined&args[]= for the full message or use the non-minified dev env ironment for full errors and additional helpful warnings. - react-dom-server.node.production.min.js:10 ba [e-sea]/[react-dom]/cjs/react-dom-server.node.production.min.js:10:312 - react-dom-server.node.production.min.js:11 r [e-sea]/[react-dom]/cjs/react-dom-server.node.production.min.js:11:166 - react-dom-server.node.production.min.js:44 a.render [e-sea]/[react-dom]/cjs/react-dom-server.node.production.min.js:44:191 : : (後略) :他の環境で動かしてみたところ、下記のような状態でした。
- macOS: 問題なし
- (VirtualBox) Windows: 問題なし
- Windows PC: クラッシュ❗️
nodeJS のバージョンがバラバラでしたので、一時的に v12.2.0 に合わせてみましたが、やはり再現します。
OS 周りの環境依存問題でしょうか?解決策
プロジェクトルートまでのパスから シンボリックリンク を除外します。
これだけで嘘のように現象が改善しました。
その他、解決策候補
今回は複数環境の実験から、早々に OS 周りの環境依存の問題と目星をつけていたので除外していましたが、ググっている最中に出てきた解決策です。
react-dom
とreact
との間で、バージョン不整合が起きている可能性
- React Hooks の絡みで、特に 16.7.x ▶︎ 16.8.x 移行時とかに起きやすいそうです。
export default
しているのに、import { Hoge } from
形式でインポートしてしまっている所感
Windows におけるシンボリックリンクとかジャンクションとかその辺り、不遇すぎませんか、、??