- 投稿日:2020-02-21T15:34:49+09:00
Magento2のフロントエンドがもうカオスじゃなくなりそうです
去年ヨーロッパのMagentoコミュニティイベントに大量に参加したところ、今年はMagentoフロントエンドにとってエキサイティングな年になるという予感が募ってきました。
そこでMagentoフロントエンドにこれから見たい変化と現状について自分なりにまとめてみました。もくじ
1. Magentoの現状のフロントエンドのイケてない点
2. Magentoフロントエンドの銀河への道筋
3. Magento開発者の銀河への道筋1. Magentoの現状のフロントエンドのイケてない点
2015年Magento2.0のリリース以来世界中のデベロッパーからフロントエンドの扱いづらさを叩かれ続けていたMagento。
主に批判されているのは以下のような点です。・jQueryをメイン機能として採用していること。(大規模プロジェクトには向かないレガシー技術という声が多い)
・Knockout.JSを始めとする大量のJSライブラリを導入したことで可読性が非常に悪いこと。
・JSの問題は実はUIコンポーネント(XMLレイアウト, Blockクラス, PHTMLテンプレート、JSバインディングのMIX)という化け物みたいな複雑な問題の一角に過ぎないこと。Magentoフロントエンドは開発、デバッグは難しいしパフォーマンスもなかなか出にくいという問題がありました。
2. Magentoフロントエンドの銀河への道筋
とはいえコミュニティドリブンが売りのMagentoなのでこういったフィードバックを取り入れてよりモダンで簡潔なフロントエンドにしていこうという動きが活発です。
大きな流れではPWAに移行していくことは固まっていている印象で、現在Magento公式の PWA Studio か、Vue StorefrontやScandiPWA などのテーマとフロントエンドをごっそり入れ替えるといった方法があります。
VueStorefrontはその名の通りVue、他の2つはReactをベースに使っています。
MagentoがPWAに移行していきたい理由としては
・API中心のアーキテクチャにすることで単方向データフローを実現し、開発、デバッグを容易にすること
・コードの可読性を上げること
・パフォーマンスを上げること
・モバイルファーストのアプリにしていきたいことなどがあり、ポイントは開発体験、顧客体験の向上だと思います。
PWA Studio、Vue Storefront、Scandiの中でどのソリューションが優れているかということは今のところなさそうで、気に入ったものを使うのがいいというのが通説のようです(ヨーロッパのMagentoイベントでは大体PWA Studioを推していましたが)。
ちなみにVue StorefrontはShopifyなど他のECパッケージにも対応しています。3. Magento開発者の銀河への道筋
すごく魅力的で、今すぐMagentoの標準フロントエンド捨てて全部PWAに変えよう!
となるかも知れないのですが、どうやら今すぐにとはいきそうにありません。。Magentoのコミュニティイベントなどの発表を聞いていると、PWAはもう準備できてる!という内容が多く、PWA Studioなどを使って実際にローンチしているサイトも出てきているようですが、
PWA Studioは日本語で使うにはまだまだ厳しいこともあるようです。
PWA-Studio(Magento2)で遊んでみた。(2019/12) - QiitaPWA Studioのマイルストーンを見る限りは機能面の開発は終わってエクステンションの手に入りやすさやパフォーマンスの問題を残すのみ…のように見えるのですが、MagentoのGraphQLエンドポイントがまだ完全に準備できてるわけではないのがやはり大きそうです。
大体90%くらいのカバレージということ。決済方法を新たに開発しないといけないというのも問題ですね。
またMagento2.4からはカタログ、顧客、カートモジュールを手始めに新しいマイクロサービスアーキテクチャに切り替えがあり、それがGraphQLにも大きく影響を与えることになります。
似たような問題はVue StorefrontやScandiPWAでも出てくるであろうことは容易に想像できるので、やはりまだ怖くて踏み切れない感じですね。。
近いうちにGraphQL100%カバーされるのでしょうか。されてほしい。PWA Studioが日本使用に耐えられるようになるまでの間に少しでもパフォーマンスを上げたフロントエンドを手に入れるために、しばらくの間はMageSuite.ioを使うという手もあります(詳細はまた次回にでも)。
とはいえいずれはPWA(めちゃくちゃ早い、低コスト、モバイルファースト、スマホにインストール可能、クロスブラウザ対応、オフラインモード)に切り替えなければECサイトオーナーとしても開発側としても淘汰されていってしまうので、なんとか時間をとって準備していきたいところです。
多言語対応への貢献は積極的にしていかねばなと思っています。あとEC x PWA勉強会とかやりませんか。
おまけ - リソースなど(この部分更新していきます)
Magento x PWAイベント
Reacticon
MagentoのフロントエンドのリーダーともいえるYireo主催のイベントで、MagentoのPWAがメインの3日間のイベントです。でもオランダ…
公式リソース
ScandiPWA Documentation
Introduction to Vue Storefront
PWA Studio Overview | Magento
動画リソース
Vue Storefront Starting Guide 2019 - YouTube
Progressive Web Application (PWA) Learning ... - Magento U
- 投稿日:2020-02-21T12:20:31+09:00
React, Redux 初心者が、Hooks 時代の React, Redux, React-Redux に触れてみて感じたこと
筆者は、 Reactも Reduxも React-Reduxも初心者で、プライベートでちょっと触っている程度のペーペーです。
React に Hooks が導入されてしばらく経ちますが、React-Reduxにも Hooks が導入されていることを最近知りました。ちょっと試して見たところ、Hooksを使うことで少しだけモヤモヤが晴れてきたので、これまで理解したことを整理したいと思います。
useState, useReducer は(規模の大きな開発には)使わない
useState
,useReducer
はとっつきやすくてわかりやすいです。なので、ちょっとしたプログラムでは使うと思います。便利です。YoutubeのReact Today and Tomorrow and 90% Cleaner React With Hooks で、初めてこれを知った時は、感動すら、覚えました。ですが、今回、自分でちょっと触ってみて、複数のコンポーネントにまたがるような情報を管理する必要が出てくると面倒そうだと感じました。
また、 テストも面倒なんじゃないかなと思ったりしてます。以下が、毎度お馴染みの、
useState
を使った実装例です。src/ComponentUseState.jsimport React, { useState } from "react"; const ComponentUseState = () => { const [num, setNum] = useState(0); return ( <div> <h2>Using useState</h2> Number: {num} <button onClick={() => setNum(num + 1)}>+</button> <button onClick={() => setNum(num - 1)}>-</button> </div> ); }; export default ComponentUseState;シンプルに書けるのですが、足し算、引き算の処理(ロジック)が、 コンポーネントの中に含まれている状態で、ちょっとテストが面倒そうです。
また、別のコンポーネントから、num
を参照したり、変更したりするのが面倒そうです。
useReducer
を使うとこんな感じになります。src/ComponentUseReducer.jsimport React, { useReducer } from 'react' const initialState = {num: 0}; const reducer = (state, action) => { switch(action.type) { case 'decrement': return {...state, num: state.num - 1} case 'increment': return {...state, num: state.num + 1} default: return state; } } const ComponentUseReducer = () => { const [state, dispatch] = useReducer(reducer, initialState) const { num } = state return ( <div> <h2>Using useReducer</h2> Number: {num} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </div> ); }; export default ComponentUseReducer;
useState
を使うより、コードが長くなってしまいますが、useReducer
を使うことによって、 足し算、引き算のロジックの部分は、reducer
関数の中に閉じ込めて、コンポーネントのComponentUseReducer
とは、分離することができます。
reducer
は、純粋な JavaScript のコードなので、単体で実行することができ、テストもしやすそうです。(本来、reducer は、コンポーネントとは別ファイルに分割すべきです。ここでは、敢えて分割しておりません。)const reducer = (state, action) => { switch(action.type) { case 'decrement': return {...state, num: state.num - 1}; case 'increment': return {...state, num: state.num + 1}; default: return state; } }例えば、以下のようにChrome のデベロッパーツールの console でも単独で実行できます。
コードの中で、初期状態を示しているのが
initialState
です。この状態をボタンが押されるなどのイベントによってどう変化させていくのかというロジックの部分が reducer になります。ですから、この reducer の部分と initialState をどうするかは実際の開発では自分で考えて実装しないといけないところですね。(私はこのことに気づく(理解する)のに、随分と、時間がかかりました。)ただ、
reducer
関数の書き方には、決まり事があります。
- 引数は、state と action の2つ。
action
は、type
で、state をどう変更させるかを指定する。- 戻り値は、変更後の新しい state にする。
ということで、コンポーネントとロジックを分けることができたのですが、やっぱり、
num
を複数コンポーネントで共有するのはちょっと面倒そうです。React-Redux の useSelector と useDispatch を使う。 connect は使わない。
そこで、 Redux と Hooks 時代の React-Redux です。
Redux と React-Redux を使うとこんな感じになります。src/ComponentUseReactRedux.jsimport React from "react"; import { createStore } from "redux"; import { Provider, useSelector, useDispatch } from "react-redux"; const initialState = { num: 0 }; const reducer = (state, action) => { switch (action.type) { case "decrement": return { ...state, num: state.num - 1 }; case "increment": return { ...state, num: state.num + 1 }; default: return state; } }; const store = createStore(reducer, initialState); const ComponentUseReactRedux = () => { return ( <div> <h2>ComponentUseReactRedux</h2> <Provider store={store}> <ChildComponentUseReactRedux /> </Provider> </div> ) } const ChildComponentUseReactRedux = () => { const num = useSelector(state => state.num); const dispatch = useDispatch(); return ( <div> <h3>Using useSelector, useDispatch</h3> Number: {num} <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "decrement" })}>-</button> </div> ); }; export default ComponentUseReactRedux;随分と長くなってしまいました
initialState
とreducer
は、useReducer
を使った例src/ComponentUseReducer.js
と同じです。初期値とロジックなので、ここは変わりません。const store = createStore(reducer, initialState);で、情報の格納先を1つ作ります。イメージ的には、これを複数のコンポーネントから参照する感じです。これはお約束の1つです。
ComponentUseReactRedux
では、 React-Redux で提供されるProvider
を使って、 以下のようにComponentUseReactRedux
の子コンポーネントからも、store
の情報にアクセスできるようにします。これもお約束ごとです。<Provider store={store}> <ChildComponentUseReactRedux /> </Provider>子コンポーネントの
ChildComponentUseReactRedux
の中では、useSelector
を使ってnum
の値を参照できるようにします。const num = useSelector(state => state.num);ここで、登場する、 state は、 reducer 関数の引数の state と同じものです。以下のように書き換えた方がわかりやすいかも知れません。
const selector = state => { return state.num; } const num = useSelector(selector);また、
useDispatch()
を使って、dispatch
を取得します。const dispatch = useDispatch()
ChildComponentUseReactRedux
の残りの部分は、useReducer
を使った場合と同じですね。で、この書き方だと、複数のコンポーネントから、同じ
num
を参照できるのです。試しに、
ComponentUseReactRedux
の中のChildComponentUseReactRedux
を2つにしてみます。const ComponentUseReactRedux = () => { return ( <div> <h2>ComponentUseReactRedux</h2> <Provider store={store}> <ChildComponentUseReactRedux /> <ChildComponentUseReactRedux /> </Provider> </div> ) }するとその2つの子コンポーネントの
num
の値は連動して変化します。まとめ
useState
やuseReducer
は 規模の大きな開発には使わない。- 初期ステータスをどう定義するか、また、
reducer
によってその初期ステータスをどう変化させるか(ロジック)は自分で考えて実装しないといけない。createStore
を使ってステータスの格納先を作成する。Provider
を使ってコンポーネント間で、ステータスを共有。useSelector
を使って、ステータスのどの値を参照するのか指定する。useDispatch
を使って、dispatch
を取得する。connect
は使わない。 (mapStateToProps とか mapDispatchToProps とか私には難しい...。)まだ整理できていないこと
useContext
を使う方法もありそうなのですが、良く調べてません。- redux-thunk や redux-saga などの middleware の部分をどうするのが良いのか、まだ自分の中で整理ができていません。 (
useCallback
が使えそうですが...)追記
今回は、 dispatch の引数(Action)を素のまま書きましたが、この部分を関数化するのが、より正しいお作法なのかも知れません。
const increment = () => { return { type: 'increment' }; } const decrement = () => { return { type: 'decrement' }; }を定義して、 dispatchを使うときに、
dispatch(increment())
やdispatch(decrement())
のようにします。