【RECRUIT Technologies NIGHT vol.5】リクルート流フロントエンド開発 に参加してきた
React / Redux を活用したリクルートテクノロジーズのフロントエンド開発
古川陽介 さん(@yosuke_furukawa)
以下メモ
言いたいこと
フレームワークは作るものに合わせて作る
リクルートのWeb
トップページに検索があり、
検索するとリストビューがでるような一般的なwebサイト
典型的なWebサイト
最近はチャットできたり、
インタラクティブなやつができた。
要望
- とにかくパフォーマンス
- NatieアプリっぽいLook & Feel
- Interactiveな動き(Chat や いいね通知)
これまでのWebフレームワークでは無理
React x Redux x Node でフレームワークを作っている
事例
フロントエンドを作る上でも気をつけていること
- サーバでもクライアントでもレンダリングする
- nginxのCacheDirの保存
- Above the foldのみレンダリング
- Historyを壊さない
- 戻る・進むで状態を壊さない
- 戻る・進む中にはレンダリングスキップする
- Consumer Driven Contract
【翻訳】リッチなWebアプリケーションのための7つの原則 - from scratch
フロントエンドエンジニアが React Native を触ってみた話
90%ほどiOS/Androidで共有できる
Hot/Live Reloadingが早い
疑似要素が使いたい
疑似要素は無理
shorthandも使えない
cssがちょっと違和感があるので戸惑うかもしれない。
プラットフォーム毎にUI/UXのベストプラクティスは違う
- ナビゲーション
- ハンバーガー・タブ
Platform.OS
で分ける- ファイル自体を分ける
何故ReactNativeなのか
- ユーザー数の多いアプリで採用されている
- Webとのコード共有ができる
ReactNativeアーキテクチャ
WebのReactをつかったこと Objc,Javaの知識は今の所必要なしに使える。
ReactNativeは想像以上に良さそうだった。
簡単なアプリならWebの延長で作れそうでよい!
是非取り入れて評価してみたい。
リクルートのフロントエンド改革に挑んだ話
太田さん
前提
外注がZip納品して、SvnでBackendが開発されている
フロントエンドがViewを見れないので、CSSの影響調査をすることができない
というまあひどい状況
改革
SIerの意識改革の話。
保守的なエンジニアのやり方を変えるにはどんなものでもいいから定量的な指標が必要で、
たとえ、LOC(Lines of codes 要はコード行数)であれ、前提あれば指標にするしかない。
でなんとかやり方の改革に成功した話。
エンジニアは保守的な人が多いのはまあ結構思う。
ただ、論理的に何%良くなったとか、定量的な指標をしっかり準備しておけば、説得できる人も多いのは確かだなと思ったし、
これはとても参考になる。
成長
バックエンド寄りのフロントエンドの人がCSSを学習するのは早いけど、
フロントエンド寄りの人がバックエンドを学ぶのはやはり時間がかかる。
きちんとした学習コンテンツを用意すべきだった。
ここはかなり同意。
ただ、学習コンテンツがどんなものか、どう動機づけするかがかなりの課題だなと思う。
まとめ
リクルートテクノロジーズさんは、というかリクルートさんは結構外注が多いイメージだったが、
内部でもかなり進んだことにチャレンジしていたっていう印象を受けた。
特にReactNativeのところは今の業務にも活かしていきたい(難しいけど)
全体的に実りのある勉強会だったなぁ。
V8 の For In の話
V8 blogに話が出ていたが、V8のfor in構文が高速化したらしいので、
コードと共におっていこうと思う。
For In とは
for (const key in object) { }
このような構文でObjectのキーをイテレーションする機能。
この構文を何故高速化したかというと、Facebookのコード実行の7% Wikipediaのコード実行の8%をForIn構文が占めていたらしく、
実行に負荷のかかる部分だったらしい。
一見この構文は単純に見えるが、処理系が行わなければならない仕事は以外に多く複雑だ。
まずはForIn構文の定義を見てみよう。上記のBlogにかかれているSpecを抜粋する。
EnumerateObjectProperties ( O ) 抽象命令であるEnumerateObjectPropertiesが引数Oを伴って呼び出される時、以下のステップをとる。
1. Type(O)がObjectか確認する。
2. Iteratorを返却する。IteratorのnextメソッドはOのenumerableな文字列のプロパティを全て列挙する。
IteratorオブジェクトはECMAScriptのコード中からアクセス不能である。プロパティの列挙順及びその方法は特に規定しないが、以下の規定に沿わなければならない。
A. Iteratorのthrowとreturnメソッドはnullであり、決して呼び出されない。
B. Iteratorのnextメソッドはオブジェクトのプロパティを処理して、次にiteratorの値として返すプロパティのキーを決める。
C. 戻り値のプロパティキーはSymbolを含まない
D. ターゲットのオブジェクトのプロパティは列挙中に削除されるかもしれない。
E. Iteratorのnextメソッドに処理される前に削除されたプロパティは無視される。もし、列挙中に新たなプロパティが追加された場合、新たに追加されたプロパティは現在の列挙中に処理される保証はない。
F. プロパティ名は列挙中に一度しかIteratorのnextメソッドによって返却されない。
G. 列挙するターゲットのオブジェクトのプロパティは、ターゲットのprototype、そのprototypeのprototypeも含むが、一度でも列挙されたプロパティのキーと同じキーを含んだprototypeのキーは列挙されない。(シャドウイングされたprototypeは列挙されない)
H. ptototypeのプロパティが既に処理されていた場合、[[Enumerable]]
属性は考慮しない。
I. prototypeの列挙可能プロパティ名を取得する場合は、必ず、EnumerateObjectProperties
をprototypeを引数に呼び出して取得しなければならない。
J.EnumerateObjectProperties
はターゲットオブジェクトのプロパティを[[OwnPropertyKeys]]
内部メソッドを呼び出して取得しなければならない。
かなり要件が複雑なのがわかる。
概要
Blogから抜粋
Map Descriptors Enum Cache -------------------------- -> ------------------- -> ----------------- -> ------- |enumLength | 3 | | | length | 3 | | | Keys | ptr | -| | 'a' | -------------------------- | ------------------- | ----------------- ------- |nofOwnDescriptors | 3 | | | EnumCache | ptr | --| | indices | ptr | | 'b' | -------------------------- | ------------------- ----------------- ------- |DescriptorArray | ptr |--- | ... | ... | | 'c' | -------------------------- ------------------- -------
MapのDescriptorが持っているEnumCacheからプロパティのキーリストを取得するようにしたので、高速になったらしい。
実装
さて実装はどうなっているか。
V8のForInはRuntime呼び出しで実装されている。
試しにd8で確認すると…
./d8 --print_code -e "for (const i in {__proto__: {a:1}}){}"
この行でForInEnumerateをRuntimeから呼び出しているのが確認できる。
0x1cb5dca0476e 398 b801000000 movl rax,0x1 0x1cb5dca04773 403 48bbc0ddc70001000000 REX.W movq rbx,0x100c7ddc0 ;; external reference (Runtime::ForInEnumerate) 0x1cb5dca0477d 413 e81efadfff call 0x1cb5dc8041a0 ;; code: STUB, CEntryStub, minor: 8
ではRuntime::ForInEnumerateを確認しよう。
ちなみにMapとかV8の基礎情報は V8祭り を参照してほしい。
では確認ー
ForInEnumerateはsrc/runtime/runtime-forin.ccにある。
Runtime::ForInEnumerate
を確認する前にこのファイルにある他のRuntimeも確認しておく。
このRuntimeに用意されているのは、
- Runtime_ForInEnumerate
- Runtime_ForInPrepare
- Runtime_ForInHasProperty
- Runtime_ForInFilter
の4つ。
ForInEnumerate以外はIgnitionインタープリタから呼び出されているようだが、今回はFullCodegenから呼び出されるコードをメインに見ていく。
RUNTIME_FUNCTION(Runtime_ForInEnumerate) { HandleScope scope(isolate); DCHECK_EQ(1, args.length()); CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0); RETURN_RESULT_OR_FAILURE(isolate, Enumerate(receiver)); }
これがForInEnumerateの本体だ。
中ではさらにEnumerate
をreceiverを引数に呼び出している。
次はEnumerate
// Returns either a FixedArray or, if the given {receiver} has an enum cache // that contains all enumerable properties of the {receiver} and its prototypes // have none, the map of the {receiver}. This is used to speed up the check for // deletions during a for-in. MaybeHandle<HeapObject> Enumerate(Handle<JSReceiver> receiver) { Isolate* const isolate = receiver->GetIsolate(); JSObject::MakePrototypesFast(receiver, kStartAtReceiver, isolate); FastKeyAccumulator accumulator(isolate, receiver, KeyCollectionMode::kIncludePrototypes, ENUMERABLE_STRINGS); accumulator.set_is_for_in(true); // Test if we have an enum cache for {receiver}. if (!accumulator.is_receiver_simple_enum()) { Handle<FixedArray> keys; ASSIGN_RETURN_ON_EXCEPTION( isolate, keys, accumulator.GetKeys(GetKeysConversion::kKeepNumbers), HeapObject); // Test again, since cache may have been built by GetKeys() calls above. if (!accumulator.is_receiver_simple_enum()) return keys; } return handle(receiver->map(), isolate); }
さて、このEnumerateで注目したいのは、FastKeyAccumulator
である。このFastKeyAccumulatorがオブジェクトのprototypeの情報、プロパティの情報を取得し、
最適な処理に振り分けている。
void FastKeyAccumulator::Prepare() { DisallowHeapAllocation no_gc; // Directly go for the fast path for OWN_ONLY keys. if (mode_ == KeyCollectionMode::kOwnOnly) return; // Fully walk the prototype chain and find the last prototype with keys. is_receiver_simple_enum_ = false; has_empty_prototype_ = true; JSReceiver* last_prototype = nullptr; for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd(); iter.Advance()) { JSReceiver* current = iter.GetCurrent<JSReceiver>(); bool has_no_properties = CheckAndInitalizeEmptyEnumCache(current); if (has_no_properties) continue; last_prototype = current; has_empty_prototype_ = false; } if (has_empty_prototype_) { is_receiver_simple_enum_ = receiver_->map()->EnumLength() != kInvalidEnumCacheSentinel && !JSObject::cast(*receiver_)->HasEnumerableElements(); } else if (last_prototype != nullptr) { last_non_empty_prototype_ = handle(last_prototype, isolate_); } }
これがFastKeyAccumulator
の初期化処理。やっていることは
- Receiverオブジェクトのprototypeを辿る
- prototypeにプロパティがあれば、
has_empty_prototype_
をfalse
にする。 - そして、全てのprototypeを辿り終わったら、
has_empty_prototype_ == true
の場合は、receiverのMapオブジェクトがEnumを持っていて、Enumerableなプロパティを持っていないければ、is_receiver_simple_enum_ = true
になる。 - それ以外の場合は
last_non_empty_prototype_
に最後のprototypeを渡す。
さて、runtime-forinのEnumerate
関数に戻ると、
if (!accumulator.is_receiver_simple_enum()) {
FastKeyAccumulator::is_receiver_simple_enum()
で処理を分けている。
つまり、自身にEnumerableなプロパティを持たず、prototypeにも持たず、MapにEnumを持っているオブジェクトはこのファストパスに入る。
このパスではFastKeyAccumulator::GetKeys()
でObjectのプロパティキーを取得する。
MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys( GetKeysConversion keys_conversion) { if (filter_ == ENUMERABLE_STRINGS) { Handle<FixedArray> keys; if (GetKeysFast(keys_conversion).ToHandle(&keys)) { return keys; } if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>(); } return GetKeysSlow(keys_conversion); }
GetKeysの定義は結構簡単で、filter_
プロパティは今回はENUMERABLE_STRINGS
なのでifに入る。
そしたらFastKeyAccumulator::GetKeysFast
を呼び出してHandle<FixedArray>
に変換する。
MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast( GetKeysConversion keys_conversion) { bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly; Map* map = receiver_->map(); if (!own_only || !OnlyHasSimpleProperties(map)) { return MaybeHandle<FixedArray>(); } // From this point on we are certiain to only collect own keys. DCHECK(receiver_->IsJSObject()); Handle<JSObject> object = Handle<JSObject>::cast(receiver_); // Do not try to use the enum-cache for dict-mode objects. if (map->is_dictionary_map()) { return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion); } int enum_length = receiver_->map()->EnumLength(); if (enum_length == kInvalidEnumCacheSentinel) { Handle<FixedArray> keys; // Try initializing the enum cache and return own properties. if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) { if (FLAG_trace_for_in_enumerate) { PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n", keys->length()); } is_receiver_simple_enum_ = object->map()->EnumLength() != kInvalidEnumCacheSentinel; return keys; } } // The properties-only case failed because there were probably elements on the // receiver. return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion); }
FastKeyAccumulator::GetKeysFast
の定義がこちら。
いろいろコードがあるのだが、重要なのは、is_dictionary_map
のチェックである。
is_dictionary_map
は拡張されるObjectの場合trueになっている。
現在の所、
- グローバルオブジェクトのMap、
Object.create(null)
の戻り値のMap
がdictionary_map
という扱いになっている。
このオブジェクトの場合はそもそもTransitionするMapを持っていない、EnumCacheも持っていないので、
GetOwnKeysWithElements
のtemplate引数をfalseで呼び出す。
それ以外のオブジェクトの場合は、enum_cacheの有無をチェックし、
存在しなければGetOwnKeysWithUninitializedEnumCache
を呼び出し、その場で作成する。
GetOwnKeysWithUninitializedEnumCache
は省略するが、enum_cacheをdescriptorから作成し、プロパティ一覧を返す。
enum_cacheを既に持っているオブジェクトの場合、GetOwnKeysWithElements
がtemplate引数にtrueを渡して呼び出される。
template <bool fast_properties> MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate, Handle<JSObject> object, GetKeysConversion convert) { Handle<FixedArray> keys; ElementsAccessor* accessor = object->GetElementsAccessor(); if (fast_properties) { keys = GetFastEnumPropertyKeys(isolate, object); } else { // TODO(cbruni): preallocate big enough array to also hold elements. keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object); } MaybeHandle<FixedArray> result = accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE); if (FLAG_trace_for_in_enumerate) { PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n", keys->length(), result.ToHandleChecked()->length() - keys->length()); } return result; }
これがGetOwnKeysWithElements
の実装。
template引数がtrueの場合はGetFastEnumPropertyKeys
を呼び出す。
// Initializes and directly returns the enume cache. Users of this function // have to make sure to never directly leak the enum cache. Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate, Handle<JSObject> object) { Handle<Map> map(object->map()); bool cache_enum_length = map->OnlyHasSimpleProperties(); Handle<DescriptorArray> descs = Handle<DescriptorArray>(map->instance_descriptors(), isolate); int own_property_count = map->EnumLength(); // If the enum length of the given map is set to kInvalidEnumCache, this // means that the map itself has never used the present enum cache. The // first step to using the cache is to set the enum length of the map by // counting the number of own descriptors that are ENUMERABLE_STRINGS. if (own_property_count == kInvalidEnumCacheSentinel) { own_property_count = map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS); } else { DCHECK( own_property_count == map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS)); } if (descs->HasEnumCache()) { Handle<FixedArray> keys(descs->GetEnumCache(), isolate); // In case the number of properties required in the enum are actually // present, we can reuse the enum cache. Otherwise, this means that the // enum cache was generated for a previous (smaller) version of the // Descriptor Array. In that case we regenerate the enum cache. if (own_property_count <= keys->length()) { isolate->counters()->enum_cache_hits()->Increment(); if (cache_enum_length) map->SetEnumLength(own_property_count); return ReduceFixedArrayTo(isolate, keys, own_property_count); } } if (descs->IsEmpty()) { isolate->counters()->enum_cache_hits()->Increment(); if (cache_enum_length) map->SetEnumLength(0); return isolate->factory()->empty_fixed_array(); } isolate->counters()->enum_cache_misses()->Increment(); Handle<FixedArray> storage = isolate->factory()->NewFixedArray(own_property_count); Handle<FixedArray> indices = isolate->factory()->NewFixedArray(own_property_count); int size = map->NumberOfOwnDescriptors(); int index = 0; for (int i = 0; i < size; i++) { PropertyDetails details = descs->GetDetails(i); if (details.IsDontEnum()) continue; Object* key = descs->GetKey(i); if (key->IsSymbol()) continue; storage->set(index, key); if (!indices.is_null()) { if (details.location() == kField) { DCHECK_EQ(kData, details.kind()); FieldIndex field_index = FieldIndex::ForDescriptor(*map, i); int load_by_field_index = field_index.GetLoadByFieldIndex(); indices->set(index, Smi::FromInt(load_by_field_index)); } else { indices = Handle<FixedArray>(); } } index++; } DCHECK(index == storage->length()); DescriptorArray::SetEnumCache(descs, isolate, storage, indices); if (cache_enum_length) { map->SetEnumLength(own_property_count); } return storage; }
でGetFastEnumPropertyKeys
がこれ。
実はGetFastEnumPropertyKeys
は先程省略した、GetOwnKeysWithUninitializedEnumCache
からも呼び出される。
とても長いコードだが、やっていることはdescriptorがenum_cacheを持っていれば、それを返すし、なければ作成して返す。
これだけ。
さて、これがForInでキーを列挙する最速のパスなのだが、遅いパターンはどうだろうか。
GetOwnKeysWithElements
のに戻るが、
keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
と、KeyAccumulator::GetOwnEnumPropertyKeys
を呼び出すらしい。
Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys( Isolate* isolate, Handle<JSObject> object) { if (object->HasFastProperties()) { return GetFastEnumPropertyKeys(isolate, object); } else if (object->IsJSGlobalObject()) { return GetOwnEnumPropertyDictionaryKeys( isolate, KeyCollectionMode::kOwnOnly, nullptr, object, object->global_dictionary()); } else { return GetOwnEnumPropertyDictionaryKeys( isolate, KeyCollectionMode::kOwnOnly, nullptr, object, object->property_dictionary()); } }
これが実装
object->HasFastProperties
であれば、GetFastEnumPropertyKeys
に戻れるらしい。
HasFastProperties
である条件は、
- mapがHashTableでなく、StringTableでも無いこと。
その条件の場合はやはり、enum_cacheから取得される。
それ以外のグローバルオブジェクトの場合、GetOwnEnumPropertyDictionaryKeys
のパスに入る。
GetOwnEnumPropertyDictionaryKeysでは
各dictionaryのCopyEnumKeysTo
が呼び出されプロパティのコピーが行われる。
template <typename Derived, typename Shape, typename Key> void Dictionary<Derived, Shape, Key>::CopyEnumKeysTo( Handle<Dictionary<Derived, Shape, Key>> dictionary, Handle<FixedArray> storage, KeyCollectionMode mode, KeyAccumulator* accumulator) { DCHECK_IMPLIES(mode != KeyCollectionMode::kOwnOnly, accumulator != nullptr); Isolate* isolate = dictionary->GetIsolate(); int length = storage->length(); int capacity = dictionary->Capacity(); int properties = 0; for (int i = 0; i < capacity; i++) { Object* key = dictionary->KeyAt(i); bool is_shadowing_key = false; if (!dictionary->IsKey(isolate, key)) continue; if (key->IsSymbol()) continue; PropertyDetails details = dictionary->DetailsAt(i); if (details.IsDontEnum()) { if (mode == KeyCollectionMode::kIncludePrototypes) { is_shadowing_key = true; } else { continue; } } if (dictionary->IsDeleted(i)) continue; if (is_shadowing_key) { accumulator->AddShadowingKey(key); continue; } else { storage->set(properties, Smi::FromInt(i)); } properties++; if (mode == KeyCollectionMode::kOwnOnly && properties == length) break; } CHECK_EQ(length, properties); DisallowHeapAllocation no_gc; Dictionary<Derived, Shape, Key>* raw_dictionary = *dictionary; FixedArray* raw_storage = *storage; EnumIndexComparator<Derived> cmp(static_cast<Derived*>(*dictionary)); Smi** start = reinterpret_cast<Smi**>(storage->GetFirstElementAddress()); std::sort(start, start + length, cmp); for (int i = 0; i < length; i++) { int index = Smi::cast(raw_storage->get(i))->value(); raw_storage->set(i, raw_dictionary->KeyAt(index)); } }
でこれが、CopyEnumKeysTo
の実装。
forでループを回してプロパティをコピーしている。
まあこれが早いわけないよねということで、ForInの高速化の実装を確かめた。
まとめ
ForInのRuntime呼び出しでは、レシーバオブジェクトがFastPropertiesさえ持っていれば、
EnumCacheから値を取得するので高速。
IgnitionがForInを処理するパスについてはまたそのうち。
「Angular 4 の最新動向と、2017年再注目のDart、そしてAngular Dart」に行ってきた
AngularとDartの勉強会でした。
以下メモ
Angular4がやってくる!?新機能ダイジェスト
Asai Masahiko
Angular 4がやってくる!? 新機能ダイジェスト.pdf - Google ドライブ
- Semantic Versioningの導入
- 非推奨ポリシーの導入(2つのメジャーリリースを挟む)
変更点ダイジェスト
- ViewEngineの改善
- templateタグが非推奨に
- @angular/animationsの独立
- typescript2.1へのアップデート
- metaタグの追加・更新・削除が可能に
- Email Validator・EqualTo Validatorの追加
Angular2で作った社内向けツールを4に移行してみた
ハーモニーお菓子管理 お菓子の在庫が見れる
14 Components 4 Service 5 Route
およそ半日くらいで移行完了
まとめ
SemanticVersioningが導入後発のMajor Release 思っていたより小さなアップデート ViewEngineの改善はでかい
15分で分かった気になるDart
小林達(さとし)
Dartを取り巻く状況
当初はjsを置き換える予定だった
AdSense/AdWordsがangular-dartにリプレイス
The new Google AdSense user interface: built with AngularDart
The new AdWords UI uses Dart — we asked why
Dart言語
発表から6年だが、まだ活発
DartPadで試せる
(DartPad)
すべてオブジェクト
intなどは初期化しないとnull
boolはtrue以外はすべてfalse
dart:collectionパッケージには(java)のような感じでコレクションがある。
- Map
- Iterable(List,Set)
- 高階関数もつかえる
- 型推論はもうちょい
- Future・async・await
- ドット2つでビルダー化する(thisを返す様になる)
- 充実した標準ライブラリ
- 公式ドキュメントが充実
Sound Dart
Stroing Mode
AngularDartで快適SPA
AngularDartの歴史
TypeScript版と分離した
ビジネスに使える事
AngularDartをさっと見る
Streamが言語自体に組み込まれている
サーバサイドDartを試してみる
All-in-oneならAqueduct
シンプルなら純正のShelfがいいかも
初心者にはとっつきづらい
中間なのはRedStone
Shelfを利用したWrapper
まとめ
とりあえず、Dartの開発がまだ続いているのに驚いた(結構活発に)
けど、ES2015 + Typescriptの躍進があったので、他のオプショナル静的型の言語がフロントエンドで使われるには、
なかなか厳しい環境だろうなと思う。
ただ、Dartはランタイムがかなり充実している印象なので、それはうらやましい。
AngularDartがAngularと全く別のラインで開発されてるのも知らんかった。
が、まあ使わないだろう。
Typescript 2.2.0 の Mixin を使ってDIしてみる
表題の通り。
今まではMixinがなかったので、泥臭く型チェックができない方法(文字列とか)でDIしていたが、
typescript 2.2.0からMixinに対応したので、DIを考察する。
Dependency Injectionとは
Dependency Injectionとはその名の通り依存性を外から注入する仕組み。
なにが嬉しいかというと、クラス内部で依存しているはずのクラスを外部からコントロールできるようにすることで、
クラスの内部実装と外部への依存を疎結合にし、テストし易いクラスを作ることができる。
猿でも分かる! Dependency Injection: 依存性の注入 - Qiita
Inversion of Control コンテナと Dependency Injection パターン
DI for Typescript
さて、Typescriptで上述のDependency Injectionを実現するにはどういうパターンがあるか、検証してみたい。
public Dependency パターン
名前は勝手につけました。
その名の通りpublicプロパティに依存性を設定する。
実装
// deps1.ts export interface Deps1 { hello(): string; } export class Deps1Impl implements Deps1 { public hello() {return 'hello'} } // deps2.ts export interface Deps2 { world(): string; } export class Deps2Impl implements Deps2 { public world() {return 'world'} } // deps1+deps2-module.ts import { Deps1 } from './deps1'; import { Deps2 } from './deps2'; export interface Deps1Deps2Module { deps1: Deps1; deps2: Deps2; } // deps1+deps2-module-impl.ts import { Deps1Deps2Module } from './deps1+deps2-module'; import { Deps1Impl } from './deps1'; import { Deps2Impl } from './deps2'; type Constructor<T> = new(...args: any[]) => T; export function Deps1Deps2ModuleImpl<T extends Constructor<Deps1Deps2Module>>(Base: T) { return class extends Base { constructor(...a) { super(...a); this.deps1 = new Deps1Impl(); this.deps2 = new Deps2Impl(); } }; } // inject-target.ts import { Deps1 } from './deps1'; import { Deps2 } from './deps2'; import { Deps1Deps2Module } from './deps1+deps2-module'; export class InjectTarget implements Deps1Deps2Module { public deps1: Deps1; public deps2: Deps2; public greet() {return `${this.deps1.hello()} ${this.deps2.world()}`} } // injected.ts import { InjectTarget } from './inject-target' import { Deps1Deps2ModuleImpl } from './deps1+deps2-module-impl'; export class Injected extends Deps1Deps2ModuleImpl(InjectTarget) { } // main.ts import { Injected } from './injected'; const injected = new Injected(); console.log(injected.greet());
deps1.ts deps2.ts
これらは単純な依存クラス
deps1+deps2-module.ts
こちらはdeps1とdeps2を注入される側の規約を定義したinterfaceクラス
依存性を注入される側はこのインターフェースを実装する。
deps1+deps2-module-impl.ts
依存性注入を実行するMixin関数。
ここで実際に依存性の注入を行う。
inject-target.ts
依存性を注入されるクラス。
interface Deps1Deps2Module
をimplementsしている以外は、通常のクラスと変わりない。
injected.ts
ここでDeps1Deps2ModuleImpl関数にInjectTargetクラスを渡したクラスを継承することで、
依存性が注入されるクラスを生成する。
main.ts
エントリーポイント
問題点
publicプロパティに実装するしか無いので、カプセル化に問題がある。
防御的なクラスが作りづらい。
method injection パターン
こちらは上記の問題を踏まえ、publicメソッドを使ってinjectionの実現をする。
実装
// deps1+deps2-module.ts import { Deps1 } from './deps1'; import { Deps2 } from './deps2'; export interface Dependencies { deps1: Deps1; deps2: Deps2; } export interface Deps1Deps2Module { __setDependencies(deps: Dependencies): void; } // deps1+deps2-module-impl.ts import { Deps1Deps2Module } from './deps1+deps2-module'; import { Deps1Impl } from './deps1'; import { Deps2Impl } from './deps2'; type Constructor<T> = new(...args: any[]) => T; export function Deps1Deps2ModuleImpl<T extends Constructor<Deps1Deps2Module>>(Base: T) { return class extends Base { constructor(...a) { super(...a); this.__setDependencies({deps1: new Deps1Impl(), deps2: new Deps2Impl()}); } }; } // inject-target.ts import { Deps1 } from './deps1'; import { Deps2 } from './deps2'; import { Deps1Deps2Module } from './deps1+deps2-module'; export class InjectTarget implements Deps1Deps2Module { private deps1: Deps1; private deps2: Deps2; public greet() {return `${this.deps1.hello()} ${this.deps2.world()}`} public __setDependencies({deps1, deps2}) { this.deps1 = deps1; this.deps2 = deps2; } }
変更点
deps1+deps2-module.ts
__setDependencies
というpublicなセッタメソッドを用意して、
外部から直接interfaceを触れられないようにした。
deps1+deps2-module-impl.ts
__setDependeciesに依存関係を渡すように修正
inject-target.ts
__setDependencies
を実装
問題点
__setDependencies
がpublicメソッドなのは変わらず。
考察
ほんとはconstructor injectionが型チェック付きでできれば良いのだが…
今回のMixinでは不可能なので、method injectionでやるのが一番良いのかなと思う。
元々はScalaのCakeパターンとかあのへんの感じでやりたかった(願望)
ただ、typescriptの貧弱な言語機能でもここまで頑張れるのがわかったので良かった。
とりあえず、文字列ベースのDIコンテナを使うよりはいいかもしれないので、一旦プロダクトで使ってみようと思う。
可能性を伸ばしていきたい。
伸びしろですねぇ!
今回の実装はこちらにありまぁーす
jspmでtypescriptの開発をする
jspmからtypescriptの開発をした時の備忘録を書く
typescriptコンパイラ + jspm
まずはjspmでtypescriptをランタイムコンパイルできるようにするため、
plugin-typescriptをインストールする。
jspm install ts
jspm.config.js
SystemJS.config({ ... packages: { "/src": { "defaultExtension": "ts", "meta": { "*.ts": { "loader": "ts" } } } } });
これだけでtypescriptをランタイムで使う準備ができた。
typescriptのバージョンを変える
typescriptのバージョンを開発環境に合わせて変える。
まず、好みのバージョンのtypescriptをインストールする。
jspm install typescript@2.2.1
インストールしたら、plugin-typescriptのtypescriptを更新する。
jspm.config.js
SystemJS.config({ ... map: { "github:frankwallis/plugin-typescript@5.3.1": { "map": { "typescript": "npm:typescript@<バージョン>" } }, } }
<バージョン>のところに先程インストールしたtypescriptのバージョンを入れる。
typescriptのオプション
これもjspm.config.jsで設定する。
SystemJS.config({ typescriptOptions: { "tsconfig": true, // tsconfigを使う場合はtrueにする "typeCheck": false // 型チェックするかどうか }, });
tsconfig
プロパティがtrueの場合は、ページのルートからtsconfig.jsonを探すので、
ファイルの置き場所に注意。
typeCheck
はtrue・false以外に'strict'も指定できる。
tsconfig
プロパティがfalseの場合はここに全てのオプションを設定する必要がある。
typescriptの設定
tsconfig.jsonに
module: system
を設定しておけば、import構文が全てSystemJS化するので、 jspm経由でロードできる。
以上
Firebase SDK for javascript
Firebase SDKの使い方・注意点等について書く。
インストール
scriptタグで直接CDNから読み込むのが一番かんたん。
<script src="https://www.gstatic.com/firebasejs/live/3.0/firebase.js"></script>
バンドルしてしまう場合は
npm install --save firebase
でnpmからインストール
準備
Firebase Consoleから新規プロジェクトを作成
コンソールから設定を取得
npmの人は// Initialize Firebase
以下をコピー
ここまでで、こんな感じになります。
index.html
<!doctype html> <html> <head> <script src="https://www.gstatic.com/firebasejs/3.6.10/firebase.js"></script> <script> // Initialize Firebase var config = { apiKey: "AIzaSyD8BQ8oDDcXEmZgEWNz1KYLhBWIZDCR8aw", authDomain: "test-proj-509cf.firebaseapp.com", databaseURL: "https://test-proj-509cf.firebaseio.com", storageBucket: "test-proj-509cf.appspot.com", messagingSenderId: "107396821085" }; firebase.initializeApp(config); </script> </head> <body> </body> </html>
使う
firebaseは主にfirebase
オブジェクトのメソッドから操作する。
データベースを参照する
const databaseRef = firebase.database().ref('production/messages');
refの引数は参照する階層になる。
データベースに値が追加されたら通知する。
databaseRef.on('child_added', snapshot => { const value = snapshot.val(); const key = snapshot.key(); });
child_added
キーでref以下の階層に値が追加された場合に通知が飛ぶ。
通知の引数になるsnapshotから値を取得する場合はval()
メソッドで値を取得する。
もし自身のパスが取得したければ、key()
で取得できる。
データベースから値が削除されたら通知する。
databaseRef.on('child_removed', snapshot => { const value = snapshot.val(); const key = snapshot.key(); });
データベースの値が変更されたら通知する。
databaseRef.on('child_changed', snapshot => { const value = snapshot.val(); const key = snapshot.key(); });
データベースの値が移動したら通知する。
databaseRef.on('child_moved', snapshot => { const value = snapshot.val(); const key = snapshot.key(); });
最初に接続した時にDBに値があれば、child_addedかvalueに通知が来る。
全てのイベントを通知する。
databaseRef.on('value', snapshot => {...});
値を追加する
基本
const newRef = databaseRef.push(); newRef.set({foo: 'bar'});
pushで新たなエントリーを生成し、setで値を設定する。
push(value: any, onComplete: () => void)
ユニークキーを生成して、そのキーで新たなエントリーを生成する。
value
を指定した場合は後述するset
も同時に行われる。
キー生成の詳細については The 2120 Ways to Ensure Unique Identifiers
set(value: any, onComplete: () => void)
現在のロケーションに値をセットする。
フィルターをかける
フィルター系で注意したいのは、firebaseは複数のフィルターを組み合わせられないという点
なので、データ構造は十分考慮する必要があるし、場合によってはjavascript側で並べ替えることも考慮した方がいい。
順序を変える。
sqlのorder by
的なもの
databaseRef.orderByChild('createdAt').on(...);
databaseRef.orderByKey().on(...);
databaseRef.orderByValue().on(...);
orderByChild(propertyName: string)
指定された子要素のプロパティ値で順序を決める。
多分一番使う気がする。デフォルトで昇順になる。
orderByKey
キーでソートする。
キーに順序をもたせた場合はこれを呼ぶだけでいい。
orderByValue
値でソートする。
プリミティブが直接子要素になっている場合以外に使い道がない気がするが。
値を指定する。
子要素の値を指定してフィルターする。
このメソッドはorderByに影響を受け、orderByの指定があった場合はそのプロパティを比較に使う。
もしorderByの指定がなければ、第二引数で比較する値を指定する。
databaseRef.startAt(Date.now(), 'createdAt'); databaseRef.orderByChild('createdAt').startAt(Date.now()); databaseRef.endAt(Date.now(), 'createdAt'); databaseRef.orderByChild('createdAt').endAt(Date.now()); databaseRef.equalTo(Date.now(), 'createdAt'); databaseRef.orderByChild('createdAt').equalTo(Date.now());
startAt(value: any, propertyName?: string)
値が一致する要素から開始する。
一致しない要素は全てスキップされる。
endAt(value: any, propertyName?: string)
値が一致したら終了する。
一致するまでは全ての要素が流れる。
equalTo(value: any, propertyName?: string)
値が一致した要素のみを流す。
件数を制限する
databaseRef.limitToFirst(1000).on(...) databaseRef.limitToLast(1000).on(...);
limitToFirst(value: number)
指定した件数を先頭から取得する。
limitToLast(value: number)
指定した件数を最後尾から取得する。
注意点・Tips等
Firebaseのイベントで気をつけなければいけないのは、初回に繋いだ瞬間全ての値が流れてくること。
キャッシュ等をしていて、差分からの値が欲しければ、最後の値をstartAt()
に渡してイベントをスキップするような実装にする必要がある。
また、valueとchild_addedを一緒に指定すると、valueとchild_addedの両方に値が流れるので注意。
基本的にはvalueは使わず、child_added/moved/changed/removedをちゃんと使い分けたほうがよい。
HTML5とか勉強会「Webパフォーマンス」に参加してLTしてきた
LTしてきた。
とりあえず、早口すぎたなと思った。
時間を気にしすぎてしまったかも。
以下勉強会メモ
先入観とバイアスを考慮したWebサイトパフォーマンス改善
t.co
竹洞さん
日本の現状
世界基準
- 表示開始時間 0.5秒以内
- Webブラウザ上に最初の1ピクセル目が表示された時間
- 表示完了時間 2秒以内
- DOM Complete
- DOM処理が終わったタイミング
アメリカのEコマース
(アメリカのEコマース)https://www.dynatrace.com/en/benchmarks/united-states/retail/ns
日本
(日本のWebサイトパフォーマンスランキング)https://www.dynatrace.com/en/benchmarks/japan/retail/ns
どのプロジェクトでも2秒切れば直帰率は50%減る。
CMOがパフォーマンスを確認する
パフォーマンスが売上に直結している。
パフォーマンスは売上を引きずる要員であり、上げる要員ではない
先入観とバイアスだらけ
最低保証パフォーマンスを規定する
寒いマンション
マンションのほうが死亡率が高い。
寒いから。寒さ・暑さ対策といえば断熱材
ほんとに?
遮熱材が入っていない。
コンクリート自体が冷たさを溜め込む 冷輻射
我々WEB業界は建築業界を責められない
この辺はたとえの話だったのだが、結構マンション・不動産の詳細な
話に踏み込んでいたので、ちょっと戸惑った。
知らないという事を認識しよう
計測してみるまでは、実際のことはわからない
Don’t guess, measure!
要は何が起きてるかを理解するまでは、最適化や修正をすべきではないってこと。
憶測だけでは状況は悪化する。
非科学的手法は削除する
以下のツールは使うな!
- Google PageSpeed Insights
- このツールだけでは高速化できない。このツールはベストプラクティスに合致しているかどうかを判定するツール。業務では使うべきではない
- Chromeの開発者ツール。あたりを付けるぶんには問題ない。でもこれは真の値ではない。ユーザーが何秒で見ているかの比率を見るべき
- カバー率の問題。自分のISPがどれほどの範囲をカバーしているのか
- カバー率(時間)の問題。自分が見たタイミングがほんとに正しいか。
- インターネットが変わらないという先入観
- Webpage Test
- この計測環境の仕様を知っているか。PCのスペック
ここで使えないと言われているサービスの問題点は、パフォーマンスをある一点からしか計測していないからである。
大事なのはユーザーの環境で実際に何秒かかったのかという一点。
統計学を学ぼう!
統計的な知識でグラフを見ることでほんとに重要なことが読み取れる。
法律での瑕疵担保責任の変化
瑕疵とは?
請負側に品質保証責任があることが明記される。
品質が達成されるまでは無料で働かなければならない。
仕様の実現から目的の実現へ
今まで
ソフトウェアはPL法の対象にならない。 ハードウェアは対象
これから
ソフトウェアの請負も品質保証書が必要になる。
つまりクライアントの目的(パフォーマンスも含む)が達成されない場合、損害賠償の可能性がある。
まとめ
- 高速化するときには先入観で見ない。
- お金をかけてでも、製造業のような品質保証をする。
まあまあ最近のパフォーマンスAPIなど
yakura @myasaka
最近みたクライアントサイドの話
jsの比重が大きくなっている。48%ほど
パフォーマンスまわりの仕様
- 計測関連のAPI
- 遅くさせないための仕組み
- スケジューリング
計測関連
User Timing
Safariにそろそろ来そう…?
Performance Observer
(使い方)https://developers.google.com/web/updates/2016/06/performance-observer
エントリのタイプもこれから増えそう
Long Tasks
長くかかったタスクを検出(50ms以上とか)
First Paint
適切なFirst Paintがどこなのか
- FirstContentPaint
- FirstMeaningfulPaint
人間の体感的なメトリクス
遅くさせないための仕様
Intersection Observer
scroll と getBoundingClientRectの組合せはおそい!
そこでIntersectionObserver。表示範囲との交差を検出して領域判定を必要としない。
LazyLoadingとかにも使える
CSSからのアプローチ
position:sticky
ある程度までスクロールすると、要素がfixedになる
CSS Contained
大きさが変わらない要素を宣言して高速化する
.widget { contain: layout style paint; }
スケジューリング
HTTP Linkヘッダ
- のHTTPヘッダ版
- HTMLのパースを待たずにDL開始
Link<style.css>:rel=stylesheet
preloadヘッダ
WebフォントやCSS等
requestTimingIdleCallback
アイドル時間に実行させる優先度の低いタスクを渡す。
大量の要素を高速に表示する婆バーチャルレンダリング入門
久保田 @anatoo
- バーチャルレンダリングとは
- 仕組みと実装
バーチャルレンダリングとは
- パフォーマンスのためのテクニック
- ウェブページに大量の要素を高速に表示するためのテクニック
普段から使っている(react-infinite)https://github.com/seatgeek/react-infiniteとかのことだったので、
あんまメモってない。
iOSのUITableViewとかもそうだったはず。
こっからLT
WebWorker & Atomics
自分
www.slideshare.net
組織にパフォーマンスを根付かせる
始めた経緯
どんな事を勉強していけばいいかわからなくて悩んでます。
半年くらい前に、こんな質問を新人さんにされたことがきっかけ。
パフォーマンスの勉強をやってもらうのがいいかも
ServiceWorkerのnavigation-preloadについて
ちょっとごたごたしてあまり聞けなかった。。。
まとめ
ソフトバンク本社凄くきれい。
LT5分てやっぱきついな