denoland/deno コミットリーディング 12/05
ファシリテータ: @ggtmtmgg セクリタリ: @ggtmtmgg
https://github.com/denoland/deno
0bec0fa594486876ff63d02e98ab454515e9c0d8
Title: Remove leaks from snapshot_creator
タイトル: snapshot_creatorからリークの可能性を取り除く
CommitDate: Mon Dec 3 19:34:01 2018 -0800
# LSAN build. We are in the process of getting a completely clean LSAN build, # but it will take some work. So for now we just run a subset of the tests.
LSANを完全にcleanにビルドしたいけど、ちょっと大変らしい。
LSANが何かよくわからない。
LSANゾーン(Logical SANゾーン): Fabric間でお互いのFabricをマージせずにIntegrated Routingというルーティングによってデバイス間をゾーンする機能
# We want to detect leaks during the build process as well as when executing # the tests. So set the ASAN_OPTIONS env var before build.py is run. export ASAN_OPTIONS=detect_leaks=1
テストを実行するときにビルドプロセスの脆弱性を特定したい。だからASAN_OPTIONSをビルド前に指定する。
- DENO_BUILD_MODE=release ./tools/build.py -j2 + ./tools/build.py -C target/release -j2
DENO_BUILD_MODEの脱却が見られる。DENO_BUILD_MODEがどこかに残ってれば書き換えてプルリク送れそう。
- std::string snapshot_str(reinterpret_cast<char*>(snapshot.data_ptr), - snapshot.data_len); std::ofstream file_(snapshot_out_bin, std::ios::binary); - file_ << snapshot_str; + file_.write(reinterpret_cast<char*>(snapshot.data_ptr), snapshot.data_len); file_.close(); + + delete[] snapshot.data_ptr; + deno_delete(d); +
あまりどういう処理なのかわからないけど、コネクションを適切にクローズして、snapshotを適切に削除できてそう。
9e839b7e2325e55147cc7e49ed9576cc000eaf63
Title: Avoid memory leak (#1265)
CommitDate: Mon Dec 3 19:07:34 2018 -0800
- // TODO(zero-copy) Use Buf::leak(buf) to leak the heap allocated buf. And - // don't do the memcpy in ImportBuf() (in libdeno/binding.cc) + // deno_respond will memcpy the buf into V8's heap, + // so borrowing a reference here is sufficient. unsafe { libdeno::deno_respond( self.libdeno_isolate, self.as_void_ptr(), req_id, - buf.into(), + buf.as_ref().into(), ) } }
deno_respondはV8のヒープ領域にメモリーコピーをする、なので参照を借りてこればここでは充分。 (何いっているかわからない。)
f6c841a6cd85b8e8db3c6cb52fa41811535a72ca
Title: Turn off kPromiseResolvedAfterResolved warning
CommitDate: Mon Dec 3 16:30:08 2018 -0800
+ case "ResolveAfterResolved": + // Should not warn. See #1272 + break;
nodeではresolveされたPromiseをPromise.raceしようとしても特に警告は出ないらしい。 なのでdenoでも無視しようという考え。
参考
アドベントペアプロ #2 react-native
ファシリテータ: @ggtmtmgg セクリタリ: @ggtmtmgg 参加者: @saitoxu レポジトリ: https://github.com/facebook/react-native Issue: https://github.com/facebook/react-native/issues/20410
Yogaメインルーチンの整理
// STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM
// STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS
// STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM
// STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES
// STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS
// STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION
// STEP 7: CROSS-AXIS ALIGNMENT
// STEP 8: MULTI-LINE CONTENT ALIGNMENT
// STEP 9: COMPUTING FINAL DIMENSIONS
// STEP 10: SIZING AND POSITIONING ABSOLUTE CHILDREN
// STEP 11: SETTING TRAILING POSITIONS FOR CHILDREN
用語の共通認識を取る
MAIN DIRECTIONS: flexboxのdirection
MAIN-AXIS: direction水平軸
CROSS-AXIS: direction直行軸
FLEX BASIS: direction方向の要素の初期の長さ
FLEXIBLE LENGTHS: direction方向の要素の長さ
JUSTIFICATION: 最適化(?)
COMPUTING FINAL DIMENSIONS: 最終的なサイズを確定する
TRAILING POSITIONS: (多分)要素の後ろ側のスペースの大きさ(自信度20%)
devidation: 違い・偏差
Yoga.cppのYGNodelayoutImpl関数のSTEP1の手前の前処理
概要: Margin, Boarder, Padding, コメントアウトで普通のアルゴリズムとの差異・引数・詳細が述べられている。
Layout Modeごとに挙動が変わる。
YGMesuerModeUndefined: max content
YGMeasuerModeExactly: fill available
YGMeasuerModeAtMost: fit content
RowDirection: 横の向き
ColumnDirection: 縦の向き
max-content: The intrinsic preferred width.
resolution:
RTL: Right To Left, アラビア語とかで使う概念
L2738: 親のnodeのdirectionをset(inherit or ltr or rtl)
L2746-2784は上下左右のマージン,ボーダー,パディングを設定
※RNのバージョンによってやや行番号がずれているので後で最新化する
BoundAxis:
Measure: 長さを図る関数
YGNodeWithMeasureFuncSetMeasuredDimensions: MesureFuncを適用して計算し直したDimensionsをnodeにセットする
node->setLayoutMesuredDimension: MesuredDimensionをセットする
node->getMeasure(): MeasureFuncを返す関数。RCTShadowViewMeasure, RCTBaseTextInputShadowViewMeasure, RCTTextShadowViewMeasureという実態だったりする。
MeasureFuncをもつShadowViewは意外と少なそう。TextとTextInput以外にMeasureFuncがSetされるシチュエーションは少なさそう。
performLayout: specifies whether the caller is interested in just the dimentions of the node or it requires the entire node and its subtree to be layed out(with final positions)
perform:
// setLayoutPadding(値, 位置): paddingをsetする // YGUnwrapFloatOptional(Optionalな変数): Optional型の変数から値を取り出す // node->getTrailingPadding(flexColumnDirection, ownerWidth): 末尾のpadding値を計算する // YGEdgeBottom: enum node->setLayoutPadding( YGUnwrapFloatOptional( node->getTrailingPadding(flexColumnDirection, ownerWidth)), YGEdgeBottom);
cppのtypedefで関数を定義するときの書き方
typedef 返り値の型 (*関数名)(引数の型);
typedef YGSize (*YGMeasureFunc)( YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode);
そしてLayoutを計算する準備が整った。
メモ
CSSのmax content, fill available, fit contentの違いが分からない
cppのexplicit: コンストラクタにつける。いまいちよくわからない。
引数の暗黙的型変換を防ぐためのもののよう
http://starpentagon.net/life/2017/01/explicit_constructor
RCTShadowViewMeasureとかの中でYGMeasureModeごとに場合分けしてたりしてなかったりしてて、結局呼び出し側でも場合分けしてるのがむだな感じする。
参考
アドベントペアプロ #1 denoland/deno
react-native-oss.hatenablog.com
ファシリテータ: @ggtmtmgg セクリタリ: @ggtmtmgg 参加者: @Haga, @binaryta レポジトリ: denoland/deno
denoはnodeの作者がnodeの欠点を補う形で作っているTypeScriptエンジン
Denoのビルドに関する手順
サブモジュールごとclone
$ git clone --recurse-submodules https://github.com/denoland/deno.git
(chromiumのdepotツール群も内部利用している)rustの処理系をインストール
$ brew install rust
pythonは2.x系を使うのでない場合はinstall
$ pyenv install 2.7.x
denoのセットアップ
$ ./tools/setup.py
denoのビルド
$ ./tools/build.py
ビルドしたdenoを動かす
$ ./target/debug/deno tests/002_hello.ts
プルリクエストまでの流れ
- テストの実行
$ ./tools/test.py
- コードフォーマットの実行
$ ./tools/format.py
- リンターの実行
$ ./tools/lint.py
立ち止まって分岐を考える
- TODOを上から見ていく
- Issueを上から見ていく
- ソースコードを上から見ていく
- ドキュメントを上から見ていく
- commitを上から見ていく⬅
11月は83個のcommitがマージされている。 commitを見ていく
feat: Support for bigints in console
bigintがconsole.logで使えるようになる
Cargo and GN should build into same directory.
CargoはRustのビルドシステム GNはv8のなんか GNはtarget/debugに出力してる、Cargoはtarget/debug/build/deno-26d2b5325de0f0cf/outとかに出力してる。 これを両方target/debugにする Rustのコードでletを二行続けて使ってるのが特徴的
let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = env::current_dir().unwrap().join(out_dir);
Upgrade Prettier to support BigInt syntax in TS
新しいprettierがBigIntに対応しててその新しいPrettierを実行する
Upgrade Rust crates
extern/cratesはRust版importのやつっぽい Cargo.tomlがRust版package.jsonっぽい
test rust version before rustup
rustupはUNIXライクシステムで動作するバージョン管理のやつ nodeでいうnodenv multirustの新しい版。 multirustもRustバイナリをインストールできるバージョン管理システム。
Add Process.output
stdoutを最終文字までUint8Arrayにして返す
Add deno.readAll()
deno.readAll()はReaderを渡すとPromise<Uint8Array>
を返す。
Fix flaky REPL test
REPLの反応速度のテスト??
clippy fixes
clippyはRustのlinter
Upgrade To TypeScript 3.2
TypeScriptを3.2に上げる
Make //build a git submodule
build_extra/toolchain/win/BUILD.gnを消してサブモジュール化している
立ち止まって分岐を考える
- TODOを上から見ていく⬅
- Issueを上から見ていく
- ソースコードを上から見ていく
- ドキュメントを上から見ていく x commitを上から見ていく
git grep
$ g grep TODO -- js/ js/blob.ts: // TODO(qti3e) Implement convertLineEndingsToNative. js/blob_test.ts:// TODO(qti3e) Test the stored data in a Blob after implementing FileReader API. js/buffer.ts: throw Error("ErrTooLarge"); // TODO DenoError(TooLarge) js/buffer_test.ts: // TODO buf.writeByte() js/buffer_test.ts: // TODO buf.readByte() js/compiler.ts: // TODO ideally this are not static and can be influenced by command line js/compiler.ts: // TODO: all this does is push the problem downstream, and TypeScript js/dom_types.ts: // TODO js/dom_types.ts: // TODO js/fetch.ts: readonly locked: boolean = false; // TODO js/fetch.ts: statusText = "FIXME"; // TODO js/fetch.ts: readonly type = "basic"; // TODO js/fetch.ts: redirected = false; // TODO js/fetch_test.ts:// TODO(ry) The following tests work but are flaky. There's a race condition js/file_info.ts: * for this file/directory. TODO Match behavior with Go on windows for mode. js/files.ts:// TODO This is just a placeholder - not final API. js/net.ts:// TODO support other types: js/net.ts:// TODO Support finding network from Addr, see https://golang.org/pkg/net/#Addr js/net.ts: * TODO: `tcp4` (IPv4-only), `tcp6` (IPv6-only), `udp`, `udp4` (IPv4-only), js/net_test.ts: // TODO Currently ReadResult does not properly transmit EOF in the same call. js/net_test.ts:/* TODO Fix broken test. js/net_test.ts:/* TODO Fix broken test. js/net_test.ts:/* TODO Fix broken test. js/net_test.ts:/* TODO Fix broken test. js/process.ts:// TODO Maybe extend VSCode's 'CommandOptions'? js/process.ts: signal?: number; // TODO: Make this a string, e.g. 'SIGTERM'. js/read_link_test.ts: // TODO Add test for Windows once symlink is implemented for Windows. js/read_link_test.ts: // TODO Add test for Windows once symlink is implemented for Windows. js/remove_test.ts: // TODO(ry) Is Other really the error we should get here? What would Go do? js/stat_test.ts:// TODO Add tests for modified, accessed, and created fields once there is a way js/symlink.ts: // TODO Use type for Windows. js/testing/testing.ts: // TODO Do this on the same line as test name is printed. js/testing/util.ts:// TODO(ry) Use unknown here for parameters types. js/testing/util.ts:// TODO(ry) Use unknown here for parameters types. js/timers.ts:// TODO(piscisaureus): fix that ^. js/timers.ts: // TODO: use a monotonic clock. js/timers_test.ts: // TODO: clearInterval(id) here alone seems not working js/v8_source_maps.ts: // TODO Support source map URLs relative to the source URL
js/net_test.ts
のコメントアウトされていたテストをコメントインしてみる
テストは通るっぽい コンパイルエラーが出ている
masterの状態で./tools/unit_tests.py target/debug/deno
を実行してみたらエラーがでた
test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out running 8 tests test getTravisDataSuccess_permW0N1E0R0 HttpOther: an error occurred trying to connect: Connection refused (os error 61) at maybeError (deno/js/errors.ts:38:12) at handleAsyncMsgFromRust (deno/js/dispatch.ts:27:17) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 211 filtered out Error: There were 1 test failures. at setTimeout (file:///Users/binaryta/git/deno/js/testing/testing.ts:110:13) at fire (deno/js/timers.ts:131:3)
-- 思考ログはここまで --
メモ
Rustの疑問
Q: どうやって実行する
A: プレイグラウンド https://play.rust-lang.org/
Q: assert_eq!(x.unwrap(), "air");
の!ってなに?
A: !がついてるのはマクロ
Q: unwrap()
ってなに?
A: Some(v) から vを取り出す
Q: Some("air")
のSomeってなに?
A: なんかOptionと一緒に使うやつ
Q: let x: Option<&str> = None;
のOption<&str>ってなに?
A: Optional型になる
Q: Panic
ってなに?
A: Rustの例外
Q: BUILD.gnってなに?
A:
やること
2018年12月 アドベントペアプロドキュメント
12/3から12/23までの三週間の平日JS Ninjaでアドベントペアプロを行います!
JS Ninja ペアプロ - Google カレンダー
JS Ninja OSSペアプロ Advent Calendar 2018 - Qiita
こんだけOSSペアプロすればいいことがいっぱいあるのでがんばろー!というモチベーションです。
確実に遂行するためにファシリテータとセクリタリという役割を定義します。 全日程のファシリテータとセクリタリはデフォルトで @ggtmtmgg です。参加できない日は他の人を任命していきます。
ファシリテータさん
その日程が確実に遂行されることに責任を持つ。
当日やること
開催の連絡とペアプロの進行
当日の前日の23:59までにやること
Googleカレンダー上で次の項目を編集する。
- タイムボックスの設定
- セクリタリの任命
- 参加者の募集
- タイトルの設定
セクリタリさん
該当日の議事録をJS Ninja OSSペアプロ Advent Calendar 2018 - Qiitaに投稿する! 簡単な思考ログになろうとも必ず投稿すること! 自分のブログに投稿してくれてもおけ! JS Ninja's blogの投稿はこちらから → ログイン - はてな
React NativeのNativeModuleの登録プロセスを追ってみた
参加者
前回の振り返り
- JavaScript側でsetNativePropsが実行され、どのようにNative側に渡される値が作られるかを追い掛けた
- 最終的に、TextInputのpropsを更新した時に、enqueueNativeCallと言うメソッドで積まれている moduleId、 methodId、 params (updatePayload) の値を特定できた
{ moduleID: 41, methodID: 6, params: { // updatePayload fontSize: 40, }, }
ここからわかること
- JavaScript側で、Native側のmoduleIdとmethodIdを知っている
- これどこに持ってる?
- TextInputと言うのは一つのNativeModule?
- TextInputと言うNativeModuleが登録された時に割り当てられたIDがmoduleIdなのでは?
- 登録を行うと、JavaScriptにも、各moduleのIDが渡されている?
今回やっていくこと
- オーバー・ザ・ブリッジ本の、NativeModulesの章を読み進めて、moduleId、methodIdがどう使われているのか突き止める
NativeModule
公式ドキュメントより
https://facebook.github.io/react-native/docs/native-modules-ios#ios-calendar-module-example
上記リンクに、iOSのCalendar APIにアクセスするNativeModuleを作成するための方法が書かれている
NativeModuleを作る方法ざっくりまとめ
- Objective-Cのclassで、
RCTBridgeModule
protocolを実装する - 下記の2つのマクロを使う
- RCT_EXPORT_MODULE()
- RCT_EXPORT_METHOD()
RCT_EXPORT_MODULE()
- moduleの名前を吐き出して、JavaScript側から見えるようにする
RCT_EXPORT_MODULE(HogeManager)
のように呼ぶと、HogeManagerと言う名前でexportされる- 名前を明示的に指定しなければ、class名がそのまま使われる
- 接頭辞として
RCT
がclass名についていれば、省略した状態でexportされる (RCTHogeManager => HogeManager)
RCT_EXPORT_METHOD()
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location) { RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); }
- methodを吐き出して、JavaScript側から呼べるようにする
- 上記の例では、
addEvent
と言うmethodがexportされ、引数としてnameとlocationの2つを受け取る設定になる - 実行されるmethodの実体は、
{}
の中に書かれ、これをJavaScript側から呼ぶことが出来るようになる - 引数として指定できる型は、基本的に、JSONと相互に変換出来る型のみ
- それ以外は、
RCTConvert
helperを使って変換する
作り方を見てわかったこと
- NativeModuleを登録する時に、moduleとmethodをそれぞれexportしていることはわかった
- TextInputも、同様な方法でNativeModuleとして登録されている?(わからない)
TextInputの継承ツリーを追う
方針
- この中のどこかでTextInputがRCT_EXPORT_MODULEされている?
- であれば、TextInputのmoduleIdとmethodIdが取れるようになり、どこが具体的に呼ばれるのか特定できそう!
- TextInputは
clear
とisFocused
という2つのメソッドをもつので、ネイティブモジュールからexportされているのでは?(以下参照)
https://facebook.github.io/react-native/docs/textinput#methods
見つかったもの
- RCTSinglelineTextInputViewManager : RCTBaseTextInputViewManager
- RCTSinglelineTextInputView : RCTBaseTextInputView
RCT_EXPORT_MODULE
していない
- RCTBaseTextInputView : RCTView
RCT_EXPORT_MODULE
していない
わかったこと
- 全然exportしてない
clear
とisFocused
は、NativeModuleと一切関係なく、普通にJSだけで実装されていた
次回やりたい事
React NativeでsetNativePropsした結果がどうNativeに反映されるのか JS編
React Native OSSもくもく会では、現在、こちらのIssueに取り組んでいます。
(メンバー: @ggtmtmgg, @takahi5, @__syumai, @binaryta)
https://github.com/facebook/react-native/issues/20971
内容としては、 setNativeProps
を実行してTextInputの文字サイズを変更しようとした時に、予想通りの挙動をしてくれないという物でした。
setNativePropsとは
通常、ReactでComponentのpropsを更新したい場合は、該当のComponentに対して値を渡しているstateをsetState
で更新します。
setState
を実行すると、VDOMのツリー全体で、前回setState
した時点と差分があるかどうかの比較が走り、もし差分があれば、その部分のDOMツリーを再構成してrenderし直します。
一方、React Nativeを使う上で、stateは更新したくないが、Viewのプロパティだけを更新したい場合があります。
例えば、アニメーションするComponentを実装したいと考えた時、Viewのプロパティを更新するのにsetState
を使うと、Reactのrenderが連続的に走ってしまい、スムーズな動作が実現できなくなってしまいます。
setNativeProps
を呼ぶと、Reactのstateを更新することなく、NativeのViewのプロパティのみを直接書き換えることが出来ます。
setNativePropsの実行例
<TextInput ref={this.inputRef} style={{ fontSize: 20, }} /> <TouchableOpacity onPress={() => this.inputRef.current.setNativeProps({ style: { fontSize: 40, }, }); } > <Text>Press me to change TextInput fontSize!</Text> </TouchableOpacity>
今回問題となっている挙動
I'm trying to implement a dynamic fontSize on React Native based on the content size, but I hit a React Native limitation / bug.
You can change the fontSize multiple times before typing anything in the TextInput, but once you type something you cannot change it anymore. See it in action in the link below.
ざっくり翻訳
React Nativeで、コンテンツの大きさに応じて、動的にfontSizeを変更するものを実装しようとしています。しかし、React Nativeの制限、もしくはバグに引っかかってしまいました。
TextInputに文字を入力していない状態では、何度もfontSizeを変更することが出来ました。しかし、文字を入力した状態では一切変更出来なくなります。
本来予想される動作
setNativeProps
する度、意図通りにTextInputのサイズが変わって欲しかった
setNativePropsの実装
setNativeProps
がNativeのViewに反映されるまでの流れは、大きく2つに分けることが出来ます。
- JS側で変更するPropsを処理して、変更依頼をenqueueする。
- Native側で変更依頼をdequeueしてビューに反映させる。
今回はJS側の変更依頼をenqueueする処理に問題がないかを調べていきます。
JS側の実装
setNativePropsのJSの実装の実体は主に以下になります。
react-native/Libraries/Renderer/oss/ReactNativeRenderer-dev.js#L16165
setNativeProps: function(nativeProps) { ... var updatePayload = create(nativeProps, viewConfig.validAttributes); if (updatePayload != null) { UIManager.updateView( maybeInstance._nativeTag, viewConfig.uiViewClassName, updatePayload ); } }
この関数を以下の2つの部分に分けて実装を見ていきます。
- updatePayloadを作成する
- UIManager.updateViewにupdatePayloadを渡して変更依頼をenqueueする
1. updatePayloadを作成する
updatePayloadはcreateという関数で作られています。
var updatePayload = create(nativeProps, viewConfig.validAttributes);
そしてそのcreateではaddPropertiesを実行しています。
function create(props, validAttributes) { return addProperties( null, // updatePayload props, validAttributes ); }
そしてそのaddPropertiesでは引数にemptyObjectを渡した状態でdiffPropertiesを実行しています。
function addProperties(updatePayload, props, validAttributes) { // TODO: Fast path return diffProperties(updatePayload, emptyObject, props, validAttributes); }
diffPropertiesはupdatePayloadを返す役割を持っており、実体はこの関数です。
だいぶ簡略化したのですがちょっと長いのでソースコード内に説明を記述していきます。
function diffProperties(updatePayload, prevProps, nextProps, validAttributes) { ... /* nextPropsに対してループ処理をします。 * 今回のシチュエーションでは以下のオブジェクトが渡ってきます。 * { * style: { fontSize: 40 } * } * */ for (var propKey in nextProps) { /* validAttributesは以下のようなオブジェクトです。 * * { * ... * autoCorrect: true * backfaceVisibility: true * backgroundColor: {diff: null, process: ƒ} * blurOnSubmit: true * ... * } * * diffは値の変更を判定する関数です。 * processは値をNative側に渡す前に処理する関数です。 * backgroundColorは実数値に変換されます。 */ // もしattributeAttributesが値を持たないpropKeyであれば無視します attributeConfig = validAttributes[propKey]; if (!attributeConfig) { continue; // not a valid native prop } ... if (prevProp === nextProp) { // もし値が変わってなくても無視します continue; // nothing changed } if (typeof attributeConfig !== "object") { /* * もしattributeConfigがただのtrueであれば * defaultDifferで差分の有無を確認し * 差分があればupdatePayloadに追加します。 */ if (defaultDiffer(prevProp, nextProp)) { (updatePayload || (updatePayload = {}))[propKey] = nextProp; } } else if ( typeof attributeConfig.diff === "function" || typeof attributeConfig.process === "function" ) { /* * diffがfunctionならdiffを利用して差分を確認する。 * processがfunctionならprocessを利用して値を処理する。 */ var shouldUpdate = prevProp === undefined || (typeof attributeConfig.diff === "function" ? attributeConfig.diff(prevProp, nextProp) : defaultDiffer(prevProp, nextProp)); if (shouldUpdate) { var _nextValue = typeof attributeConfig.process === "function" ? attributeConfig.process(nextProp) : nextProp; (updatePayload || (updatePayload = {}))[propKey] = _nextValue; } } else { ... /* * もしattributeConfigがObjectじゃなければ、再帰的にdiffPropertiesを呼び出す。 * 例えばpropKeyが "style" のような場合にここを通ります。 */ updatePayload = diffNestedProperty( updatePayload, prevProp, nextProp, attributeConfig ); ... } } ... return updatePayload; }
これで晴れてupdatePayloadを返せました。
ちなみに、setNativeProps({ style: { fontSize: 40 } });
を実際に実行してみるとupdatePayloadは以下の値になりました。
{ fontSize: 40 }
要求通りにupdatePayloadが作られていたことがわかりました。
2. UIManager.updateViewにupdatePayloadを渡す
if (updatePayload != null) { UIManager.updateView( maybeInstance._nativeTag, viewConfig.uiViewClassName, updatePayload ); }
UIManager.updateViewは内部的にenqueueNativeCallを呼んでいます。
enqueueNativeCallは(かなり簡略化すると)以下のような関数です。
enqueueNativeCall( moduleID: number, methodID: number, params: any[], onFail: ?Function, onSucc: ?Function, ) { ... this._queue[MODULE_IDS].push(moduleID); this._queue[METHOD_IDS].push(methodID); ... this._queue[PARAMS].push(params); ... }
enqueueNativeCall
はキューにmoduleId, methodId, paramsを積んでいて、
UIManager.updateView
は、enqueueNativeCallで下記の値をキューに積んでいました。
{ moduleID: 41, methodID: 6, params: updatePayload }
まとめ
setNativeProps
の実装を追い掛けた結果、渡したい値がちゃんと作られて、enqueueNativeCall
でキューに積まれていることが確認出来ました。
JavaScript側の実装には問題が無さそうと言うことがわかったので、次は、Native側の実装(今回は、iOS)を追い掛けてみようと思います。
【ReactNative】FlastListがスクロールできなくなるissueに取り組んだログ
@ggtmtmggです。
FlatList
の小要素にmargintTop: '5%'
とかを指定するとスクロールできなくなる問題に、
@saitoxuさんと取り組んだ学びのメモです。
前回までのメモはこちら https://gist.github.com/ggtmtmgg/9aaea0c6ddb1819623e3ef83e44e3b51
今回は主にObjective-Cのコードを読んでました。Objective−Cに関して初心者同然なのでそれに関する学びが多かったです。
- Isuue: marginTop=percent: FlatList does not scroll #20410
- Demo: https://github.com/ggtmtmgg/TestReactNative/tree/20410/0.57
Issueの再定義
元々は「FlatList
の小要素にmargintTop: '5%'
とかを指定するとスクロールできなくなる問題」だったのですが、問題の根源を追っていった結果、「ScrollView
がcontentHeight
を取るタイミングで小要素のheight
を正しく取れていない問題」に帰着しました。
これがどのようにしては導き出されたかは割愛します。
思考のフロー
前回までの思考で、RCTScrollContentShadowView
の中で、ScrollViewのContent
のLayout(要するにHeight)の計算をしているという認識を持っています。
このLayoutの計算を正しくすればIssueは解決するというのがこの瞬間の一番の仮説です。
今気になっていることを整理
RCTScrollContentShadowView
の実装はどうなっているのかScrollView
のConetntHeight
は本当に間違っているのかScrollView
のContentHeight
が間違っていることによってスクロールできなくなるという仮説が本当に正しいのか再調査したい
ScrollView
のConetntHeight
はいつ確定するのかContentHeight
が間違っているのであれば実際に計算して値が確定する瞬間を見たい
ScrollView
のConetntHeight
はどういった計算式で確定するのかContentHeight
が間違っているのであれば実際にどんな計算をしているのか知りたい
RCTScrollContentShadowView
の実装を覗いていく
"shadow"は"follow and observe (someone) closely and secretly"という意味で使っているっぽい。にしてもShadowViewの比喩はもっと腹落ちできそう。
ざっくりRCTScrollContentShadowView
は、ScrollView
のContent部分の裏側で計算とかしてくれるViewという認識。
RCTScrollContentShadowView
はRCTShadowView
を継承している。
@implementation RCTScrollContentShadowView - (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics layoutContext:(RCTLayoutContext)layoutContext { ... [super layoutWithMetrics:layoutMetrics layoutContext:layoutContext]; } @end
[super layoutWithMetrics:layoutMetrics layoutContext:layoutContext]
でScrollContentのLayoutoの計算をしてそう。
今でこそ簡単に読めるけどこのコードは[インスタンス名 メソッド名:第一引数 引数名:名前付き引数]
という意味でインスタンスメソッドを実行している。
super
はRCTShadowView
なのそちらを見に行く
RCTShadowView
の実装を読み解く
https://github.com/facebook/react-native/tree/master/React/Views/RCTShadowView.m
RCTShadowView+Layout.m
ってファイル見かけたけどなんだろう。UIEdgeInsets
ってクラスについて- UIKitの構造体。insetというレイアウトの概念があるらしい。
- yogaConfigはシングルトンらしい ref: ObCのシングルトンの話
+ (YGConfigRef)yogaConfig { static YGConfigRef yogaConfig; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ yogaConfig = YGConfigNew(); YGConfigSetPointScaleFactor(yogaConfig, RCTScreenScale()); YGConfigSetUseLegacyStretchBehaviour(yogaConfig, true); }); return yogaConfig; }
- Yoga.cppのL3957に必要のなさそうな分岐を発見
// We store points for Pixel as we will use it for rounding if (pixelsInPoint == 0.0f) { // Zero is used to skip rounding config->pointScaleFactor = 0.0f; } else { config->pointScaleFactor = pixelsInPoint; }
↓ 以下の一行だけで良さそう
config->pointScaleFactor = pixelsInPoint;
- RCTShadowView.mのL22
meta_prop_t
という列挙型を作っている。enumの使い方として参考になった ref: Modern Objective-Cのenumの書き方
typedef NS_ENUM(unsigned int, meta_prop_t) { META_PROP_LEFT, META_PROP_TOP, META_PROP_RIGHT, META_PROP_BOTTOM, META_PROP_START, META_PROP_END, META_PROP_HORIZONTAL, META_PROP_VERTICAL, META_PROP_ALL, META_PROP_COUNT, };
RCTShadowView.m L200
(__bridge void *)self
はどういう記法だろうRCTComponent.hにプルリクのチャンスを発見。RCT系インスタンスがReactRootViewであるかどうかの判定がイマイチみたい。
// TODO: this is kinda dumb - let's come up with a // better way of identifying root React views please! static inline BOOL RCTIsReactRootView(NSNumber *reactTag) { return reactTag.integerValue % 10 == 1; }
- objcにはid型がある
Objective-Cでは「id」型という型が定義されています。このid型は汎用的な型なのでどんな型でもセットすることができます。(JavaでいうObject型のようなものです。) (id型は内部的には対象オブジェクトへのポインタを持っています。) このid型も参照型の1つですが、id型に限り宣言時に「*」(アスタリスク)は不要です。
今回よくわからなくて調べたこと
- @synthesizeってアノテーションはなんだろう
- @propertyを使うと、コンパイル時に勝手にゲッターとセッターを定義してくれる
- @synthesizeは、ヘッダーファイルで@propertyとして定義したプロパティをどのメンバ変数に適用するかを示すのに使う
- 要するに@propertyではゲッターセッター名、@synthesizeでは実際のメンバ変数を指定する
- ちなみに、synthesizeは合成するという意味
- deallocってメソッドはなんだろう
- NSObjectのインスタンスメソッド
- TipsとしてObjective-Cの全てのオブジェクトはNSObjectを継承している
- インスタンスが破棄される時に呼ばれると予想される。自信はない。
- deallocはオブジェクトの参照の開放のために使う
- C系のメモリの管理をもう少し理解してからまたしらべます。
- ARCってなんだろう
- Automatic Reference Countingの略
- NSObjectのretain/release/autoreleaseをいい感じにしてくれる
無名カテゴリってなんだろう
関数ではなくマクロをつかっている理由はなんだろう
- 結果よくわからなかったです
#define RCT_SET_YGVALUE(ygvalue, setter, ...) \ switch (ygvalue.unit) { \ case YGUnitAuto: \ case YGUnitUndefined: \ setter(__VA_ARGS__, YGUndefined); \ break; \ case YGUnitPoint: \ setter(__VA_ARGS__, ygvalue.value); \ break; \ case YGUnitPercent: \ setter##Percent(__VA_ARGS__, ygvalue.value); \ break; \ }
+ (YGConfigRef)yogaConfig;
の+
の意味はなんだろう+
で定義されるのはクラスメソッド-
で定義されるのはインスタンスメソッド