- 投稿日:2020-01-26T22:39:43+09:00
React で書いたコードを Svelte に書き換えて記述量の少なさを実感
ユーザーリストアプリを作成する
以前 React を用いて作成したユーザーリストを表示するアプリを Svelte でも作成してみます。
まずは input form を作成する
InputForm.svelte<script> let firstName = '' let lastName = '' function addUser() { console.log(`${firstName} ${lastName} を追加します`) } </script> <div className="input-field"> <label htmlFor="first_name">First Name</label> <input id="first_name" type="text" bind:value={firstName} /> </div> <div className="input-field"> <label htmlFor="last_name">Last Name</label> <input id="last_name" type="text" bind:value={lastName} /> </div> <button class="btn waves-effect waves-light" on:click={addUser}>Add</button>React と比べてかなりシンプルに書けているようです。
script タグ内で変数を宣言し、使いたい場所に変数を設置します。
React のように値を setState するための関数は必要ありません。代わりに必要なのは value に bind: をつけるだけです。関数を子コンポーネントに渡す
追加するユーザーリストは親コンポーネントで値を保存しておきたいので、ユーザーを追加する処理は親コンポーネントで実装します。
Svelte も function を子コンポーネントに渡すことが可能です。App.svelte<script> import InputForm from './InputForm.svelte' let users = [] function addUser(form) { users = [ ...users, { firstName: form.firstName, lastName: form.lastName, }, ] } </script>親コンポーネントの script 内で addUser を定義して
<InputForm {addUser} />で子コンポーネントに渡します。
ショートハンドを用い短いコードで実現できます。InputForm.svelte<script> export let addUser let form = { firstName: '', lastName: '', } function onClickAddButton() { addUser(form) form.firstName = '' form.lastName = '' } </script> <div className="input-field"> <label htmlFor="first_name">First Name</label> <input id="first_name" type="text" bind:value={form.firstName} /> </div> <div className="input-field"> <label htmlFor="last_name">Last Name</label> <input id="last_name" type="text" bind:value={form.lastName} /> </div> <button class="btn waves-effect waves-light" on:click={onClickAddButton}> Add </button>InputForm.svelt を改修しました。
親コンポーネントから渡ってきた関数は、export let addUserで使用できるようになります。
Object の値の更新を React で使用するときは Object 丸ごと更新されてしまうためスプレット構文を使用するなどして工夫する必要がありました。
しかし、 Svelte では直接入れ子になった値を更新するので記述を減らせます。ユーザーを表示するテーブルを実装する
UserList.svelte<script> export let users </script> <table> <thead> <tr> <th>First Name</th> <th>Last Name</th> </tr> </thead> <tbody> {#each users as user} <tr> <td>{user.firstName}</td> <td>{user.lastName}</td> </tr> {/each} </tbody> </table>Reactにおいて map で実現していたことを each を使用して実現しています。
テンプレートエンジンらしい書き方なのかなぁと思います。
key を設定する必要のないところも違いですね。完成
所感
React : 約 130 行 ⇨ Svelte : 約 80 行
![]()
個人的にはthis.state.hogeなど、 React の書き方に慣れていて少ないコードに少し不安感を覚えましたが、慣れれば快適に書けそうです。
フレームワークを触ったことない方や Vue 使いには取っつき易く良い開発体験を感じられると思うのでぜひ試してみてください。
- 投稿日:2020-01-26T22:07:52+09:00
[書評] "ゲーム&モダンJavaScript文法で2倍楽しい" グラフィックスプログラミング入門
著者のdoxas様より御恵贈頂きました。内容のご紹介と感想を記したいと思います。
結論を先に書いておきますが、JavaScriptの初心者の方から、これまでゲームの開発をされたことが無い方、ブラウザ上でのCanvasを用いたプログラミングによる画像生成に興味のある方等にとてもお勧めです技術評論社: https://gihyo.jp/book/2020/978-4-297-11085-7/
ソースコード: https://github.com/doxas/graphics-programming-book
本書指定推奨開発環境:
- Chrome
- Visual Studio Code著者doxasさんについて
私とdoxasさんとは直で会ったことは2、3度しかないのですが、初めてお会いしたのは2014年6月のことでした。当時、既に私はdoxasさんのWebGL開発支援サイトに1読者としてかなりお世話になっておりました。
こちらのサイトはWebGLのコードの書き方を日本語で1から完全に教えるという当時は非常に珍しくありがたいサイトです。そのdoxasさんがHTML5のイベントにて講師として登場されるというので非常に楽しみにしながら1観客として参加しました。現れたdoxasさんはまだ若く、塾講師のようにハキハキと話される方でした。その時にdoxasさんが、自分はサンデープログラマであり、(当時は)通常は全く異なる仕事をされていると伺って非常に驚いた覚えがあります。WebGLをご存知の方ならおわかり頂けるかと思うのですが、WebGLはOpenGL ESを元にしたWeb上でGPUを用いた3Dグラフィックスを実現する基礎技術です。当然、中身はかなり低レイヤーの技術であり、難しいものです。専門の技術者以外の方がこのような詳細なサイトを作られたとは信じられない思いでした。
私は優秀な技術者というのは技術に対する「好き」という感情が桁外れな人達だと常日頃から感じています。私の周りの優秀なプログラマはまるで呼吸をするように常日頃からプログラムを書いたり技術に触れている人達でした。doxasさんもWebGLに対するその愛は上記のサイトでの実に細かな解説に溢れています。doxasさんの初心者を落ちこぼれにさせまいとする事細かな説明は時にある程度わかっている人には冗長にもなりえるのですが、知識がなく、周りに頼る人がいない人にはこれほど頼りになるものはありません。ある方はこの説明の仕方を"「なんとしてでもお前に基礎を叩き込むぞ」みたいなdoxas節"と表現されております
実際、doxasさんは上記の解説サイトだけでは足りずにWebGLを利用したサイトを紹介するWebGL総本山を開設し、さらには御自身でWebGLの塾を開設してしまいました。
WebGL総本山: https://webgl.souhonzan.org
WebGLスクール: https://webgl.souhonzan.org/entry/?v=0003正直、1つの技術だけに入れ込むというのは中々難しいものです。しかしdoxasさんはWebGLを広めたいという欲求にかられ、実際に何年も有料でのスクールの運営を成功させました。そしていつもTwitterでどうすれば難しい数学に基づく3DCGの世界を理解してもらえるのか、その点について苦心されています。
非常に稀有な方だと感じます。
書籍の内容について
そんなdoxasさんが書籍を書かれたというので当然、中身はWebGLだと思いました。ところが中身は2Dだったのです。これには驚きました。しかし本書はいかにもdoxasさんの本でありました。
本書のタイトルはグラフィックスプログラミング入門とされています。通常のグラフィックスの本ですと内容がお絵描きに寄せてあったり、画像処理に寄せてあったりします。本書でも簡単なお絵描きや画像処理は行っています。しかし、本書が大きく力を入れており、さらに本書の大きな特徴となっているのはベクトルをメインとした数学的な空間の捉え方とそれを用いたゲーム開発だったのです。
本書は8章で構成されています。
1章は「基礎の基礎」と名付けられCGの世界がどのような世界かを紹介します。
2章は「ES2015入門」としてJavaScriptを学びます。
バージョン管理の説明を避けるためソースの配布はGitHubながらもzipで落とせることを説明。
環境をChromeに限定することで説明を最小限にしていますが、その分デバッグコンソールの使い方を説明することで実用性が高くなっています。本書特有な説明としてはコメントにJSDocを徹底しているところが面白いと思いました。この章のみでなく、全体的ですが仕事にも使えるような美しいコードを重視される説明が多いかと思います。5章の中に説明が分かれていますが、JSに関してはprototype basedであることを重点的に説明されているのが印象的でした。ES2015なのでclass構文を使うのですが、一般的なクラスによるオブジェクト指向言語ではないということをしっかりと説明されているのが良かったと思います。3章は一転してグラフィックプログラミングに必要な数学の基礎、三角関数と行列を学びます。
いつだったか、どこかの政治家が三角関数は実生活には何の役にも立たないと発言して炎上しました。もちろん、実際には三角関数は現代社会ではなくてはならないものです。そのことを読者は5章からのゲーム開発で嫌というほど知ることになりますここで数学が出てくると引く人がでるかもしれません。しかし、実際には三角関数はただの比でしかありません。関数自体はコンピュータが計算してくれます。大事なことは内積や外積などの関係とその利用方法を知ることです。本書は主にベクトル、三角関数、行列を扱いますが、これは小学生でも算数の成績が良い子なら問題無く理解できるでしょう。ベクトルは数の組み合わせでしかなく、行列演算は足し算とかけ算の延長でしかありません。できれば親御さんやお兄さん、お姉さんがちょっと手助けしてあげると良いかと思います。
82ページのコラムで説明される内積、外積の説明は至高の一文だと思います。5章以降でわからなくなったらこのページに戻ると良いでしょう。4章ではWeb上でグラフィックを書くための基本としてCanvas2Dを学びます。これは難しいことは何もありません。命令通りにブラウザが絵を描いてくれますので楽しんで下さい。
ゲーム開発 (5章~7章)
5章から7章までは楽しい、楽しい、ゲームの開発です。題材はシューティングゲームです。
ここからの章ではプログラムを漸進的に徐々に拡張していくことで開発します。
バージョン管理の説明を省くためにプログラムはディレクトリ別にバージョンが分けられております。
何と3章にわたり、1つのゲームで23もの別バージョンでの解説になります。doxasさんの「なんとしてでもお前に基礎を叩き込むぞ」の解説はわざと失敗を体験させることも含みます。例えば最初は自機の移動がカクカクになりますが、その後、理由と改善策が提示されます。また自機の弾も最初は一気に弾が出てしまい、弾切れになりますが、それをどう調整するかが提示され、さらには3-wayへと改善され、画像の回転表示等へと説明が続きます。
コードは日本語でほぼ1行毎のようにwhyを示すコメントが付けられています。これは本当に大変だったのではと思います。
本書のゲーム実装では私は結構な衝撃を受けることになりました。ちょっと脱線するのですが読者はゲームを開発されたことがありますでしょうか?
昔はゲームもかなり単純でした。画面は文字列だけだったり、グラフィックが使えても色が8色だけだったり、狭い空間であったりしました。この時代ゲームは整数を使うのが普通でした。計算機というのは整数のほうが実数よりも計算が速いものです。昔のPCは今のものに比べると信じられない程遅かったので整数を使い座標を指定し、画面の値を直接読んで衝突判定をしたりしたものでした。ゲームの開発と言えば最適化との戦いでした。
例えば2004年に出版された「シューティングゲーム アルゴリズム マニアックス (著・松浦健一郎)」ではDDA(デジタル微分解析器)を用いた整数のみで自機狙い弾を実装するアルゴリズム等が紹介されています。
本書のプログラムはとても富豪的です。全てのキャラクターは自機、
敵機の弾を問わずCharacterクラスを継承し、位置ベクトルと速度ベクトルを持ちます。移動は位置ベクトルに速度ベクトルを足すだけです。自機の速度ベクトルを回転させれば自機の3-way弾は斜めに飛んで行きます。敵機の自機狙い弾は自機と敵機の間のベクトルを取るだけです。圧巻は最後のホーミング弾でしょう。ホーミング弾自身のベクトルと自機との間のベクトルと、内積で回転量を、外積で回転の向きを決定するだけとなります。衝突判定はオブジェクト間の距離の問題のみになります。これには3章で習った数学がバンバンと利用されます。
これは頭が古い人間には考えられない世界です。PCが遅くて仕方が無い時代の人間はこのようなやり方では組み合わせの爆発が起こってとても遅くなるのではと余計なことを考えてしまいます。しかし、今のPCは、JavaScriptは流石です。これだけ複雑で、莫大な量の演算をきちんと60FPSを実現する16msの間にやってくれます。
本書はゲーム開発が本題ではありません。あくまでもグラフィックスプログラミングが主題です。そのような本書では最適化の話等は枝葉でしょう。そういう意味で、本書の解説はゲームを通して複雑な関係性を持つグラフィックをどのように構築するか、初心者に向けての説明に実にうまく成功していると感じました。
また言語がJavaScriptなのも利点です。過去多くのゲームの教科書はC++で、アドレス参照等の難解な文法を理解することが必要でした。しかし、本書ではJSでほぼarrayを用いて本質的な問題だけに集中することができます。これは本当に良い時代になったと感じます。
8章 画像処理
8章は一転して画像処理を行います。ImageDataにより画像のpixelデータを得て、演算によりノイズ除去、エッジ検出、ネガ、モザイクを作成します。
この章はちょっとだけ、物足りないとは感じました。本書がグラフィックプログラミングの本であるために、これらのテーマも避けては通れなかったのかもしれません。画像処理の基礎中の基礎を学ぶという意味では成功しておりますが、欲を言えばこの内容を本書のこの場所で扱うのであればもう一工夫が欲しかったと感じます。
ピクセルの処理はあまり3章の数学を活かせてはいません。できればデモで良くある画像に対するアニメーションの波の加工等、より高度な内容も付属できれば良かったのではないかと思います。そうすれば3章の数学と5章からのゲーム開発とanimationの内容が活かせたのではと考えます。
重箱の隅
本書は大変に満足できる内容だったのですが、重箱の隅に過ぎないのですが気になる点もありました。その内の1つがmap関数の扱いです。
JSのmapは他言語でも良くあるものですがarrayの要素全てに関数を適用し、arrayを返す関数です。関数型では基礎中の基礎で、BigDataの分野では多大な量のデータを並列的に処理するためのMapReduceの仕組みにも採用されたものでした。
本書ではmap関数をreduceやforEachのほうが適切な場合にも、あえてmap関数を利用しています。これはJSの説明を省くためにあえてそうした可能性があります。
しかし、例えば本書で各画像のload終了を確認する場合にbool値に対し全てandを取る場合等はreduceのほうが適切です。また全てのobjectに対し個別に衝突判定を行う部分ではforEachのほうが適切だったと言えるでしょう。
まとめ
本書はグラフィックスプログラミング入門と題して、ブラウザ上でのCanvasを用いた画像描画に関して非常に良くできた教材になったと感じます。
著者の得意である、基礎と数学を重視した詳細な説明が十分に発揮されていました。願わくはdoxasさんが好きなWebGLの書籍もぜひ、続編で読めればと思います。
- 投稿日:2020-01-26T22:04:55+09:00
Web Animations API
CSS transition、CSS animationとの比較
キーフレームの制御まで含めてJavaScriptだけで管理可能
CSSによるアニメーション(CSS Transition、CSS Animation)もご参照ください。
Web Animations APIのオプション CSS Animationsのオプション duration animation-duration delay animation-delay fill animation-fill-mode iterations animation-iteration-count direction animation-direction easing animation-timing-function iterationStart なし endDelay なし
web animations api独自のオプション 内容 iterationStart アニメーションの実行開始位置を設定 0.5 の場合 50% の位置から始まり1周する (50% -> 100%/0% -> 50%) endDelay アニメーションが終わってから後続のアニメーションを開始するまでの時間 キーフレーム
offsetで指定できます。範囲(0~1)
未指定の場合は等間隔になります。アニメーションの例
大きさ変更のアニメーション
html<div class="rect"></div>css.rect { background-color: red; width: 100px; height: 100px; }jsconst element = document.querySelector(".rect"); element.animate( [ { transform: "scale(1)", offset: 0 }, { transform: "scale(2)", offset: 0.8 }, { transform: "scale(10)", offset: 1 } ], { duration: 1000, fill: 'forwards', easing: 'ease' } );移動のアニメーション
html<div class="rect"></div>css.rect { background-color: red; width: 100px; height: 100px; } `` ```:js const element = document.querySelector(".rect"); element.animate( [ { transform: "translateX(0px)", offset: 0 }, { transform: "translateX(10px)", offset: 0.8 }, { transform: "translateX(100px)", offset: 1 } ], { duration: 1000, fill: 'forwards', easing: 'ease' } );透明度のアニメーション
html<div class="rect"></div>css.rect { background-color: red; width: 100px; height: 100px; }jsconst element = document.querySelector(".rect"); element.animate( [ { opacity: 0, offset: 0 }, { opacity: 0.5, offset: 0.8 }, { opacity: 1, offset: 1 } ], { duration: 10000, fill: 'forwards', easing: 'ease' } );明度のアニメーション
html<div class="rect"></div>css.rect { background-color: red; width: 100px; height: 100px; }jsconst element = document.querySelector(".rect"); element.animate( [ { filter: "brightness(0%)", offset: 0 }, { filter: "brightness(50%)", offset: 0.2 }, { filter: "brightness(100%)", offset: 1 } ], { duration: 1000, fill: 'forwards', easing: 'ease' } );彩度のアニメーション
html<div class="rect"></div>css.rect { background-color: red; width: 100px; height: 100px; }jsconst element = document.querySelector(".rect"); element.animate( [ { filter: "grayscale(0%)", offset: 0 }, { filter: "grayscale(50%)", offset: 0.2 }, { filter: "grayscale(100%)", offset: 1 } ], { duration: 1000, fill: 'forwards', easing: 'ease' } );複合アニメーション
例:四角が徐々に不透明になり、回転しながら右に移動
html<div class="rect"></div>css.rect{ background-color: red; width:100px; height:100px }jsconst element = document.querySelector(".rect"); element.animate( [ { transform: "translateX(0px) rotate(0deg)", opacity: 0, offset: 0 }, { transform: "translateX(10px) rotate(180deg)", opacity: 0.2, offset: 0.5 }, { transform: "translateX(1000px) rotate(270deg)", opacity: 1, offset: 1 } ], { duration: 1000, fill: 'forwards', easing: 'ease' } );アニメーション実行中のコントロール
メソッド 内容 play() 再生する pause() 一時停止する reverse() 逆再生する cancel() 再生をやめる finish() 終了時点まで進める updatePlaybackRate() 再生速度を設定
プロパティ 内容 currentTime アニメーションの現在時間を表示(ミリ秒) effect アニメーションのAnimationEffectReadOnlyの取得、設定 finished アニメーション終了時に Promiseを返す id アニメーションを識別するStringの取得、設定 pending アニメーションが再生の非同期処理(初期化、再生停止)のため現在待ち状態かどうか playState アニメーションの再生状況を返す playbackRate アニメーションの再生速度の取得、設定 ready アニメーションの再生準備ができた時にPromiseを返す startTime アニメーションが再生される時間の取得、設定 timeline アニメーションのtimelineの取得、設定
再生速度のプロパティの値 内容 playbackRate =2 2倍速 playbackRate =-1 逆再生 playbackRate *= 1.5 実行されるたびに再生速度が 1.5 倍
イベント 内容 oncancel アニメーション中止時に発生するイベント onfinish アニメーション終了時に発生するイベント html<div class="rect"></div> <div class="pause">Pause</div> <div class="play">Play</div> <div class="reverse">Reverse</div> <div class="finish">Finish</div> <div class="cancel">Cancel</div>css.rect { background-color: red; width: 100px; height: 100px; }jsconst element = document.querySelector(".rect"); const pause = document.querySelector(".pause"); const play = document.querySelector(".play"); const reverse = document.querySelector(".reverse"); const cancel = document.querySelector(".cancel"); const finish = document.querySelector(".finish"); const move = element.animate( [ { transform: "translateX(0px) rotate(0deg)", offset: 0 }, { transform: "translateX(10px) rotate(180deg)", offset: 0.5 }, { transform: "translateX(1000px) rotate(270deg)", offset: 1 } ], { duration: 10000, fill: 'forwards', easing: 'ease' } ); move.pause(); move.onfinish = () => { console.log("アニメーションが終了した"); }; move.oncancel = () => { console.log("アニメーションがキャンセルされた"); }; play.addEventListener("click", () => { move.play(); }); pause.addEventListener("click", () => { move.pause(); }); reverse.addEventListener("click", () => { move.reverse(); }); cancel.addEventListener("click", () => { move.cancel(); }); finish.addEventListener("click", () => { move.finish(); });polyfill
https://github.com/web-animations/web-animations-js/tree/866e01726cf61ffeabad2baa7f6c10e3cadee138
- 投稿日:2020-01-26T20:56:47+09:00
React hooksを基礎から理解する (useState編)
React hooksとは
React 16.8 で追加された新機能です。
クラスを書かなくても、stateなどのReactの機能を、関数コンポーネントでシンプルに扱えるようになりました。React hooksのうち、実際によく使用される3つに絞って紹介します。
- React hooksを基礎から理解する (useState編)
今ここ
- React hooksを基礎から理解する (useEffect編)
- React hooksを基礎から理解する (useContext編)
クラスコンポーネントと関数コンポーネント
クラスコンポーネントと関数コンポーネントの違いを確認してみます。
クラスコンポーネント
import React from 'react' import './styles.css' // countの初期値として、1~10までのランダムな数値を生成 const intialState = Math.floor(Math.random() * 10) + 1 class Counter extends React.Component { constructor(props) { super(props) this.state = { // クラスでは、コンストラクタ内で、this.stateの初期値{ count: intialState }をセット count: intialState, // this.stateの初期値{ open: false }をセット open: true } } // toggleメソッドを作成 toggle = () => { this.setState({ open: !this.state.open }) } render() { return ( <> <button onClick={this.toggle}> {this.state.open ? 'close' : 'open'} </button> <div className={this.state.open ? 'isOpen' : 'isClose'}> <p>現在の数字は {this.state.count} です</p> {/*ボタンをクリックした時に、this.setState()を呼ぶことでcountステートを更新 */} <button onClick={() => this.setState({ count: this.state.count + 1 })} > + 1 </button> <button onClick={() => this.setState({ count: this.state.count - 1 })} > - 1 </button> <button onClick={() => this.setState({ count: 0 })}>0</button> <button onClick={() => this.setState({ count: intialState })}> 最初の数値に戻す </button> </div> </> ) } } export default Counterちなみにstyles.cssの中身はこれだけ。
.isClose { display: none; } .isOpen { display: block; }関数コンポーネント
hooks の
useStateを使ってクラスコンポーネントから関数コンポーネントに書き換えてみます。
関数コンポーネントの基本形は以下の通り。const ExComponent = props => { // ここでhooksを使える return <div /> }useStateの基本形
useStateによって React のstateの機能を関数コンポーネントに追加します。const [count, setCount] = useState(intialState) // ちなみにクラスコンポーネントでは、、、 this.state = { count: intialState }
useStateの左辺のstate変数には任意の名前を付けることが出来ます。
(分割代入構文をイメージすると理解しやすいです。)
- 1つ目の要素:
stateの現在の値- 2つ目の要素:
stateの現在の値を更新するための関数stateが更新されてもintialStateはintialStateとして保持される// 関数コンポーネント内で state を使えるようにするため、useState をインポート import React, { useState } from 'react' import './styles.css' const Counter = () => { // countの初期値として、1~10までのランダムな数値を生成 const intialState = Math.floor(Math.random() * 10) + 1 // count という名前の state 変数を宣言、初期値 intialState をセット const [count, setCount] = useState(intialState) // open という名前の state 変数を宣言、初期値 true をセット const [open, setOpen] = useState(true) // toggleの関数を宣言 const toggle = () => setOpen(!open) return ( <> <button onClick={toggle}>{open ? 'close' : 'open'}</button> <div className={open ? 'isOpen' : 'isClose'}> <p>現在の数字は{count}です</p> {/* setCount()は、countを更新するための関数。countを引数で受け取ることも出来る */} <button onClick={() => setCount(prevState => prevState + 1)}> + 1 </button> <button onClick={() => setCount(count - 1)}>- 1</button> <button onClick={() => setCount(0)}>0</button> <button onClick={() => setCount(intialState)}>最初の数値に戻す</button> </div> </> ) } export default Counterクラスで書いた場合と、同じ結果になりました
再レンダリング後も React はその変数の現在の
stateの値をそのまま持っており 、最新のstateの値を関数に渡します。現在のstateの値を更新したい場合は、setStateを呼びます。最後に
次回は
useEffectについて書きたいと思います。参考にさせていただいたサイト
- 投稿日:2020-01-26T19:10:02+09:00
ajaxで取得したデータでrenderすると、Cannot read property xx of undefined エラーになる
問題点
例えば以下のように、バックグラウンドで取得したデータを表示させたいとします。
xxx.jsconstructor(props) { super(props); this.state = { result: {} } fetch("http://localhost/api/test") .then(response => response.json()) .then(json => this.setState({ result: json })); // /api/testは、以下のようなレスポンスを返す // { // test1 : "aaa", // test2 : { // test3 : "bbb" // } // } } render() { return ( <div> <p>{this.state.result.test1}</p> // "aaa"が表示される <p>{this.state.result.test2.test3}</p> // "bbb"が出てほしいがエラーになる </div> ) }このとき、this.state.result.test2.test3の部分で、以下のようなエラーが出てしまいます。
Cannot read property 'test3' of undefined
test3 の親であるthis.state.result.test2がundefinedですよ、というエラーです。原因
恐らく、this.state.resultの中身が全部セットされる前にrenderメソッドが動いてしまっているためだと思います。
解決法
1.描画前にチェックを行う
undefinedチェックを入れてあげると、エラーなく動くようになります。
xxx.jsrender() { return ( <div> <p>{this.state.result.test1}</p> // "aaa"が表示される <p>{this.state.result.test2 ? this.state.result.test2.test3 : ""}</p> // "bbb"が表示される </div> ) }JSX内ではif文が使えないので、三項演算子を使っています。
2.あらかじめ空のプロパティをセットしておく
どのような要素が返ってくるかが最初からわかっているのであれば、以下のように予めセットしておくことでも対応できます。
xxx.jsconstructor(props) { super(props); this.state = { result: { test2 : {} // あらかじめ空をセットしておく } } fetch("http://localhost/api/test") .then(response => response.json()) .then(json => this.setState({ result: json })); } render() { return ( <div> <p>{this.state.result.test1}</p> // "aaa"が表示される <p>{this.state.result.test2.test3}</p> // "bbb"が表示される </div> ) }最後に
もっといい方法がありそうな気もするので、何かわかったら追記します。
- 投稿日:2020-01-26T18:47:07+09:00
モバイル初心者がQiitaのスマホアプリを爆速で個人開発した話【React Native】
はじめに
何についての記事?
React Nativeを使って3週間弱でQiitaの非公式スマホアプリを個人開発・公開したのでその時の所感を書き連ねようと思います.
個人開発する上での反省点や、使用したフレームワークであるReact Nativeのこと等を書くつもりです.
なお、現状Android版しか開発していないので以下はすべてAndroidの話になります.作ったもの
「Qiita API v2」を用いてアプリ上で閲覧や記事のストック、タグのフォロー等、一通りの操作ができるものを作りました.
「Qiita in Mobile App」ということで、アプリ名は「QiiMa!」です.「某ニュースアプリ的な感覚でQiitaの記事が読めたらいいな」
っていう自分の欲望を解決するアプリになってます.機能としては,
- 記事の閲覧、いいね、ストック
- ストックした記事の一覧表示
- ユーザー情報の閲覧、フォロー
- タグ情報の閲覧、フォロー
- フォローしているタグがついた記事の閲覧
- 記事検索
- アプリ上で読んだ記事の履歴閲覧
- アプリトップページに表示する記事一覧の並び替え
等必要十分な機能を搭載したつもりです.
個人的には、
- フォローしているタグのトレンド、新着記事が見れる.
- さらにその表示するタブの順序をカスタマイズできる.
点が素晴らしいと思ってます(自画自賛)
使用技術
このアプリはReact Nativeというクロスプラットフォームのモバイル開発フレームワークで作ってます.
開発について
期間
githubのログを見る限り、開発着手から公開まで19日間です.
完成物を見ると、「19日も掛けてこんな完成度かい」って気はしますが、
- モバイル開発全然したことない(数年前に電卓アプリ作ってみた程度)
- React Nativeに初めて触れた
- そもそもJavaScript歴すら1ヶ月位
って状態だからなのでそのへんは温かい目で見てください.
手法
ぶっちゃけノリと勢いでゴリゴリコーディングするスタイルでやってました.
反省点としては、
- ロードマップが不明瞭すぎた
- 行き当りばったりなので、午前中に書いたコードを午後に全部消して書き直したりしてた
- 何を実装して何を見送るのかの基準があいまいだった
などなど、枚挙に暇がないくらい思いつきますが、全ては 計画性のNASA に帰着します.
というのも、もともと
「ちょっとReact Native勉強すっか」
くらいの気持ちで始めたので、コーディング以外の部分を疎かにしていました.
仕様書とか、ロードマップとかを最初に定義しておくことの大切さを身を以て実感しました.
そのへんの手順をしっかり踏んでいれば結果的にもっと短い期間で完成できたと思います.ただ、あくまで今回の本質的な目標は「
React Nativeの勉強」なので上記の反省点は、「アプリリリース」の観点から見た結果論ですけども・・・React Nativeについて
今回始めて
React Nativeに触れたので、良かった点や辛かった点について書いてみようと思います.良かった点
- そのへんに便利なライブラリが転がってる
- ホットリロードが便利
- 直感的に書ける(個人的感想)
- component思想が書きやすい(個人的感想)
パッと思いつくのはこのぐらいです.
そのへんに便利なライブラリが転がってる
React Nativeにはサードパーティ製の便利なライブラリがたくさんあって、簡単にインストールして使うことが出来ます.
例えば、アプリ内でスクロールできるタブバーがありますが、これはreact-native-scrollable-tab-viewというライブラリを使用しています.
これを自力実装するとなると結構しんどいと思うのですが、うまくこういうライブラリを利用すれば、効率的に開発をすすめることが出来ます.
さらに、「流石にこんなニッチなライブラリないやろ」っていうものも案外公開されてたりもします.
ただ割とこれは諸刃の剣のような気はしています(後述)ホットリロードが便利
コード上での変更点が、リビルドとか無しでもすぐにアプリ側に反映されます.
細かいデザインの修正などのときに重宝しました.直感的に書ける(個人的感想)
他の言語やフレームワークを用いたモバイル開発の経験がないのでかなり主観的ですが、
React nativeは直感的にコーディングできて書きやすいと感じました.例えば、何かしらのボタンが押されたときに処理を走らせるような実装にするとき、
Javaとかだと、別ファイルに記述したボタンのid取ってきイベントリスナ設定して...
みたいな感じだと思います(違ったらごめんなさい).
これがReact Nativeだと、ボタンの描画を設定する際にそのままonPressプロパティとして関数を渡せます.
このあたりは、設計思想とか色々あるとは思うのですが、とりあえず初心者の自分にとっては直感的で書きやすかったという感想です.component思想が書きやすい(個人的感想)
これも初心者の自分としての感想ですが、アプリ全体を細かいcomponentの寄せ集めみたいなイメージで構築できるので書きやすかったです.
どのcomponentがどのデータを持っていて、どういうふうに描画されるのかを考えてcomponent を作り、最後にデータの受け渡しなどを考えながらアプリ全体を構築していくといった感じです.辛かった点
- 使うライブラリを間違えると地獄
- 情報がそこまで多くない
- 先行きがちょっと不安
使うライブラリを間違えると地獄
良い点で便利なライブラリが多いということを書きましたが、反面それは辛い点にもなり得ました.
まず、
React Native自体まだvarsion 1.0に達していないので、結構破壊的アップデートが多いようです(自分はまだ3週間目くらいなので伝聞ですが).
そのため、良さげなライブラリを見つけても、ちょっと更新されてなかったりすると簡単にエラー祭り開催が決定してしまいます.
また、エラーは出ないが挙動は正しくないみたいなケースもあります.なので、便利なライブラリを見つけても、githubのissueやstar数などから、使用に耐えるかどうかを判断する力が大事になってくると思います.
自分が開発してる中でも、「便利なライブラリがあったけど、結局自分で実装したほうが確実で安定している」というケースのも多々ありました.情報がそこまで多くない
そこまでメジャーなフレームワークではないということで、まだまだ情報は多くはなかったです.
せっかくヒットした情報も、バージョン違いで現バージョンには対応していないとかいうケースも多々あります.
なのでしょうもないことが原因で時間が溶けていくこともありました.
ただ、OSSなのでそのへんのライブラリ含めソースコードがたくさん落ちているので、それらを見れば意外となんとかなったりはします.先行きがちょっと不安
辛い点1個めの話にも繋がるのですが、今動いているコードが次のバージョンでは動かないということは有り得るので、そこが不安ではあります.
枯れてない技術は全部そうでしょって言われたらそうなのですが...
React Native自体は今後もっと発展していくと信じているのですが、周辺ライブラリはちょっとどうなるか分からないものもあるので、効率と安定のいいバランスを保ちながらライブラリを選定する力が大事になりそうですね(2回目).アプリの改善点
このアプリはノリと勢いで開発して、ノリと勢いで公開したので、結構改善点があります...
一番大きいものとしては
- 記事の表示がたまに正しくない
という改善点があります.
記事は、APIで得られたMarkdownをライブラリを用いてレンダリングしているのですが、表記の揺れなどの影響でうまくレンダリングされずにそのままの文字列として表示されてしまうことがあります.
気づいた範囲で対処しているのですが、なかなか全部には対応しきれていないです.
また、数式のレンダリングは、ブラウザであればMathjaxで表示できるのですが、そこにも対応しきれておらず...
h1タグやリンクが正しく表示されない問題は、APIでhtmlを取得してレンダリングすれば解決できるのですが、そうなると今度はインラインブロックやブロックコードのシンタックスハイライトの描画が難しくなったり...
しかも、結局数式をレンダリングしようと思ったらhtmlでも無理なので、Web Viewを用いることになるのですが、それはそれでブラウザでもQiitaにログインしなければならなかったりで...
現状はとりあえず潰せる表記の揺れは潰して、それでも読みづらい記事は「ブラウザで開くボタン」からお好きなブラウザで読めるようにしています.
アプリのコンセプトとしては、トレンドやストックした記事へのアクセスを簡単にしたいというところがメインなので、正しいレンダリングは後回しになってしまっています...iOS対応について
現状
iOSには対応する予定はありません.
なぜならば、developer登録年間1万円は厳しいので....その他
今回このアプリを開発・公開するにあたって、QiitaのAPI等を利用しているので、ちゃんとQiitaのサポートの方に問い合わせを行ったのですが、
どこぞのよく分からん学生にも関わらず懇切丁寧に対応していただいてびっくりしました.
軽くあしらわれても仕方ないなくらいの気持ちで問い合わせを行ったのですが、なんでも言ってみるもんですねぇ.最後に
React Nativeの勉強が目的だったのですが、せっかく作ったので興味があれば使って改善点などフィードバックしていただけると泣いて喜びます.また、自分と同じく
React Native初心者のためになりそうな知見なども得れたので、
需要があれば そのうち記事化しようと思います!最後にもう一回リンクを張っておきます!
Google Play Storeで見る
- 投稿日:2020-01-26T18:47:07+09:00
【React Native】Qiitaのスマホアプリを3週間弱で個人開発した話
はじめに
何についての記事?
React Nativeを使って3週間弱でQiitaのスマホアプリを個人開発・公開したのでその時の所感を書き連ねようと思います.
個人開発する上での反省点や、使用したフレームワークであるReact Nativeのこと等を書くつもりです.
なお、現状Android版しか開発していないので以下はすべてAndroidの話になります.作ったもの
「Qiita API v2」を用いてアプリ上で閲覧や記事のストック、タグのフォロー等、一通りの操作ができるものを作りました.
「Qiita in Mobile App」ということで、アプリ名は「QiiMa!」です.「某ニュースアプリ的な感覚でQiitaの記事が読めたらいいな」
っていう自分の欲望を解決するアプリになってます.機能としては,
- 記事の閲覧、いいね、ストック
- ストックした記事の一覧表示
- ユーザー情報の閲覧、フォロー
- タグ情報の閲覧、フォロー
- フォローしているタグがついた記事の閲覧
- 記事検索
- アプリ上で読んだ記事の履歴閲覧
- アプリトップページに表示する記事一覧の並び替え
等必要十分な機能を搭載したつもりです.
個人的には、
- フォローしているタグのトレンド、新着記事が見れる.
- さらにその表示するタブの順序をカスタマイズできる.
点が素晴らしいと思ってます(自画自賛)
使用技術
このアプリはReact Nativeというクロスプラットフォームのモバイル開発フレームワークで作ってます.
開発について
期間
githubのログを見る限り、開発着手から公開まで19日間です.
完成物を見ると、「19日も掛けてこんな完成度かい」って気はしますが、
- モバイル開発全然したことない(数年前に電卓アプリ作ってみた程度)
- React Nativeに初めて触れた
- そもそもJavaScript歴すら1ヶ月位
って状態だからなのでそのへんは温かい目で見てください.
手法
ぶっちゃけノリと勢いでゴリゴリコーディングするスタイルでやってました.
反省点としては、
- ロードマップが不明瞭すぎた
- 行き当りばったりなので、午前中に書いたコードを午後に全部消して書き直したりしてた
- 何を実装して何を見送るのかの基準があいまいだった
などなど、枚挙に暇がないくらい思いつきますが、全ては 計画性のNASA に帰着します.
というのも、もともと
「ちょっとReact Native勉強すっか」
くらいの気持ちで始めたので、コーディング以外の部分を疎かにしていました.
仕様書とか、ロードマップとかを最初に定義しておくことの大切さを身を以て実感しました.
そのへんの手順をしっかり踏んでいれば結果的にもっと短い期間で完成できたと思います.ただ、あくまで今回の本質的な目標は「
React Nativeの勉強」なので上記の反省点は、「アプリリリース」の観点から見た結果論ですけども・・・React Nativeについて
今回始めて
React Nativeに触れたので、良かった点や辛かった点について書いてみようと思います.良かった点
- そのへんに便利なライブラリが転がってる
- ホットリロードが便利
- 直感的に書ける(個人的感想)
- component思想が書きやすい(個人的感想)
パッと思いつくのはこのぐらいです.
そのへんに便利なライブラリが転がってる
React Nativeにはサードパーティ製の便利なライブラリがたくさんあって、簡単にインストールして使うことが出来ます.
例えば、アプリ内でスクロールできるタブバーがありますが、これはreact-native-scrollable-tab-viewというライブラリを使用しています.
これを自力実装するとなると結構しんどいと思うのですが、うまくこういうライブラリを利用すれば、効率的に開発をすすめることが出来ます.
さらに、「流石にこんなニッチなライブラリないやろ」っていうものも案外公開されてたりもします.
ただ割とこれは諸刃の剣のような気はしています(後述)ホットリロードが便利
コード上での変更点が、リビルドとか無しでもすぐにアプリ側に反映されます.
細かいデザインの修正などのときに重宝しました.直感的に書ける(個人的感想)
他の言語やフレームワークを用いたモバイル開発の経験がないのでかなり主観的ですが、
React nativeは直感的にコーディングできて書きやすいと感じました.例えば、何かしらのボタンが押されたときに処理を走らせるような実装にするとき、
Javaとかだと、別ファイルに記述したボタンのid取ってきイベントリスナ設定して...
みたいな感じだと思います(違ったらごめんなさい).
これがReact Nativeだと、ボタンの描画を設定する際にそのままonPressプロパティとして関数を渡せます.
このあたりは、設計思想とか色々あるとは思うのですが、とりあえず初心者の自分にとっては直感的で書きやすかったという感想です.component思想が書きやすい(個人的感想)
これも初心者の自分としての感想ですが、アプリ全体を細かいcomponentの寄せ集めみたいなイメージで構築できるので書きやすかったです.
どのcomponentがどのデータを持っていて、どういうふうに描画されるのかを考えてcomponent を作り、最後にデータの受け渡しなどを考えながらアプリ全体を構築していくといった感じです.辛かった点
- 使うライブラリを間違えると地獄
- 情報がそこまで多くない
- 先行きがちょっと不安
使うライブラリを間違えると地獄
良い点で便利なライブラリが多いということを書きましたが、反面それは辛い点にもなり得ました.
まず、
React Native自体まだvarsion 1.0に達していないので、結構破壊的アップデートが多いようです(自分はまだ3週間目くらいなので伝聞ですが).
そのため、良さげなライブラリを見つけても、ちょっと更新されてなかったりすると簡単にエラー祭り開催が決定してしまいます.
また、エラーは出ないが挙動は正しくないみたいなケースもあります.なので、便利なライブラリを見つけても、githubのissueやstar数などから、使用に耐えるかどうかを判断する力が大事になってくると思います.
自分が開発してる中でも、「便利なライブラリがあったけど、結局自分で実装したほうが確実で安定している」というケースのも多々ありました.情報がそこまで多くない
そこまでメジャーなフレームワークではないということで、まだまだ情報は多くはなかったです.
せっかくヒットした情報も、バージョン違いで現バージョンには対応していないとかいうケースも多々あります.
なのでしょうもないことが原因で時間が溶けていくこともありました.
ただ、OSSなのでそのへんのライブラリ含めソースコードがたくさん落ちているので、それらを見れば意外となんとかなったりはします.先行きがちょっと不安
辛い点1個めの話にも繋がるのですが、今動いているコードが次のバージョンでは動かないということは有り得るので、そこが不安ではあります.
枯れてない技術は全部そうでしょって言われたらそうなのですが...
React Native自体は今後もっと発展していくと信じているのですが、周辺ライブラリはちょっとどうなるか分からないものもあるので、効率と安定のいいバランスを保ちながらライブラリを選定する力が大事になりそうですね(2回目).アプリの改善点
このアプリはノリと勢いで開発して、ノリと勢いで公開したので、結構改善点があります...
一番大きいものとしては
- 記事の表示がたまに正しくない
という改善点があります.
記事は、APIで得られたMarkdownをライブラリを用いてレンダリングしているのですが、表記の揺れなどの影響でうまくレンダリングされずにそのままの文字列として表示されてしまうことがあります.
気づいた範囲で対処しているのですが、なかなか全部には対応しきれていないです.
また、数式のレンダリングは、ブラウザであればMathjaxで表示できるのですが、そこにも対応しきれておらず...
h1タグやリンクが正しく表示されない問題は、APIでhtmlを取得してレンダリングすれば解決できるのですが、そうなると今度はインラインブロックやブロックコードのシンタックスハイライトの描画が難しくなったり...
しかも、結局数式をレンダリングしようと思ったらhtmlでも無理なので、Web Viewを用いることになるのですが、それはそれでブラウザでもQiitaにログインしなければならなかったりで...
現状はとりあえず潰せる表記の揺れは潰して、それでも読みづらい記事は「ブラウザで開くボタン」からお好きなブラウザで読めるようにしています.
アプリのコンセプトとしては、トレンドやストックした記事へのアクセスを簡単にしたいというところがメインなので、正しいレンダリングは後回しになってしまっています...iOS対応について
現状
iOSには対応する予定はありません.
なぜならば、developer登録年間1万円は厳しいので....その他
今回このアプリを開発・公開するにあたって、QiitaのAPI等を利用しているので、ちゃんとQiitaのサポートの方に問い合わせを行ったのですが、
どこぞのよく分からん学生にも関わらず懇切丁寧に対応していただいてびっくりしました.
軽くあしらわれても仕方ないなくらいの気持ちで問い合わせを行ったのですが、なんでも言ってみるもんですねぇ.最後に
React Nativeの勉強が目的だったのですが、せっかく作ったので興味があれば使って改善点などフィードバックしていただけると泣いて喜びます.また、自分と同じく
React Native初心者のためになりそうな知見なども得れたので、
需要があれば そのうち記事化しようと思います!最後にもう一回リンクを張っておきます!
Google Play Storeで見る
- 投稿日:2020-01-26T18:39:56+09:00
UI設計覚書
ウェブアプリケーションの UI を設計、実装するに当たり、常日頃注意している事等をまとめてみました。
ボタンは消さない。非活性化する
操作不可能なボタンを画面から消してしまうのは好ましくないと思ってます。ユーザーは完全に操作を理解してアプリケーションを利用しているとは限りません。たとえ自分のアクションが原因で後続の操作ができなかったとしても、いつもある位置にボタンが無い事をすぐに認識できない場合があります。「あのボタンはどこだっけ?」と画面内を探し回る事になり、余計なストレスを与えてしまいます。
解決策
利用できないボタンは消すのではなく淡色表示や暗転させて利用できない事をアピールします。ツールチップ(吹き出し)等で操作できない理由を明示してあげるとより親切かも知れません。
アクションの送信を認識させる
重たい操作を実行する時等、操作が完了するまで画面に何も変化が発生しないのは好ましくありません。ユーザーは自分がボタンクリックを失敗したのかと思い、何度も同じ操作を繰り返してしまう場合があります。
解決策
ユーザーのアクションを受領した事を、視覚的に表現します。表現方法としては、例えばボタンを非活性化させる、プログレスバーを表示する、ウェイトカーソルに変更する等があります。重たい操作を実行する前に、確認ダイアログボックスを表示するのも有効です。
「戻るボタン」は常に利用可能にする
ウェブブラウズにおいてユーザーがもっとも利用するボタンは「戻るボタン」であると言われています。スマホにおいてもスワイプによってバックする操作はよく利用されるでしょう。「戻るボタン」やスワイプ等、一般的な「戻る」のアクションでいつでも前の画面に戻れる事は、ユーザーを安心させます。
解決策
非 SPA アプリケーションにおいては、POST メソッドが実行された直後の画面は一旦リダイレクトを行う事で「戻るボタン」は有効に作用します。大抵はフレームワークのサポートがあるので昨今ではあまり考えなくても良いかも知れません。SPA では実装が難しい局面もありますが、各画面において戻るボタンが意図に反するような動作になっていないか、注意しましょう。
戻った時はスクロール位置を元に戻す
従来的ウェブサイトにおいては、「戻るボタン」を押してひとつ前の画面に戻った時はスクロール位置も保たれているのが基本です。SPA においては実装が難しい事もあり、戻った時にスクロール位置がリセットされてしまうアプリケーションが少なくありません。一覧画面から進む、戻るを繰り返してブラウズするケースはよくあります。このような時にスクロール位置がリセットされてしまうとストレスが溜まります。
解決策
これは実際難しい場合も多いと思います。特に最下部までスクロールさせた時に追加のコンテンツを読み込むようなページでは、状況によっては実装が難しいと思います。別タブを開く等の対応は暫定的な解決策としては有効かも知れません。なお、ページトップに戻った方が良い場合もあるので使い分けが必要です。
エラーメッセージは簡潔に2つの文で書く
エラーメッセージで重要な事は「何が失敗したのか」「どうすれば良いのか」の2つを伝える事です。特に「どうすれば良いのか」が重要です。また「何が失敗したのか」もユーザー目線で伝えるようにしましょう。
解決策
例示した方が分かりやすいと思います。
悪い例 悪い理由 良い例 制約違反が発生しました 開発者の言葉で説明している。どうすれば良いか書いていない 商品名が重複しているので商品を追加できませんでした。別の商品名を利用して下さい。 エラーです。管理者に連絡して下さい このエラーによって何ができなかったのかが書いていない システム障害により商品を追加できませんでした。管理者に連絡して下さい。 バックトレースをそのまま表示 言うまでもなく、処理系のエラーをそのまま表示というのは駄目です。バックトレースが欲しいのならログに記録しましょう それぞれの情報に URL を持たせる
ある情報を表示している時に、その情報に紐づく URL を用意しましょう。これは JavaScript を利用しないような古典的なサイトではむしろ当たり前でした。高度化した昨今のページにおいては、表示されている情報に URL が紐づいていないケースが昔より増えているような気がします。例えば一覧画面の各項目をクリックした時にポップアップして詳細を表示するようなアプリケーションでは、表示中の詳細情報に紐づく URL が存在しないケースを見かけます。URL が無いとメール等で誰かに情報の所在を通知する時に不便になります。
解決策
各フレームワークのルーティング機能等を利用して、それぞれの情報に対して一意な URL を紐づけるようにすると良いと思います。
スマホに傾向しすぎない
レスポンシブデザインが当たり前の昨今ではスマホやタブレットで出来ない事はやらないというのがひとつの考え方ではありますが、その事が PC ユーザーにストレスを与えていないか考慮してみる必要があります。外出先ではスマホやタブレットで操作しつつ、オフィスでは PC で効率良く作業したいというケースも少なくありません。
解決策
大量の情報を一画面に表示した方が効率が上がる場合は、レスポンシブデザインをあきらめた方が良い場合もあります。
見せながら作る
昨今の高度化した UI はいくら仕様書で説明しても、操作した時の感覚も含めて理解してもらうのは大変難しいです。設計者自身が理解できない場合も多いです。ユーザーに見せながら作る事でこの問題は大きく緩和できます。アジャイル開発の現場においては「何を当たり前の事を」と言われると思いますが、契約上の問題等でウォーターフォール開発の体を外せない開発においても重要です。
解決策
ウォーターフォール開発においては、要件定義フェーズにプロトタイプ開発の期間を設けておきましょう。
UI は視覚言語であるという意識でデザインする
UI とはユーザーのやりたい事をコンピューターに伝えるための言語であると言えます。言語は整然としており、憶えた知識を元に新しい事を表現しようとした時に、意図通りに表現できる事が重要であると思います。これこそが「直感的な操作」であると、個人的には常々思っています。
解決策
同じ機能、似た機能のボタンは、同じような色、形、位置にするべきだと思います。例えばユーザー詳細画面のユーザー削除ボタンが画面右上にあるとしたら、商品詳細画面の商品削除ボタンも画面右上に配置しましょう。
かつてソフトウェアは、マニュアルを熟読し十分理解した上で利用する事が当たり前でした。昨今では覚えながら使ってもらうのが当たり前ですし、利用者の増加に伴い、平均的なリテラシーも下がってきています。熟練者を満足させつつ初心者を迷わせない UI は実際なかなか難しいと思いますが、頑張って取り組んでいきたいですね。
- 投稿日:2020-01-26T17:33:46+09:00
各言語で、継承の挙動はかなり違うという話
はじめに
この記事は、私が色んな言語でひたすら似たようにクラス継承を書いてみて、実際にどんな値が出力されるのかを調査した結果をまとめたものです。時には既知の言語でも「こんな文法あったんだ」と思いながら、時にはHello Worldから頑張りました。
まとめるのが大変だった割に誰得?という内容ですが、同じことが気になった人のために置いておきます。
いやでも新しい発見があるかもしれないのでとりあえず読んでみてください。
意外と面白い結果になったかもしれません。調べた言語
静的型付け
- Java (Corretto 1.8.0_232)
- C# (3.4.0)
- C++ (11.0.0)
- Scala (2.13.1)
- Kotlin (1.3.61)
- Swift (5.1.3)
動的型付け
- Python (3.7.1)
- Ruby (2.6.5)
- PHP (7.1.32)
- JavaScript (node v12.14.1)
オプショナルな静的型付け
- TypeScript (3.7.2)
- Dart (2.7.0)
調査で使うコード
ある親クラスを子クラスが継承します。
その親クラスと子クラスには、同じ名前のインスタンス変数があります。(クラス変数ではありません)
親クラスにはメソッドがあり、そのメソッドを使うとインスタンス変数をコンソールに出力できます。実行時には、子クラスのインスタンスで、継承した親のメソッドを呼びます。
さぁ親と子どっちのインスタンス変数が出力されるでしょうか……というストーリーです。多分読める人が多いであろうJS(ES6以降)のコードで書くと、こういうコードです。
class SuperClass { constructor() { // ※JSではインスタンス変数は実行時に定義されるので、コンストラクタに書く this.instanceVariable = "SuperClass" // 同じ名前で違う値 } superClassMethod() { console.log(this.instanceVariable) } } class SubClass extends SuperClass { constructor() { super() this.instanceVariable = "SubClass" // 同じ名前で違う値 } } const sub = new SubClass() // 子クラスをインタンス化 sub.superClassMethod() // 継承した親クラスのメソッドを呼ぶ自分が普段使っている言語でどういう値が出力されるかぜひ想像してみてください。
(良し悪しはともかくとして)たまにある書き方なので、自分の得意な言語のものは知っているかもしれません。
でも他の言語でどう動くのかを知っていたら、いつか別の会社や別の案件に入った時、もしくは興味本位で他言語に入門した時に役に立つかもしれません。今回は一応、インスタンス変数のみならず、メソッド(クラスメソッドではないインスタンスのメソッド)で
return "SuperClass"をした時にどうなるのかも見たりしています。
この場合は、親クラスと子クラスに同じ名前のメソッドがあり、親クラスのメソッドでそれを呼ぶ……というシナリオです。どんなコードを書いて調べたかは、
コードを見る(hoge言語)
class Hoge {}こんな風に詳細折りたたみ要素が置いてあるので、クリックして開いてもらえると中身のコードを読むことができます。
何をしているか見やすくするために原則としてコピペを採用したかなりWETなコードですが、それはわざとなのでマサカリを投げないでください。今回のポイント
ご存知の通り、プログラミング言語によって存在する文法は異なりますし、評価の仕方も違います。
今回の結果が変わるポイントは、下記であると思います。
- アクセス修飾子が存在するか。また、どんな種類のものがあるか
- 親クラスと子クラスで同名のインスタンス変数をそもそも定義可能か
- インスタンス変数のオーバーライド、メソッドのオーバーライドがそれぞれ存在するか。
- 継承そのものを、各言語がどう処理しているか
結果発表
Java
エンタープライズシステムの覇者、Java。
Javaにはアクセス修飾子として、privateやprotectedがあります。
privateは、現在のクラスからしか見ることはできません。protectedは、現在のクラスと子クラスからアクセスできます。Javaではインスタンス変数のオーバーライドは存在しません。
なので、メソッドでオーバーライドをした時にどうなるのかも見てみます。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected "SuperClass" インスタンス変数の代わりにprivateなメソッド "SuperClass" インスタンス変数の代わりにprotectedなメソッド(override) "SubClass" オーバーライドは文字通り「上書き」してしまいます。
今回は関係ありませんでしたが、Javaは、子クラス側のメソッドで親クラスのインスタンス変数に
super.instanceVariableのようにしてアクセスすることができます。オーバーライドしないゆえにできる芸当ですね。
コードを見る(Java)
public class JavaSample { public static void main(String[] args) { System.out.println("---- SubClass ----"); SubClass sub = new SubClass(); sub.superClassMethod(); System.out.println("---- SubClassProtected ----"); SubClassProtected subp = new SubClassProtected(); subp.superClassMethod(); System.out.println("---- SubClassGetter ----"); SubClassGetter subg = new SubClassGetter(); subg.superClassMethod(); System.out.println("---- SubClassGetterProtected ----"); SubClassGetterProtected subgp = new SubClassGetterProtected(); subgp.superClassMethod(); } } class SuperClass { // privateのインスタンス変数はサブクラスから参照できない private String instanceVariable = "SuperClass"; public void superClassMethod() { System.out.println(instanceVariable); } } class SubClass extends SuperClass { private String instanceVariable = "SubClass"; } // ------------------------------------------- class SuperClassProtected { protected String instanceVariable = "SuperClass"; public void superClassMethod() { System.out.println(instanceVariable); } } class SubClassProtected extends SuperClassProtected { protected String instanceVariable = "SubClass"; // public void subClassMethod() { // System.out.println(instanceVariable); と書くと、 // System.out.println(this.instanceVariable); と同じ。"SubClass"が出る。 // superをつけると、"SuperClass"と表示させることもできる // System.out.println(super.instanceVariable); // } } // ------------------------------------------- class SuperClassGetter { private String instanceVariable() { return "SuperClass"; } public void superClassMethod() { System.out.println(instanceVariable()); } } class SubClassGetter extends SuperClassGetter { private String instanceVariable() { return "SubClass"; } } // ------------------------------------------- class SuperClassGetterProtected { protected String instanceVariable() { return "SuperClass"; } public void superClassMethod() { System.out.println(instanceVariable()); } } class SubClassGetterProtected extends SuperClassGetterProtected { protected String instanceVariable() { return "SubClass"; } }C#
Javaの次はやっぱりC#。歴史的経緯からかなりJavaに近い文法を持ちます。その結果もまたJavaや、後述するC++と似ていますが、Javaには無いものもあります。
ちなみにC#にはプロパティがあるので、メソッドの代わりにプロパティで書きました。C#には
virtualとoverrideがあります。virtualは、「このメソッドはオーバーライドされる可能性があるぞ」と教える修飾子で、overrideは、ここでメソッドはオーバーライドしているぞと教える修飾子です。そこまではいいのですが、C#には更に、
newという変わった修飾子があります。このnewは、インスタンス生成のときのnew Human()のnewとは別モノです。overrideの代わりにnewをつけることで、子クラスからの呼び出しであっても、親クラスのメソッドを親クラスの文脈のままで評価させることができます。なぜかというと、親クラスのメソッドをオーバーライド(上書き)しているわけではなく、ただ隠しているだけだからです。ややこしいですね。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected "SuperClass" プロパティがprivate "SuperClass" プロパティがprotected(override) "SubClass" プロパティがprotected(new) "SuperClass"
コードを見る(C#)
using System; public class CSharpSample { public static void Main(string[] args) { Console.WriteLine("---- SubClass ----"); var sub = new SubClass(); sub.SuperClassMethod(); Console.WriteLine("---- SubClassProtected ----"); var subp = new SubClassProtected(); subp.SuperClassMethod(); Console.WriteLine("---- SubClassGetter ----"); var subg = new SubClassGetter(); subg.SuperClassMethod(); Console.WriteLine("---- SubClassGetterProtectedOverride ----"); var subgpo = new SubClassGetterProtectedOverride(); subgpo.SuperClassMethod(); Console.WriteLine("---- SubClassGetterProtectedNew ----"); var subgpn = new SubClassGetterProtectedNew(); subgpn.SuperClassMethod(); } } class SuperClass { private string instanceVariable = "SuperClass"; public void SuperClassMethod() { Console.WriteLine(instanceVariable); } } class SubClass : SuperClass { // warning CS0414: The field 'SubClass.instanceVariable' is assigned but its value is never used private string instanceVariable = "SubClass"; } // ---------------------------- class SuperClassProtected { protected string instanceVariable = "SuperClass"; public void SuperClassMethod() { Console.WriteLine(instanceVariable); } } class SubClassProtected : SuperClassProtected { // newはオーバーライドではなく、継承元のインスタンス変数隠しているよと明示している new protected string instanceVariable = "SubClass"; } // ---------------------------- class SuperClassGetter { private string instanceVariable { get { return "SuperClass"; } } public void SuperClassMethod() { Console.WriteLine(instanceVariable); } } class SubClassGetter : SuperClassGetter { private string instanceVariable { get { return "SubClass"; } } } // ---------------------------- class SuperClassGetterProtected { protected virtual string instanceVariable { get { return "SuperClass"; } } public void SuperClassMethod() { Console.WriteLine(instanceVariable); } } class SubClassGetterProtectedOverride : SuperClassGetterProtected { protected override string instanceVariable { get { return "SubClass"; } } } class SubClassGetterProtectedNew : SuperClassGetterProtected { protected new string instanceVariable { get { return "SubClass"; } } }C++
C++は組み込みの現場などで使われることが多いらしいですが私は何も知りません。
C++ナニモワカラナイ……。Javaとともに、C#の元となった言語なだけあって、C#に挙動がとても似ています。
インスタンス変数の代わりにメソッドを使った場合で、かつoverrideしないというのは、C#でいうところのnewと同じ挙動をします。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected "SuperClass" インスタンス変数の代わりにprivateなメソッド "SuperClass" インスタンス変数の代わりにprotectedなメソッド(override) "SubClass" インスタンス変数の代わりにprotectedなメソッド(overrideしない) "SuperClass"
コードを見る(C++)
#include <iostream> class SuperClass { // デフォルトだとprivate(クラス外からアクセス不可になる) std::string instanceVariable = "SuperClass"; public: void superClassMethod() { std::cout << instanceVariable << std::endl; } }; class SubClass : public SuperClass { std::string instanceVariable = "SubClass"; }; // ------------------------------- class SuperClassProtected { protected: std::string instanceVariable = "SuperClass"; public: void superClassMethod() { std::cout << instanceVariable << std::endl; } }; class SubClassProtected : public SuperClassProtected { protected: std::string instanceVariable = "SubClass"; }; // ------------------------------- class SuperClassGetter { std::string instanceVariable() { return "SuperClass"; } public: void superClassMethod() { std::cout << instanceVariable() << std::endl; } }; class SubClassGetter : public SuperClassGetter { std::string instanceVariable() { return "SubClass"; } }; // ------------------------------- class SuperClassProtectedGetter { protected: std::string instanceVariable() { return "SuperClass"; } public: void superClassMethod() { std::cout << instanceVariable() << std::endl; } }; class SubClassProtectedGetter : public SuperClassProtectedGetter { protected: std::string instanceVariable() { return "SubClass"; } }; // ------------------------------- class SuperClassProtectedGetterOverride { protected: virtual std::string instanceVariable() { return "SuperClass"; } public: void superClassMethod() { std::cout << instanceVariable() << std::endl; } }; class SubClassProtectedGetterOverride : public SuperClassProtectedGetterOverride { protected: std::string instanceVariable() override { return "SubClass"; } }; int main() { std::cout << "---- SubClass ----" << std::endl; SubClass sub; sub.superClassMethod(); std::cout << "---- SubClassProtected ----" << std::endl; SubClassProtected subp; subp.superClassMethod(); std::cout << "---- SubClassGetter ----" << std::endl; SubClassGetter subg; subg.superClassMethod(); std::cout << "---- SubClassProtectedGetter ----" << std::endl; SubClassProtectedGetter subpg; subpg.superClassMethod(); std::cout << "---- SubClassProtectedGetterOverride ----" << std::endl; SubClassProtectedGetterOverride subpgo; subpgo.superClassMethod(); return 0; }Scala
関数型の風を取り込んだAltJavaです。
JavaやC#と大きく違うところは、インスタンス変数のオーバーライドができることです。新機能来ました!
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected(override) "SubClass"
コードを見る(Scala)
object ScalaSample { def main(args: Array[String]): Unit = { println("---- SubClass ----") val sub = new SubClass sub.superClassMethod() println("---- SubClassProtected ----") val subp = new SubClassProtected subp.superClassMethod() } } class SuperClass { private val instanceVariable = "SuperClass"; def superClassMethod(): Unit = { println(instanceVariable); } } class SubClass extends SuperClass { private val instanceVariable = "SubClass"; } // ---------------------------- class SuperClassProtected { protected val instanceVariable = "SuperClass"; def superClassMethod(): Unit = { println(instanceVariable); } } class SubClassProtected extends SuperClassProtected { override protected val instanceVariable = "SubClass"; }Kotlin
Scalaに大きな影響を受けたAltJavaです。Androidアプリの開発で多く用いられています。
オーバーライドする予定のインスタンス変数にはopenをつけなくてはならないこと以外、Scalaと同じです。
つまりopenはC++やC#のvirtualみたいなものですね。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected(override) "SubClass"
コードを見る(Kotlin)
fun main(args: Array<String>) { println("---- SubClass ----"); val sub = SubClass(); sub.superClassMethod(); println("---- SubClassOverride ----"); val subo = SubClassOverride(); subo.superClassMethod(); } open class SuperClass { private val instanceVariable = "SuperClass"; fun superClassMethod() { println(instanceVariable); } } class SubClass : SuperClass() { private val instanceVariable = "SubClass"; } // ----------------------------------- open class SuperClassOverride { open val instanceVariable = "SuperClass"; fun superClassMethod() { println(instanceVariable); } } class SubClassOverride : SuperClassOverride() { override val instanceVariable = "SubClass"; }Swift
アプリと言ったらAndroidだけではありません。iOSを忘れて貰っては困ります。
Swiftのprivateはバージョンによって挙動が変わったりするようですが、今回はSwift 5で検証したので、Javaと同じく、クラス内のみ有効で、継承されません。Swiftの
letは再代入を禁止した変数宣言です。JSでいうとconstです。
そしてSwiftのvarは再代入可能な普通の変数宣言です。JSでいうletです。ややこしや。Swiftでは、インスタンス変数(Swiftではプロパティという)を、継承元と同じものを定義することはできません。
代わりにComputed Property(C#でいうプロパティのようなもの)を使うと、オーバーライドすることができます。
privateのようなアクセス修飾子をつけないと、internalというスコープになります。internalはJavaでいうpublicみたいなものです(雑)。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がinternal 定義不可 Computed Propertyがprivate "SuperClass" Computed Propertyがinternal(override) "SubClass"
コードを見る(Swift)
class SuperClass { private let instanceVariable = "SuperClass"; func SuperClassMethod() { print(instanceVariable); } } class SubClass: SuperClass { private let instanceVariable = "SubClass"; } // -------------------------------- class SuperClassInternal { let instanceVariable = "Error"; func SuperClassMethod() { print(instanceVariable); } } class SubClassInternal: SuperClassInternal { // error: cannot override with a stored property 'instanceVariable' // let instanceVariable = "SubClass"; } // -------------------------------- // Computed Propertyにしてgetterを生やす。こうしたら関数扱いなのでoverrideができるようになる。 class SuperClassGetter { // letだと、error: 'let' declarations cannot be computed properties private var instanceVariable: String { get { return "SuperClass" } } func SuperClassMethod() { print(instanceVariable); } } class SubClassGetter: SuperClassGetter { private var instanceVariable: String { get { return "SubClass" } }; } // -------------------------------- // Computed Propertyにしてgetterを生やす。こうしたら関数扱いなのでoverrideができるようになる。 class SuperClassInternalGetter { // letだと、error: 'let' declarations cannot be computed properties var instanceVariable: String { get { return "SuperClass" } } func SuperClassMethod() { print(instanceVariable); } } class SubClassInternalGetter: SuperClassInternalGetter { override var instanceVariable: String { get { return "SubClass" } }; } print("---- SubClass ----"); let sub = SubClass(); sub.SuperClassMethod(); print("---- SubClassInternal ----"); let subi = SubClassInternal(); subi.SuperClassMethod(); print("---- SubClassGetter ----"); let subg = SubClassGetter(); subg.SuperClassMethod(); print("---- SubClassInternalGetter ----"); let subig = SubClassInternalGetter(); subig.SuperClassMethod();Python
ここからは動的型付け言語!
Qiitaのタグ投稿ランキングで1位を取り続けるなど、その人気を盤石なものにしたPythonです。Pythonのクラスで特徴的なのは、メソッドの第一引数にインスタンス自身がやってくるので宣言時には第1引数に
selfと書くところです。別にselfじゃなくても良いのですが、みんなselfと書きます。あとは動的型付け言語らしく、インスタンス変数を動的に生み出すので、クラス宣言直下ではなくコンストラクタの中でインスタンス変数を定義します。ここはRubyもJavaScriptも同じですね。
Pythonにはメンバのアクセス制限がありません。修飾子
privateはありません。
関数名の先頭にアンダースコアを1つつけて「privateだからアクセスしないでね」とする文化です。しかしPythonでは、子クラスと名前が衝突した場合に備えた機能があります。アンダースコアを2つつけると、内部的なメンバ名が変わり(ネームマングリング)、元の変数名に簡単にアクセスできなくすることができます。この機能を使うことで、子クラスで同名のメンバがあった場合でも、実行時に元の親クラスの文脈で結果を評価することができるようになります。
pep8-ja - 命名規約 - メソッド名とインスタンス変数
結果を見てみましょう。
条件 結果 インスタンス変数 "SubClass" インスタンス変数の代わりにメソッド "SubClass" インスタンス変数(アンスコ2つ付き) "SuperClass" インスタンス変数の代わりにメソッド(アンスコ2つ付き) "SuperClass"
コードを見る(Python)
class SuperClass: def __init__(self): self.instance_variable = "SuperClass" def super_class_method(self): print(self.instance_variable) class SubClass(SuperClass): def __init__(self): super().__init__() self.instance_variable = "SubClass" # ------------------------------ class SuperClassPrivate: def __init__(self): self.__instance_variable = "SuperClass" def super_class_method(self): print(self.__instance_variable) class SubClassPrivate(SuperClassPrivate): def __init__(self): super().__init__() self.__instance_variable = "SubClass" # ------------------------------ class SuperClassGetter: def instance_variable(self): return "SuperClass" def super_class_method(self): print(self.instance_variable()) class SubClassGetter(SuperClassGetter): def instance_variable(self): return "SubClass" # ------------------------------ class SuperClassPrivateGetter: def __instance_variable(self): return "SuperClass" def super_class_method(self): print(self.__instance_variable()) class SubClassPrivateGetter(SuperClassPrivateGetter): def __instance_variable(self): return "SubClass" print('---- SubClass ----') sub = SubClass() sub.super_class_method() print('---- SubClassPrivate ----') subp = SubClassPrivate() subp.super_class_method() print('---- SubClassGetter ----') subg = SubClassGetter() subg.super_class_method() print('---- SubClassPrivateGetter ----') subpg = SubClassPrivateGetter() subpg.super_class_method()Ruby
「オブジェクト指向の動的型付け言語といったら?」
という連想クイズをされたプログラマは、真っ先にこの言語を思い浮かべる方も多いのではないでしょうか。Rubyにはメソッドに対して使える
privateがありますが、Javaのような言語のprivateとは挙動が違います。詳しくはレシーバとかを説明しなきゃいけなくなるので書きませんが、とりあえず言えることはprivateがあっても継承先にはそのメソッドが見えるということです。なのでJavaでいうとprotectedの方が近いかもしれません。
[Ruby] privateメソッドの本質とそれを理解するメリットちなみにRubyは変数とメソッドの名前空間が別れているので、メソッドをカッコなしで書いても呼び出せます。
それからメソッドはreturnを書かなくても、最後に評価した式の結果を返します。
@変数名がインスタンス変数です。Rubyでは変数名の1文字目で変数の種別(スコープ)がわかります。
条件 結果 インスタンス変数 "SubClass" インスタンス変数の代わりにメソッド "SubClass" インスタンス変数の代わりにprivateなメソッド "SubClass"
コードを見る(Ruby)
class SuperClass def initialize() @instance_variable = "SuperClass" end def super_class_method p @instance_variable end end class SubClass < SuperClass def initialize() super() @instance_variable = "SubClass" end end # -------------------------------- class SuperClassGetter def instance_variable() "SuperClass" end def super_class_method p instance_variable end end class SubClassGetter < SuperClassGetter def instance_variable() "SubClass" end end # -------------------------------- class SuperClassGetterPrivate def super_class_method p instance_variable end private def instance_variable() "SuperClass" end end class SubClassGetterPrivate < SuperClassGetterPrivate private def instance_variable() "SubClass" end end p '---- SubClass ----' subc = SubClass.new subc.super_class_method p '---- SubClassGetter ----' subg = SubClassGetter.new subg.super_class_method p '---- SubClassGetterPrivate ----' subgp = SubClassGetterPrivate.new subgp.super_class_methodPHP
PHPにはJavaのような
privateやprotectedやpublicがあります。
PythonやRubyやJavaScriptには基本的に無いのに! 羨ましい!
でもJavaとは違って、protectedなインスタンス変数はサブクラスのものを読みに行きます。
そこは他の動的型付け言語と足並みを揃えた動きですね。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected "SubClass" インスタンス変数の代わりにprivateなメソッド "SuperClass" インスタンス変数の代わりにprotectedなメソッド "SubClass"
コードを見る(PHP)
<?php function println($message) { echo $message.PHP_EOL; } // ---------------------------------------- class SuperClass { private $instanceVariable = "SuperClass"; public function superClassMethod() { println($this->instanceVariable); } } class SubClass extends SuperClass { private $instanceVariable = "SubClass"; } // ---------------------------------------- class SuperClassProtected { protected $instanceVariable = "SuperClass"; public function superClassMethod() { println($this->instanceVariable); } } class SubClassProtected extends SuperClassProtected { protected $instanceVariable = "SubClass"; } // ---------------------------------------- class SuperClassGetter { private function instanceVariable() { return "SuperClass"; } public function superClassMethod() { println($this->instanceVariable()); } } class SubClassGetter extends SuperClassGetter { private function instanceVariable() { return "SubClass"; } } // ---------------------------------------- class SuperClassGetterProtected { protected function instanceVariable() { return "SuperClass"; } public function superClassMethod() { println($this->instanceVariable()); } } class SubClassGetterProtected extends SuperClassGetterProtected { protected function instanceVariable() { return "SubClass"; } } // ---------------------------------------- println("---- SubClass ----"); $sub = new SubClass(); $sub->superClassMethod(); println("---- SubClassProtected ----"); $subp = new SubClassProtected(); $subp->superClassMethod(); println("---- SubClassGetter ----"); $subg = new SubClassGetter(); $subg->superClassMethod(); println("---- SubClassGetterProtected ----"); $subgp = new SubClassGetterProtected(); $subgp->superClassMethod();JavaScript
フロントエンドの覇者です。
プロトタイプベースオブジェクト指向言語という、プログラミング言語Selfから影響を受けた言語です。
ES6以降のJavaScriptではクラスベースの書き方も取り入れているため、extendsして継承もできます。
条件 結果 インスタンス変数 "SubClass" 古い書き方でインスタンス変数 "SubClass" インスタンス変数で、呼び出しメソッドがアロー関数 "SubClass" インスタンス変数の代わりにアロー関数 "SubClass"
コードを見る(JavaScript)
class SuperClass { constructor() { this.instanceVariable = "SuperClass" } superClassMethod() { console.log(this.instanceVariable) } } class SubClass extends SuperClass { constructor() { super() this.instanceVariable = "SubClass" } } // ------------------------ function LegacySuperClass() { this.instanceVariable = "SuperClass"; }; LegacySuperClass.prototype.superClassMethod = function() { console.log(this.instanceVariable) }; function LegacySubClass() { this.instanceVariable = "SubClass"; }; LegacySubClass.prototype = new LegacySuperClass(); // ------------------------ class SuperClassArrow { constructor() { this.instanceVariable = "SuperClass" } superClassMethod = () => { console.log(this.instanceVariable) } } class SubClassArrow extends SuperClassArrow { constructor() { super() this.instanceVariable = "SubClass" } } // ------------------------ class SuperClassGetterArrow { instanceVariable = () => { return "SuperClass" } superClassMethod = () => { console.log(this.instanceVariable()) } } class SubClassGetterArrow extends SuperClassGetterArrow { instanceVariable = () => { return "SubClass" } } // ------------------------ console.log('---- SubClass ----') const sub = new SubClass() sub.superClassMethod() console.log('---- LegacySubClass ----') var lsub = new LegacySubClass() lsub.superClassMethod() console.log('---- SubClassArrow ----') const suba = new SubClassArrow() suba.superClassMethod() console.log('---- SubClassGetterArrow ----') const subga = new SubClassGetterArrow() subga.superClassMethod()TypeScript
静的に型をつけられる、イケてるJavaScriptですね。
union型という、高度な型システムを持つHaskellに見られるような型に近いものがあったり、Conditional TypesやらMapped Typesやら表現力のある型があるのが特徴です。TypeScriptでは
privateな同名のメンバを親クラス子クラスで定義するとコンパイルエラーになります。protectedであれば問題ありません。Swiftとは真逆ですね。
条件 結果 インスタンス変数がprivate 定義不可 インスタンス変数がprotected "SubClass" インスタンス変数の代わりにprivateなメソッド 定義不可 インスタンス変数がprotectedで、呼び出しメソッドがアロー関数 "SubClass" インスタンス変数の代わりにprotectedなアロー関数 "SubClass"
コードを見る(TypeScript)
class SuperClass { private readonly instanceVariable: string constructor() { this.instanceVariable = "Error" } public superClassMethod() { console.log(this.instanceVariable) } } class SubClass extends SuperClass { // instanceVariableで定義しようとすると、 // TS2415: Class 'SubClass' incorrectly extends base class 'SuperClass'. // private readonly instanceVariable: string // constructor() { // super() // this.instanceVariable = "SubClass" // } } // ------------------------ class SuperClassGetter { private instanceVariable() { return "Error" } public superClassMethod() { console.log(this.instanceVariable()) } } class SubClassGetter extends SuperClassGetter { // instanceVariableで定義しようとすると、 // TS2415: Class 'SubClassGetter' incorrectly extends base class 'SuperClassGetter'. // Types have separate declarations of a private property 'instanceVariable'. // private instanceVariable() { // return "SubClass" // } } // ------------------------ class SuperClassProtected { protected readonly instanceVariable: string constructor() { this.instanceVariable = "SuperClass" } public superClassMethod() { console.log(this.instanceVariable) } } class SubClassProtected extends SuperClassProtected { protected readonly instanceVariable: string constructor() { super() this.instanceVariable = "SubClass" } } // ------------------------ class SuperClassProtectedGetterArrow { protected instanceVariable = () => { return "SuperClass" } public superClassMethod = () => { console.log(this.instanceVariable()) } } class SubClassProtectedGetterArrow extends SuperClassProtectedGetterArrow { protected instanceVariable = () => { return "SubClass" } } // ------------------------ // アロー関数版 class SuperClassProtectedArrow { protected readonly instanceVariable: string constructor() { this.instanceVariable = "SuperClass" } public superClassMethod = () => { console.log(this.instanceVariable) } } class SubClassProtectedArrow extends SuperClassProtectedArrow { protected readonly instanceVariable: string constructor() { super() this.instanceVariable = "SubClass" } } console.log('---- SubClass ----') const sub = new SubClass() sub.superClassMethod() console.log('---- SubClassGetter ----') const subg = new SubClassGetter() subg.superClassMethod() console.log('---- SubClassProtected ----') const subp = new SubClassProtected() subp.superClassMethod() console.log('---- SubClassProtectedArrow ----') const subpa = new SubClassProtectedArrow() subpa.superClassMethod() console.log('---- SubClassProtectedGetterArrow ----') const subpga = new SubClassProtectedGetterArrow() subpga.superClassMethod()Dart
Flutterが流行りだしてから急に存在感が出てきた印象な言語です。
Dartではprivateやpublicを書くことはありません。メンバの名前にアンダースコアをつけることでprivateにすることはできますが、ライブラリ外から見えなくするというだけなので、Javaなどのprivateとは違います。
条件 結果 インスタンス変数(アンスコ付き) "SubClass" インスタンス変数 "SubClass" インスタンス変数の代わりにメソッド "SubClass"
コードを見る(Dart)
void main() { print("---- SubClassPrivate ----"); final subp = new SubClassPrivate(); subp.superClassMethod(); print("---- SubClass ----"); final sub = new SubClass(); sub.superClassMethod(); print("---- SubClassGetter ----"); final subg = new SubClassGetter(); subg.superClassMethod(); } class SuperClassPrivate { // アンダースコアをつけるとPrivateになるが、 // privateは同一ライブラリに対して可視のため、ここではサブクラスから普通に見えちゃう final String _instanceVariable = "SuperClass"; void superClassMethod() => print(_instanceVariable); } class SubClassPrivate extends SuperClassPrivate { final String _instanceVariable = "SubClass"; } // ------------------------------------ class SuperClass { final String instanceVariable = "SuperClass"; void superClassMethod() => print(instanceVariable); } class SubClass extends SuperClass { final String instanceVariable = "SubClass"; } // ------------------------------------ class SuperClassGetter { String instanceVariable() => "SuperClass"; void superClassMethod() => print(instanceVariable()); } class SubClassGetter extends SuperClassGetter { String instanceVariable() => "SubClass"; }まとめ
ある程度使い慣れた言語でも知らないことがあったりして、意外と楽しかったです。
挙げた例の中には今回初めて書いた言語もあるので、こんな書き方あるよ! とか、間違ってるよ! とかあったらコメントください。全体的にまとめると、動的型付け言語だとスーパークラスのメソッドを呼んだ時でもサブクラスの文脈でまずは評価させようとするような印象でした。一方の静的型付け言語では、「スーパークラスのメソッドなんだから基本はスーパークラスで評価する。ただしオーバーライドしたらその限りじゃない」という感じでした。
個人的に印象深かったのは、下記の5つでした。
- C#には
newというオーバーライドしない用の修飾子がある- ScalaとKotlinは静的型付け言語なのにインスタンス変数のオーバーライドができる
- Pythonのアンダースコア2つのメンバ名はただの名前じゃなく効果もあった
- PHPには動的型付け言語なのにJavaみたいな
privateがある- TypeScriptは重複する
privateな同名メンバをコンパイル時にエラーにして予期せぬ動作になることを防いでくれる更新履歴
- Pythonのprivateな命名規約の説明について誤りがあったため、修正を行いました。 @shiracamus さんありがとうございました。
- 投稿日:2020-01-26T17:26:17+09:00
コード内で環境名 (dev,stg,prod) を使用しないことのすすめ
サービスを運用する場合、開発、運用で複数の環境でサーバーを動かしている場合がよくあると思います。このとき、パラメータ設定の口が柔軟でないと設定値の変更がしにくかったり、サーバーのビルドを環境毎にしなければならなず(特に docker 使っている場合)、デプロイに時間がかかったりします。
そうならないために環境名 (dev,stg,prod) を使用して動作変更するのをやめることをおすすめします。記事内では以下のような環境があると想定し、Javascript (Node.js) でいくつかの例を示します。
- 開発時ローカル PC
- ステージングサーバー環境
- 本番サーバー環境
変数を環境名に依存させるのをやめよう
サーバーでエラーが起きたときに管理者にメールを送るというケースで送り先を分ける場合で説明します。
悪い例(コード内で環境ごとのメールアドレスを決めている)
コード内で設定されているアドレスにしか送ることができません、送信するメールアドレスが変更になったときはコードの変更が必要。
// process.env.NODE_ENV に環境名が指定されている function sendErrorMail() { let email = 'default@example.com'; switch(process.env.NODE_ENV) { case 'development': email = 'dev@admin.com'; break; case 'staging': email = 'stg@admin.com'; break; case 'production': email = 'prod@admin.com'; break; default: break; } // sendMail は email アドレスとメール本文を指定してメールを送る関数 sendMail(email, 'Server Error Occurred'); }良い例(環境変数としてメールアドレス自体を指定している)
メールアドレスが変更になったときでも環境変数の指定を変更するだけで送信先が変更できる
// process.env.ADMIN_EMAIL_ON_ERROR にエラー時のメール送り先が指定されている function sendErrorMail() { // sendMail は email アドレスとメール本文を指定してメールを送る関数 sendMail(process.env.ADMIN_EMAIL_ON_ERROR, 'Server Error Occurred'); }動作を環境名に依存させるのをやめよう
次も似た例ですが、ログのレベル設定を例に説明します。
logWarnという警告ログを出力する関数があるとします。開発環境だったら debug ログを出す、というパターンはよくあると思いますが、一つの環境変数としてログレベルを設定すると柔軟です。悪い例(コード内で環境名でレベルを決めている)
// process.env.NODE_ENV に環境名が指定されている function logWarn(message) { if (process.env.NODE_ENV == 'development') { console.warn(message); } }良い例(ログレベルを環境変数として設定する)
const LogLevel = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, TRACE: 4, }; // process.env.LOG_LEVEL にログレベルが設定されている function logWarn(message) { if (process.env.LOG_LEVEL <= LogLevel.WARN) { console.warn(message); } }最後に
この話は主流にありつつある(すでに主流?) Docker でのサーバーデプロイの背景が大きいです。Docker image を build して デプロイしてという流れで運用している場合、ちょっとした動作の変更によって Docker image の build し直ししなければならないとか、環境毎に image を build し直さなければけ無いとなると、build 時間はわりとかかるケース(色々なケースがありますが、数分から10分は最低でもかかると思います)が多いので、build し直ししなくてもいいように(実行時に環境変数として設定すればいいだけにするために)設定類は個別の設定毎に外出ししていくと便利だと考えています。
- 投稿日:2020-01-26T15:21:49+09:00
コッホ曲線
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title></title> <script> var ctx; function init() { var canvas = document.getElementById('main_canvas'); if (!canvas || !canvas.getContext) { return; } ctx = canvas.getContext('2d'); var N = 3; Koch(0, 260, 150, 0, N); Koch(150, 0, 300, 260, N); Koch(300, 260, 0, 260, N); } function Koch(a1, a2, b1, b2, n) { var o1 = a1; var o2 = a2; var p1 = (b1 - a1) / 3 + a1; var p2 = (b2 - a2) / 3 + a2; var q1 = (b1 - a1) * 2 / 3 + a1; var q2 = (b2 - a2) * 2 / 3 + a2; var r1 = b1; var r2 = b2; var distance = Math.sqrt((b1 - a1) * (b1 - a1) + (b2 - a2) * (b2 - a2)) / Math.sqrt(3); if (b1 - a1 >= 0) { var angle1 = Math.atan(-(b2 - a2) / (b1 - a1)) + Math.PI / 6; var s1 = a1 + (distance * Math.cos(angle1)); var s2 = a2 - (distance * Math.sin(angle1)); } else { var angle2 = Math.atan(-(b2 - a2) / (b1 - a1)) - Math.PI / 6; var s1 = b1 + (distance * Math.cos(angle2)); var s2 = b2 - (distance * Math.sin(angle2)); } if (n == 0) { draw(o1, o2, p1, p2); draw(p1, p2, s1, s2); draw(s1, s2, q1, q2); draw(q1, q2, r1, r2); } else { Koch(o1, o2, p1, p2, n - 1); Koch(p1, p2, s1, s2, n - 1); Koch(s1, s2, q1, q2, n - 1); Koch(q1, q2, r1, r2, n - 1); } } function draw(f1, f2, g1, g2) { ctx.beginPath(); ctx.moveTo(f1, f2); ctx.lineTo(g1, g2); ctx.stroke(); } </script> </head> <body onload="init()"> <canvas id="main_canvas" width="350" height="350" style="background-color:#eeeeee;"></canvas> </body> </html>
- 投稿日:2020-01-26T15:21:49+09:00
コッホ曲線 JavaScript
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title></title> <script> var ctx; function init() { var canvas = document.getElementById('main_canvas'); if (!canvas || !canvas.getContext) { return; } ctx = canvas.getContext('2d'); var N = 3; Koch(0, 260, 150, 0, N); Koch(150, 0, 300, 260, N); Koch(300, 260, 0, 260, N); } function Koch(ox, oy, rx, ry, n) { var dx = rx - ox; var dy = ry - oy; var px = ox + dx / 3; var py = oy + dy / 3; var qx = ox + dx / 3 * 2; var qy = oy + dy / 3 * 2; var distance = Math.sqrt(dx * dx + dy * dy) / Math.sqrt(3); var radian; var sx; var sy; if (dx >= 0) { radian = Math.atan(-dy / dx) + Math.PI / 6; sx = ox + distance * Math.cos(radian); sy = oy - distance * Math.sin(radian); } else { radian = Math.atan(-dy / dx) - Math.PI / 6; sx = rx + distance * Math.cos(radian); sy = ry - distance * Math.sin(radian); } if (n == 0) { draw(ox, oy, px, py); draw(px, py, sx, sy); draw(sx, sy, qx, qy); draw(qx, qy, rx, ry); } else { Koch(ox, oy, px, py, n - 1); Koch(px, py, sx, sy, n - 1); Koch(sx, sy, qx, qy, n - 1); Koch(qx, qy, rx, ry, n - 1); } } function draw(x1, y1, x2, y2) { ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } </script> </head> <body onload="init()"> <canvas id="main_canvas" width="350" height="350" style="background-color:#eeeeee;"></canvas> </body> </html>
- 投稿日:2020-01-26T15:15:08+09:00
React+Hookで作るメニュー外クリックで閉じるドロップダウンメニュー
はじめに
これまでドロップダウンメニューが必要な時はReact Bootstrapを用いていたのですが、自力でドロップダウンメニューを作る必要があったので、そのときの試行錯誤をメモとして残します。
ドロップダウンメニューが満たしてほしい仕様は以下の通りです。
- ボタンクリックでメニューを表示/非表示
- メニュー外をクリックしてもメニューが非表示になる
Step 1
まずは上記の一つ目の条件のみを満たす、基本的なドロップダウンメニューを作ります
const Dropdown1 = () => { const[isOpenMenu, setIsOpenMenu] = React.useState(false); const handleClick = (text) => () => { alert(text); setIsOpenMenu(false); }; return ( <div className="menu-container" onClick={() => setIsOpenMenu(!isOpenMenu)}> <div className="menuButton"> Menu 1 </div> <ul className="menu" hidden={!isOpenMenu}> <li className="item" onClick={handleClick("a")}> a </li> <li className="item" onClick={handleClick("b")}> b </li> <li className="item" onClick={handleClick("c")}> c </li> </ul> </div> ); };Step 2
このStep 1のメニューではメニューを表示した状態でメニュー外をクリックしてもメニューが閉じません。
なのでuseRefとuseEffectを使って、ボタンをクリックするとドロップダウンメニューにフォーカスが来るようにし、フォーカスが外れた時にonBlurイベントにメニューを閉じる関数を登録します。const Dropdown2 = () => { const[isOpenMenu, setIsOpenMenu] = React.useState(false); const menuRef = React.useRef(null); React.useEffect(() => { isOpenMenu && menuRef.current.focus(); }, [isOpenMenu]); const handleClick = (text) => () => { alert(text); }; return ( <div className="menu-container" onClick={() => setIsOpenMenu(!isOpenMenu)} ref={menuRef} onBlur={() => setIsOpenMenu(false)} tabIndex={0}> <div className="menuButton"> Menu 2 </div> <ul className="menu" hidden={!isOpenMenu}> <li className="item" onClick={handleClick("a")}> a </li> <li className="item" onClick={handleClick("b")}> b </li> <li className="item" onClick={handleClick("c")}> c </li> </ul> </div> ); };Step 3
Step 2のドロップダウンメニューにメニューアイテム間のセパレータとサブメニューを同様に追加してみます。
const Dropdown3 = () => { const[isOpenMenu, setIsOpenMenu] = React.useState(false); const menuRef = React.useRef(null); React.useEffect(() => { isOpenMenu && menuRef.current.focus(); }, [isOpenMenu]); const handleClick = (text) => () => { alert(text); }; return ( <div className="menu-container" onClick={() => setIsOpenMenu(!isOpenMenu)} ref={menuRef} onBlur={() => setIsOpenMenu(false)} tabIndex={0}> <div className="menuButton"> Menu 3 </div> <ul className="menu" hidden={!isOpenMenu}> <li className="item" onClick={handleClick("a")}> a </li> <li className="separator"></li> <li className="item" onClick={handleClick("b")}> b </li> <li className="item"> c <span>▶</span> <ul className="submenu"> <li className="item" onClick={handleClick("c-1")}> c-1 </li> <li className="item" onClick={handleClick("c-2")}> c-2 </li> </ul> </li> </ul> </div> ); };一見動作するのですが、少しおかしい挙動が見られます。
というのは、セパレータとサブメニュー「c」をクリックしてもメニューが閉じてしまうのです。
これは親要素へクリックイベントが伝播してしまい、div要素のonClick関数が実行されてしまうからです。(恐らく)Step 4
そこでドロップダウンメニュー全体の親である
ulとサブメニューの親であるliに(e) => e.stopPropagation()を追加したものが以下です。
これにより先述のおかしい動作は見られなくなります。const Dropdown4 = () => { const[isOpenMenu, setIsOpenMenu] = React.useState(false); const menuRef = React.useRef(null); React.useEffect(() => { isOpenMenu && menuRef.current.focus(); }, [isOpenMenu]); const handleClick = (text) => () => { alert(text); }; return ( <div className="menu-container" onClick={() => setIsOpenMenu(!isOpenMenu)} ref={menuRef} onBlur={() => setIsOpenMenu(false)} tabIndex={0}> <div className="menuButton"> Menu 4 </div> <ul className="menu" hidden={!isOpenMenu} onClick={(e) => e.stopPropagation()}> <li className="item" onClick={handleClick("a")}> a </li> <li className="separator"></li> <li className="item" onClick={handleClick("b")}> b </li> <li className="item" onClick={(e) => e.stopPropagation()}> c <span>▶</span> <ul className="submenu"> <li className="item" onClick={handleClick("c-1")}> c-1 </li> <li className="item" onClick={handleClick("c-2")}> c-2 </li> </ul> </li> </ul> </div> ); };以上
です。
- 投稿日:2020-01-26T14:39:02+09:00
SPA向けのCSS設計 - COCSS
Component Oriented CSS
CSSをもっと便利に使うための努力
CSSの歴史は、言うまでもなく長いです。
テーブルレイアウトの時代から使われ、テンプレートエンジンの時代を経て、JSが定着した最近はフレームワークやライブラリーなどにテーマが移っています。もはやウェブ開発者・ウェブデザイナーと言う概念から、FE・BE、細かい場合はマークアップエンジニアなど、専門的かつ細分化になっています。
CSSも、専門のエンジニアが活躍する場合もあるくらいです。こういう流れとともに、CSSの構造に関しても様々な工夫が行われてきました。
代表的には下記の例があります。
- OOCSS
- BEM
- SMACSS
- FLOCSS
- RSCSS
各例の概念と、不便なところだけ軽く見てみます。
経験者の方は、共感する部分もあると思います。既存の設計、これが不便だった
1. OOCSS
コンテナーとコンテンツで分けてコンテンツはコンテナーから独立している。
基本構造と見た目を分けて考える(再利用性向上)。作業して不便だと思った点
- コンテナーとコンテンツの基準が主観的。
- コンテンツがまたコンテンツを持つ場合の判定が難しい。
- クラス名が被ることが多い。
2. BEM
Block(基となる要素)、Element(Block内の子要素)、Modifier(変化した状態)に分けられる。
作業して不便だと思った点
- BlockとElementの区分が主観的。
- リファクタリングなどでElementがBlockに変わる場合に対応が難しい。
- Modifierの選定基準が曖昧。
- ある要素の変化を動作として見なす場合もある。
__、--などを使って、クラス名が長くなる。3. SMACSS
Base、Layout、Module、State、Themeに役割を分ける。
作業して不便だと思った点
- プレフィックスなどルールが増える。
- 階層が増えると、クラス名も長くなる。
4. FLOCSS
Foundation、Layout、Objectで分けます。
作業して不便だと思った点
- 小規模サイト・サービスでは運用が難しい(パーフォマンスが悪い)。
- 徹底に守ると、とりあえずファイルが多くなる。
- プレフィックスと
__、--などを併用して使うので、クラス名も長くなる。5. RSCSS
Components、Elements、Variant、Layout、Helpersに役割を分ける
作業して不便だと思った点
- Componentsは必ず2単語以上で組み合わせないといけない。
- Componentsの中にComponentsが入る場合、Elementsの中にElementsが入る場合わかりづらい。
- リファクタリングなどでElementsがComponentsに変わる場合に対応が難しい。
だったら、改善していこう
ファイル構造はシンプルに
概ね下記の4つに分けられます。
- setting.css(sass)
リセットCSSの役割と、Sassなどだと変数やmixinなどを定義します。
- style.css(sass)
ヘッダー、フッターなど全領域に渡るパーツで共通的に使うCSSを指定します。
- font.css(sass)、icon.css(sass)、svg.css(sass)...
各目的に合わせて外部から持ってくるCSSです。
あまり触ることがないことと、運用の手間を減らすため、ルール外とします。
- コンポネント内
コンポネント内で影響するCSSは、基本そのコンポネント内でScopeを限定して使います。
クラス名から始めよう
一番解消したいのは、クラス名の短縮と最適化です。
クラス名が長くなったり重複が発生してしまうと下記の短所ができてしまいます。
- CSS設計時に構造がわかりにくくなる。
- 作業時にパーフォマンスが悪い。
- JSと連携する際に把握しづらい。
- HTMLコード上で見づらい。
変化に強いかつ単純なルール
作業の大体は、修正や維持補修です。
新規事業の場合でも、何回も仕様が変わります。
やり直す度に構造を意識してしまうと、限った時間内に作業できなくなるので、大体の場合諦めます。
結果、あれでもこれでもない形でリリースされ、下記の3つでの一択になります。
- いいケース:CSSを整理するため、また時間をかけます。
- 悪いケース:放置してしまいます。
- 最悪のケース:「今回これ使ったからダメだった。。。次回はあれ使おう!」になって、救いの手は遠がる限りです。
なので、「シンプルなファイル構造、わかりやすいクラス名、単純なルール」の3つに集中しました。
ついでにコンポネントと言う発想が、ヒントとなりました。Component Oriented CSS - COCSS
いよいよ本題に入ります。
COCSSと言う概念をご提案します。・CSS命名規則だけでなく、SPAのコンポネント構造に合わせた概念です。
・仕様変更やリファクタリングに強いです。
・クラス名が極端的に短くなります。簡単なVue製SPAアプリの構造を想定しましょう。
<template lang="pug"> section div.search Routes#from-route Routes#to-route Calendar div.-number-panel span.icon.icon-checkin div.-number-selectors NumberSelector.-hotel NumberSelector.-adult NumberSelector.-child NumberSelector.-baby hr.transparent Button.-submit(button_text="検索") </template>航空券、ホテルを検索する基本的な画面です。
出発地・到着地の入力コンポネントRoutes、日程を決めるコンポネントCalendar、数を決めるコンポネントNumberSelector、パラメタやステータスを保存するコンポネントButtonで構成されています。ルールはいたって簡単です。
画面(コンポネント)の最上位階層のクラス名
- アルファベット小文字で始まる
- 1つの単語で構成する(.date)
- 2つ以上の単語で構成される場合は
-で連結する(.date-today)最上位以下の階層のクラス名
-から始まる- 2つ以上の単語で構成される場合は
-で連結するこれで、各コンポネントの領域がHTMLソースでもわかりやすくなります。
コンテナーやコンテンツなど、主観的な概念を徹底に排除しました。
CSSの設計に終わらず、コードの構造もシンプルに反映できるので連携がしやすいです。
-は、Sassなどでも良い区切りになります。構造と独立したパーツのクラス名
- アルファベット小文字で始まる
- 1つの単語で構成する(.transparent)
- 2つ以上の単語で構成される場合は
-で連結する(.icon-checkin)単体に使うパーツは、コンポネントに拘束されないので、独立したクラス名を使います。icomoonなど外部のアイコンライブラリーを使う時に、他の設計だと、構造に合わせて名前を変えたりしないといけないですが、COCSSだとそのまま実装できるので、手軽に適用できます。
ID名
- アルファベット小文字で始まる
- 2つ以上の単語で構成される場合は
-で連結するIDは、CSSで拘束しないことを原則にしているので厳しい規則は決めてないです。
ただ、他のクラス名などと違和感がないかつクラス名と区分するくらいで決めます。
IDは関数内で変数名として再指定して使う場合が多いので、特にJSの命名規則に揃わなくてもいいはずです。
むしろ変数名と職別できるので間違いする可能性は低くなります。変則的にコンポネントが追加された場合を考えてみましょう。
上記の場合では、部屋数や年齢別の人数を調整するため、NumberSelectorと言うコンポネントを追加しています。<template lang="pug"> div.number table tbody tr td.-title {{ title }} td button.-minus - td.-amount p {{ result }} {{ type }} td button.-plus + input </template>このコンポネントでも、最上位階層はアルファベットの小文字で始まっています。
なので、これが独立したコンポネントであることがコード上で明らかになります。結論
いくら有名なルールでも、いくら人気があるルールでも、今の自分の作業環境に合ってなかったらむしろ作業に悪影響します。
自分が得意だと押しているルールでも、細かい命名規則を追うには、新人やプロジェクトにいきなり参加した人にはわかりづらい場合も多いです。
少人数で速くルールを熟知して短期間に成果を出したいと思いましたら、試してみたはいかがでしょうか?
- 投稿日:2020-01-26T13:37:49+09:00
Obnizで戦車ラジコンを作ろう
Obnizでラジコンを作ってみました。
いろいろ参考となる記事がたくさんあったので、自分でもできるのではないかと思い、挑戦してみました。
できたのがこれですっ!そう、戦車です。
コントローラは、スマホを使います。なぜならば、マルチタッチ対応だからです。
戦車は、右側のキャタピラと左側のキャタピラの両方を制御することで、左右に曲がることができるんです。ちなみに、実は未完成です。
M5Cameraに電源がつながっていないんです。2ポートの小型モバイルバッテリを入手したらほんとの完成ですね。毎度の通り、GitHubに上げておきました。
https://github.com/poruruba/obniz_motor以下からもページを参照できます。(Obnizにつながないと使えないですが)。
https://poruruba.github.io/obniz_motor/
※スマホのブラウザから参照してください。また、画面は横がよいです。使う部品
タミヤ:ダブルギヤボックス(左右独立4速タイプ)
https://www.tamiya.com/japan/products/70168/index.htmlタミヤ:トラック&ホイールセット
https://tamiya.com/japan/products/70100/index.htmlタミヤ:ユニバーサルプレートセット
https://tamiya.com/japan/products/70098/index.htmlObniz BoardまたはObniz Board 1Y
https://obniz.io/ja/doc/obniz_board_1y/hw_overview
ESP32のObnizOSでもできなくはないですが、Obnizほど電流容量は無いので無理です。間にモータ制御チップを介在させる必要があります。なるべく小さなモバイルバッテリー
1ポートでよいのですが、2ポートあれば、ObnizとM5Cameraの両方に給電できます。USBケーブル
モバイルバッテリーとObnizやM5Cameraを接続します。トラック&ホイールセットとユニバーサルプレートセットを別々に買うのではなく、以下でもよいです。
ただし、付属のモータは使いません。私はこちらを使いました。タミヤ:タンク工作基本セット
https://www.tamiya.com/japan/products/70108/index.html以下は、もしあれば。
M5Camera
戦車に乗っけることで、スマホからM5Cameraでの撮影画像を見ながら戦車をコントロールするができます。
https://www.switch-science.com/catalog/5207/なるべく小さなブレッドボード
モータから直接Obnizに接続してもよいですし、ブレッドボードを間に挟むと配線の取り回しが楽になります。
例:https://www.yodobashi.com/product/100000001003914672/?gad1=&gad2=g&gad3=&gad4=56278881131&gad5=14692682459493758266&gad6=1o12&gclid=CjwKCAiA66_xBRBhEiwAhrMuLQsNvccSwFoVrlGymIlQejwZaBhlqjL5WYVoImEpNxsjFP2WWzQbrxoCds8QAvD_BwE&xfr=plaレゴ
https://www.lego.com/ja-jp
キャタピラ台の上はごちゃごちゃしますので、レゴブロックで整理したり固定したりします。組み立て
以下の順番で組み上げていきます。
ダブルギアボックスを組み立てる。
マニュアルを見ながらやります。半田・はんだごてと、プラスドライバが必要です。モータに配線を付ける際にはんだを使います。
ギア比は今回は、114.7:1にしました。キャタピラを組み立てる
トラック&ホイールセットとユニバーサルプレートセット、またはタンク工作基本セットで、キャタピラを組み立てます。キャタピラ台にモータを固定します。
Obnizにモータを配線します。
Obnizをキャタピラ台に配置し、モータから出ている配線をObnizに接続します。配線は以下の通りにしました。
IO0:右側モータのマイナス
IO1:右側モータのプラス
IO2:左側モータのマイナス
IO3:左側モータのプラス
右側か左側かは、動かしてからでないとわからないと思うのでその時直しましょう。Obnizにモバイルバッテリを接続します。
キャタピラ台にモバイルバッテリを配置し、USBケーブルでモバイルバッテリとObnizを接続して給電します。(補足)
・ギアボックスの組み立ては結構細かい作業が必要だったりします。イライラしないようにしましょう。
・私はタンク工作基本セットを使ったのですが、キャタピラ台に相当する部分が木なので、穴をあけたり、モータの幅に合わせるために多少切れ込みを入れる作業が必要でした。結局、以下の感じでつながります。
操作画面
操作には、スマホのブラウザを使います。あらかじめObnizのidを覚えておきます。
あとで、ソースコードを示します。【Obniz接続前】
obniz idを入力して、「接続」ボタンを押下すると、Obnizとの接続を試行します。
【Obniz接続後】(M5Camera画像にはモザイクをかけています)
あとは、左右のスライダーを前後に動かすことで戦車を動かします。
両方のスライダを上に動かすことで前進し、両方下に動かすと後退します。
左側のスライダが右側のスライダより上であれば右に曲がり、右側のスライダが左側のスライダより上であれば左に曲がります。キャタピラなので、結構いろんな障害物でも走破できます。やりすぎると横転しますが。。。
(補足)
・Obnizのファームウェアは最新に上げておきましょう。ソースコード
まずはJavascriptから。
start.js'use strict'; //var vConsole = new VConsole(); let obniz; var motor_right; var motor_left; var power_left_sign; var power_right_sign; const COOKIE_EXPIRE = 365; const POWER_MARGIN = 10; const POWER_MAX = 30; var vue_options = { el: "#top", data: { progress_title: '', obniz_id: '', obniz_connected: false, power_left: 0, power_right: 0, power_max: POWER_MAX + POWER_MARGIN, power_min: -(POWER_MAX + POWER_MARGIN), camera_url: 'http://192.168.1.248:81/stream', }, computed: { }, methods: { obniz_connect: function(){ obniz = new Obniz(this.obniz_id); this.progress_open('接続試行中です。', true); obniz.onconnect = async () => { this.progress_close(); Cookies.set('obniz_id', this.obniz_id, { expires: COOKIE_EXPIRE }); this.obniz_connected = true; motor_left = obniz.wired("DCMotor", {forward:0, back:1}); motor_right = obniz.wired("DCMotor", {forward:2, back:3}); this.motor_reset(); } }, motor_reset: function(){ if( this.obniz_connected ){ motor_right.power(0); motor_right.move(true); motor_left.power(0); motor_left.move(true); } power_right_sign = 0; this.power_right = 0; power_left_sign = 0; this.power_left = 0; }, motor_change_right: function(){ if( !this.obniz_connected ){ this.power_right = 0; return; } var sign = Math.sign(this.power_right); var power = Math.abs(this.power_right); if( power <= POWER_MARGIN ) power = 0; else power -= POWER_MARGIN; motor_right.power(power); if( sign != power_right_sign ){ motor_right.move(sign >= 0); power_right_sign = sign; } }, motor_change_left: function(){ if( !this.obniz_connected ){ this.power_left = 0; return; } var sign = Math.sign(this.power_left); var power = Math.abs(this.power_left); if( power <= POWER_MARGIN ) power = 0; else power -= POWER_MARGIN; motor_left.power(power); if( sign != power_left_sign ){ motor_left.move(sign >= 0); power_left_sign = sign; } }, motor_end_right: function(){ if( !this.obniz_connected ){ this.power_right = 0; return; } motor_right.power(0); motor_right.move(true); power_right_sign = 0; this.power_right = 0; }, motor_end_left: function(){ if( !this.obniz_connected ){ this.power_left = 0; return; } motor_left.power(0); motor_left.move(true); power_left_sign = 0; this.power_left = 0; }, }, created: function(){ }, mounted: function(){ proc_load(); this.obniz_id = Cookies.get('obniz_id'); } }; vue_add_methods(vue_options, methods_utils); var vue = new Vue( vue_options );DCMotorというパーツライブラリを使わせていただきました。
DCMotor
https://obniz.io/ja/sdk/parts/DCMotor/README.md接続するポート番号を変える場合は以下の部分を変更してください。
motor_left = obniz.wired("DCMotor", {forward:0, back:1}); motor_right = obniz.wired("DCMotor", {forward:2, back:3});以下の部分は、お好みで変更してください。動かしだすまでに少しマージン(遊び)を入れています。また、マックスパワーの100にしてもよいのですが、すんごい早くて戦車を操作しにくかったので、30に抑えています。
const POWER_MARGIN = 10; const POWER_MAX = 30;HTMLです。
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;"> <meta name="format-detection" content="telephone=no"> <meta name="msapplication-tap-highlight" content="no"> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <!-- Optional theme --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> <!-- Latest compiled and minified JavaScript --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <title>Obnizラジコン</title> <script src="js/methods_utils.js"></script> <script src="js/vue_utils.js"></script> <script src="dist/js/vconsole.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://unpkg.com/obniz/obniz.js"></script> <link rel="stylesheet" href="./css/index.css" /> </head> <body> <div id="top" class="container-fluid"> <div class="form-inline" v-if="!obniz_connected" > <h1>Obnizラジコン</h1> <button class="btn btn-default btn-sm" v-on:click="obniz_connect">接続</button> <label>obniz id</label> <input type="text" class="form-control" v-model="obniz_id"> <label>camera url</label> <input type="text" class="form-control" v-model="camera_url"> </div> <!-- <button class="btn btn-default btn-block" v-on:click="motor_reset">Stop</button> --> <div class="controller"> <div class="control" style="float: left;"> <center><label>Left</label> {{power_left}}</center> <input id="motor_left" type="range" v-bind:min="power_min" v-bind:max="power_max" v-model.number="power_left" v-on:input="motor_change_left()" v-on:touchend="motor_end_left()"><br> </div> <div class="control" style="float: right;"> <center><label>Right</label> {{power_right}}</center> <input id="motor_right" type="range" v-bind:min="power_min" v-bind:max="power_max" v-model.number="power_right" v-on:input="motor_change_right()" v-on:touchend="motor_end_right()"><br> </div> </div> <img v-if="obniz_connected" v-bind:src="camera_url" style="margin:0 auto;" class="img-responsive"> <div class="modal fade" id="progress"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title">{{progress_title}}</h4> </div> <div class="modal-body"> <center><progress max="100" /></center> </div> </div> </div> </div> </div> <script src="js/start.js"></script> </body> </html>v-on:inputイベントで、スライダの位置の変化を即時モーターのパワーに同期しています。
v-on:touchendイベントで、スライダから指を話したら自動的にパワーを0にしています。(ちなみに、このイベントはタブレットやスマホなど、タッチに対応したブラウザしか対応していないはず)CSSも使ってます。スライダの位置を調整しています。
index.css.controller { position: absolute; width: 100%; margin: auto; } .controller input[type="range"] { transform: rotate(-90deg) scale(1.5); margin-top: 80px; background-color: rgb(235, 239, 244); -webkit-appearance: none; } .controller input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; height: 50px; width: 50px; background: rgb(255, 255, 255); border-radius: 15px; border: 5px solid rgb(0, 110, 179); }(参考) M5Cameraをつなげる
M5Cameraをつなげることで、より遠隔操作している感が出ます。
M5CameraはそのままではAPモードで動作しているので、STAモードで動作するようにします。
Obnizを操作するために、インターネット接続が必要であるためです。
Arduino IDEを使います。こちらを参考にさせていただきました。
M5Cameraでスマホから操作できるWebカメラを作る(Windows10編)プログラムを書き込んで起動するとWiFiアクセスポイントにSTAモードで接続しIPアドレスが割り当てられたかと思います。このIPアドレスを覚えておきます。
Obniz接続時に、camera urlに、「 http://IPアドレス:81/stream 」を指定すれば、Obnizに接続完了したときに、撮影動画が表示されるようになります。
以上
- 投稿日:2020-01-26T12:53:29+09:00
型について
たまに苦しむ型について改めて調べてみました。
JavaScriptの基本的な型
型名 例 判定で表示される値 Null型 null object Undefined型 null undefined 他のオブジェクト {a: 1} object 数値型 1 number 文字列型 abc string 真偽値型 true / false boolean 関数 function() {} function 型を判定する typeofの使い方
typeof "123" // "string" typeof 123 // "number" typeof {} // "object"new String と new Number()
new Stringとnew Numberいう書き方もあります
const testStr = new String('test') const testNum = new Number(1)一見、string型とNumber型を作っている感じですが、違います.
他にも多くに型がありますが、よく使うもの書いてみました。
みてわかるようにobject型はなんでもありな感じなので、型定義をるする時はなるべく使わない方がよいです。参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date
- 投稿日:2020-01-26T12:47:02+09:00
JSで簡単な4択クイズを作る
vanilla JSで簡単に4択クイズゲームを作ってみました。
コードは以下のリポジトリに置いてありますので、説明いらないよという方はそちらご利用ください。
https://github.com/taiga-jp/quiz-game01. 用意するもの
表示するためのHTMLファイル、処理を記述するためのJSファイルのみで作成できます。
スタイル割り当てたい方は適宜CSSファイル読み込んでください。
以下、JSの中身について説明していきます。02. 画面構成
画面構成は以下の通り
①スタート画面・・・スタート画面です。スタートボタンを押すと問題が開始されます。
②ゲーム画面・・・問題を表示する画面です。1問ごとに答えを選択していきます。
③リザルト画面・・・結果を表示する画面です。リセットボタンを押すとスタート画面に戻ります。03. 変数の用意
// sceneXXXは、各ゲーム画面の要素です const sceneTop = document.getElementById("sceneTop"); const sceneGame = document.getElementById("sceneGame"); const sceneResult = document.getElementById("sceneResult"); // 問題文を表示する要素です const textQuestion = document.getElementById("textQuestion"); // 選択肢を表示する要素です const listAnswer = document.getElementById("listAnswer"); // 正解数を表示する要素です const numResult = document.getElementById("numResult"); // トップ画面にて、ゲームを開始するボタン要素です const btnStart = document.getElementById("btnStart"); // リザルト画面にて、ゲームをリセットしトップへ戻るボタン要素です const btnReset = document.getElementById("btnReset"); //問題文を格納する要素です const question = [ { text: "江戸時代から使われていた言葉はどれ?", choice: ["うざい", "むかつく", "えもい", "ばえ"], ansewer: "むかつく" }, { text: "超ド級の「ド」って何?", choice: ["ドイツ軍", "ドレッドノート", "ドラゴン", "ドッグ"], ansewer: "ドレッドノート" }, { text: "パンケーキの名前の由来は?", choice: [ "パンみたいなケーキだから", "フライパンで調理するケーキだから", "膨らみすぎてパンクしたケーキだから", "パンダが食べたケーキだから" ], ansewer: "フライパンで調理するケーキだから" }, { text: "たぬき寝入りを英語で言うと?", choice: ["recoon dog sleep", "sheep sleep", "cat sleep", "fox sleep"], ansewer: "fox sleep" } ]; // ゲームで使用する共通の変数です // answer...プレイヤーの答えと比較する、正解のテキストです // gameCount...プレイヤーが答えた数です // success...プレイヤーが答えて、正解した数です let state = { answer: "", gameCount: 0, success: 0 };htmlの要素を格納する変数や問題を準備する変数を定義します。
questionにはobjectで問題文、選択肢、答えを格納します。04. 初期化、スタート画面関数
function init() { state.gameCount = 0; state.success = 0; changeScene(sceneResult, sceneTop); btnStart.addEventListener("click", gameStart, false); } function changeScene(hiddenScene, visibleScene) { hiddenScene.classList.add("is-hidden"); hiddenScene.classList.remove("is-visible"); visibleScene.classList.add("is-visible"); }現在の問題数、正答数をリセットしてスタート画面を開始します。
スタートボタンをクリックすることでゲームを開始することができます。
changeScene関数は各画面を切り替える役割を果たします。0.5 ゲーム画面
function showQuestion() { var str = ""; question[state.gameCount].choice.forEach(function(value) { str += '<li class="questionChoice">' + value + "</li>"; }); textQuestion.innerHTML = question[state.gameCount].text; listAnswer.innerHTML = str; } function choiceQuestion() { let questionChoice = document.querySelectorAll(".questionChoice"); questionChoice.forEach(function(choice) { choice.addEventListener( "click", function() { state.answer = this.textContent; checkAnswer(question[state.gameCount].ansewer); }, false ); }); } function checkAnswer(answer) { if (answer === state.answer) { correctAnswer(); } else { incorrectAnswer(); } state.gameCount++; if (state.gameCount < question.length) { showQuestion(); choiceQuestion(); } else { gameEnd(); } } function correctAnswer() { state.success++; console.log("正解"); } function incorrectAnswer() { console.log("不正解"); } function gameStart() { changeScene(sceneTop, sceneGame); showQuestion(); choiceQuestion(); } function gameEnd() { changeScene(sceneGame, sceneResult); numResult.innerHTML = state.success; btnReset.addEventListener("click", init, false); }ゲーム画面を構成する関数です。
showQuestion関数はquestionに格納されている問題文、選択肢をinnerHtmlを使ってhtml側の各要素に挿入します。
choiceQuestion関数は選択肢を選んでクリックした際の処理です。state.answerに選択したテキストを代入し、chackAnswer関数で回答の正解、不正解の判定を行います。
checkAnswer関数では正解、不正解の判定をします。またstate.gemeCountが用意されている問題数より小さければstate.gameCountをインクリメントし次の問題へ、大きければゲームを終了する関数を呼び出しています。06. 関数の呼び出し
init();色々関数を定義してきましたが、最終的にはinit関数を呼び出すだけで動作します。
JSの処理は以上になります。
まだJS触り始めなのでコードが読みにくかったり、もっと最適な関数をかけたりすると思います。
4択クイズゲームの作成は初心者からしても作りやすかったので、JSの基礎的なことを覚えたらぜひ作ってみてください。
- 投稿日:2020-01-26T12:34:37+09:00
js 値の操作_1
引数の初期値設定
function myfunc(price, tax = 0.1){ const result = price + price * tax; return result; } myfunc(10); //11 myfunc(10, 0.08); //10.8複数の引数を渡す
function myfunc(...numbers){ let result = 0; for(const number of numbers){ result += number; } return result; }反復処理のスキップ
//奇数だけ表示 for(let i = 0; i <10; i++){ if(i % 2 === 0){ continue; } console.log(i); }文字列のトリミング
const string1 = ' hello '; string.trim(); //hello文字列の操作
//文字列の検索 const mystring = 'javascript'; const string1 = mystring.indexOf('script'); //4 const string2 = mystring.indexOf('java'); //0 const string3 = mystring.lastIndex('a'); //3 const string4 = mystring.indexOf('html'); //-1 const string5 = mystging.search('script') //4 mystring.includes('script'); //true mystring.startsWith('java'); //true mystring.endsWith('script'); //true //文字列を取り出す mystring.slice(0,4); //java mystring.slice(0); //javascript mystring.substr(4,6); //script //文字列の置き換え const imageName = 'image1.png'; imageName.replace('1.png','2.png'); //images2.png let phoneNumber = '080-1234-5678'; phoneNumber.replace('-', '');//0801234-5678 phoneNumber.replace(/-/g, '');//08012345678
- 投稿日:2020-01-26T10:57:31+09:00
ES6構文をRailsでプリコンパイル
AWSにRailsアプリをデプロイしようとした時、、、
諸々設定も終盤、EC2へSSHでログインし、Railsアプリをプリコンパイルする
はい、怒られました。
原因は
Uglifier::Error: Unexpected token: punc ()). To use ES6 syntax, harmony mode must be enabled with Uglifier.new(:harmony => true).この部分に書いてありますね。
google翻訳先生に聞くと、「Uglifier :: Error:予期しないトークン:punc())。 ES6構文を使用するには、ハーモニーモードを>Uglifier.new(:harmony => true)で有効にする必要があります。」
なるほど、、、
と言うわけで、
config/environments/production.rbconfig.assets.js_compressor = :uglifierこれを、、、
config/environments/production.rbconfig.assets.js_compressor = Uglifier.new(harmony: true)こう!
お疲れ様でした。
- 投稿日:2020-01-26T04:41:56+09:00
[W.I.P.] Vue の 50 倍の速度が出るらしい「Svelte」の新しい CLI「sirv-cli」を使って人生初のポートフォリオを作ってみる
前提として,
1. GitHub にリポジトリを作っただけの段階で記事を書き始めています。
2. Svelte を触ったことは一度もありません。ドキュメントも今から読みます。
3. 2日以内を目標に GitHub Pages に公開するまで随時更新します。目次
0. はじめに - Svelte って何?
あなたは Svelte をご存知ですか? 私は2日前にはじめて知りました
公式サイトには次のように説明があります。
Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the browser, Svelte shifts that work into a compile step that happens when you build your app.
Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.
"Svelte・Cybernetically enhanced web apps" from https://svelte.dev
「ほーん(Google 翻訳つかお)」
グーグル先生「Svelte は、ユーザーインターフェイスを構築するための根本的な新しいアプローチです。 React や Vue のような従来のフレームワークはブラウザーで大部分の作業を行いますが、Svelte はその作業をアプリのビルド時に発生するコンパイルステップに移行します。 仮想 DOM 差分のような手法を使用する代わりに、Svelte はアプリの状態が変化したときに外科的に DOM を更新するコードを記述します。」
「よくわからんけど
『React や Vue は仮想 DOM 越しに JavaScript を動かすからブラウザに負荷がかかって重い』
『仮想 DOM は遅いから使わないようにしたぜ』
『ついでにコンパイルのときに最適化もかけてやるぜ』
みたいな話かな(ふんいきでものを言っています)」1. 動機
2日前に Qiita でこういった記事を見かけまして,Vue 好きの私1としては「Vue の 50 倍!?」「構文は Vue ライク!?」「これは試さなければ!!!」という感じで Svelte 使って何か作ることにしました
ReactとVueを改善したSvelteというライブラリーについて
ベンチマークでReactの35倍、Vueの50倍速いです。
ToDo アプリみたいな完成しても使わないものは作ってる途中で飽きちゃうので,ちょうどポートフォリオサイトを何で作るか悩んでいたので,自分のポートフォリオサイトを作ることにしました実装
前述のとおりこれからやって行きます(2020/01/26 04:37:42)
参考
nuxt-create-app の TypeScript 対応が待ちきれなかったので Nuxt+TypeScript のテンプレート を作ったりしています ↩
- 投稿日:2020-01-26T04:41:56+09:00
[W.I.P.] Vue の 50 倍の速度が出るらしい「Svelte」を使って人生初のポートフォリオを作ってみる
前提として,
1. GitHub にリポジトリを作っただけの段階で記事を書き始めています。
2. Svelte を触ったことは一度もありません。ドキュメントも今から読みます。
3. 2日以内を目標に GitHub Pages に公開するまで随時更新します。目次
0. はじめに - Svelte って何?
あなたは Svelte をご存知ですか? 私は2日前にはじめて知りました
公式サイトには次のように説明があります。
Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the browser, Svelte shifts that work into a compile step that happens when you build your app.
Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.
"Svelte・Cybernetically enhanced web apps" from https://svelte.dev
「ほーん(Google 翻訳つかお)」
グーグル先生「Svelte は、ユーザーインターフェイスを構築するための根本的な新しいアプローチです。 React や Vue のような従来のフレームワークはブラウザーで大部分の作業を行いますが、Svelte はその作業をアプリのビルド時に発生するコンパイルステップに移行します。 仮想 DOM 差分のような手法を使用する代わりに、Svelte はアプリの状態が変化したときに外科的に DOM を更新するコードを記述します。」
「よくわからんけど
『React や Vue は仮想 DOM 越しに JavaScript を動かすからブラウザに負荷がかかって重い』
『仮想 DOM は遅いから使わないようにしたぜ』
『ついでにコンパイルのときに最適化もかけてやるぜ』
みたいな話かな(ふんいきでものを言っています)」1. 動機
2日前に Qiita でこういった記事を見かけまして,Vue 好きの私1としては「Vue の 50 倍!?」「構文は Vue ライク!?」「これは試さなければ!!!」という感じで Svelte 使って何か作ることにしました
ReactとVueを改善したSvelteというライブラリーについて
ベンチマークでReactの35倍、Vueの50倍速いです。
ToDo アプリみたいな完成しても使わないものは作ってる途中で飽きちゃうので,ちょうどポートフォリオサイトを何で作るか悩んでいたので,自分のポートフォリオサイトを作ることにしました実装
前述のとおりこれからやって行きます(2020/01/26 04:37:42)
インストール
とりあえず公式ドキュメントのここを参考に, GitHub で作ったリポジトリを clone して,
degit コマンドを使ってテンプレを作りました。u-sho@~/github/u-sho/svelte-app (master=)$ npx degit sveltejs/template app npx: 32個のパッケージを3.88秒でインストールしました。 > cloned sveltejs/template#master to app u-sho@~/github/u-sho/svelte-app (master %=)$ mv app/* ./ u-sho@~/github/u-sho/svelte-app (master *%=)$ mv app/. ./ ../ .gitignore u-sho@~/github/u-sho/svelte-app (master *%=)$ mv app/.gitignore ./ u-sho@~/github/u-sho/svelte-app (master *%=)$ git add . u-sho@~/github/u-sho/svelte-app (master +=)$ git commit -m ":tada: initial commit"公式は npm を推してくるけど私は yarn 派です
u-sho@~/github/u-sho/svelte-app (master >)$ yarn u-sho@~/github/u-sho/svelte-app (master *>)$ yarn devdev モードの初期画面です。port が 5000 なのが配慮してる感じが出てて嬉しい
これもコミット&プッシュしたところでリポジトリ名を
svelte-appからportfolioに変えました(2020/01/26 05:50:39)
Qiita 書くのに時間かかりすぎてるなあ
参考
変更履歴
- 2020/01/26 05:50:39 インストールまで
- 2020/01/26 05:53:55 sirv-cli の使いどころが思ったよりなかったのでタイトルを編集しました
nuxt-create-app の TypeScript 対応が待ちきれなかったので Nuxt+TypeScript のテンプレート を作ったりしています ↩
- 投稿日:2020-01-26T04:41:56+09:00
[W.I.P.] 【実況】Vue の 50 倍の速度が出るらしい「Svelte」のライブラリ「Sapper」を使って人生初のポートフォリオを作ってみる
前提として,
1. GitHub にリポジトリを作っただけの段階で記事を書き始めています。
2. Svelte を触ったことは一度もありません。ドキュメントも今から読みます。
3. 2日以内を目標に GitHub Pages に公開するまで随時更新します。目次
0. はじめに - Svelte って何
1. 動機
2. 実装0. はじめに - Svelte って何?
あなたは Svelte をご存知ですか? 私は2日前にはじめて知りました
公式サイトには次のように説明があります。
Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the browser, Svelte shifts that work into a compile step that happens when you build your app.
Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.
"Svelte・Cybernetically enhanced web apps" from https://svelte.dev
「ほーん(Google 翻訳つかお)」
グーグル先生「Svelte は、ユーザーインターフェイスを構築するための根本的な新しいアプローチです。 React や Vue のような従来のフレームワークはブラウザーで大部分の作業を行いますが、Svelte はその作業をアプリのビルド時に発生するコンパイルステップに移行します。 仮想 DOM 差分のような手法を使用する代わりに、Svelte はアプリの状態が変化したときに外科的に DOM を更新するコードを記述します。」
「よくわからんけど
『React や Vue は仮想 DOM 越しに JavaScript を動かすからブラウザに負荷がかかって重い』
『仮想 DOM は遅いから使わないようにしたぜ』
『ついでにコンパイルのときに最適化もかけてやるぜ』
みたいな話かな(ふんいきでものを言っています)」1. 動機
2日前に Qiita でこういった記事を見かけまして,Vue 好きの私1としては「Vue の 50 倍!?」「構文は Vue ライク!?」「これは試さなければ!!!」という感じで Svelte 使って何か作ることにしました
ReactとVueを改善したSvelteというライブラリーについて
ベンチマークでReactの35倍、Vueの50倍速いです。
ToDo アプリみたいな完成しても使わないものは作ってる途中で飽きちゃうので,ちょうどポートフォリオサイトを何で作るか悩んでいたので,自分のポートフォリオサイトを作ることにしました2. 実装
前述のとおりこれからやって行きます(2020/01/26 04:37:42)
2.1 インストール
とりあえず公式ドキュメントのここを参考に, GitHub で作ったリポジトリを clone して,
degit コマンドを使ってテンプレを作りました。u-sho@~/github/u-sho/svelte-app (master=)$ npx degit sveltejs/template app npx: 32個のパッケージを3.88秒でインストールしました。 > cloned sveltejs/template#master to app u-sho@~/github/u-sho/svelte-app (master %=)$ mv app/* ./ u-sho@~/github/u-sho/svelte-app (master *%=)$ mv app/. ./ ../ .gitignore u-sho@~/github/u-sho/svelte-app (master *%=)$ mv app/.gitignore ./ u-sho@~/github/u-sho/svelte-app (master *%=)$ git add . u-sho@~/github/u-sho/svelte-app (master +=)$ git commit -m ":tada: initial commit"公式は npm を推してくるけど私は yarn 派です
u-sho@~/github/u-sho/svelte-app (master >)$ yarn u-sho@~/github/u-sho/svelte-app (master *>)$ yarn devdev モードの初期画面です。port が 5000 なのが配慮してる感じが出てて嬉しい
これもコミット&プッシュしたところでリポジトリ名を
svelte-appからportfolioに変えました(2020/01/26 05:50:39)
Qiita 書くのに時間かかりすぎてるなあ
2.2 方針転換
React に対する Next のように,Svelte には Sapper というライブラリがあるそうです。
とにかく速く実装を終えたいので,今回はこれを使うことにしました![]()
もう夜も終わりそうなので,一旦寝て,夜から再会したいと思います(2020/01/26 06:13:35)
参考
変更履歴
- 2020/01/26 05:50:39 インストールまで
- 2020/01/26 05:53:55 sirv-cli の使いどころが思ったよりなかったのでタイトルを編集しました
- 2020/01/26 06:13:35 Sapper がいいらしいので Sapper を使うことにしました
nuxt-create-app の TypeScript 対応が待ちきれなかったので Nuxt+TypeScript のテンプレート を作ったりしています ↩
- 投稿日:2020-01-26T00:44:05+09:00
AppiumでFlutterアプリのテストを自動化する 実践編(JavaScript)
はじめに
AppiumでFlutterアプリのテストを自動化する 環境構築編 - Qiita
の続きになります。
実際にテストコードを書いて、それを実行し、レポートを出力するところまでやります。
今回は「JavaScript」を使います。前提条件
AppiumでFlutterアプリのテストを自動化する 環境構築編 - Qiita
で、Appiumの環境構築が完了していることなぜAppiumで自動化するのか
Flutterには、「Integration Test」という仕組みが存在します。
これは結合テストを行うための仕組みですが、この仕組みを利用することで、UIテストを自動化することも可能になります。また、各OSごとにUIテストを用意せずとも、ワンソースで実装することができます。
ですが、テストしたいWidgetに対して逐一Keyを設定していかなければならないのと、全て手動で実装しないといけないのが難点になります。
Appiumはワンソースで実装することが容易ではありませんが、OSごとのデバイス設定やWidgetの取得方法などをうまく共通化することができればワンソースでも実現可能ですし、何よりレコード機能があるため、Appiumで自動化する選択肢もありだと考えています。
ただ何を優先するかはプロジェクトによりけりではあるので、どちらが正解というわけでもないと思います。プロジェクトの準備
appium/sample-code/javascript-wd at master · appium/appium · GitHub
をベースにプロジェクトを準備します。
なぜこのプロジェクトを流用したかというと、レコード機能で取得した内容をコピペするだけで実装が容易にできるのと、Mochaというテスティングフレームワークがなかなか良さげ(レポート機能ついてたりとか)だったので採用しました。
基本的にはほぼ流用している形になります。package.json{ "name": "appium_test_js_wd", "version": "1.0.0", "description": "", "scripts": { "test": "mocha test/**/*.test.js", "clean": "rm -rf node_modules && rm -f package-lock.json && npm install" }, "author": "", "license": "ISC", "engines": { "node": ">=6", "npm": ">=6" }, "devDependencies": { "@babel/register": "^7.0.0", "@babel/core": "^7.0.0", "@babel/preset-env": "^7.0.0", "chai": "^4.1.2", "mocha": "^6.0.0", "wd": "^1.5.0" } }.babelrc{ "presets": [ [ "@babel/preset-env", { "targets": { "node": "6" } } ] ] }mocha.optsでオプションを設定することができます。
--reporterオプションでレポートの出力形式を設定することができます。test/mocha.opts--require @babel/register --timeout 1800000 --reporter spectest/.eslintrc{ "rules": { "func-names": 0 } }Appium Desktopでテストコードを記録
テストコードの記録方法については以下に記載していますので参照してください。
AppiumでFlutterアプリのテストを自動化する 実践編(Python) - Qiita
使用するアプリは上記ページと同様です。
記録する言語は、「JS(wd)」を選択します。記録したテストコードを実行できるようにする
例えば以下のように記載します。
test/top/top.test.jsimport wd from 'wd'; import chai from 'chai'; const {assert} = chai; describe('カウントアップアプリ', function () { let driver; before(async function () { driver = await wd.promiseChainRemote("http://127.0.0.1:4723/wd/hub"); const caps = { "platformName": "Android", "automationName": "Appium", "deviceName": "Android Emulator", "app": "/Users/Hitoshi/AndroidStudioProjects/flutter_app_for_appium/build/app/outputs/apk/release/app-release.apk" }; await driver.init(caps); // ここで待たないと要素の取得に失敗してしまうので待つ await driver.setImplicitWaitTimeout(5000); }); after(async function () { await driver.quit(); }); it('初期状態', async function () { let el1 = await driver.elementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.view.View[3]"); const countText = await el1.text(); assert.equal(countText, '0'); }); it('カウントアップされるか', async function () { let el1 = await driver.elementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.widget.Button"); await el1.click(); let el2 = await driver.elementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.view.View[3]"); const countText = await el2.text(); assert.equal(countText, '1'); }); });テストコードの実行
コマンドでテストコードを実行します。
npm testすると、以下のように表示されるはずです。
端末側もアプリが起動し、ボタンが押されてカウントアップされるはずです。> appium_test_js_wd@1.0.0 test /Users/Hitoshi/src/appium-test/appium_test_js_wd > mocha test/**/*.test.js カウントアップアプリ ✓ 初期状態 (2195ms) ✓ カウントアップされるか (2199ms) 2 passing (42s)ソースコード
以下にアップしましたので参考にしてください。
- 投稿日:2020-01-26T00:22:00+09:00
[Chrome拡張機能]ローカルのフォルダ/ファイルを開く機能を作ってみた
はじめに
自分が勤めている会社に、社内Wikiみたいなものがあるのですが、各ページに書かれている参考資料へのパスが、社内のファイルサーバやローカルのパスになっていることが、多々あります。
パスを毎回コピーしてエクスプローラに貼り付けるのが、だんだん面倒になってきました。
そこで、Chrome拡張機能で自動化することにしました。
- 文字列を選択し、右クリックした時のコンテキストメニューから実行する
- 選択文字列がフォルダ/ファイルのパスなら開く
- 選択文字列の中に
file:とか<とか>とかあったら、事前に取り除く先に言っておきますが、苦労の割にあまり自動化されません。。。
しかも、Chromeウェブストアにないため、Chromeを起動する度に毎回「無効化する」かどうか聞かれます。
作った機能からして、Chromeウェブストアに置かせてもらえる気がしません。そのため、毎回聞かれても無効化せずに利用いただくか、投稿が部分的にでも何かの参考になれば幸いです。
やったこと
以下を
丸パク、いや、参考にさせていただき、やったことを挙げていきます。
- Native Message1(外部ソフト登録)
- Native Message2(拡張機能)
- Native Message3(通信設定 拡張側)
- Native Message5(2byte文字等の対応)(1) 拡張機能のマニフェストファイル作成
name、description、versionはお好きな値で。
あと、以下の例なら、48x48の好きなアイコン画像も必要です。manifest.json{ "manifest_version": 2, "name": "OpenSelectedText", "description": "Open Selected Text", "version": "1.0", "background": { "scripts": ["background.js"], "persistent": false }, "permissions": [ "contextMenus", "nativeMessaging" ], "icons": { "48": "icon48.png" } }(2) 拡張機能の本体となるスクリプト作成
今回は、パスとなる文字列を選択後の、コンテキストメニューから呼ぶことにしました。
manifest.jsonで"persistent": falseにしているため、chrome.contextMenus.create()の中でonclickは指定しないで、メニューのIDから判断することにします。スクリプトの終わり際にある、以下2点が重要です。
-chrome.runtime.sendNativeMessage()を呼んでいること
-chrome.runtime.sendNativeMessage()の第1引数を、後述するレジストリキーと合わせること
特に1点目は、参考サイトの方法
(chrome.runtime.connectNative()で取得したportに対してport.postMessage()を呼ぶ)
と異なります。
その理由は、ホスト側のプロセスが勝手に終わっても、Chrome側にエラーNative host has exited.が発生しないようにするためです。background.js//コンテキストメニューのクリック時イベントハンドラ function onClickHandler(info, tab) { if (info.menuItemId == "OpenSelectedText") { sendText(info, tab); } }; chrome.contextMenus.onClicked.addListener(onClickHandler); //拡張機能インストール時のみ、自メニュー追加 chrome.runtime.onInstalled.addListener(function() { chrome.contextMenus.create( { id : "OpenSelectedText", title : "選択文字列をパスとして開く", type : "normal", contexts : ["selection"] }); }); //選択文字列を送信 function sendText(info, tab) { var SelectedText = encodeURIComponent(info.selectionText.replace(/\\/g, '/')); chrome.runtime.sendNativeMessage( "host1", { SelectedText }, function(response) { var message = decodeURIComponent(response); console.log(message); if (message != "OK") { alert(message); } } ); }(3) 拡張機能の読み込み
作ったマニフェストファイルとスクリプト(とアイコン画像)を、ローカルの任意フォルダに集めます。
(以降、フォルダをC:\Work\OpenSelectedTextと仮定しますが、各自読み替えてください)
そして、Chromeのメニュー「その他のツール」-「拡張機能」から、
「パッケージ化されていない拡張機能を読み込む」ボタンを押し、上記フォルダを指定します。
読み込んだ拡張機能に表示されたIDの値が次に必要なので、控えておいてください。(4) 拡張機能と通信するホストのマニフェストファイル作成
拡張機能と通信するホスト用に、任意名称のマニフェストファイルを作ります。
(以降、ファイル名をOST_Host.jsonと仮定しますが、各自読み替えてください)
nameは後述のレジストリキーと合わせます。
descriptionはお好きな値で。
pathには、この後作るホスト(*.exe)へのパスを書きます。
allowed_originsには、下記の例からID部分を、読み込んだ拡張機能のIDに修正します。OST_Host.json{ "name": "host1", "description": "Open Selected Text Host", "path": "OST_Host.exe", "type": "stdio", "allowed_origins": ["chrome-extension://bgkppcgfghmbmlfljpldaaddklfeaafg/"] }で、作ったマニフェストファイルも
C:\Work\OpenSelectedTextに置いちゃいます。(本当はどこでもいいと思いますが)(5) ホストを登録するためのレジストリ編集
レジストリ上、
HKEY_CURRENT_USER\SOFTWARE\Google\ChromeにキーNativeMessagingHostsがなければ、作成しておきます。
さらに、NativeMessagingHosts直下に、スクリプトで呼ぶchrome.runtime.sendNativeMessage()の第1引数と同じ名称のキー(今回ならhost1)を作成します。
作成したキー(今回ならhost1)の値に、ホストのマニフェストファイルへの絶対パスを設定します。レジストリ登録用のファイル(*.reg)風に書くと、こんな感じです。
Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\host1] @="C:\\Work\\OpenSelectedText\\OST_Host.json"(6) ホスト作成
C#で作ります。参考サイトには「コンソールアプリケーション」とあったのですが、DOS窓をチラ見せしたくないので、筆者は以下の手順で作り始めました。
(この手順が正しいかは不明ですが)
- Visual Studio起動(筆者は、PCにまだ入っていたVisual C# 2008 Express使用)
- 新規プロジェクトの作成で、「Windowsフォームアプリケーション」を選択
- 作成したプロジェクトから、「Form1.cs」を削除
- プロジェクトのプロパティにて、スタートアップを「Program」に変更
Chromeから来るデータはJSONなので、(ただ使ってみたかっただけですが)DataContractJsonSerializerを使ってみます。
作成したプロジェクトには以下3点、参照を追加します。
- Microsoft.JScript
- System.Runtime.Serialization
- System.ServiceModel.Web(これだけは.NET Framework 4以降なら不要)新規クラス「NativeMessage.cs」を追加して、「Program.cs」とともに、以下のように実装します。
NativeMessage.csusing Microsoft.JScript; using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Text; namespace OST_Host { [DataContract] public class Message { [DataMember] public string SelectedText { get; set; } } class NativeMessage { public static string StringRead() { // JSONデータの受信 string inStr = OpenStandardStreamIn(); inStr = GlobalObject.decodeURIComponent(inStr); // JSONデータのデシリアライズ var serializer = new DataContractJsonSerializer(typeof(Message)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(inStr))) { var data = (Message)serializer.ReadObject(ms); return data.SelectedText; } } public static void StringWrite(string stringData) { int limit = 1024 * 1024 - 2; string stringText = GlobalObject.encodeURIComponent(stringData); while (stringText.Length >= limit) { OpenStandardStreamOut("\"" + stringText.Substring(0, limit) + "\""); stringText = stringText.Substring(limit); } OpenStandardStreamOut("\"" + stringText + "\""); } private static string OpenStandardStreamIn() { Stream stdin = Console.OpenStandardInput(); byte[] bytes = new byte[4]; stdin.Read(bytes, 0, 4); int length = BitConverter.ToInt32(bytes, 0); string input = ""; for (int i = 0; i < length; i++) input += (char)stdin.ReadByte(); stdin.Close(); return input; } private static void OpenStandardStreamOut(string stringData) { byte[] bytes = BitConverter.GetBytes(stringData.Length); Stream stdout = Console.OpenStandardOutput(); for (int i = 0; i < 4; i++) stdout.WriteByte(bytes[i]); Console.Write(stringData); stdout.Close(); } } }Program.csusing System; using System.Diagnostics; using System.IO; namespace OST_Host { static class Program { [STAThread] static void Main(string[] args) { // 受信文字列(¥が全て/になっている) string inStr = NativeMessage.StringRead(); int index; string[] prefixes = new string[2] {"file://", "file:"}; if (inStr == string.Empty) { NativeMessage.StringWrite("選択文字列が空です。"); } else { // 不要な文字の削除 index = inStr.LastIndexOf("<"); if (index >= 0) { inStr = inStr.Substring(index + 1); } index = inStr.IndexOf(">"); if (index >= 0) { inStr = inStr.Substring(0, index); } // "file:"の削除("FILE:"と書く方はまれだと思うが、一応は考慮) for (index = 0; index < prefixes.Length; index++) { if (inStr.StartsWith(prefixes[index], StringComparison.OrdinalIgnoreCase)) { inStr = inStr.Substring(prefixes[index].Length); } } // UNCパス先頭の"//"と、"file://"の"//"が合体していたケースの対処 if (!inStr.StartsWith("//") && !inStr.Contains(":")) { inStr = "//" + inStr; } // 通信~デシリアライズ前とは逆の変換 inStr = inStr.Replace("/", "\\"); if (Directory.Exists(inStr)) { try { Process.Start("explorer.exe", "/e, \"" + inStr + "\""); NativeMessage.StringWrite("OK"); } catch (Exception) { NativeMessage.StringWrite("フォルダを開けません。"); } } else if (File.Exists(inStr)) { try { ProcessStartInfo psi = new ProcessStartInfo(inStr); psi.WorkingDirectory = Directory.GetParent(inStr).FullName; Process.Start(psi); NativeMessage.StringWrite("OK"); } catch (Exception) { NativeMessage.StringWrite("ファイルを開けません。"); } } else { NativeMessage.StringWrite("不正なパスです。"); } } } } }ビルドして生成したファイルも、
C:\Work\OpenSelectedTextに置いちゃいます。
(ホストのマニフェストファイルに書いたpathと合わせます)終わりに
選択範囲を狭くすれば、ファイルパスに対しても親フォルダを表示できるので、我ながら便利だと思います。
あとは、以下2点だけが気になります。
- Chromeウェブストアに置かせてもらえるか
- 置かせてもらえたとしても、他PCにインストールするとき、レジストリ編集はChromeがやってくれるのか、バッチか何かを用意しないといけないか、それとも手動しかないか


















