のんラボ

自分的Storybook + Vue環境の作成方法(CLIなし)

2021/02/10 2021/02/10

自分的Storybook + Vue環境の作成方法 こんにちは。Nonです。 今回は @storybook/addon-actionsを利用したときのTips | のんラボ @storybook/addon-actionsを利用したときのTipsこんにちは。Nonです。最近、フロントのこともガッツリと関わるようになってきました。デザインのセンスはあまりありませんが、デザイ... でTipsだけまとめているStorybookの環境構築や設定について自分なりに試行錯誤したものを書こうと思います。 弊社 株式会社スマレジ 企業サイト 株式会社スマレジは、インターネット社会においてデザインとテクノロジーを駆使して「いい未来をつくる」ことを理念としたテクノロジー企業です。レジ、自販機、券売機、eコマースなど、世界中に広がる販売データの... では、デザイナーとフロントの協業というのがまだまだ弱く、フロント開発におけるテンプレートといいますか、明確なフローというのが確立していません。私はバックエンドの人間なので、デザイナーとどのように仕事をしていくかなどもわからなかったので、色々調べていった結果Storybookというものを発見しました。 なのでここでの目標は デザイナーとコンポーネント単位のデザインレビューや、コードレビューを行うこと Nuxtのようなフレームワークに依存せず、コンポーネント単位での開発ができること 同様にNuxtのようなフレームワークに依存せず、コンポーネントをinstall or importできること としています。 Storybook導入 storybookを使用するので、当然インストールしなければなりません。他のサイトやブログなどではNuxt + storybook のような構成を取っていますが、Nuxtに依存したくないと思いましたので、このようなフレームワークの導入は検討していません。 しかし、Vue.jsには依存しています。 また、storybook-cliも利用していません。使った方が簡単ですが、最小構成にしたい and cliのinstallしたくない などの理由があります。そのうちCLI版の記事も書くかも。 storybook インストール copied.npm i @storybook/vue --save-dev コマンドでstorybookを起動できるようにpackage.jsonに下記を追加。 この時点ではstorybookの設定をしていないので起動できません。 copied."scripts": { "storybook": "start-storybook", "build-storybook": "build-storybook", }, Vue インストール copied.npm i vue --save-dev Vue loader インストール copied.npm i vue-loader vue-template-compiler --save-dev eslint インストール copied.npm i eslint eslint-loader eslint-plugin-vue --save-dev linterの設定はプロダクトに合わせて設定してください。 コマンドでlinterを起動できるように、package.jsonに下記を追加 copied."scripts": { "eslint": "eslint --ext .js,.vue ./", "eslint-fix": "eslint --fix --ext .js,.vue ./" }, Storybook設定 {project_root}/.storybook/main.js に下記を追加 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], }; Storybook用のjsの場所はプロジェクト毎に決めていいと思います。 コンポーネント追加 {project_root}/src/components/atoms/Button/BaseButton.vue を配置。 copied.<template> <button>button</button> </template> <script> export default { name: 'BaseButton' }; </script> <style scoped> </style> {project_root}/src/components/atoms/Button/Button.stories.js を配置。 copied.import {storiesOf} from '@storybook/vue'; import BaseButton from './BaseButton'; storiesOf('Button', module) .add('buttons', () => ({ components: {BaseButton}, template: '<div>' + '<base-button></base-button>' + '</div>', })); 外部でパッケージ利用できるように設定する npm i でパッケージインストールしたときに使用できるようにコンポーネントを登録するスクリプトを記載。 これに関しては色々なリリース方法があるので、用途に応じて変更して下さい。(CDNとかplane jsとか) 詳細はこちら。 Vue コンポーネントを npm パッケージ化する — Vue.js Vue.js - The Progressive JavaScript Framework copied.import BaseButton from './src/components/atoms/Button/BaseButton'; const Components = { BaseButton, }; export function install(Vue) { if (install.installed) return; install.installed = true; Object.keys(Components).forEach((component) => { Vue.component(component, Components[component]); }); } const plugin = { install, }; let GlobalVue = null; if (typeof window !== 'undefined') { GlobalVue = window.Vue; } else if (typeof global !== 'undefined') { GlobalVue = global.Vue; } if (GlobalVue) { GlobalVue.use(plugin); } export default Components; css / scss / sass 関連 2021/02現在、最新のwebpackでsass-loaderを動かすとバージョンの整合性チェックでエラーが発生するので、10系 の最新バージョンを指定してインストールする。 copied.npm i style-loader css-loader sass-loader@10.1.1 --save-dev {project_root}/.storybook/main.js に下記を追加 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', '@storybook/addon-storysource', '@storybook/addon-viewport', 'storybook-readme', ], webpackFinal: (config) => { config.module.rules.push({ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }); return config; } }; copied.npm run storybook ※↓エラーが発生する場合あり。 copied.Node Sass does not yet support your current environment: OS X 64-bit with Unsupported runtime (88) For more information on which environments are supported please see: https://github.com/sass/node-sass/releases/tag/v4.13.1 storybookを起動しようとしてエラーが出た場合、node-sassをインストールしてビルドしておくこと。 copied.npm i node-sass --save-dev npm rebuild node-sass npm run storybook 試しにscssファイルなどを作成してimportしてみる。 VueのScoped CSSで試しても大丈夫です。 {project_root}/src/sass/atoms/_button.scss copied..btn { color: red; } {project_root}/src/sass/app.scss copied.// My atom's scss. @import 'atoms/button'; {project_root}/.storybook/preview.js でscssファイルをimportする copied.import {addDecorator, addParameters} from "@storybook/vue"; import {INITIAL_VIEWPORTS} from "@storybook/addon-viewport"; import {addReadme} from 'storybook-readme/vue'; import '../src/sass/app.scss'; addParameters({ viewport: { viewports: INITIAL_VIEWPORTS } }); addDecorator(addReadme); アドオンを追加 storybookを使うにあたって便利なアドオンを追加します。これも各プロダクトによって導入するかどうかを決めて大丈夫です。 @storybook/addon-actions copied.npm i --save-dev @storybook/addon-actions {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', ] }; {project_root}/src/components/atoms/Button/BaseButton.vue にクリックイベント設置。 copied.<template> <button @click="$emit('click')"> button </button> </template> {project_root}/src/components/atoms/Button/Button.stories.js を書き換え。 copied.import {storiesOf} from '@storybook/vue'; import BaseButton from './BaseButton'; import {action} from '@storybook/addon-actions'; storiesOf('Button', module) .add('buttons', () => ({ components: {BaseButton}, template: '<div>' + '<base-button @click="action"></base-button>' + '</div>', methods: { action: action('clicked') } })); @storybook/addon-knobs copied.npm i @storybook/addon-knobs --save-dev {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', ] }; {project_root}/src/components/atoms/Button/Button.stories.js を書き換え。 copied.import {storiesOf} from '@storybook/vue'; import BaseButton from './BaseButton'; import {action} from '@storybook/addon-actions'; import {text} from '@storybook/addon-knobs'; storiesOf('Button', module) .add('buttons', () => ({ components: {BaseButton}, props: { buttonLabel: { default: text('label', 'button') }, }, template: '<div>' + '<base-button @click="action">{{ buttonLabel }}</base-button>' + '</div>', methods: { action: action('clicked') } })); {project_root}/src/components/atoms/Button/BaseButton.vue を書き換え。 copied.<template> <button>button</button> </template> <script> export default { name: 'BaseButton' }; </script> <style scoped> </style> @storybook/addon-storysource copied.npm i @storybook/addon-storysource --save-dev {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', '@storybook/addon-storysource', ] }; @storybook/addon-viewport copied.npm i @storybook/addon-viewport --save-dev {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', '@storybook/addon-storysource', '@storybook/addon-viewport', ] }; {project_root}/.storybook/preview.js にviewport(iPhoneなどの画面情報)情報を追加。デフォルトでも使用可能。 copied.import {addParameters} from "@storybook/vue"; import {INITIAL_VIEWPORTS} from "@storybook/addon-viewport"; addParameters({ viewport: { viewports: INITIAL_VIEWPORTS } }); storybook-readme copied.npm i storybook-readme --save-dev {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', '@storybook/addon-storysource', '@storybook/addon-viewport', 'storybook-readme', ] }; {project_root}/.storybook/preview.js に設定を追加。 copied.import {addDecorator, addParameters} from "@storybook/vue"; import {INITIAL_VIEWPORTS} from "@storybook/addon-viewport"; import {addReadme} from 'storybook-readme/vue'; addParameters({ viewport: { viewports: INITIAL_VIEWPORTS } }); addDecorator(addReadme); {project_root}/src/components/atoms/Button/Button.stories.js を書き換え。 copied.import {storiesOf} from '@storybook/vue'; import ButtonReadme from './README.md'; import BaseButton from './BaseButton'; import {action} from '@storybook/addon-actions'; import {text} from '@storybook/addon-knobs'; storiesOf('Button', module) .addParameters({ readme: { sidebar: ButtonReadme } }) .add('buttons', () => ({ components: {BaseButton}, props: { buttonLabel: { default: text('label', 'button') }, }, template: '<div>' + '<base-button @click="action">{{ buttonLabel }}</base-button>' + '</div>', methods: { action: action('clicked') } })); {project_root}/src/components/atoms/Button/README.md を追加。 copied.# Usage🛠 その他設定はご自由に @babel/core vue-svg-loader @fortawesome/fontawesome-free bootstrap のようなプロジェクトの要件などに関わるパッケージなどはご自由にインストールして下さい。 最後に ほぼ、私のメモの用に書いてみました。 「こうしたほうがいいよ」とか、「ここ間違ってるくない?」とかあるかもしれませんが、その辺は自身の環境に合わせて設定を変えていただければと思います。 特にコンポーネントのパッケージ化についてはインストールする側がVueを利用していることを前提にしているので、使いにくいところがあるかもしれません。 CDN化やpure jsの対応はそのうちしておきたいなとは思っています。 個人的にはフレームワーク依存したくない(Vue / ReactのようなFWを除く)ので、それ単体で動く環境をCLIなどなしで構築できるようになっているはずです。 みなさんの役に立てれば幸いです。 React記事書こうとしていましたが、Storybookのまとめしてなかったなぁと思ったのでこちらを優先しました。Nuxtの案件も近いのでついでに...といった感じ。 Nuxtについてもそのうち書きたい。 そのときはよしなに。 .

ようやく今年の抱負メーカーをバージョンアップしたよ

2021/01/24 2021/01/24

ようやく今年の抱負メーカーをバージョンアップしたよ こんにちは。Nonです。 皆さんお待たせしました。 ようやく 今年の抱負メーカー | Twitterで今年の抱負をつぶやこう!筆書き風の画像をダウンロードして背景に設定したりしてください! 今年の抱負メーカー | Twitterで今年の抱負をつぶやこう!筆書き風の画像をダウンロードして背景に設定したりしてください! をバージョンアップしました。 結構要望を受け取っていて、これを解決するのに、結構な時間が経過しちゃっていました... みんなごめんね。 今回はそのお知らせという感じで、更新内容をサクッと説明しつつ、バージョンアップのお知らせ記事にしていこうと思います。 前に書いた記事 【個人開発】新年早々、今年の抱負をつぶやくWEBアプリをつくってみた | のんラボ 【個人開発】新年早々、今年の抱負をつぶやくWEBアプリをつくってみた明けましておめでとうございます。本年もよろしくお願いいたします。早速ですが、新年早々アプリを作成しました。 ... 3行で入力できるようにしてほしい 今までのバージョンでは1行25文字までにして、プログラム側が定形処理に従って自動的に改行していましたが、それを3行までユーザーが指定できるようにしました。 こんな感じで入力できるようになりました これが結果になります。 改行位置が指定というか、固定されるようになったので、見た目の調整がしやすくなったと思います。 本当は、プレビューを見せてあげたいので、そのへんは後のバージョンでなんとか実装したいと思います。 色んな使い方① 箇条書きっぽく こんな感じで箇条書きにみたいに見せることもできます。 抱負がたくさんある方はこのような使い方をしてみてくださいね。 色んな使い方② これまで通り文章でも入力できるよ 文節毎に区切って入力するとわかりやすくていいかも。 最大21文字なので、そのへんも考慮して抱負の内容を入力すると、見栄えが良くなるかも! この要望がとても多かった... 正直、勉強用に作成していたので、ユーザービリティとか考慮してませんでした... 申し訳ない🙇 こんな感じでどうだろうか? 違う年の抱負を見たい 今までは総合PVでしたが、これからはその年毎のPVランキングにしました。 個人の抱負を見たい場合はTwitterからリンクたどって来てください。 見られたくない抱負の場合は、画像だけ作成して、ツイートしないようにしたら大丈夫です。 セレクトボックスを変更すれば、以前の抱負が見れます。 遅くなってすみません。 本当は、2020年内に対応しておきたかった まぁこのアプリって1月初旬にしか需要ありませんからねww それまでに間に合わなくて申し訳ない。 12月に予想外の仕事が入ってしまってこちらに取り組む余裕がありませんでした😅 本当はほとんどの機能をリプレースしたかった 画像のサイズがでかすぎて表示に時間がかかっているから、画像の軽量化処理を入れたかった。 プレビューを表示したかった。 DBの登録処理が複数回行われるので、画像を保存するかどうかを選択させてあげたかった。 他にも色々あるので、今年また時間があるときに頑張ろうと思います。 最後に 今年の抱負メーカー | Twitterで今年の抱負をつぶやこう!筆書き風の画像をダウンロードして背景に設定したりしてください! 今年の抱負メーカー | Twitterで今年の抱負をつぶやこう!筆書き風の画像をダウンロードして背景に設定したりしてください! の新バージョンの通知でした。 前に書いた記事 【個人開発】新年早々、今年の抱負をつぶやくWEBアプリをつくってみた | のんラボ 【個人開発】新年早々、今年の抱負をつぶやくWEBアプリをつくってみた明けましておめでとうございます。本年もよろしくお願いいたします。早速ですが、新年早々アプリを作成しました。 今年の抱負メーカーア... 約2年前のコードでしたが、いやぁやばかったww これは設計レベルで少し見直す必要があるかもしれません。 少しずつ更新していくので、次のバージョンアップ時も通知記事を入れたいと思います。 その時はよしなに。 .

(今さら)2021年の抱負とこれまでの振り返り

2021/01/09 2021/01/09

(今さら)2021年の抱負とこれまでの振り返り 明けましておめでとうございます。 本年も何卒よろしくお願い申し上げます。 はい、こんにちは。Nonです。 昨年の振り返りと本年の目標 | のんラボ 昨年の振り返りと本年の目標こんにちは。Nonです。少し遅れてしまいましたが、去年の振り返りと、本年の目標を書いていこうと思います。2019年はどんな年だったか?職場実は2018年に転職をしておりまして... を書いてから早1年が経過しました。(去年の記事のタイトルが悪い) 今回はこの記事を見ながらできていること、できていないこと、をまとめながら今年はどのようにエンジニアとして活動していこうかを書いていこうと思います。 そう言えば家庭教師はじめました 無事、私の生徒さんが自社開発会社に内定をもらって現在勤めています。 現場レベルの人間が毎週2時間くらいみっちり教えてますので、かなりいい授業のはずです。 しかも、サイトの課題ポチポチとか、基本の構文の写経とかじゃなくて、作りたいアプリの仕様を二人で考えて、OJT方式にプルリクを出して私と生徒でレビューしていく方式なので、かなり現場に近いかも。 私もプルリク出すので、私のコードで勉強できるというメリット付き。 いくらでしょう...?内緒です。 (でも、かなり厳しい授業です。ちょっと悪いことをしてしまったかも。反省) 個人開発 一昨年(2019)は個人開発を中心に頑張っていた年でしたね。去年の記事にあるように色々なクソアプリを作成してはクローズしてきました。Trelloのパクリなので、公表はしていませんがtodogってアプリは現役で私が使っていまして、案外使いやすいので結局丸一年使ってしました。 https://todog.nozomi.bike/ https://todog.nozomi.bike/を見る リンク貼ったはいいけど登録はできないようになってます(パクリの上に個人で使っているツールなので)。フロントとバックが完全に別々になっているので、フロント重視にできたはず。 今年の抱負メーカー | ツイッターで今年の抱負をつぶやこう! 今年の抱負メーカー | ツイッターで今年の抱負をつぶやこう! は2021年の新年にみんなにもう一度使ってもらうとバージョンアップを画策していたのですが、12月は諸事情(横からのヘルプに追われていた)があったので達成ならず...口惜しい... ということもあり、2020年は個人開発あまりできませんでした。 といったところで話したいことも終わったので振り返り なにか1つのアプリをじっくりと開発する できませんでした... 2020年は個人開発に割くリソースがなく、できたものといえば、 スマレジアプリコンテスト 第2回 スマレジアプリコンテスト開催中!大賞賞金1,000万円、優秀賞100万円!スマレジと連携して利用する店舗向けアプリを開発して、ご応募ください。お店のいい未来をつくるアプリ募集。 用のアプリが一つだけ、しかも機能不足で公開できるレベルでは無いものです。 のんラボ のんラボ の調整や https://todog.nozomi.bike/ https://todog.nozomi.bike/を見る の調整などは何回か行ってきていましたし、社内向けのサンプルコードやOSSは作成していたのですが、これは個人開発とは言えないかな〜と自分の中で決着しています。 でも https://github.com/nonz250/simple-rss-maker-php は作った RSS生成アプリをPHPで作りました(なんで令和にこんなものをww) 詳細記事は TDDでRSSを生成するライブラリを作成した話 | のんラボ TDDでRSSを生成するライブラリを作成した話こんにちは。Nonです。前回に引き続き、TDDでRSSを作成するライブラリについて話をしてみたいと思います。完成したもの ... に書いています。 問題なく使用できていますが、OSS用のライブラリにたいしてDDDやりすぎた感とかその辺勉強できたのは大きかったな。 自作アプリを紹介するHP作成 こちらは今年の目標に棚上げかも。 アプリ充実してないから仕方ないね。 その他 技術などの勉強はかなりできた 個人開発ができなかったのにはわけがあって、何故か開発作業をせずにひたすら勉強していた年になりました。 TDDとかもともと知っていたのですが、昨年の初めにDDDの読書会から始まった社内勉強会をしていたのもあり、勉強にかなりの時間を割いていました。 あと、幸か不幸かコロナということもあって勉強会がオンラインになっていたというのもあります。 ちょっとずつOSSに貢献できるようになっていきたい。(英語力...) 貯金 まぁバイク一台を余裕で買える程度には頑張った...予想外の収入もあったので。 しかしバイク買うかは迷い中 おしゃれに気を使う がんばれている...よね? コロナが全ての努力を無に帰したかも。 2021年の抱負 自作アプリを紹介するHP作成(去年に引き続き) OSS貢献(私の中で必要なものは揃ったはず。もちろんOSS開発も。) 今年の抱負メーカーなど、ユーザーが居る or 可能性のある アプリの強化 ですかね。 ブログ更新 は言わずもがなですねw ここに書いたこと以外は我慢しない こちらは去年に引き続きです!気楽に行きましょう! 最後に こんなものでしょうか? なんか去年と似たりよったりの部分もありますが、停滞してしまった今年の状況を見ると結構うまく行っているような... ブログの更新頻度下がっているので、こちらも頑張りましょう。(やめちゃわない程度に!) そのときはよしなに。 .

Reactを触ってみてVueとの違いをまとめてみた

2020/12/13 2020/12/13

Reactを触ってみてVueとの違いをまとめてみた こんにちは。Nonです。 私は、Laravelに付属しているという理由からVueを採用してフロントをいじってきた経緯があります。 Vueしか触ったことのない私が、フロントをしっかりと勉強する機会が最近増えてきました。 React勉強会なるものを社内で実施した結果、VueとReactの違いに気づいてきたので、そろそろまとめようと思い記事にしました。 とはいえ、何番煎じやねん。という感じなので、感想は他のサイトで上がっている内容と重複する部分もあるかもしれません。(できるだけ私個人の感覚も取り入れて記事にしたいと思います。) 参考記事はページ最下部に貼っておきます。 ということで、下記から「ある一点」に注目して比較してみます。 なので、一概にこっちが...というわけではありません。 それぞれの特徴を考慮してプロダクトを進めていくべきであり、〇〇さんが言ってたからこっちとか言うわけではありません。 採用する背景で重要なのは 経験者の有無、または今後のスキルセット ビジネスサイドを考えたアーキテクト など、包括的な開発背景の裏付けを基に採用を進めるべきというのが持論となります。(両方わかって両方触れる人がいれば完璧なんですが...w) 特徴 特徴については、他のサイトでもいっぱい語られていますね。 なので、私の価値観でサクッと済ませます。 Vue 技術の分離 Vueのテンプレート技術の採用はほぼ必須 HTML、CSS、jsの技術を明確に分けやすい。(Scoped CSSとか) UI / UX的。 画面の内容がメイン HTML / CSS がわかるだけでなんとかなる。(長所であり短所) 冗長ではないため、簡潔。 React 関心事の分離 Class / Entity / Component的。 各コンポーネントはClassでありEntity。 HTML(つまり各コンポーネント)をEntityクラスと受け止めるとすんなり開発が進む javascriptのパワーを最大限発揮できる。 jsxはjsの拡張(これがいいって感じる人は多いはず。) js(jsx)を中心にしているため冗長な分、設計がしっかりしている お手軽さ お手軽さという点に注目して比較してみます。後述する開発効率と重複してしまいそうですが、ここでは学習コスト、設計コストという面に注目します。(初速とも捉えることができます。) Vue > React Vueの特徴の一つに技術の分離があります。WEB / CSS さえ知っていればある程度のコンポーネントを作成できますので、学習コストはVueに軍配が上がるでしょう。 Reactはjsxに設計構想 / 方針についての深い理解を特に求められます。いい意味でも悪い意味でも作業者に高い期待を求めることになります。 フロントでガッツリとした処理を書かない 実装へのお手軽さを重視 フロント技術の網羅的な知識を持っていない人が参画し、作業者となっている こういう時はVueの方がお手軽です。 特にマークアップエンジニアやデザイナーが担当者の場合顕著です。 Vue WEB / CSS 部分を技術的に分離できるので、コーダー、デザイナーはこれまで通りの作業で作業を終えることができ、あとはjsをある程度知っている人にバトンタッチすることができます。 また、画面の動きを実装するという面でも、簡単なjavascriptの動作を勉強するだけで実装することができます。非同期処理や設計に関しては他のエンジニアに任せるという開発運用方法ですね。 例えば、styleやclass要素の変更を加えるときstringで十分扱えるので、特定の関数がフラグを判定しstringを返すというだけならjs入門したばかりの人でも十分しっかりと書けることでしょう。 React Reactのjsxにstyleやclass要素の変更を加えるときにはjsxの構文を知っている必要があります。 基本的にpropsは関数、オブジェクトで有るべきなのがReactです。マークアップエンジニアやデザイナーにはここが最初の障壁となるでしょう。 (もちろんプログラマ的にはClassと受け止めることができるのでメリットです。今は他の方を巻き込む時を想定しています。) (adsbygoogle = window.adsbygoogle || []).push({}); 更に詳しく 技術の分離が明確。HTML / CSS / jsの技術を明確に分けやすい。(特徴の一つ) 私の関わっているプロダクトは新機能追加の重要度が高く、開発スピードも求められます。フロントでの重大な処理もありません。今思うとVueを採用してよかったなと思います。 また、 コーディングができないが、UI / UXに特化したデザイナー javascriptをあまり知らないが、HTML / CSSを熟知しているコーダー がプロダクトに参画しています。 この時、デザイナーが作成した画面デザインをコーダーが再現することになるのですが、コンポーネント設計がされているフロントにコーダーが手を入れるとき、 javascriptにHTMLが実装されているReact templateとjavascriptを分離されているVue とではVueのほうが学習コストが低く、コーダーレベルでも手を出しやすいように感じました。 (もちろん、HTML / CSS / javascriptをきっちり分離しているプロダクトではこの対象ではありませんが、私のプロダクトは前述の通り実装スピードを重視しており、引き継ぎ時点でこのような設計はされていませんでした。) 特に。 Atomic Design by Brad Frost Learn how to create and maintain digital design systems, allowing your team to roll out higher quali... を採用しているとき、Reactはjavascript、jsxの知識をコーダーに求めることになりますが、Vueの場合はtemplateに書かれたpropertyに注意するだけでHTMLをそのまま書く感覚に近い気がします。 設計 設計という点に注目して比較してみます。 先に書いてしまうと、この章をうまく言語化できていません...申し訳ない。 Vue < React 少し具体例を上げるとするなら下記のような感じ。 私はPHPをよく使うので、Smartyをいじる機会も多いのですが、 Vue Vueは予め存在するテンプレートに対して処理を実行するイメージ 処理技術と描画技術を分けて書くため、Smarty(テンプレートエンジン)に処理を書きまくる感覚 Smartyに処理が書かれまくっているのとても嫌。 私が関わっているプロジェクトではそれを嫌って処理部分と描画部分を切り分けるような構造にしていますが、どうしても描画部分に引っ張られる部分がまだある。 React PHPでHTMLをどう描画するか記述しているイメージ 処理オブジェクトと描画オブジェクトを定義して関心事に分けて書くため、処理部分でHTMLを書きまくる感覚 上記から 設計を厳密にしたい 堅牢性を高めたい ビジネスロジックをフロントにバリバリ書く この辺を考慮するならReactを採用するべきでしょう。 更に詳しく フロントでバリバリ処理を実装する。(ここで言う処理とはデザインの動的変更や、アニメーションの実装ではなく、文字通りフロントでビジネスロジックを実装するパターンを指します。) jsxを採用しているReactは関心事の分離に特化している印象を受けます。 jsxについての説明は JSX の導入 – React ユーザインターフェース構築のための JavaScript ライブラリ マークアップとロジックを別々のファイルに書いて人為的に技術を分離するのではなく、React はマークアップとロジックを両方含む疎結合の「コンポーネント」という単位を用いて関心を分離します。後のセクションでコンポーネントについては改めて詳しく紹介しますが、現時点で JavaScript にマークアップを書くことが気にくわない場合、こちらの議論で考えが改まるかもしれません。 Vueはどちらかといえば技術の分離。お手軽さの章で述べたように、チームメンバーの技術の守備領域が明確になっている場合はこの恩恵を受けることができる。 対して、Reactは関心事の分離。プログラマはHTMLのことを熟知していなければなりませんが、ビジネスロジック処理と描画処理(あくまで処理であることが重要と思います。)を切り分けて考えることができるので、アーキテクトに恩恵を受けることでしょう。 これのどこかいいか、何が言いたいかと言うと、技術の分離ではないので全てをjs(jsx)でまとめる事ができます。 js(ts)のみでコンポーネントを設計したい jsコードの中でコンポーネント呼び出しを行い、htmlを変更したい などの実装がすべてjs(jsx)で完結するのがReact。とも感じています。 例えば、jsの中でコンポーネントを実装しようとするとき、 Vueの場合 copied. export default { props: { label: { type: String, required: true, } }, data() { return { // データ定義 } }, template: '<button>{{ label }}</button>', // or render(createElement) {} } となるでしょう。ここのtemplateの部分がstringで書かれることになります。これがとても気持ち悪く感じます。 それなら<template></template>を採用しろ、という話になるのですが、その場合、jsのみで完結することができません(それマークアップじゃんと捉えられてしまいます)。 どうしても文字列を書く部分で出てきてしまい、設計がきれいという話にならなくなってします。 ではrenderではどうか? という話になりますが、jsxではないVueではcreateElementを利用して一つ一つDOMを作成しなければなりません。これが非常に面倒臭いです。プログラマチックといえばそのとおりですが、可読性が下がるのは間違い無いと思います。 Reactの場合 いつもの感じで書けばいいです。 copied.import React from 'react'; class MyButton extend React.Component { construtor(props) { super(props); this.state = { // データ定義 } } // 関心事の分離的。 render() { const button = <button>{this.props.label}</button> return button; } // 技術の分離的。 render() { return ( <button>{this.props.label}</button> ); } } renderの部分でHTMLを書くのが基本となるので、設計上とてもわかり易い上に、jsxでHTMLを直接書く感覚にとても近いです。 何より、突然このコードを書いてもjsなら起動します。これがtsと相性のいい理由でもあります。フレームワーク間で結合のための設定を(比較的)しなくても済むのですから。 jsxについての説明は JSX の導入 – React ユーザインターフェース構築のための JavaScript ライブラリ このおかしなタグ構文は文字列でも HTML でもありません。 これは JSX と呼ばれる JavaScript の構文の拡張です。UI がどのような見た目かを記述するために、React とともに JSX を使用することを私たちはお勧めしています。JSX はテンプレート言語を連想させるでしょうが、JavaScript の機能を全て備えたものです。 とあるように、一つのObjectと捉えることができるもの大きな違いです。 (adsbygoogle = window.adsbygoogle || []).push({}); typescript(堅牢) 堅牢性に注目してみました。 Vue < React 堅牢性、特にtypescriptを採用する場合が顕著です。 このあたりはそれぞれのフレームワークにtypescriptを導入してみたらわかると思います。 導入については他の有用な記事にお任せしたいと思っていますが、Reactはtsとの相性が抜群です。 この時点でプログラマ視点からみた場合、堅牢さで軍配が上がるでしょう。 TypeScriptの導入障壁としてならVue ≒ React 結局は両フレームワークともに状態管理をメインに行うフレームワークなので、この部分さえ型厳密にしてしまえばいいのです。なので、導入障壁は同じ位と勝手に見てます。 しかし、導入後恩恵を受けやすいのはReactなので、このようにしました。 (adsbygoogle = window.adsbygoogle || []).push({}); 理由 render部分に注目するとVueはstringで、Reactはjsxで書かれていることにも注目できます。 tsで型厳密したときもReactの方がrenderへの恩恵を受けやすく、マークアップ部分が書かれたところも型厳密にしやすいことがわかります。(jsxはtsxとして型厳密にできますので。) 処理部分でも同じです。jsxはjavascriptの拡張なのでtsに置き換えやすく、関心事の分離が容易な上に型厳密までできるという堅牢さを持っています。 VueのtemplateはjsxではなくstringかcreateElementなので、この恩恵を受けづらいです。 Vueにjsxを導入する方法はありますが、それならReactを採用するでしょう。(そもそもそこまでしてVueの便利なテンプレート技術を捨てる理由がわからないです。私がそこまでするならReact移行を考えます。) また、Vueではpropsのバリデーションを実装できるのですが、typescriptの型厳密とVueのバリデーションでtsでNGでもVueでOKとなる場合があります。(設定のせいかもですが、この設定がそもそもめんどい) あと、Vueにts導入しようとすると、正味わけわからんくなる。 完全に独断と偏見だけどReact vs Vue してみた - Qiita 比較内容大まかに以下の点で比較検討をしました。私個人はVueよりもReactに慣れているので、偏った意見かもしれないけど参考になれば。ReactVue手軽さ△◎開発効率○○TypeS...... VueのTSサポートについて TSのサポートは微妙。Vueは手軽さとアバウトさが売りなのでしょうがないのかも。 確かに。 型付けがやりにくい(子Componentの場合いちいちデコレータを書かなくてはいけない) 型定義が間違っていてもコンパイルが通ってしまう(スキーマ !== 型定義 の場合でもとりあえずコンパイルは通ってしまう??) 上述した、Vueのprops問題。 子コンポーネントにpropsを渡す際に必須であれば required: true と定義するが、これは型定義で解決するべきことのはず。 そのとおりだ... 開発効率 開発効率という点に注目してみたいと思います。お手軽さと重複してしまいそうですが、ここではどちらかというと 実装時間 という観点に注目したいと思います。 Vue >= React 実装スピードは慣れという部分も大きいので、最終的には同様の効率になるかと思います。設計がしっかりできる分Reactのほうが効率いいように見えますが、そのために冗長な記述を大量に記載するのが特徴なので、結局トントンになるかもしれません。 Vue いい意味でアバウト 完全に独断と偏見だけどReact vs Vue してみた - Qiita 比較内容大まかに以下の点で比較検討をしました。私個人はVueよりもReactに慣れているので、偏った意見かもしれないけど参考になれば。ReactVue手軽さ△◎開発効率○○TypeS...... より引用 役割分担に非常に相性がいい 画面に必要な機能をVueテンプレートがたくさん用意してくれている React 堅牢なのはいいが、その分厄介(面倒)な部分もある。(〇〇したいだけなのに、ここまでしなきゃいけないの!?ってのは慣れてきた今でも感じることが多い) 役割分担がされておらず強力なフロントエンジニアがいる場合は個人のパワーに任せやすい。(↔エンジニアの力量に左右されやすいかも) 設計手法が明確にチーム内で共有されている場合はそこまで遅くならない(はず) 結局どっちを採用すればいいの? 冒頭にも書きましたが、 Vue デザイナーがフロントのメイン担当 役割分担がしっかりしていて、同時にその作業者である。 デザイナーもコーディングすることがあるとか コーダーがフロントではなくマークアップエンジニア的とか フロントにそこまで興味がない人が多い フロントでビジネスロジックを書くことが少ない 特定のページにのみ導入したい ちょっと試しにやってみたい。 部分的導入 速さを求めたい 学習コストをかけたくない このような場合はVueでしょう。 React プログラマがフロントのメイン担当 フロントエンジニアがたくさんいる 強烈なフロントエンジニアがいる 作業者がHTML / CSSに精通しているエンジニア 役割分担というよりチーム開発 デザイナーは画面デザインのみで作業者ではない コーダーのjsの理解が深い(エンジニアと呼べるレベル) 堅牢さを重視 重要なビジネスロジックが存在する 画面でもEntity的な考えを要求する フロントはこのようにあるべきという方針が存在する フロントとバックの明確な分離 デザインとフロントの明確な分離 このような場合はReactでしょう。 最後に ちょっと文章多めですみません。書き出したらこれもあるな。あっ、あれも。といった感じで追記していったので、まとまりが悪いかもしれません。 私はVueのほうが、Reactの方が...と言及するつもりはありません。 やはりチーム内、会社内で事情知るエンジニアに選択をせまり、答えを出した人に従うべきかと思っています。 結局、私の担当するプロダクトでは デザイナーが技術的背景を持たない HTML / CSSに特化したコーダーがいる 納期がすぐそこ といった、止むに止まれぬ事情があったのでVueを採用して書かれています。 しかし、弊社にもフロントを重視する考えが浸透してきて、フロントに対して前向きに取り組むような方が増えてきました。 この背景から中長期的にはReactの方がよかったかもと考えるようになってきて、少し違いをまとめてみようと記事にしました。 Reactへの移行も考えていますが、Vueが堅牢ではないとは明確に言えないので、Vueのts対応を進めて過去の頑張りをブラッシュアップする方針でもいいかもと思っています。(優柔不断) React勉強会については記事にできていませんが、PWA周りや、React Nativeについては今後も投稿していきたいと思います。 そのときはよしなに。 参考 完全に独断と偏見だけどReact vs Vue してみた - Qiita 比較内容大まかに以下の点で比較検討をしました。私個人はVueよりもReactに慣れているので、偏った意見かもしれないけど参考になれば。ReactVue手軽さ△◎開発効率○○TypeS...... 概ね私と同じ考え方でした。引用させていただいた部分も多いかも。 ありがとうございます。 あと、パフォーマンス面にも触れていていいと思った記事です。GraphQLについて言及しているものいいなと思いました。 ReactとVueのどちらを選ぶか - Qiita 主に非Web系のバックエンド開発者(C/C++, Java, Python等を使用)がReactとVueをそれぞれ簡単に触れて、感じたメリット、思ったことなどをまとめています。色々と書いてますが、どち... 私はこの記事に続いて、中長期的な戦略としてVue→React移行がしやすい設計をVueの段階でしておくことも重要だと考えています。

Z250で高山ダムへラーツー!

2020/12/06 2020/12/06

Z250で高山ダムへラーツー! こんにちは。のんです。 今回はまたまたバイクでツーリングへ行ったことを書こうと思います。 最高に良い一枚 時期は12月初旬頃です。 高山ダムへラーツー。ラーツーってなに? ラーツーって言うのは「ラーメンツーリング」のことですね。 ラーメンの材料と、材料を調理するコンロとかを持ってツーリングして、昼食などにラーメンを食べるツーリングのことです。 調理するって言っても大抵インスタントラーメンとかになるんですが、外で食べるラーメンはうまいから別にヨシ! 高山ダムへ到着するも火気厳禁らしい 高山ダムへ到着したけど火気厳禁だった...かなしい。 ちなみにちょうど放水時間でした。 この量確かに決壊したら下流の街は大変なことになりそう😅 ダムの近くの自然公園はすべて火気厳禁だったので、この川の下流でデイキャンすることになりました。 ちょうどいい感じのところを発見。地名は知らん。 ということで早速ラーメンを作って食べよう これが こうなる ただのインスタントラーメンなので、水を沸かしてスープとチャーシュー入れておしまい。 やっぱ外で食うご飯は美味しい。この日は意外と寒くなくて、ラーメン食べたらむしろ暑いくらいでした。 飯テロ この写真最高に良くないか??🤔 あと、椅子持って来たらよかったかも。 食後のコーヒーとプリン 川で遊びだす自分 ススキがいい味出してる。 最後に コロナ第3波+寒くなってきて走り納め感ある。 でも寒い中温かい思いをするのは何故かやめられないんだよなぁ... 呪術廻戦おもろいぞ ちょっとポーズ違くないか?🤔 最後に...バイクはいいぞ!! .

Z250で職場の仲間と美山かやぶきの里「雨」ツーリング!

2020/11/29 2020/12/03

職場の同僚と美山かやぶきの里「雨」ツーリング! こんにちは。Nonです。 今回は久しぶりにバイクでツーリングに行ったことを記事にしたいと思います。 何度目かの美山かやぶきの里へ行きました もう何回行ったかわからない美山かやぶきの里へツーリングしました。 ちょうど2時間くらいで行けるので、かる~くツーリングするには道も所要時間もお手頃な目的地なんですよね。 天気予報「曇りのち晴れ」だったよな...? この日は午前中天気が良くていいツーリングになると思っていました。 同僚が最近 レブル250 を購入して、せっかくなのでツーリング行こう!とも思い誘ってみたところ...って感じです。 レブルかっちょいいいいいいいいいい😆😆😆 ストファイ系が好きな私でも、これは欲しくなりました。座り心地も良さそう。250なので取り回しも楽だろうし乗ってて楽しいバイクかも。 で、肝心の天気ですが 案の定降られた。 なんで?かなり楽しみしていたのに、割と思いっきり振られました。 山の天気は変わりやすいとか言うけど変わりやすすぎやろ... バイクびっしょびしょ。 相方には悪いことをしました。 寒いし濡れるし、新車だし、で😇 でもなんだかんだ楽しかった かやぶきの里を観光はできなかった(滞在時間は昼飯食べただけ)けど、温かいそば美味しかったし、帰りは比較的天気安定してたしでまぁまぁ楽しかった。(もちろん晴天だったら最高だった) また行きたいですね。 最後に バイクはいいぞ! .

TDDでRSSを生成するライブラリを作成した話

2020/11/27 2020/11/27

TDDでRSSを生成するライブラリを作成した話 こんにちは。Nonです。 前回に引き続き、TDDでRSSを作成するライブラリについて話をしてみたいと思います。 完成したもの GitHub - nonz250/simple-rss-maker-php Contribute to nonz250/simple-rss-maker-php development by creating an account on GitHub. https://packagist.org/packages/nonz250/simple-rss-maker-php https://packagist.org/packages/nonz250/simple-rss-maker-phpを見る 構造 RSS1.0に対応する余地を残しているので、SimpleRssMakerクラスの構造が少しおかしいかも。 ユースケース層も作ったけど、これいらなかったかなー。でもファイルを作成する処理をここに書きたかったので、まぁ良しとしましょう。 RSS1.0に対応していないですが、一応composerからはinstallできるようにしてる バージョンとしては1.0.0として作成しました。 https://packagist.org/packages/nonz250/simple-rss-maker-php https://packagist.org/packages/nonz250/simple-rss-maker-phpを見る しかし、RSS1.0にはまだ対応してないです。(RSS2.0の利用のほうが多いみたいだし別にいいよね😅) 構造的に悩んだところ ChannelをEntityにしたけどよく考えたらValueObejctなのかな?もっとシンプルな構造にすべきだったかも。DDDに固執しすぎた感ある。うーん。配列は作成したくなかったので、クラスにするのはあってるけどEntityとして定義するのは間違えた気がする。 SimpleXMLElementをつかってRSSを作成したのは、余計なstringとかで書かなくて済んだので良かったと思う。 RSS2エンティティの振る舞いだけで十分だったかなと思いました。 使い方 Packagistにも書いてありますが、 copied.$simpleRssMaker = new SimpleRssMaker(); $xml = $simpleRssMaker // Channel settings. ->setChannel('title', 'link', 'description', 'language', 'copyright', 'category', 'pubDate') // Image settings. ->setImage('title', 'link', 'url') // Item settings. ->addItem('title', 'link', 'description', 'author', 'category', 'datetime') ->addItem('title', 'link') // Generate RSS2.0 string. ->rss2(); // If you have more than one article. $simpleRssMaker = $simpleRssMaker // Channel settings. ->setChannel('title', 'link', 'description', 'language', 'copyright', 'category', 'pubDate') // Image settings. ->setImage('title', 'link', 'url'); foreach($items as $item) { $simpleRssMaker = $simpleRssMaker ->addItem('title', 'link', 'description', 'author', 'category', 'datetime'); } // Generate RSS2.0 string. $xml = $simpleRssMaker->rss2(); こんな感じ。SimpleRssMakerだけで全部設定できるように作成しました。 最初の頃はChannelFactoryとか作ってChannelクラスをユーザーに使用してもらおうと思ってたけど、使いにくいかなと思ってやめました。 最後に フィード系のライブラリは他にも結構いいのがあったけど、総じてPHP5.3とかのライブラリだったので、差別化は出来てるかなと思っています。(PHP7.4以上が対象です。) RSS1.0に対応はしたいけどAtomは別にいいかな... ということで、しょぼいけどOSS作れてよかったと思いました。 そろそろ私も色々な人に貢献していきたいなとおもいますので、他のパッケージも作って行きたいと思います。 そのときはよしなに。 .

PHPでTDDをしながら簡単なRSS生成アプリを作ってみた

2020/11/13 2020/11/15

PHPでTDDをしながら簡単なRSS生成アプリを作ってみた こんにちは。Nonです。 今回は事情によりRSS用のXMLを作成するライブラリをTDDしながら作成したみた話をしたいと思います。 GitHub - nonz250/simple-rss-maker-php Contribute to nonz250/simple-rss-maker-php development by creating an account on GitHub. TDDについて 以前関連する記事をかいていました。これをより実践的にやってみようという話になりますね。 以前の記事はこちら。 テスト駆動はテストのための手法ではない | のんラボ テスト駆動開発(TDD)は死んだって記事正直、開発手法などの記事については上辺をすくっただけなので、よくわかりませんが、TDDは時代遅れの開発手法ということなのでしょうか?しかし、 @t_wada さ... TDDしながらコードを書いてみる TDDはRED→Green→Refactoringが基本ですので、まずはテストコードを失敗させるところからスタートですね。 copied.<?php declare(strict_types=1); namespace Tests; use SimpleRssMaker\SimpleRssMaker; use PHPUnit\Framework\TestCase; class SimpleRssMakerTest extends TestCase { public function test__construct() { $simpleRssMaker = new SimpleRssMaker(); $this->assertInstanceOf(SimpleRssMaker::class, $simpleRssMaker); return $simpleRssMaker; } /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", $rss2); } } copied.<?php declare(strict_types=1); namespace SimpleRssMaker; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { return ""; } } 空文字を返す処理と理想的な<?xml version="1.0" encoding="UTF-8"?>\nを確認するテストを用意してテストを実行します。 copied..F 2 / 2 (100%) 結果はもちろん失敗です。 こちらを成功させるためにリファクタリングしましょう。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; } } 空文字を返すのではなく、理想的な<?xml version="1.0" encoding="UTF-8"?>\nを返すようにリファクタリングしました。 copied... 2 / 2 (100%) 結果はもちろん成功です。しかしこれでは、XMLタグを作成したとは言えないですね。思いっきりマジックナンバーになってしまっています。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { $dom = new \DOMDocument('1.0', 'UTF-8'); $xml = $dom->saveXml(); return (string)$xml; } } XMLタグを動的に生成してくれるように変更しました。 copied... 2 / 2 (100%) うまくいきましたね。 しかしこれではRSSを作成したとは言えないので、引き続きテストを修正します。 copied. /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"/> XML; $this->assertEquals((string)$expected, (string)$rss2); } RSSに必要な<rss>タグを期待するテストへ変更しました。 ついでに、ヒアドキュメントを利用し、ファイル入力文字としてで期待するように変数を修正しました。 copied..F 2 / 2 (100%) 当然テスト失敗します。 次はこのテストをクリアするようにコードを修正しましょう。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { $dom = new \DOMDocument('1.0', 'UTF-8'); $rss = $dom->createElement('rss'); $dom->appendChild($rss); $xml = $dom->saveXml(); $rss = new \SimpleXmlElement($xml); $rss->addAttribute('version', '2.0'); $xml = $rss->asXML(); return (string)$xml; } } PHPのDOMDocumentクラスとSimpleXmlElementを利用して、XMLタグとRSSタグを作成するコードを作成しました。 copied... 2 / 2 (100%) テストもクリアしたので、問題なさそうです。 次は<rss>タグの中身がなければRSSとして機能しませんので、期待値を変更します。 copied. /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <language>ja</language> </channel> </rss> XML; $this->assertEquals((string)$expected, (string)$rss2); } RSS2.0の必須項目である<channnel>タグを用意し、その中に<language>タグを期待するテストへ変更しました。 copied..F 2 / 2 (100%) テストはもちろん失敗です。 copied. public function rss2(): string { $dom = new \DOMDocument('1.0', 'UTF-8'); $rss = $dom->createElement('rss'); $dom->appendChild($rss); $xml = $dom->saveXML(); $rss = new \SimpleXMLElement($xml); $rss->addAttribute('version', '2.0'); $channel = $rss->addChild('channel'); $channel->addChild('language', 'ja'); $dom = dom_import_simplexml($rss)->ownerDocument; $dom->formatOutput = true; $xml = $dom->saveXML(); return (string)$xml; } 必要タグとその値をXMLに追加する処理を追加し、テストします。 copied... 2 / 2 (100%) クリアできたので、コードに問題はありませんね。 copied. /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $expectedDate = \DateTime::createFromFormat('Y-m-d H:i:s', '2020-01-01 00:00:00', new \DateTimeZone('UTC')); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>タイトル</title> <link>https://example.com</link> <description>説明</description> <language>ja</language> <copyright>コピーライト</copyright> <pubDate>{$expectedDate->format(\DateTimeInterface::RFC822)}</pubDate> <category>カテゴリ</category> </channel> </rss> XML; $this->assertEquals((string)$expected, (string)$rss2); } では本格的にRSSの期待値を変更します。 copied..F 2 / 2 (100%) copied. public function rss2(): string { $dom = new \DOMDocument('1.0', 'UTF-8'); $rss = $dom->createElement('rss'); $dom->appendChild($rss); $xml = $dom->saveXML(); $rss = new \SimpleXMLElement($xml); $rss->addAttribute('version', '2.0'); $channel = $rss->addChild('channel'); $channel->addChild('title', 'タイトル'); $channel->addChild('link', 'https://example.com'); $channel->addChild('description', '説明'); $channel->addChild('language', 'ja'); $channel->addChild('copyright', 'コピーライト'); $channel->addChild('pubDate', 'Wed, 01 Jan 20 00:00:00 +0000'); $channel->addChild('category', 'カテゴリ'); $dom = dom_import_simplexml($rss)->ownerDocument; $dom->formatOutput = true; $xml = $dom->saveXML(); return (string)$xml; } copied... 2 / 2 (100%) かなり直列なコードですが、テストがクリアしたのでOKです。 しかしこれでは、動的に作成することができませんので、更に修正します。 copied.<?php declare(strict_types=1); namespace Tests; use SimpleRssMaker\SimpleRssMaker; use PHPUnit\Framework\TestCase; class SimpleRssMakerTest extends TestCase { public function test__construct() { $simpleRssMaker = new SimpleRssMaker(); $this->assertInstanceOf(SimpleRssMaker::class, $simpleRssMaker); return $simpleRssMaker; } /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $expectedDate = \DateTime::createFromFormat('Y-m-d H:i:s', '2020-01-01 00:00:00', new \DateTimeZone('UTC')); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>title</title> <link>https://example.com</link> <description>description</description> <language>ja</language> <copyright>copyright</copyright> <pubDate>Wed, 01 Jan 20 00:00:00 +0000</pubDate> <category>category</category> <image> <url>https://example.com/sample.png</url> <title>title</title> <link>https://example.com</link> </image> </channel> </rss> XML; $this->assertEquals((string)$expected, (string)$rss2); } } 期待値を微調整し、欲しいタグを少しだけ追加しました。 copied..F 2 / 2 (100%) テストは失敗しましたね、先程、動的に値を取得作成していきたいと言ったのでそろそろコードも本格的にしていきましょう。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker; use SimpleRssMaker\Rss2\Command\UseCases\CreateRss2; use SimpleRssMaker\Rss2\Command\UseCases\CreateRss2Input; use SimpleRssMaker\Rss2\Command\UseCases\CreateRss2Output; use SimpleRssMaker\Rss2\Models\Entities\Rss2; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { $input = new CreateRss2Input(); $output = new CreateRss2Output(); $useCase = new CreateRss2(); $useCase->process($input, $output); $rss2dom = $output->rss2(); return (string)$rss2dom; } } final class CreateRss2Input implements CreateRss2InputPort { } final class CreateRss2Output implements CreateRss2OutputPort { private Rss2 $rss2; public function output(Rss2 $rss2): void { $this->rss2 = $rss2; } public function rss2(): Rss2 { return $this->rss2; } } final class CreateRss2 implements CreateRss2Interface { public function process(CreateRss2InputPort $inputPort, CreateRss2OutputPort $outputPort): void { $outputPort->output(new Rss2()); } } copied.<?php declare(strict_types=1); namespace SimpleRssMaker\Rss2\Models\Entities; use DOMDocument; use SimpleRssMaker\Shared\Models\ValueObjects\XmlEncoding; use SimpleRssMaker\Shared\Models\ValueObjects\XmlVersion; use SimpleXMLElement; final class Rss2 { public function createXMLDOM(): DOMDocument { $dom = new DOMDocument((string)$this->xmlVersion, (string)$this->xmlEncoding); $rss = $dom->createElement('rss'); $dom->appendChild($rss); return $dom; } public function createSimpleXmlElement(): SimpleXMLElement { $xml = $this->createXMLDOM()->saveXML(); $rss = new SimpleXMLElement($xml); $rss->addAttribute('version', '2.0'); $channel = $rss->addChild('channel'); $channel->addChild('title', 'title'); $channel->addChild('link', 'https://example.com'); $channel->addChild('description', 'description'); $channel->addChild('language', 'ja'); $channel->addChild('copyright', 'copyright'); $channel->addChild('pubDate', 'Wed, 01 Jan 20 00:00:00 +0000'); $channel->addChild('category', 'category'); $image = $channel->addChild('image'); $image->addChild('url', 'https://example.com/sample.png'); $image->addChild('title', 'title'); $image->addChild('link', 'https://example.com'); return $rss; } public function __toString(): string { $dom = dom_import_simplexml($this->createSimpleXmlElement())->ownerDocument; $dom->formatOutput = true; return $dom->saveXML(); } } とりあえずユースケースクラスとエンティティを作成し、その中で固定のXMLを返す処理にしてみました。 先程までSimpleRssMakerに書いていた処理をそのままエンティティに書き直しただけですね。 copied... 2 / 2 (100%) テストはクリアしたので、構造に問題はなさそうです。 では次に値を動的に生成できるようにしていきましょう。 結構かっちり作成したいので、ValueObejctを作成します。 ValueObejctを作成するからにはこの子のテストを書かなければなりませんね。 最初と同様に期待するテストと失敗する処理を書いてからのスタートです。 copied.<?php declare(strict_types=1); namespace Tests\Rss2\Models\ValueObjects; use SimpleRssMaker\Rss2\Models\ValueObjects\Link; use PHPUnit\Framework\TestCase; use Tests\TestHelper\StrTestHelper; class LinkTest extends TestCase { public function test__construct() { $expected = StrTestHelper::createRandomUrl(Link::MAX_LENGTH); $link = new Link($expected); $this->assertInstanceOf(Link::class, $link); $this->assertEquals($expected, (string)$link); return $link; } public function testFormatException() { $this->expectException(LinkFormatException::class); $expected = StrTestHelper::createRandomStr(); new Link($expected); } public function testLengthException() { $this->expectException(StrLengthException::class); $expected = StrTestHelper::createRandomUrl(Link::MAX_LENGTH + 1); new Link($expected); } } 期待するテストを書きました。 実装したコードはこちら。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker\Rss2\Models\ValueObjects; final class Link { private string $link; public function __construct(string $link) { $this->link = $link; } public function __toString(): string { return (string)$this->link; } } copied.There were 2 failures: 1) Tests\Rss2\Models\ValueObjects\LinkTest::testFormatException Failed asserting that exception of type "SimpleRssMaker\Shared\Exceptions\LinkFormatException" is thrown. 2) Tests\Rss2\Models\ValueObjects\LinkTest::testLengthException Failed asserting that exception of type "SimpleRssMaker\Shared\Exceptions\StrLengthException" is thrown. クラスのインスタンス型のテストはクリアしていますが、バリデーション関連のテストで失敗していますね。 処理を書き直します。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker\Rss2\Models\ValueObjects; use SimpleRssMaker\Foundation\Helpers\Rules\UriRule; use SimpleRssMaker\Shared\Exceptions\LinkFormatException; use SimpleRssMaker\Shared\Exceptions\StrLengthException; final class Link { /** * Maximum character length of URLs when using IE */ public const MAX_LENGTH = 2083; private string $link; public function __construct(string $link) { if (!UriRule::isValidHttp($link)) { throw new LinkFormatException(sprintf('%s must start at https:// or http://', get_class())); } $this->link = $link; } public function __toString(): string { return (string)$this->link; } } httpかhttps://から始まるリンクでないと検証失敗するコードを追加しました。 copied.There was 1 failure: 1) Tests\Rss2\Models\ValueObjects\LinkTest::testLengthException Failed asserting that exception of type "SimpleRssMaker\Shared\Exceptions\StrLengthException" is thrown. テストのエラーが一つ減ったのでうまく行ったようです。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker\Rss2\Models\ValueObjects; use SimpleRssMaker\Foundation\Helpers\Rules\UriRule; use SimpleRssMaker\Shared\Exceptions\LinkFormatException; use SimpleRssMaker\Shared\Exceptions\StrLengthException; final class Link { /** * Maximum character length of URLs when using IE */ public const MAX_LENGTH = 2083; private string $link; public function __construct(string $link) { if (!UriRule::isValidHttp($link)) { throw new LinkFormatException(sprintf('%s must start at https:// or http://', get_class())); } if (mb_strlen($link) > self::MAX_LENGTH) { throw new StrLengthException(sprintf('%s must be less than %s chars.', get_class(), self::MAX_LENGTH)); } $this->link = $link; } public function __toString(): string { return (string)$this->link; } } 続いて、最大文字長の検証コードを追加しました。 copied... 2 / 2 (100%) これでLinkのValueObejctは感性しましたね。 copied.public function test__construct() { $expected = StrTestHelper::createRandomUrl(Link::MAX_LENGTH); $link = new Link($expected); $this->assertInstanceOf(Link::class, $link); $this->assertEquals($expected, (string)$link); return $link; } このテストでValueObjectとしての機能をテストして、他のExceptionテストで検証のテストがクリアしています。 こんな感じでTDDは進めるといいかも TDDの強み このように、画面でデバッグを進めるのではなく、テストで開発を進めるのがTDDです。 テストは当然プログラムで動作しているので、プログラム上のエラーも拾ってくれるので、非常に進めやすいです。また、自分の期待する値をあらかじめテストに記載してやれば、正常に動作したときの動作確認もできて一石二鳥だということがわかるかと思います。 最終的にはこんな感じ 最終的なメインクラスのテストコードはこんな感じになりました。 (仕様的にはまだ満たしていなけどひとまず雰囲気だけでも感じてください) copied.<?php declare(strict_types=1); namespace Tests; use DateTime; use DateTimeZone; use Exception; use SimpleRssMaker\Rss2\Models\Entities\Channel; use SimpleRssMaker\Shared\Exceptions\ChannelNotExistException; use SimpleRssMaker\SimpleRssMaker; use PHPUnit\Framework\TestCase; use SimpleRssMaker\SimpleRssMakerInterface; use Tests\TestHelper\StrTestHelper; class SimpleRssMakerTest extends TestCase { public function test__construct() { $simpleRSSMaker = new SimpleRssMaker(); $this->assertInstanceOf(SimpleRssMaker::class, $simpleRSSMaker); return $simpleRSSMaker; } /** * @depends test__construct * @param SimpleRssMakerInterface $simpleRSSMaker */ public function testException(SimpleRssMakerInterface $simpleRSSMaker) { $this->expectException(ChannelNotExistException::class); $simpleRSSMaker->rss2(); } /** * @depends test__construct * @param SimpleRssMakerInterface $simpleRSSMaker */ public function testChannelFactory(SimpleRssMakerInterface $simpleRSSMaker) { $channel = $simpleRSSMaker->channelFactory( StrTestHelper::createRandomStr(), StrTestHelper::createRandomUrl(), StrTestHelper::createRandomStr(), ); $this->assertInstanceOf(Channel::class, $channel); $simpleRSSMaker->setChannel($channel); $this->assertIsString($simpleRSSMaker->rss2()); } /** * @depends test__construct * @param SimpleRssMakerInterface $simpleRSSMaker * @throws Exception */ public function testRss2(SimpleRssMakerInterface $simpleRSSMaker) { $title = StrTestHelper::createRandomStr(); $link = StrTestHelper::createRandomUrl(); $description = StrTestHelper::createRandomStr(); $copyright = StrTestHelper::createRandomStr(); $pubDate = new DateTime('now', new DateTimeZone('UTC')); $category = StrTestHelper::createRandomStr(); $channel = $simpleRSSMaker ->channelFactory($title, $link, $description); $channel->setCopyright($copyright); $channel->setPubDate($pubDate); $channel->setCategory($category); $rss2 = $simpleRSSMaker ->setChannel($channel) ->rss2(); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>{$title}</title> <link>{$link}</link> <description>{$description}</description> <language>ja</language> <copyright>{$copyright}</copyright> <pubDate>{$pubDate->format(DateTime::RFC822)}</pubDate> <category>{$category}</category> </channel> </rss> XML; $this->assertEquals((string)$expected, (string)$rss2); } } 最後に こちらがGithubのコードです。 GitHub - nonz250/simple-rss-maker-php Contribute to nonz250/simple-rss-maker-php development by creating an account on GitHub. 機能的にはまだまだ途中なので、引き続き経過を記事にしたいと思います。 その時はよしなに。 .

AIを使っておすすめ商品を表示するスマレコ

2020/10/30 2020/10/30

AIを使っておすすめ商品を表示するスマレコ こんにちは。のんです。 今回はスマレジでスマレジ4のアプリコンテストが開かれましたでも言っていた、スマレコというアプリについて記事にしようと思います。 前から使ってみたかった AIが技術として普及し始めて結構経ちました。当時からAIを使って大きなデータを分析したいと思っていたので、スマレコ作成しました。 AIの作り方はそのうち別記事にしようかなと思います。 今回は紹介記事?的なものになるかと。 スマレジ4APIを利用した同期処理 元になるデータはスマレジ4APIを利用しています。しかしAIの処理には大量の元データがいるので、たくさんAPIをコールしなければなりません。(1000とかその程度の件数では無い。) この辺取引一覧で取得できるのかと思ったら、仕様上一覧で取得できるのはHeadの情報のみで、明細(商品情報など)は1件ずつ取得するしかないらしい。 ちょっとつらみですね。 Webhookで取引が登録されるたびにその情報を教えてもらって同期を取る手段も考えましたが、ちょっと訳あって保留中です。 ともかく、自前のDBにAPIで取得した情報をコピーしたあとで、分析を開始するという手段を取っています。(その方が速度的にも早い) スマレコの使い方 導線 スマレジでログインする 会員情報を同期する 分析ボタンを押す のみです。本当は分析終了時にメールで通知する処理を作成したかったのですが、スマレジ4のユーザー情報取得にメールアドレス取得はまだ実装していないので、この辺はスマレジのバージョンアップを待って、後日バージョンアップします。 課題 機能を持たない(例えばお会計の補助アプリとか、注文の補助アプリとか)ではなく、データをこねくるアプリなので、使い所が難しいです。店舗のデータ分析に熱心な方くらいで、普通の店長さんはこんなの考えないと思いますし。 相関値とか急に言われても、専門的な勉強をしてきた人しかわからない。見せ方に工夫しないとダメですね。ランキングのように変更して、多少の誤解は恐れず、一般化した方がいいかも知れません。 AIサーバーについては特に問題ないと思いますが、なにせ計算に時間がかかるので、同時に複数の処理を要求されたら流石に不安...ちょっと検証していかないとダメかも。 直近で抱えてる課題はこんなもんですかね? 安定稼働するまでは確実に持っていきたい。どれくらいの大きさのデータなら安定するかというのを探らなきゃいけないのも、こういうAI系アプリの面白いところかも。 予定 通知機能 これがなきゃ使おうと思わない 他の演算を利用した分析機能 AI演算処理の安定化、安全化 上記3点がクリアされれば、一段落といったところでしょうか。もちろん、現状の機能でも使用できるようにしていますが、より良くするための機能ですね。 最後に 今回はスマレコの紹介記事になりました。 本当は動画とかも用意したかったのですが、このブログまだ動画に対応していないので、その辺勉強してから出直してきます。 スマレジ4APIを利用したアプリは軽くてシンプルで業務の助けになるアプリがいいのかもと使ってみてわかりました。いろいろな会社のAPIを分析中心に使用してきた私の価値観が変わったかも。 次回はまだ何書くか決まってませんが、そろそろ技術的なアウトプットしたい。VueやDDDを習得してからというもの新しいインプットがないから焦ってます;; また記事にします。 その時はよしなに。 .

Pixel 5を買いました!

2020/10/17 2020/10/17

Pixel 5を買いました! こんにちは。Nonです。 久しぶりの記事になります。今回はPixel 5を購入したので、そのレビュー記事になります。 Pixel 5を購入する前はPixel 3aをユーザーだったので、その比較もできたらしていきたいです。 外観 筐体はガラス製から樹脂(?)製へ。 私、手汗を結構かくので、Pixel 3のときは手汗で指紋がついたり、少しベタついたりしていましたが、この筐体だとサラサラのまま使用できます。 大きさは男の手のひらでこのくらい。 Pixel 3aとほぼ変わらず、気持ち小さくなった感じもします。 (マットなブラックでかっこいい) ケース 【Spigen】 Google Pixel 5 ケース TPU 米軍MIL規格取得 耐衝撃 衝撃吸収 ラギッド・アーマー (マット・ブラック) ケースはこちらを購入。 Pixel 3を盛大に落として画面バキバキにした経験が忘れられなくて、本当はケースが大嫌いですが、購入してみました。 バイクにも利用するので一応、耐衝撃ケースです。 装着時はこちら。 表 裏 カメラの出っ張りが気になる人は気になるはず。私は気になる人でした。 しかしケースをつければちょうどいい感じでカメラも保護してくれます。 ただし、その分厚みも増しましたね。こちらは慣れでしょうか? 機能 Qi 機能面についてはただ一点!Qi充電機能しか目当てにしてませんでした!! こちらはAirPods Proです。 光ってるのがわかるように、スマホからの充電が可能になっています!地味に便利かも。(今後にも期待。) こちらはQi充電用のスタンドを購入しました。 スタンドはこちら。 Anker PowerWave 10 Stand(改善版), ワイヤレス充電器 Qi 認証 iPhone 11 / 11 Pro / 11 Pro Max/XS/XS Max/XR/X / 8 / 8 Plus Galaxy LG 対応 5W & 7.5W & 10W 出力 ブラック AirPods ProのQi充電機能に気づいてなかったので、スタンド型固定のものを購入しました。 こんなことだったら変形するタイプを購入すればよかった... くそぅ... バックキー 実は完全ベゼルレスのスマホを使うのは初めてで、バックキーを前提にこれまでスマホを利用していました。 しかしPixel 5はホームキーがiPhoneのようになっています。当然それに従ってバックキーもなくなってしまいました。 (バックキー魅力的だったのになー。せめて選択できればいいんだけど) バックキーが表示されるときもありますが、ある時とない時があるので、ないのも同義ですね。 こちらも少しずつ慣れていくしかありません。 5G 契約は4Gです!!w 5Gどこの契約しようかな。 その他 完全にAmazonのオススメに釣られましたが、ガラスカバーを画面用とカメラ用を購入しました。カバー嫌い派だったんですが、これはいいものですね。ガラス製のカバーとかあるの知りませんでした。 最後に 今回はPixel 5のレビュー記事でした。 5Gのパワーはまだ感じることはできませんが、Pixel 5のQi充電だけでもかなりいいものです!Google Homeの代わりにしたり、音楽プレイヤー的にしたり、色々使い道は多そう! あとはモバイル充電ですね。旅行先でThunderboltのコードはもう必要はないですね。 今回はこれまでにしておきましょう。 スマレコの進捗がありそうなので、次はその記事になりそうです。 その時はよしなに。 .