- 投稿日:2020-01-25T20:42:39+09:00
SPA入門色々(Vue.js/Blazor/React)
概要
SPA(Single Page Application)という技術があることを知り、フロントエンドフレームワークを触ってみるいい機会になるのでないかと思い、Vue.js/Blazor/Reactをまとめて触ってみた。
SPAのルータのページ切り替えを見て従来のiframeや生のJavaScriptとできることがどのように違うのかということも疑問に思ったので同じようなこちらでも同じような感じで実装を試みた。作ったSPAの仕様
- 上部にメニューバー
- Home/Counter/Todoの独立したWebアプリをコンポーネントとして所持。
- Home
- 別のコンポーネントを読み込んで文字を表示するだけなアプリ。
- Counter
- Blazorの実行サンプルに付いてきたカウンターアプリ。
- Todo
- Vue.jsミニハンズオン(TODOリスト作成)を8割位実装したTodoアプリ。
成果物
Vue.js | Blazor | React | iframe | Vanilla-JS
ソースとか(GitHub)感じたメリット・デメリット
SPA全体
メリット
- 本来サーバーサイドで受け持っていたような大幅なhtmlの書き換えが簡単にできる。
- htmlを分割して部品化できる。
- 単一htmlとしてシームレスにページ切り替えられる。
デメリット
- フレームワークありきな存在であること。
- 読み込み時に時間がかかりがち。
- サーバーありきな存在なので気軽に公開しにくい。
Vue.js
メリット
- 本来のhtmlに近い書き方でhtmlを複数ファイルに分割できる(.vue)。
- 変数をフォームとバインド(同期)できる。
- v-for\v-ifでタグをループできる。
- CSSをコンポーネントに閉じ込めることができる。
- vue-cliがあれこれ揃えてくれるのでセットアップが簡単。
- 地味にラップされてるWebStorage。
デメリット
- ルーターの設定が少し煩雑。
- exportするオブジェクトの構成が少し煩雑。
Blazor
メリット
- C#だけでほぼ完結して処理を書くことができる。
- 本来のhtmlに近い書き方でhtmlを複数ファイルに分割できる(.razor)。
- 変数をフォームとバインド(同期)できる。
- @for\@ifでタグをループできる。
- ルーターの設定が容易。
- イベントハンドラの呼び出しが素直。
- 他のコンポーネントを呼びだすためのimportが不要。
デメリット
- まだリリース前。
- ページを開いた時の読み込みが遅い。
- @がゲシュタルト崩壊する。
- DOM由来のAPIを扱うのが面倒。
- 変数バインドと@onchangeを同時に使うことができない。
- CSSに対するサポートがない。
- ライフサイクルが少し心もとない?
React
メリット
- あくまでもJavaScriptが主体となっている。
デメリット
- htmlじゃないhtml。
- htmlとして書くとJSXの形式が直感的じゃない。
- フォームと変数のバインドに癖がある。
- stateの更新が手間。
- 必要なプラグインは基本的物でも各自そろえる必要がある。
- CSSの扱いに癖がある(スコープ化するのも手間っぽい)。
- 専用のループ構文は存在しない。
iframe
メリット
- フレームワークが不要(htmlとJavaScriptの知識だけで良い)。
- いにしえの枯れた技術。
- 適当なホームページスペースに上げても動く。
デメリット
- そもそも、SPAではない。
- htmlが独立しておりそれぞれの連携が面倒。
- URLが項目ごとに変わらない。
- 各フレームのサイズ調整が困難。
- フォームと変数のバインドとかはない。
生JavaScript
メリット
- あるかな?
デメリット
- 生成した要素の状態管理が絶望的。
- 再レンダリングが困難
まとめ
触り始めたばかりの素人の感覚としてはReactよりもVue.jsが触りやすいもののように思えた。
アニメーションを付けたりする段階まで行くとまた印象が変わってくるのかもしれない。
Blazorはwasmというものの可能性を感じるものだと感じた。
また、全体的な構成がVue.jsと似ており、触りやすいような印象を受けた。
ちなみにAngularは触ろうとしたけど面倒くさくなって投げた(情報量少なくないですか…?)
- 投稿日:2020-01-25T17:31:01+09:00
React向けチャートライブラリRechartsで円グラフ
React向けチャートライブラリRechartsで円グラフ
?グラフの種類
AreaChart:面グラフ。折れ線グラフに基づき、定量データを表示したグラフ。
BarChart:棒グラフ。四角い棒の長さで何らかの値を表現するグラフ。
LineChart:折れ線グラフ。散布図の一種であり、プロットされた点を直線でつないだグラフ。
ComposedChart:折れ線グラフ、面グラフ、棒グラフで構成されるグラフ。線のような単一のタイプのチャートを描画する場合は、LineChartを使う。
PieChart:円グラフ。丸い図形を扇形に分割し、何らかの構成比率を表したグラフ。
RadarChart:レーダーチャート。複数の項目の大きさを一見して比較することのできるグラフ。
RadialBarChart:円弧で表現する棒グラフ。
ScatterChart:散布図(分布図)。縦軸、横軸に2項目の量や大きさ等を対応させ、データを点でプロットしたグラフ。
FunnelChart:ファンネルチャート。販売プロセスの段階を表し、各段階の潜在的な収益額を示すためによく使用されるグラフ。
Treemap:ツリーマップ。ネストされた長方形のセットとして階層状のツリー構造のデータを表示したもの。円グラフに関連するコンポーネント
<PieChart />
コンポーネント円グラフの大元となるコンポーネント。全体のサイズ等を定義します。
☘️プロパティ
- width : チャートコンテナーの幅。
- height : チャートコンテナーの高さ。
- margin : コンテナの周りの空白のサイズ。例){ top: 5, right: 5, bottom: 5, left: 5 }
?イベント
- onClick
- onMouseEnter
- onMouseLeave
?子コンポーネント
<PolarAngleAxis />
:<PolarRadiusAxis />
:<PolarGrid />
:<Legend />
:<Tooltip />
:<Pie />
:<Customized />
:
<Pie />
コンポーネントグラフのセクター全体についてを定義します。
プロパティ
- cx : 中心のx座標。パーセンテージを設定した場合、最終値はコンテナ幅のパーセンテージを乗算することにより取得されます。
- cy : 中心のy座標。パーセンテージを設定すると、コンテナの高さのパーセンテージを乗算することで最終的な値が取得されます。
- innerRadius : すべてのセクターの内半径。パーセンテージを設定した場合、最終値は、幅、高さ、cx、cyで計算されるmaxRadiusのパーセンテージを乗算することにより取得されます。
- outerRadius : すべてのセクターの外半径。パーセンテージを設定した場合、最終値は、幅、高さ、cx、cyで計算されるmaxRadiusのパーセンテージを乗算することにより取得されます。
- startAngle : 最初のセクターの開始角度。円の頂点から反時計回りで始めたい場合は、{startAngle:90, endAngle:450}、同じく円の頂点から時計回りで始めたい場合は、{startAngle:450, endAngle:90}を指定する。
- endAngle : 最後のセクターの終了角度。
- minAngle : 各非ゼロデータの最小角度。
- paddingAngle : 2つのセクター間の角度。
- nameKey : 各セクターの名前のキー。
- dataKey : 各セクターの値のキー。
- legendType : 凡例のアイコンのタイプ。
'none'
に設定すると、凡例項目はレンダリングされません。'line'
'square'
'rect
'circle'
'cross'
'diamond'
'square'
'star'
'triangle'
'wye'
'none'
- label :
true
の時、値がラベルとして表示されます。デフォルトはfalse
。- labelLine :
true
の場合、セクターとラベルを結ぶ線を描画。- data : 各要素がオブジェクトであるソースデータ。
- activeIndex : Pieのアクティブセクターのインデックス。このオプションは、マウスイベントハンドラーで変更できます。
- activeSphape : アクティブなセクターの形状。
- isAnimationActive :
true
に設定すると、パイのアニメーションが有効化。- animationBegin : アニメーションをいつ開始するかを指定します。このオプションの単位はmsです。
- animetionDuration : アニメーションの継続時間を指定します。このオプションの単位はmsです。
- animationEasing : イージング関数のタイプ。
'ease'
'ease-in'
'ease-out'
'ease-in-out'
'linear'
Function
イベント
- onAnimationStart
- onAnimationEnd
- onClick
- onMouseDown
- onMouseUp
- onMouseMove
- onMouseOver
- onMouseOut
- onMouseEnter
- onMouseLeave
子コンポーネント
-<Cell />
-<LabelList />
<Cell />
コンポーネント各セクターの色や枠を定義します。
プロパティ
- fill : セクターの塗りつぶし色を指定。
- stroke : セクターの枠線の色を指定。
- strokeWidth : セクターの枠線の太さを数値で指定。
<LabelList />
コンポーネント各セクターに対応するラベルを定義します。
<Legend />
コンポーネントグラフの凡例を定義します。
プロパティ
- width : 凡例の幅。
- height : 凡例の高さ。
- verticalAlign : 凡例の位置。
- formatter : 凡例の項目表示をカスタマイズ。
<ToolTip />
コンポーネントセクターにマウスオーバーしたときのツールチップを定義。
プロパティ
- content : ツールチップをカスタマイズ。
- 投稿日:2020-01-25T12:33:26+09:00
react-hooks で redux を書いてみた話
react-hooksでreduxを真剣にいじる機会に恵まれないので、練習がてらボタンをおしたら数字が増えるカウンターを作ってみました
。環境構築
まずは環境構築。絶対に必要なredux関連と型情報をワンセットでインストールしましょう。
npx create-react-app hooks --template typescript npm install react-redux npm install @types/react-redux npm install redux npm install @types/redux生成されたテンプレートのプロジェクトをいじっていきます。
action
まずはactionから定義をします。
今回実装するアクションは以下の通りです。
- カウントが増える
INCREMENT
- カウントが減る
DECREMENT
export const INCREMENT = "INCREMENT"; export const DECREMENT = "DECREMENT"; export function increment() { return { type: INCREMENT } } export function decrement() { return { type: DECREMENT } }reducer
次に実際に値を更新したり、編集したりするreducerを実装します。
import {INCREMENT, DECREMENT} from "./action"; const initialState = { count: 0 } function reducer(state = initialState, action: {type: string}) { switch(action.type) { case INCREMENT: return { count: state.count + 1 }; case DECREMENT: return { count: state.count - 1 }; default: { return { count: 0 } } } } export default reducer;普通であれば、
combinereducer
で結合しますが、今回はreducerを一つしか作らないので、生のreducerをそのままexportしています。store
値を格納するstoreです。
reducerを受け取ってstoreを生成していますが、このままだとstateの型が不定で後々のつなぎこみで不都合が生じるので、RootState
を定義しています。import {createStore} from "redux"; import reducer from "./reducer"; export type RootState = ReturnType<typeof reducer> export const store = createStore(reducer);
ReturnType
は関数の戻り値の型として生成して返します。今回であれば、reducerの戻り値(stateのproperty構造)を型として返してくれます。つまり、今回のRootStateの中身はこのような感じ
type RootState = { count:number }connect(component)
hooks を使ったものはこの部分が大きく違っています。いままでは、
mpaStateToProps
mapDispatchToProps
connect
を使ってましたが、
useSelector
: storeからstateを取得
useDispatch
: storeへ変更を伝えるためのdispatch関数を取得↑の二つを使うだけでよくなります。
先程、作った
RootState
はuseSelector
に使います。 stateから情報を受け取るのですが、この時stateの情報がないと、型が解決できなくなるので、RootState
を作成して型を確定させるのです。import React from "react"; import { useSelector, useDispatch} from "react-redux"; import {RootState} from "./store"; import {increment, decrement} from "./action"; export const Counter: React.FC = () => { const dispatcher = useDispatch(); const state = useSelector((state:RootState) => state); return ( <div className="counter"> <h1> counter </h1> <p>{state.count}</p> <button onClick={() => dispatcher(increment())} >count up</button> <button onClick={() => dispatcher(decrement())} >count down</button> </div> ) }つなぎ込み
あとは、 Providerにstoreを渡して終了です。
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import {store} from "./store"; import {Provider} from "react-redux" ReactDOM.render(<Provider store={store}> <App /> </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();最後に
mpaStateToProps
mapDispatchToProps
connect
という黒魔術を使わなくてよくなり、且つhooksの文法をしっていれば処理の流れを追いやすくなったと思います。
というわけで、時間があれば昔のreactコードのリファクタを死体お思います。それでは、よいreactライフを
- 投稿日:2020-01-25T11:32:58+09:00
【超初心者】React のcontextAPIを使ってみる
はじめに
reactのcontextAPIについて勉強したのでメモ。
最終的には文字を入力して、submitを押すと入力した文字が出てくるようにして、todoアプリのようにしたいのですが、そこにくる前に力着きました。
現状では、文字を入力するとその文字が表示されてます!
初心者なので間違ってたりしたり、アドバイスがあったらコメントよろしくお願いします。コードは親コンポーネントのApp.jsの中に、inputやsubmitできるbuttonなどがあるform.js と 入力された文字がPrivider Consumerを通して渡されて表示されるtodo.jsの2つが子コンポーネントとしてあります。
参考記事
先に上記の記事を読んで、違うバージョンのcontextAPIを見てみたいと思った人用です。上記の記事がめちゃくちゃわかりやすかったです!!しかも動画つき。
実装方法
npm init react-app contextAPI作った物を yarn start します。
ファイルの構造は
-- App.js
-- src
がありsrcの中にcomponents,contexts
components の中に form.js todo.js
contexts の中に formContext.jsディレクトリのわかりやすい書き方がわかりませんでした。見にくくてすみません。
コード
App.jsimport React from "react"; import "./App.css"; import Form from "./components/form"; class App extends React.Component { constructor(props) { super(props); this.state = { text: "" }; } render() { return ( <div className="App"> <h1>hello</h1> <Form /> </div> ); } } export default App;src/conponents/form.jsimport React from "react"; import Todo from "./todo"; import FormContext from "../contexts/formContext"; class Form extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.state = { text: "" }; } onSubmit = event => { this.setState({ text: this.state.text }); }; handleChange = event => { this.setState({ [event.target.name]: event.target.value }); console.log(event.target.value) }; render() { return ( <div> <input type="text" name="text" onChange={this.handleChange} /> <button onClick={this.onSubmit}>submit</button> <FormContext.Provider value={this.state}> <Todo /> </FormContext.Provider> </div> ); } } export default Form;FormContext.Providerでvalueとして上のconstructorのtextをtodo.jsに渡してます。textだけじゃなくて、handleChange、onSubmit などの関数も渡せます。
src/components/todo.jsimport React from "react"; import FormContext from "../contexts/formContext"; const Todo = () => ( <FormContext.Consumer> {({ text }) => { console.log(text); return <div>{text}</div>; }} </FormContext.Consumer> ); export default Todo;ここではtextを受け取ってます。つまり、inputで入力された文字を受け取ってます。
src/contexts/formContexts.jsimport { createContext } from "react"; const FormContext = createContext(); export default FormContext;ここはあまり詳しくわからなかったのですが、Providerとconsumerの間で処理している物らしいです。
- 投稿日:2020-01-25T10:24:40+09:00
【OSS貢献記録】emoji-martをReact 17に対応
前回のOSS貢献記録はこちら。
はじめに
emoji-martは、下の画像のように絵文字選択フォームを実現するReactコンポーネントです。
このコンポーネントを実際に利用していたら、次のような警告文がJavaScriptのコンソールに表示されました。
Warning: componentWillReceiveProps has been renamed, and is not recommended for use. See https://fb.me/react-unsafe-component-lifecycles for details. * Move data fetching code or side effects to componentDidUpdate. * If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://fb.me/react-derived-state * Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder. Please update the following components: NimblePicker
NimblePicker
コンポーネントでReact 17で廃止予定のメソッドcomponentWillReceiveProps
が使われていて、このままだとemoji-martはReact 17で使用できなくなります。 詳細はReact公式ブログの記事に記載されています。自分がこの問題に気付いたときはReact初心者でした。なので、とりあえずissueを作って、これを見た誰かに直してもらおうと思いました。しかし、2ヶ月が経っても直される気配がありませんした。
これはまずいと思って、自分で改修できないと決意しました。2ヶ月間Reactを触り続けてReactの理解度が上がっていたので、自分で改修できるのではないかという自信が生まれていました。
改修内容
実際に出したPRはこちらです。
Qiitaの記事『React.js のライフサイクルメソッド componentWillReceiveProps の廃止対応』を参考しながら、改修方法を考えました。
Nimble Picker
のcomponentWillReceiveProps
ではprops
から新しいstate
を作っています。なので、今回は記事中の2番目の方法「getDerivedStateFromProps に置き換える」方法をで採用しました。今気付いたこと
getDerivedStateFromProps
ではstateの変更箇所だけを返せばいいので、static getDerivedStateFromProps(props, state) { if (props.skin) { return { skin: props.skin } } else if (props.defaultSkin && !store.get('skin')) { return { skin: props.defaultSkin } } return null }の方が適切でしたね。