.notes
JS/TS の Prettier のような単一のコーディングスタイルを強制する Formatter は書きやすさ、読みやすさともに優れている。何が楽って、コードのリファクタリングや編集をしているときに、雑に書いても、
constructor(data: MemberInitializer) {
this.organizationRole = data.organizationRole;
this.user = data.user;
}
static fromResponse(data: TrainingJobResponse): Member {
this.organizationRole = data.organization_role;
this.user = new User(data.user);
}
VSCode で保存すれば、瞬時にキレイにしてくれる。
constructor(data: MemberInitializer) {
this.organizationRole = data.organizationRole;
this.user = data.user;
}
static fromResponse(data: TrainingJobResponse): Member {
this.organizationRole = data.organization_role;
this.user = new User(data.user);
}
「普通、こんなふうに書かんやろ」と思うかもしれないが、別のテキストエディタ(検索/置換がやりやすい)でコード片の整形や変形をやってからコピペすることが多いので割とある。
しかし、Python のように、Statement のブロックを表現するときにインデントを用いる言語だと、この方法がうまくいかないことがある。コンパイルエラーならいいが間違った解釈をされると痛い。
Python の場合「インデントを強制させる = 読みやすい」という意味もあったと思うが、その需要は自動 Formatter で満たされているだろう。あと、現実的に長いコードになると、インデントによるブロックは、{...}
や do...end
による明示的なブロックにくらべて特別読みやすいとも感じない。
Wren
System.print("Hello, world!")
class Wren {
flyTo(city) {
System.print("Flying to %(city)")
}
}
var adjectives = Fiber.new {
["small", "clean", "fast"].each {|word| Fiber.yield(word) }
}
while (!adjectives.isDone) System.print(adjectives.call())
- 所謂 C 族シンタックス
- 動的スクリプティング言語
- C で書かれている。VM は約 4,000 行でコードも読みやすい
- 依存ライブラリがない
- Erlang のプロセスっぽい Fiber がある。メッセージ・パッシング
-
何故、Wren はこんなにも速いのか?
- いくつかのベンチマークで Ruby や Python よりも高速 [1]
- VM は Bytecode Interpreter であり、JIT は使っていない
- すべての値は 8 bytes の倍精度浮動小数点として表現される
- これは number type でもあるので数値計算に余計なオーバーヘッドがない
- 数値以外は
NaN
の使われていないビットで表現. NaN boxing - 似た手法としてポインタの特定ビットにフラグを立てて、その場合は比較的小さい整数とみなす、などがある. Pointer tagging
- Wren のオブジェクトはすべてクラスのインスタンスであり、クラスは実行時に変更できない。なので、コンパイル時にインスタンスのレイアウトが分かる。また、プロパティにアクセスするときもハッシュテーブルから検索する必要はなく、直接、プロパティのアドレスにアクセスするだけで済む。
- 継承ツリーは固定で変更できない。そのため、すべての継承されたメソッドはそのクラスにコピーされている。メソッドを探すときに継承ツリーを辿る必要がない
- Wren のメソッドは引数の数でオーバーロードされるので、文法的に間違った数の引数で呼び出せない。そのため、実行時にチェックは不要
- VM の命令ディスパッチは Computed Goto で実装
- 1 pass compiler なのでコンパイルも効率的
- Wren の速さは言語デザインの選択によるところが大きい
- パフォーマンスに影響するところの自由度(動的)は低い
- C99, IEEE double precision floats が必要
V programming language
fn main() {
areas := ['game', 'web', 'tools', 'science', 'systems',
'embedded', 'drivers', 'GUI', 'mobile']
for area in areas {
println('Hello, $area developers!')
}
}
- 同名のプログラミング言語がもうひとつある
-
なぜ LLVM ではなく、C をバックエンドに選んだのか
- 開発時の高速なビルドのために、x64 のアセンブリを直接生成することもできる
-
Swift, Rust のように GC はない
- リファレンス・カウントは GC と看做されないのか...
- マルチスレッド環境でアトミックに RC を更新するときのパフォーマンスの問題がある気がするけど、どうやって対処してるんだろう?
- swift/ARCOptimization.rst at main · apple/swift
- Volt という製品のために書かれた。そのときの拡張子が
.v
だったので V 言語
型システム入門 プログラミング言語と型の理論の第1章(公式サイトから無料で読める)の脚注には次の記述がある。
配列の境界検査を静的になくしてしまうことは、型システム設計者の長年の目標である。原理的に必要な仕組み(依存型(30.5 節を見 よ)に基づくもの)はよく理解されているが、表現力、型検査の予測可能性と計算量的な扱いやすさ、およびプログラム注釈の複雑さ をうまく均衡させた形でその機能を言語に組み込むことは重要な課題として残されている。この分野に関する最近の進んだ研究成果は Xi and Pfenning [502; 503] に記載されている。
依存型は名前しか知らなかった。「そういえば、Idris って言語がサポートしてたな...」程度の認識。なんとなく気になって適当に検索した結果を読んでみた。
F* という言語も面白そう。C や WASM も出力できる。ソフトウェア検証に興味があったのでちょうどいいタイミング。
配列の境界検査という話だと、D 言語には配列の長さを型で指定できた覚えがある。[1]
import std.stdio;
void main()
{
int[3] foo;
writeln("Hello World!");
int x = foo[3];
writeln(foo);
}
これを Playground で実行してみると以下のエラー
onlineapp.d(8): Error: array index 3 is out of bounds foo[0 .. 3]
onlineapp.d(8): Error: array index 3 is out of bounds foo[0 .. 3]
この場合、配列は値型になり同じ長さのものしか代入できない。
import std.stdio;
void main()
{
int[3] foo = [1, 2, 3];
// int[4] bar = foo; /* Error: mismatched array lengths, 4 and 3 */
int[3] bar = foo;
foo = [4, 5, 6];
writeln("foo = ", foo);
writeln("bar = ", bar);
}
あと、馴染みが深いものとしては Tuple とか? まあ、本で書かれているのはそういうことではないんだろうけど、D 言語を思い出して懐かしかった。
-
[Arrays - D Programming Language]( ↩︎
Elixir のコンパイルと実行について理解してないと混乱する
これはコンパイルできなくて、
defmodule User do
defstruct name: nil, age: nil
def drive?(%User{} = user), do: user.age >= 18
end
IO.inspect(User.drive?(%User{age: 10}))
これはコンパイルも実行もできる問題
defmodule User do
defstruct name: nil, age: nil
def new(fields), do: struct(__MODULE__, fields)
def drive?(%User{} = user), do: user.age >= 18
end
IO.inspect(User.drive?(User.new(%{age: 10})))
理由が分かれば納得はできるものの、初心者には難しい…。
Visual Studio Code Extensions to Enhance Productivity in 2021 | by Vonage Dev | Jan, 2021 | codeburst より。最後に紹介されていた、この拡張が面白い。
起動してコードをコピペすると、スクリーンショットをプレビュー、表示幅を変えて見た目を調整できる。 https://marketplace.visualstudio.com/items?itemName=pnp.polacode
ReScript (ReasonML) の React 実装である ReasonReact では Hooks も使える。ポリモルフィックな関数がないから useEffect1, useEffect2... みたいな感じになるのか https://github.com/alexfedoseev/reason-react/blob/90033ea292239b07f6f71ddea9b354c11fd24392/src/React.re#L152
- ポリモルフィックな関数がないから、とかの表現にしたい
-
Bind to JS Function | ReScript Language Manual ここを見る限り、bs.unwrap で出来そうなきがする
- あー、引数の数が違うから?? と思ったけど違うな
大域脱出を伴う「例外」という仕組みはなくすか、型で「例外を投げる可能性があるかどうか」だけはチェックしてほしい気がする。例外の種類まで宣言させるのは不要。
英訳した記事を dev.to に公開するときの手順
自身のブログ記事を dev.to にも公開するときの手順メモ。なお、クロスポストは許可されている。[1]
Can I cross-post something I've already written on my own blog or Medium?
Absolutely, as long as you have the rights you need to do so! And if it's of high quality, we'll feature it.
カバー写真を探す
流行りに乗ってそれっぽいカバー写真を探す。いつも使っているのは Unsplash
- クレジット表記を忘れないように追記
- 画像サイズも幅 4096 以下にする
Photo by Quinton Coetzee on Unsplash
FrontMatter を修正
Editor Guide - DEV Community 👩💻👨💻
-
date をその時点の日時に直す。
- このコマンドで出力
date '+%Y-%m-%dT%H:%M:%S%z'
- このコマンドで出力
- canonical_url 元記事へのリンク。Google bot が見てくれる
-
cover_image
これはちょっと分かりづらいが、記事編集画面でアップロードしてからその URL をコピペする最初の投稿時にアップロードすれば不要っぽい? - series シリーズにしたい場合はシリーズタイトルをつけておくといい
-
published 最後に
true
に設定して公開
補完の効きやすい文法
テキストでプログラムを記述する場合、その文法はエディタで補完が効きやすいと助かる。
たとえば、JS や TypeScript の import
はインポート元を最後に書くので補完が効かない。
import { myFunction } from './MyModule';
一方、Python の import
はインポート元を先に書くので補完が効く。
from mymodule import myfunc
昔は、プロトタイプや書き捨てのプログラムを書くときは静的型付け言語は面倒くさいなーと思ってたけど、IDE やエディタが高性能になった結果、補完も効くし静的型付け言語の方が楽になった。
cargo-generate を入れようとして、
error[E0283]: type annotations needed
--> /Users/takanori.ishikawa/.cargo/registry/src/github.com-1ecc6299db9ec823/cargo-0.46.1/src/cargo/util/config/de.rs:471:63
|
471 | seed.deserialize(Tuple2Deserializer(1i32, env.as_ref()))
| ----^^^^^^--
| | |
| | cannot infer type for type parameter `T` declared on the trait `AsRef`
| this method call resolves to `&T`
|
= note: cannot satisfy `std::string::String: AsRef<_>`
で怒られる件、Issue で報告されている。serde のバージョンアップが起因みたい。
$ cargo install cargo-generate --locked cargo
がワークアラウンドとして言及されていた。
npm を使うか yarn を使うか問題。monorepo で workspace 使いたい場合は yarn ということになりそう。npm v7 で workspace が実装されているとはいえ、まだ開発途上という感じ。
WebAssemby のテキスト表現には、いくつかのシンタックス・シュガーがある。
(module
(func (export "addTwo") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))
(module
(func (export "addTwo") (param i32 i32) (result i32)
(local.get 0)
(local.get 1)
(i32.add)))
(module
(func (export "addTwo") (param i32 i32) (result i32)
(i32.add (local.get 0) (local.get 1))))
これらはすべて同じ
package.json
の files に指定したファイルが npm にアップロードしたパッケージに含まれずに悩んでいたが解決した。files
の記述の仕方が悪かった。
変更前の files
"files": [
"./dist",
"./jest-setup.js"
],
これだと、dist
ディレクトリは含まれるが、jest-setup.js
が含まれない。
変更後の files
"files": [
"dist",
"jest-setup.js"
],
先頭の ./
を外すと含まれる。
- 公開するときに使っているコマンドは
yarn workspace <package> publish
-
npm publish
でも同じ結果
-
- npm にアップロードされた tarball の URL は
npm view node-expo-random dist.tarball
で取得可能
Rust で Enum をテストしたいとき、Enum に PartialEq を実装して、
assert_eq!(node, Node::Integer(1))
する方法があるけど、Node::Integer の構造が変わったら修正が大変。std::matches
を使って、
assert(matches!(node, Node::Integer(1)))
こう書く方法が一般的みたいだけど、これだと、エラーメッセージに node
の内容が出なくなってしまう。
assert_matches を使えばエラーメッセージに node
の内容も出ていい感じだけど、あまり認知度高くないのかな?
こんな感じで書けて、失敗したときはテスト対象の debug メッセージが出力されるので便利。
#[test]
fn operator_associative() {
let node = parse("1 + 2 + 3").unwrap();
assert_matches!(*node, Node::Add(lhs, rhs) => {
assert_matches!(*lhs, Node::Add(x, y) => {
assert_matches!(*x, Node::Integer(1));
assert_matches!(*y, Node::Integer(2));
});
assert_matches!(*rhs, Node::Integer(3));
});
}
Rust の match expression で自動的に de-reference される RFC。適当に書いたから通るからなんで?と思ってた。
Rust の match が多段になってネストが深くなる問題、if let
構文だったり、Option
, Result
の便利メソッドだったりである程度の救済はあるんだけど、自分で雑に定義した Enum だと結局は match
にするしかなくなる。ネストが深くなることを除けば、match
は読みやすいし一番いいとは思うけど。
Gatsby の Markdown plugin で mermaid.js を適用した。Route が変わったときも mermaid.init()
を呼ぶ必要があるんだけど、DOM が描画されるタイミングが取れないっぽいので、Route が変わったときに setTimeout()
で 10 回まで呼んでる。。
型システムをプログラミング言語におけるダイアグラム(図)の表現として考えるとスッキリした。
古典的な型システムは UML のように、オブジェクトのプロパティと関係性を記述できることを目的にしていた。今はそこにフローチャートやシーケンス図を導入しようとしている。そうなるとオブジェクトはアクターになるし、型にはコミュニケーションと副作用を記述できる表現力が求められる。
つまり、ダイアグラムは宣言的ってことだと思う。逆にダイアグラムに付与されているメモや Excel などの自由記述形式の文字列は手続き的。これらは実装の一部であり、実装言語の採用するパラダイムが何であれ手続き的に書いて実行するしかない。ただ、そうやって手続き的に実装していくときも、ダイアグラムという仕様から導出された型が制約をつけてくれる。
やっぱり、こういうネストしない構文ほしくなるよね。
個人的には Elixir の with
が好みで、エラーハンドリングがもう少しやりやすければ最高。
Gatsby のエラー画面のスタックトレースからエディタが起動するのいいな。
Rust の Lifetime を毎回書くのは面倒くさいし、単純なルールで省略できるようになっている。[1] これは分かっている人には便利な反面、初学者には「Lifetime がなぜ必要なのか」を分かりづらくしていると思う。また、省略ルールの前提から外れた使い方をされる構造体や関数を実装したときにハマる。そのときはたまたま上手くいっていても、構造体/関数の使い方を変えたときに前提が崩れて謎のエラーに悩まされることも多い。
Rust で文字列を受け取る関数を書くときに型をどうするか?
Idiomatic string parmeter types: &str vs AsRef<str> vs Into<String> - The Rust Programming Language Forum
-
&str
が一般的かつ分かりやすいが、それよりも効率的な方法もあり一概にどれがベストとは言えない -
&str
をString
として保持したい場合は callee がコピーする必要がある -
Into<String>>
とAsRef<str>
であれば、callee が own したいのか ref したいのか caller に分かる- ただし、型変数を導入する必要があり、それはそれで分かりづらい
こんなことを考えながら型を書きたくない、というのが正直な感想...
初心者なので、安易につけた Lifetime を外す作業が発生しがち
[Rust] よくやる Option 型の扱い
なんだかんだ match
が一番分かりやすいと思うが、以下のケースではそれ以外を使う。
if let ...
値があるときだけ何かをしたい場合は if let ...
による取り出しを使う。
if let Some() = fun_node.body {
...
}
else
が必要な場合は match
を使う。
unwrap(), unwrap_or_else()
今のところエラーハンドリングは手抜きなので使ってる。
map(), and_then()
中身を別の値に変換したいときは Option の map() や and_then() メソッドで変換する。
let maybe_some_len = maybe_some_string.map(|s| s.len());
replace(), take()
変更可能な Option の中身を置き換えたり、取り出したり。前者は構文解析しているときに徐々にメタデータを追加していくときによく使う。後者は Builder パターンで最終結果に値を移したいときに。
?
演算子
使ってない。return
する演算子というのは馴染みがないせいかもしれない。
最近使うようになった。?;
この並びに違和感がないわけじゃないけど。ただ、やはり、ここで return
されることを見逃しそうな不安はある。
[Rust] 文字列をキーにした HashMap で要素を更新したいときに、キーをコピーせずに更新する方法はないのかな?
Add HashMap.entry_or_clone() method · Issue #1203 · rust-lang/rfcs
Rc を使えばコピーは起こらないけど、あれは型が変わるので限定的にしか使えない。
rustc か rust analyzer どちらの挙動かちゃんと見てないけど、VS Code でエラーを直していると、
- 型エラーを全部直す
- borrow checker が動いて、今度はそちらのエラーを直す
という順番になり、プロジェクト内のファイルを 2 周しないといけない。
Lifetime を使おうとして最初はうまくいってもすぐにダメなパターンが見つかるのは、再帰的なデータ構造を相手にしているからかもしれない。フラットな構造を処理するときは問題なくても、ネストすると temporary value を返すことになってダメになってしまう気がする。
そもそも、Rc
を使っているのだから、Lifetime で頑張る必要性もあまりない。
HTTPoison (hackney) がタイムアウト時にどういうエラーを返すのか知りたかったので、久しぶりに httpbin を使った。
iex(3)> HTTPoison.get("https://httpbin.org/delay/10", [], recv_timeout: 12_000)
{:ok,
%HTTPoison.Response{
body: "{\n \"args\": {}, \n \"data\": \"\", \n \"files\": {}, \n \"form\": {}, \n \"headers\": {\n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"hackney/1.17.0\", \n \"X-Amzn-Trace-Id\": \"Root=1-603702a7-234329b5589db7a01fed959c\"\n }, \n \"origin\": \"14.3.63.179\", \n \"url\": \"https://httpbin.org/delay/10\"\n}\n",
headers: [
{"Date", "Thu, 25 Feb 2021 01:51:45 GMT"},
{"Content-Type", "application/json"},
{"Content-Length", "285"},
{"Connection", "keep-alive"},
{"Server", "gunicorn/19.9.0"},
{"Access-Control-Allow-Origin", "*"},
{"Access-Control-Allow-Credentials", "true"}
],
request: %HTTPoison.Request{
body: "",
headers: [],
method: :get,
options: [recv_timeout: 12000],
params: %{},
url: "https://httpbin.org/delay/10"
},
request_url: "https://httpbin.org/delay/10",
status_code: 200
}}
iex(4)> HTTPoison.get("https://httpbin.org/delay/10", [], recv_timeout: 5_000)
{:error, %HTTPoison.Error{id: nil, reason: :timeout}}
Rust のライフタイムを明示的に書く必要が生じたら危険信号、と思うようになった。一度立ち止まって、関数から返すとき、他の構造体に渡すときでも動くか考える必要がある。できれば、それらを網羅したユニットテストを書きたいところだが、たいていの場合はさっさと Box
や Rc
にすることが多い。
Rc
は Rc::clone(...)
したい派なので、clippy に -D clippy::clone_on_ref_ptr
を追加した。
ビルディングブロックをひとつひとつ積み上げて、徐々に便利になっていくのが実感できると楽しい。
VSCode の Semantic Hightlight に対応していないテーマを対応させる
VSCode の Semantic Hightlight に対応していないテーマを対応させるには、以下の記述を settings.json に追加する。
"editor.semanticTokenColorCustomizations": {
"[Monokai Pro]": {
"enabled": true
},
"[Monokai Pro (Filter Octagon)]": {
"enabled": true
},
"[Monokai Pro (Filter Spectrum)]": {
"enabled": true
},
"[Monokai Pro (Filter Machine)]": {
"enabled": true
},
"[Monokai Pro (Filter Ristretto)]": {
"enabled": true
}
},
Rust で関数から Iterator を返す方法
よくわかっていなかったところ。
- rust - What is the correct way to return an Iterator (or any other trait)? - Stack Overflow
- rust - Conditionally iterate over one of several possible iterators - Stack Overflow
- rust - Why is adding a lifetime to a trait with the plus operator (Iterator<Item = &Foo> + 'a) needed? - Stack Overflow
単純なときは impl Iterator
, 条件が入るときは Box<dyn Iterator>
かな? また、Box<dyn>
を使うときは、RFC599 により、Lifetime が static
になるので注意。
+
で適切な Lifetime を与えてやる必要がある。
fn iter(&self) -> Box<dyn Iterator<Item = &'_ MyItem> + '_> {
...
}
React Native の box モデル
デフォルトで border-box
らしい。なので、width
で指定した値がそのまま幅になる。
React Native で SVG を扱うためには react-native-svg/react-native-svg: SVG library for React Native, React Native Web, and plain React web projects. というライブラリがあり、割と積極的にサポートされているのだが、
- React Native のバージョンと合わせていくのが大変そう
- メンテナ交代はありそうだけど、Issue のコメントを見る限り、あまり心配はいらない?
無理せず PNG 使うのがいいかな...。
useEffect()
の第 2 引数を省略できることを忘れていて、useEffect(..., [Math.random()])
とか試していた😁
Expo SDK を 41 にアップグレードした
手順通り、expo upgrade
で問題なく完了。すごい。
- TypeScript が古いものになったので手動で最新にした
- expo-auth-session がだいぶ変わっているようで、iOS シミュレータで動かなくなったので一旦 3.2.1 に downgrade して対応。
React Hook Form はデフォルトだと Submit 時にのみ validation を実行する。これを制御するには、useForm()
に mode
パラメータを与える。
[Rust] RefCell 内の参照を返す関数を書きたい時にどうするか?
- rust - How do I return a reference to something inside a RefCell without breaking encapsulation? - Stack Overflow
- self の中身をキャプチャしているときは
impl Trait
にライフタイムの指定が必要- 匿名ライフタイムも使える '_, the anonymous lifetime - The Edition Guide
impl Trait + '_
- Optional な値を返すときは unstable だが、filter_map が使える。
- 正直、Rc で clone して返すのが一番楽
[Rust] _ in Rust
Scala では _
が多様な意味で使われている、という記事を読んだ記憶があるが、Rust の場合、
let _ = ...
- パターンマッチでの
_
- 匿名ライフタイム
'_
- 型引数の省略
Vec<_>
Rc
を含む構造体、RefCell よりも Cell で保持しておく方が楽だな、と思ったけど、Copy trait を実装できない (Rc を含むので) -> Debug が実装できないという地味に辛かった。RefCell で行く。
結局「mutable な参照はひとつだけ」と「Rc は循環参照つらい」という現実があるので、
- Tree や Graph を構築するだけなら、循環参照を気にしなくていい Arena が便利
- ただし、多様な型を扱うのが難しい
- ノードのデータを変更したい場合は
Cell/RefCell
使う- Arena は mutable な参照を取得できるが、それを Tree/Graph struct に保持しておくのは難しい
- ノードからの参照を変更したい場合は
Cell<Option<'a T>>
ということなのかな?
"strawman syntax" というのをたまに見かけて「藁人形の形ににた演算子とか?」と思ってたけど、strawman が「たたき台」ってことなのか
React Navigation (v5) でひとつ前の画面の Route name が知りたい
// Get the name of previous name (UNSTABLE?)
let previousRoute: string | null = null;
const state = navigation.dangerouslyGetState();
if (state != null) {
const index = state.index - 1;
if (index >= 0 && index < state.routes.length) {
previousRoute = state.routes[index].name;
}
}
起動中の iOS シミュレータの UUID を知る
$ xcrun simctl list | egrep '(Booted)'
iPhone 12 Pro Max (1FEAF45D-B3F1-XXXX-81B4-XXXX) (Booted)
Phone: iPhone 12 Pro Max (1FEAF45D-XXXX-4A8B-81B4-XXXX) (Booted)
シミュレータのデータ
$ ls -la ~/Library/Developer/CoreSimulator/Devices
total 16
drwxr-xr-x 43 takanori_is staff 1376 4 2 09:48 .
drwxr-xr-x 5 takanori_is staff 160 1 13 19:53 ..
drwxr-xr-x@ 4 takanori_is staff 128 1 28 08:28 014F278D-F9C7-4B10-A97B-6926D478FC05
drwxr-xr-x@ 4 takanori_is staff 128 1 28 08:33 01CF44F0-BDE2-4A50-8464-E789597C1BC3
...
キーチェインを探す
$ ls -la ~/Library/Developer/CoreSimulator/Devices/1FEAF45D-XXXX-4A8B-81B4-XXXX/data/Library/Keychains
total 14568
drwx------ 15 takanori_is staff 480 5 26 13:03 .
drwxrwxr-x 78 takanori_is staff 2496 4 30 16:52 ..
drwxrwxr-x 14 takanori_is staff 448 3 29 10:37 Analytics
-rw------- 1 takanori_is staff 182 5 26 13:03 PriorMitmRoots.plist
drwx--x--x 3 takanori_is staff 96 5 26 13:03 SupplementalsAssets
-rw------- 1 takanori_is staff 16384 3 26 13:42 TrustStore.sqlite3
-rw------- 1 takanori_is staff 0 3 26 13:43 com.apple.security.exception_reset_counter.plist
drwx--x--x 5 takanori_is staff 160 5 26 15:52 crls
-rw------- 1 takanori_is staff 831488 5 26 13:03 keychain-2-debug.db
-rw------- 1 takanori_is staff 32768 5 26 13:03 keychain-2-debug.db-shm
-rw------- 1 takanori_is staff 3052952 5 26 16:03 keychain-2-debug.db-wal
-rw------- 1 takanori_is staff 53248 5 18 09:56 ocspcache.sqlite3
-rw------- 1 takanori_is staff 32768 5 26 13:03 ocspcache.sqlite3-shm
-rw------- 1 takanori_is staff 3077672 5 26 12:34 ocspcache.sqlite3-wal
-rw------- 1 takanori_is staff 163840 3 26 13:42 pinningrules.sqlite3
初めて Rust のマクロを書いた。Option から値を取り出して、None なら return する。
/// Unwraps an optional value or early return.
#[macro_export]
macro_rules! pick {
($expr:expr $(,)?) => {
match $expr {
Some(val) => val,
None => {
return;
}
}
};
}
[Rust] 文字列のリストを作る
Rust で「文字列」の「リスト」を作るとき、できるだけ読みやすいコードで書きたい。もちろん、一番 idiomatic なコードは
vec!["a".to_string(), "b".to_string(), "c".to_string()]
なのだが、それぞれの要素が離れすぎていて、個人的には読みづらい。Iterator と .map
を使えば、要素を凝集できる。
vec!["a", "b", "c"].iter().map(|s| s.to_string()).collect()
もう少し改良すると、最初に Vec を作る必要はない
["a", "b", "c"].iter().map(|s| s.to_string()).collect()
更に、要素の型 String
は自明なので、.to_string()
よりもう少し短い .into()
が使える
["a", "b", "c"].iter().map(|&s| s.into()).collect()
ただ、これは参照を外すための &
が必要になるので、少し noisy かもしれない。
本当は、クロージャを使わずに書きたいのだが無理な気がしている...。
Rust で RefCell から Iterator を返すパターン。難しい。
Ref
が見えるのは別にいい。
なるほど。ちゃんと理解できてる自信ないけど、間接参照を挟まないといけない。
KeyboardAvoidingView と Android
サンプルには
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
となっているが、Android ではそもそも behavior
は指定しない方がいい。そうしないとキーボードの高さが変化したときに追従しなくなる。また、これによってレイアウトにも問題が出てくる。結局、両 OS を判定しながらレイアウトのロジックを書くことになる。
Keyboard の Hooks
PostgreSQL で外部制約キーの名前を CREATE TABLE
時に指定するときは `CONSTRAINTP 句で指定できる。
CREATE TABLE IF NOT EXISTS my_table (
...
CONSTRAINT "my_fkey" FOREIGN KEY (id, name) REFERENCES other_table(id, name)
);
PostgreSQL(というか標準 SQL の)timestamp with time zone
はタイムゾーン情報を格納しているわけではない。
出力時にローカルタイムゾーンに変換されて文字列化されるので、テストとかで単純に文字列比較をしていると、環境が変わったときに失敗する。
あまり知りたくはなかった Makefile での動的な変数定義
React Native の zIndex, 要素の構造を工夫しないと思うように効かない理解だったけど、少なくともマイナスの値は iOS/Android ともに効くようになっている気がする?
同じ「階層」に存在している View 同士で zIndex が比較されてる感じ。それ以外は要素の出現順と親子関係に準じる。
[Go] rand を Read するとランダムなバイト列を生成できるの、なるほど、という感じだ。Go を触ってると、こういう「自分の感覚とは違う設計」が多くて勉強になる。
package main
import (
"math/rand"
"fmt"
)
func main() {
token := make([]byte, 4)
rand.Read(token)
fmt.Println(token)
}
「自分の感覚とは違う設計」
Python の "".join(...)
もそう
Expo で NetInfo を試した。コネクション変更は簡単に取得できる。
export default function App() {
const [netInfoState, setNetInfoState] = useState<NetInfoState | null>(null);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
setNetInfoState(state);
});
return unsubscribe;
}, []);
...
ドキュメントにも書かれている通り、SSID/BSSID はそのままでは取得できない。
[JS] ArrayBuffer または Typed Array かどうかをチェックしたい
- 型については Typed Array を扱える ArrayBufferView がある
- 実行時のチェックでは Typed Array か DataView かを ArrayBuffer.isView() でチェックできる
- ArrayBuffer かどうかは
instanceof
でチェックする
これを読んで、React Hooks が「コンポーネントのトップレベルでしか呼んではいけない(render のたびに毎回同じ順序で呼ばれなくてはいけない)」理由が分かった。
あと、Dan Abramov の
But Aren't Hooks Magic?
以降の文章は必読。
Spread Love, Not Hype
このフレーズ、めちゃいい。
React Native の StyleSheet.flatten() や hairlineWidth
知らんかった...。
作図のためのアプリがほしいなーと思ったけど、Keynote が優秀だった。オブジェクト同士を接続して、移動してもいい感じに表示してくれる。
TypeScript で URLSearchParams の .entries()
とかを認識させるには tsconfig の lib に dom.iterable
を追加する
Go: 長い文字列リテラルを複数行に分割して書きたいとき、Raw string literal が使えるが、これは改行も含まれてしまう。
`First
Second`
こういう場合は単純に +
で連結すればいい。
"First"+
"Second"
これは Constant expression なので、コンパイラによって一つの文字列に最適化される。
ブラウザで CORS を無効にできるの知らなかったなー。
useSWR() のキャッシュ戦略
「stale-while-revalidate
の仕様に従ってます」という説明があるけれど、別に HTTP レスポンスヘッダーを見てくれるわけではない(というか API の仕様上読めない)
マニュアルで更新するか、更新イベントが発生するまではキャッシュが使われる。
Expo ファイルを消したり名前を変更したら、そのあとずっとエラーが出る、みたいなときは以下のコマンドでキャッシュを消す
$ expo r -c
React Hooks の dependency array の各要素の比較は Object.is
なんらかの配列に依存していて、しかもその配列のサイズが変わる場合はどうしたらいいのだろう?
const objectIds = items.map((x) => x.id);
return useMemo(
() => ({
items
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[...objectIds]
);
こんな風にすると、以下の警告が出てしまう。
Warning: The final argument passed to useMemo changed size between renders. The order and size of this array must remain constant.
変更を検知できるプロパティを連結して文字列化するのが手軽かな...?
TypeScript
ある値が unknown
型の場合、null
チェックをしても Object is possibly 'null'.
と怒られてしまう。
function foo(v: unknown): boolean {
return v != null && typeof v === 'object' && v.a === 1;
// ^^^
// Object is possibly 'null'
}
これは、
-
v != null
のチェックの後もunknown
型のまま -
typeof v === 'object'
のチェックの後にObject | null
になる Object is possibly 'null'
ということらしい(typescript - TS2531: Object is possibly 'null' even if I check that it is not - Stack Overflow)。
Expo 以下のエラーが出て expo run build:ios
できなくなった。
Building optimized bundles and generating sourcemaps...
Starting Metro Bundler
src/App.tsx: Unexpected token name «_000», expected punc «,» in file src/App.tsx at 191:18
Error: Unexpected token name «_000», expected punc «,» in file src/App.tsx at 191:18
at /home/myapp/node_modules/metro/src/JSTransformer/worker.js:437:17
at Generator.next (<anonymous>)
at asyncGeneratorStep (/home/myapp/node_modules/metro/src/JSTransformer/worker.js:75:24)
at _next (/home/myapp/node_modules/metro/src/JSTransformer/worker.js:95:9)
at /home/myapp/node_modules/metro/src/JSTransformer/worker.js:100:7
at new Promise (<anonymous>)
at /home/myapp/node_modules/metro/src/JSTransformer/worker.js:92:12
at JsTransformer._minifyCode (/home/myapp/node_modules/metro/src/JSTransformer/worker.js:444:7)
at /home/myapp/node_modules/metro/src/JSTransformer/worker.js:374:33
minify するライブラリを変えることで解決できそうだったので、metro.config.js で指定してみる。
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.minifierPath = 'metro-minify-terser';
config.transformer.minifierConfig = {
// Terser options...
};
module.exports = config;
これで解決した。
TypeScript enum のように反復可能な union 型
なるほどー。
export const DiagnosticSeverity = {
/**
* Reports an error.
*/
Error: 1,
/**
* Reports a warning.
*/
Warning: 2,
/**
* Reports an information.
*/
Information: 3,
/**
* Reports a hint.
*/
Hint: 4,
} as const;
export type DiagnosticSeverity = typeof DiagnosticSeverity[keyof typeof DiagnosticSeverity];
ReactNative NetInfo の isInternetReachable
が初回呼び出しだと常に undefined
になる。
たぶん、実際にインターネットアクセスしてるからだと思う。どちらにせよ API が basic すぎるので、rgommezz/react-native-offline とか使った方が良さそう。
SWR を React Testing Library で使うときにキャッシュを回避したい
SWR >= 1.0.0
Custom Cache Provider で、テストごとに新規のインメモリキャッシュを設定する。
import React from 'react';
import { SWRConfig } from 'swr';
export const SWRWithoutCache: React.VFC<{ children: React.ReactNode }> = ({ children }) => (
<SWRConfig
value={{
provider: () => new Map(),
dedupingInterval: 0,
}}>
{children}
</SWRConfig>
);
SWR < 1.0.0
とりあえず、SWR 0.5 で動くやつ。
Jest の setup に以下を追加
import { cache } from 'swr';
...
// clear SWR cache globally.
// NOTE: SWR 1.0 will introduce custom cache mechanism.
// https://swr.vercel.app/docs/advanced/cache
afterEach(async () => {
await waitFor(() => cache.clear());
});
dedupingInterval を 0 にする
import React from 'react';
import { SWRConfig } from 'swr';
export const SWRWithoutCache: React.VFC<{ children: React.ReactNode }> = ({ children }) => (
<SWRConfig value={{ dedupingInterval: 0 }}>{children}</SWRConfig>
);
wrapper に指定
const { result, waitForNextUpdate } = renderHook(() => useFoo(undefined), {
wrapper: SWRWithoutCache,
});
TeX の花文字。論文とかでよく見る「ひげ」が特徴的な文字。
-
は花文字だとT \mathscr{T}
これは以下のように書く
$\mathscr{T}$
[Rust] なぜ、for-loop では mut が不要なのか?
[Rust] 以下のようなコードを書いていて、Option#map
が値を move する (= レシーバが self
) であることに初めて気づいた。
if r.order > result.map_or(0, |r| r.order) {
result.replace(r);
}
as_ref()
を使うことで、Option<T>
-> &Option<T>
-> Option<&T>
にできる。
if r.order > result.as_ref().map_or(0, |r| r.order) {
result.replace(r);
}
リファレンスは Copy できるので、これで問題なくなる。今まで若干雰囲気で使ってたなー。
Upgrading Guide に従って、v5.x からアップグレードしている。
navigate
params が自動でマージされなくなったので、この挙動を期待しているところは、オブジェクトによる指定に変更し、merge: true
を追加する。
navigation.navigate({
name: 'MyStackScreen',
params: { name: newName },
merge: true,
});
モーダル周り
mode="modal"
はなくなり(というか mode
プロパティ自体がなくなり)、options で指定するようになった。
<RootStack.Navigator screenOptions={{ presentation: 'modal' }}>
...
デフォルトでヘッダが表示されるようになっている箇所があるので、適宜、headerShown: false
追加
<RootStack.Screen
name="MyScreen"
component={MyStackScreen}
options={{ headerShown: false }}
/>
また、モーダルのスタイルも iOS 13 以降のものがデフォルトになったので、以前の全体を覆うものにしたい場合は TransitionPresets.ModalSlideFromBottomIOS
でアニメーションを変更する。
<RootStack.Screen
name="MyScreen"
component={MyStackScreen}
options={{ ...TransitionPresets.ModalSlideFromBottomIOS }}
/>
ただ、これのおかげで、今まで cardOverlayEnabled
と cardStyleInterpolator
を指定していたものは不要になった。
<RootStack.Screen
name="MyScreen"
component={MyStackScreen}
options={{
cardOverlayEnabled: true,
cardStyleInterpolator: CardStyleInterpolators.forModalPresentationIOS,
}}
/>
ヘッダーの高さも微調整されているので、ここも修正が必要。
Go の GCS SDK で SignedURL を作成するためには、SignedURLOptions に
GoogleAccessID
-
PrivateKey
orSignBytes
を指定する必要があるが、これを Cloud Run や GCE ではデフォルトのものを使いたい。
そのための PR がレビュー中なので、差分を読み解いてみる。以下の手順で上記オプションを自動設定できそう。
-
transport.Cred() で google.Credential を取得する
- これは内部で google.FindDefaultCredentials を呼ぶ
-
GOOGLE_APPLICATION_CREDENTIALS
環境変数または$HOME/.config/gcloud/application_default_credentials.json
あるいは環境依存の方法 (GCE, AppEngine の場合は JWT アクセストークンから) で読む- ただし、最後の方法は JSON ファイルがないので、最終的に defaultSignBytesFunc(GoogleAccessID) に行く
- PR では scope に
https://www.googleapis.com/auth/cloud-platform
を追加しているが不要??必要なスコープに絞るべきだが、SignedURL は許可する HTTP メソッドを指定できるので、とりあえず FullAccess でもいいかも-
https://www.googleapis.com/auth/cloud-platform
スコープは signBlob のために必要
- GoogleAccessID
- JSON を unmarshal
-
client_email
プロパティがあれば使用する -
metadata.OnGCE() なら
- metadata.Email("default") で取得できれば使用する
- PrivateKey or SignBytes
- JSON を unmarshal
-
private_key
プロパティがあれば使用する - なければ、defaultSignBytesFunc(GoogleAccessID) を SignBytes に設定
- defaultSignBytesFunc はプライベートで保持している HTTP Client を再利用しているが、これは別途作成しておけばいい?
- IAM Service Account Credentials API を有効にする必要あり
- signBlob の permission
iam.serviceAccounts.signBlob
も必要
[React Native] 環境を切り替える方法
環境ごとに異なるビルドを TestFlight を配布したい。
- React Native の
__DEV__
グローバル変数- ローカルビルドの場合は
true
- ローカルビルドの場合は
Expo の app.config.js
で環境変数を設定する
-
app.json
ではなく、app.config.js
を用意することで、動的に設定を切り替えられる- 環境変数に応じて、bundleIdentifier を切り替えたりできそう
- 環境変数の設定は npm script に入れてしまうので良いかも。ちゃんとやるなら dotenv
-
.extra
フィールドで自由に値を設定し、Constants.manifest
で読み出せる - Environment variables in Expo - Expo Documentation
- アプリ名を
ios.infoPlist
とlocales
で設定している場合、アプリ名も切り替えるためには言語ファイル JSON を複数用意することになる(JSON は実行時に処理できないので)- これは管理が大変なので、開発やステージング用のビルドでは、
locales
を設定せず、config でname
のみ設定するのが良さそう
- これは管理が大変なので、開発やステージング用のビルドでは、
[React Native] URL モジュールは独自実装。期待する動作と違う場合がある
new URL('http://facebook.com'); // throws "not implemented" error
jest-expo で app.config.js
を読めない
● Test suite failed to run
ConfigError: Failed to read config at: /home/app.config.js
at getDynamicConfig (node_modules/@expo/config/build/getConfig.js:70:9)
at getConfig (node_modules/@expo/config/build/Config.js:339:51)
at readExpoConfig (node_modules/jest-expo/src/preset/createMockConstants.js:50:10)
at createMockConstants (node_modules/jest-expo/src/preset/createMockConstants.js:19:22)
at Object.<anonymous> (node_modules/jest-expo/src/preset/setup.js:112:8)
これは Jest の設定で moduleFileExtensions
に json
を追加すれば直る。
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
forwardRef と useImperativeHandle と React native
Chrome のポップアップブロッカーの挙動について。とても詳しい。
Ruby の async フレームワーク。Elixir の Task っぽいインターフェースで簡単に並列タスクを実行できる。Fiber スケジューラを利用しており、全ての blocking オペレーションに対応。数百万のタスクを並列実行可能。
ただし、Fiber スケジューラーは preemptive ではなさそう。
React Native の FlatList で要素の追加/削除をするときにアニメーションさせたい。
LayoutAnimation を使うのが公式の方法だが、Android でうまくいかない問題もあるようだ。
Reanimated で置き換えた、という記事
From my experience working on various react native projects, animations based on LayoutAnimation tend to be pretty reliable on iOS. However, when it comes to using it on Android, many times I’ve encountered inconsistencies, visual glitches, or crashes caused by using it.
現行プロジェクトで使っている SWR が 0.5.4 だったので、1.0 への移行を進めたい。
Custom Cache Provider が導入されたことで、テストでキャッシュをクリアする方法を変える必要がある。
LayoutAnimation を使うのが公式の方法だが、Android でうまくいかない問題もあるようだ。
手元で試している限り、Android でも問題なく動いている (RN 0.64.3)
-
TypeScript で
{}
をタイプに指定することの危険性について- React の props では実質的には大丈夫
- webpack で node_modules 以下のモジュールが parse できないときの対処法
React Native Web では、Pressable の style や children で props.hovered
が使える
React Native Testing Library の act warning
Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
I’ve noticed that this error/warning happens when I use more than 1 await within a it statement
単に、react-test-renderer の判定方法が RN と合ってない気がする。
iPad で、デバイスの向きを
-
app.json
でlandscape
に設定。 - 起動時に ScreenOrientation.lockAsync(orientationLock) で Landscape Left に固定
しているのだが、何らかのタイミングで Landscape Right になってしまう。原因不明なので ScreenOrientation も app.json による指定もせず、デバイスの向きに任せることにした...。
Rust の if let ...
で複数の条件を書きたい。すでに Experimental feature では入っている。
今は上記 RFC にも書かれているように、tuple を使って書くことが多い。
npm 8.3 以降を強制して、package.json で overrides
オプションを使いたい。
EAS build で npm 8.3 以降を指定する方法
-
eas.json で
node
を指定しても、指定したバージョンがインストールされなかった🤔 - package.json の hooks で npm をインストールするようにした。
"scripts": {
"eas-build-pre-install": "npm install -g npm@8.5.3",
[Rust] 異なる trait 同士で同名のメソッドを実装している場合、Universal Function Call Syntax を使うことで、それぞれのメソッドを明示的に呼び出すことができる。
Foo::f(&b);
Bar::f(&b);
メソッドではない Associated function も呼び出すことができる。
impl clap::ArgEnum for MyArg {
...
fn from_str(input: &str, ignore_case: bool) -> Result<Self, String> {
if ignore_case {
FromStr::from_str(input.to_lowercase().as_str())
} else {
FromStr::from_str(input)
}
}
}
impl FromStr for MyArg {
...
}
Phone as Security key について
Google ブログの One step closer to a passwordless future を読んで気になったのでメモ
FIDOアライアンスが言う「パスワードレス認証の普及を加速させる取り組み」とは?
これは caBLEっていう仕組みで以前から知られているもので、特徴としては
- スマートフォンの認証器として利用する
- Bluetoothを使って認証器とFIDOを利用する端末をつなげる
と言うあたりです。
パスワードの不要な世界はいかにして実現されるのか - FIDO2 と WebAuthn の基本を知る
Google は caBLE (cloud assisted BLE、「ケーブル」) という FIDO2 の拡張を提案しています。これはスマートフォンを BLE の cross-platform な認証器として使えるようにする、というもので、これが実現すれば、わざわざ専用の認証器を用意しなくても、多くの人がすでに持ち歩いているスマートフォンをそのまま認証器として使えるようになり、デバイスを跨いでパスワードなしのログインが可能になる、と期待されています。
公開情報から読むCloud-assisted BLE(caBLE)をつかったWebAuthn - Speaker Deck
- 公開鍵暗号方式による認証
- サービスへの登録時に鍵ペアを生成、公開鍵を RP に登録
- Authenticator = 認証器
- 内部認証器 = ローカルに実装されている組み込み認証器
- 指紋、虹彩、顔
- 外部認証器 = 移動可能な認証器
- この文脈ではスマートフォンが該当
- 内部認証器 = ローカルに実装されている組み込み認証器
- FIDO2
- WebAuthn
- CTAP2
- caBLE
- cloud-assisted BLE
- BLE を使う。ペアリングレス
Expo 45 が来た。
- Expo 45
- Expo 44 は React Native 0.64.3
- Expo SDK 45. Today we’re announcing the release of… | by Brent Vatne | May, 2022 | Exposition
- React Native 0.68.1
- barthap/expo-mega-demo: Experimenting with awesome React Native + Expo features.
ただし、Dev Client を使っている場合、Android で Hermes がクラッシュする問題がある、とのこと。
expo-dev-client
for Android currently does not work with Hermes —the app will crash in development. This is being tracked in expo/expo#17339, and we hope to have a solution soon. In the meantime, we recommend holding off on upgrading to SDK 45 if you use Hermes together withexpo-dev-client
on Android.
React Native 0.68
しかし、PKCS #1 v1.5は200万ペアほどの選択暗号文を取ることで攻撃可能なことが知られており、IND-CCA2ではない。
なお、OpenSSLのrsautlコマンドでは、パディング方式としてPKCS #1 v1.5がデフォルトとなっている。
RSAに対する適応的選択暗号文攻撃とパディング方式 - ももいろテクノロジー
主旨を要約すると次のようになります。
相互運用性の観点からアルゴリズムの種類を制限するのがよい。PKCS1-v1.5 は安全ではないので、
RS256
は排除している(JWS 仕様策定時もこの問題は認識していたがライブラリ群のアルゴリズムサポート状況を勘案して PKCS1-v1.5 を選択)。言うまでもなくアルゴリズムnone
も排除している。
iOS/Mac の SecKeyCopyExternalRepresentation で生成される PEM は PKCS#1 フォーマット。これを X.509 フォーマットに変換しないと他プラットフォームで使えない場合がある。これを実現する方法を探していたが、以下の方法が簡単そう。
つまり、以下のような公開鍵がある場合、
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwyNIPR4pavGx7NUMcSnvbesw3ITCyoN8Scxe/ynV/gE3cDKkeN86
1L41A5Uk0cj58FxeT1f8Vo3vQDKPaLUnEkmsZQvUagkjJRnGit3Q1Ngai7DAmjhk
+P7zwotxdUiQAYmOMnvQFjlORU606VKhXaojue2BcuBDAAxbq7MJAOP8KGbUIm9v
0hb3+eZ45UKMvdL/XTpGV5t1UuXX9g6zU7qR1h5VgQinVlaSFz1NFe1SztFOLnNR
cN8zkZe0oqQo6h6yie3fTpwxppdRcRpobcdk6UjGhsl5jeu1y48iIBKj97N2olEq
5bf86kCPEVyirDkUzYTWjefao+VoGDfQdQIDAQAB
-----END RSA PUBLIC KEY-----
まずは、BEGIN RSA PUBLIC KEY
と END RSA PUBLIC KEY
を変更する。
-----BEGIN PUBLIC KEY-----
MIIBCgKCAQEAwyNIPR4pavGx7NUMcSnvbesw3ITCyoN8Scxe/ynV/gE3cDKkeN86
1L41A5Uk0cj58FxeT1f8Vo3vQDKPaLUnEkmsZQvUagkjJRnGit3Q1Ngai7DAmjhk
+P7zwotxdUiQAYmOMnvQFjlORU606VKhXaojue2BcuBDAAxbq7MJAOP8KGbUIm9v
0hb3+eZ45UKMvdL/XTpGV5t1UuXX9g6zU7qR1h5VgQinVlaSFz1NFe1SztFOLnNR
cN8zkZe0oqQo6h6yie3fTpwxppdRcRpobcdk6UjGhsl5jeu1y48iIBKj97N2olEq
5bf86kCPEVyirDkUzYTWjefao+VoGDfQdQIDAQAB
-----END PUBLIC KEY-----
先頭に MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
を追加
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAwyNIPR4pavGx7NUMcSnvbesw3ITCyoN8Scxe/ynV/gE3cDKkeN86
1L41A5Uk0cj58FxeT1f8Vo3vQDKPaLUnEkmsZQvUagkjJRnGit3Q1Ngai7DAmjhk
+P7zwotxdUiQAYmOMnvQFjlORU606VKhXaojue2BcuBDAAxbq7MJAOP8KGbUIm9v
0hb3+eZ45UKMvdL/XTpGV5t1UuXX9g6zU7qR1h5VgQinVlaSFz1NFe1SztFOLnNR
cN8zkZe0oqQo6h6yie3fTpwxppdRcRpobcdk6UjGhsl5jeu1y48iIBKj97N2olEq
5bf86kCPEVyirDkUzYTWjefao+VoGDfQdQIDAQAB
-----END PUBLIC KEY-----
Base64 を 1 行 64 文字に揃える。
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwyNIPR4pavGx7NUMcSnv
besw3ITCyoN8Scxe/ynV/gE3cDKkeN861L41A5Uk0cj58FxeT1f8Vo3vQDKPaLUn
EkmsZQvUagkjJRnGit3Q1Ngai7DAmjhk+P7zwotxdUiQAYmOMnvQFjlORU606VKh
Xaojue2BcuBDAAxbq7MJAOP8KGbUIm9v0hb3+eZ45UKMvdL/XTpGV5t1UuXX9g6z
U7qR1h5VgQinVlaSFz1NFe1SztFOLnNRcN8zkZe0oqQo6h6yie3fTpwxppdRcRpo
bcdk6UjGhsl5jeu1y48iIBKj97N2olEq5bf86kCPEVyirDkUzYTWjefao+VoGDfQ
dQIDAQAB
-----END PUBLIC KEY-----
Expo の Camera を使った FaceDetector は気軽に使えるのだが、実際に使ってみると問題があった。
- iPad で使うと顔検出できないケースが多い
- 顔を動かすと検知される?
そこで、VisionCamera と vision-camera-face-detector で置き換えてみた。結果、
- 検出頻度は格段に上がった。ほぼ 100% 検出できる
- Reanimated の Worklet で実装されているため、パフォーマンスも十分
- Worklet は個別の JS VM で別スレッドで実行される
- Reanimated の Worklet で実装されているため、パフォーマンスも十分
ただし、
- npm で公開されている vision-camera-face-detector (0.1.8) は iOS で bounding box が取れない
- この PR で修正されているので、これ以降のコミットで npm install する
npm 7 以降であれば、以下のようにしてインストールできる。EAS build でも問題なく動く。
npm install github:rodgomesc/vision-camera-face-detector#acbc0b866c05e8739ce58087bc66179adb225322
ただ、bounding box が正確ではない気がする...- bounding box は、Camera.format の videoWidth/videoHeight の領域での値なので、カメラ上に表示するときは変換が必要
- contours (Landmark) が機能していない
- EyeOpenProbability: 目が画面外にある場合は不正確な値
vision-camera-face-detector を有効にするには、babel.config.js に以下を記述。必ず plugins 配列の最後に記述する
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
...
[
'react-native-reanimated/plugin',
{
globals: ['__scanFaces'],
},
],
],
};
};
使う側では、以下のような感じ
import 'react-native-reanimated'; // これは先頭に書く
import { Camera, PhotoFile, useCameraDevices, useFrameProcessor } from 'react-native-vision-camera';
import { Face, scanFaces } from 'vision-camera-face-detector';
const frameProcessor = useFrameProcessor(
(frame) => {
'worklet';
const scannedFaces: Face[] = scanFaces(frame);
console.log('scannedFaces', scannedFaces);
},
[]
);
VisionCamera では、Camera コンポーネントに format
を指定できる。
フォーマットによっては Frame Processor が動かなかったり、画像サイズが大きすぎて ML の推論が重くなってしまうので気をつける。
Selecting a Format for a Frame Processor
- If you are running heavy AI/ML calculations in your frame processor, make sure to select a format that has a lower resolution to optimize it's performance.
- Sometimes a frame processor plugin only works with specific pixel formats. Some plugins (like MLKit) don't work with
x420
.
カメラがサポートしているフォーマットは CameraDevice の formats から取得できるので、適切なものを選ぶ。フォーマットは例えばこんな感じ
{
"photoWidth": 1280,
"isHighestPhotoQualitySupported": false,
"pixelFormat": "420f",
"videoHeight": 768,
"supportsPhotoHDR": false,
"colorSpaces": ["srgb"],
"autoFocusSystem": "none",
"videoStabilizationModes": ["auto", "off"],
"maxZoom": 60,
"maxISO": 1504,
"minISO": 47,
"photoHeight": 960,
"supportsVideoHDR": false,
"frameRateRanges": [
{
"maxFrameRate": 30,
"minFrameRate": 1
}
],
"videoWidth": 1024,
"fieldOfView": 58.205101013183594
}
Pixel format について
VisionCamera: CameraDevice の format には pixel format が含まれているが、
-
420f
,420v
がある-
420v
Video Range -
420f
Full Range - どちらかを選べるなら 420f を選ぶ
-
-
x420
もある- compressed 10-bit HDR format, whereas 420f and 420v are 8-bit non-compressed formats.
- MLKit とかはこのフォーマットだと動かない
VisionCamera のパーミッション確認が微妙に面倒臭いので、以下のようなカスタムフックを書いた。
import { useState } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { Camera, CameraPermissionStatus } from 'react-native-vision-camera';
export type UseCameraPermissionReturn = {
hasPermission: boolean | undefined;
permission: CameraPermissionStatus | undefined;
};
export function useCameraPermission() {
const [cameraPermissionReturn, setCameraPermissionReturn] = useState<UseCameraPermissionReturn>({
hasPermission: undefined,
permission: undefined,
});
// Request permission
useAsync(async () => {
const cameraPermission = await Camera.getCameraPermissionStatus();
switch (cameraPermission) {
case 'denied':
case 'restricted':
setCameraPermissionReturn({
hasPermission: false,
permission: cameraPermission,
});
return;
case 'authorized':
setCameraPermissionReturn({
hasPermission: true,
permission: cameraPermission,
});
return;
case 'not-determined':
break;
}
const newCameraPermission = await Camera.requestCameraPermission();
switch (newCameraPermission) {
case 'denied':
setCameraPermissionReturn({
hasPermission: false,
permission: newCameraPermission,
});
return;
case 'authorized':
setCameraPermissionReturn({
hasPermission: true,
permission: newCameraPermission,
});
return;
}
}, []);
return cameraPermissionReturn;
}
パーミッションが取得できてないときはアラートを出したいので以下のように使う。
// Request permission
const { hasPermission: hasCameraPermission } = useCameraPermission();
useEffect(() => {
if (hasCameraPermission === false) {
alert('No camera access permission.');
}
}, [hasCameraPermission]);
vision-camera-code-scanner は、QR コードの読み取りに Google の MLKit を採用しているが、どうやら、分割された QR コード(シンボルの連結機能)は未サポートのようだ。
.rawData というフィールドは取得できるが、試してみたところ、テキスト部分のデータが手に入るだけだった。
AVFoundation の QR コード読み取り機能を使えば取得できるらしいが、AVCaptureSession は VisionCamera が握っているので、実装できなさそう。
zxingify/zxingify-objc: An Objective-C Port of ZXing でもサポートされている、とのこと。
Frame processor の中で CMSampleBufferRef を変換して zxingify で処理すればいけそう?
CMSampleBufferRef -> CMSampleBufferGetImageBuffer() -> CVImageBufferRef -> [ZXCGImageLuminanceSource createImageFromBuffer:videoFrame]
NativeBase でボタンに使われてる色とかを取得したい。React Navigation のヘッダー色を合わせたい、というのが元々の動機。
<Button mt="2" colorScheme="indigo">
ボタン
</Button>
テーマで設定された各カラーは useTheme
で取得できる。
import { useTheme } from 'native-base';
const { colors } = useTheme();
const backgroundColor = colors.indigo[800];
colorScheme から色を取得する方法は分からない...。
VisionCamera の frame processor を書いてみたが、ほぼ公式の手順通りで動いた。
カメラからの入力 CMSampleBuffer を画像に変換するには、Video Toolbox の VTCreateCGImageFromCVPixelBuffer() を使う。
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(frame.buffer);
CGImageRef videoFrameImage = NULL;
if (VTCreateCGImageFromCVPixelBuffer(pixelBuffer, NULL, &videoFrameImage) !=
errSecSuccess) {
// error
}
この関数が回転もしてくれる(っぽい)ので、自前で画像の回転をする必要はない。
Zxingify-ObjC で QR コードの矩形を表示するためには、そもそも QR コードの仕様を理解しておく必要があった。
QR コードの 3 隅に配置されている位置検出用パターンを finder pattern という。そして、バーコードスキャナで検出される位置情報は、この検出パターンの位置
points related to the barcode in the image. These are typically points identifying finder patterns or the corners of the barcode. The exact meaning is specific to the type of barcode that was decoded.
コードを読んだ感じ、位置は
- 左上
- 右上
- 左下
の順で、最後が
- アライメントパターン (モデル2より採用)
TypeScript 4.7 以上 (4.7.3 で確認) にしていると、@types/react (17.0.2) + react-native (0.68.2) で、React Native のコンポーネントを使うと、以下のようなエラーが出る。
Its instance type 'SafeAreaView' is not a valid JSX element.
The types returned by 'render()' are incompatible between these types.
Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react-native/node_modules/@types/react/index").ReactNode'.
Type '{}' is not assignable to type 'ReactNode'.
128 <SafeAreaView style={styles.container}>
~~~~~~~~~~~~
TypeScript 4.5 に戻すことで解決した。
zxingify-objc の kResultMetadataTypeStructuredAppendSequence と kResultMetadataTypeStructuredAppendParity を組み立てているソースコードの箇所
// sequence number and parity is added later to the result metadata
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
symbolSequence = [bits readBits:8];
parityData = [bits readBits:8];
その直前に 4bits をモード判定のために読み込んでいる
mode = [ZXQRCodeMode forBits:[bits readBits:4]]; // mode is encoded by 4 bits
QR コードの structured append は以下のような構成になっているはず
+--------------------+-------------------+-------------------+
| mode (4bits = 0x3) | seq index (4bits) | seq total (4bits) |
+--------------------+-------------------+-------------------+
インデックスも総数も 0 始まりなので、
seq = metadata[kResultMetadataTypeStructuredAppendSequence]
index = seq >> 4
total = (seq & 0x0f) + 1
で計算できる。
ZXingify-Objc: 複数のバーコードを読み込むためのクラス
ZXMultipleBarcodeReader (protocol)
このインターフェースの実装は、1枚の画像から複数のバーコードを読み取ろうとするものである。
ZXGenericMultipleBarcodeReader
画像の一部を繰り返しデコードすることで、画像内の複数のバーコードの位置を特定しようとするものです。1つのバーコードが見つかると、バーコードのZXResultPointsの左、上、右、下の領域が再帰的にスキャンされる。
呼び出し側は、QRコードのような複数の2次元バーコードを画像から検出しようとしたときに、ZXByQuadrantReaderを使用したい場合があります。
つまり、ZXReaderを渡す代わりに、[[ZXByQuadrantReader alloc] initWithDelegate:reader] を渡せばよい。
このクラスは、画像全体ではなく、画像の部分集合をスキャンして、画像からバーコードをデコードしようとするものである。これは、画像中に複数のバーコードがある場合に重要で、 バーコードを検出すると複数のバーコードの一部を見つけてしまい、 デコードに失敗することがある(例えばQRコード)。また、バーコードが中央にある場合に備えて、中央の「四分円」もスキャンします。
Zxingify-Objc: ZXMultiFormatReader
複数フォーマットをサポートするための ZXMultiFormatReader は ZXReader プロトコルを実装しているので、以下のふたつのメソッドを実装している。
- (ZXResult *)decode:(ZXBinaryBitmap *)image error:(NSError **)error;
- (ZXResult *)decode:(ZXBinaryBitmap *)image hints:(ZXDecodeHints *)hints error:(NSError **)error;
しかし、これらのメソッドを使うと ZXMultiFormatReader の hints がリセットされてしまうので非効率。それよりは予め hints を指定して、独自に実装している
- (ZXResult *)decodeWithState:(ZXBinaryBitmap *)image error:(NSError **)error;
を使った方がパフォーマンスは良い。
ただ、ZXGenericMultipleBarcodeReader や ZXByQuadrantReader のように ZXReader をデリゲートにとるオブジェクトもあり注意が必要。解決策としては、
- ZXMultiFormatReader をラップし、常に
decodeWithState:error
を呼び出す ZXReader を実装する
のが良さそう。
React Native の Reanimated の useSharedValue() では Map や Set が使えない。
指定した長さの配列を作る。
中身の値がどうでもいいので Array.from(Array(5))
とかにしている。
React Native のカメラ (VisionCamera) で、動画フレームをキャプチャして画像化するだけの Frame Processor を書いた。
npm の依存に git が含まれていると、GitHub Action で Permission error
npm ERR! Warning: Permanently added the ECDSA host key for IP address '192.30.255.113' to the list of known hosts.
npm ERR! git@github.com: Permission denied (publickey).
npm ERR! fatal: Could not read from remote repository.
node 16.x 未満を対象から外すことで解決する。
Sentry SDK で capture error するときにメッセージを追加したい。しかし、あまりいい方法がない。Transaction name は設定できるが、あまり目立たないし、使い所も微妙
Sentry.Native.captureException(syncError, (scope) =>
scope.setTransactionName('MyContext:syncKey')
);
Expo app.config.js
の updates.url
を定義(し、更にそれが https://u.expo.dev
で始まっている場合)、manifest のプロトコル (?) が変わる。これが何に影響するか、というと、Constants.manifest が null
になり、Constants.manifest2 になる...。
EAS Update では、EAS Build で利用できる環境変数 (Secrets / eas.json で指定した環境変数) は利用できない。そのため、これらの変数に依存した処理を実行時に書いていると EAS Update 時にバグる。
- 環境変数はビルド環境 (dev/staging/prod) を区別するひとつのみとする
- 残りの変数は上記の環境変数で切り替える
- コマンド実行時は常に上記変数を指定する
- 秘匿情報はビルド時にしか使わない
[iOS] アプリに Bluetooth を許可していない状態で、Core Bluetooth の startAdvertising: を呼ぶと、delegate の peripheralManagerDidStartAdvertising:error: が呼ばれるわけではなく、以下のようなログが出力されるだけ。
2022-07-16 09:28:41.043052+0900 example[56292:7309870] [CoreBluetooth] API MISUSE: <CBPeripheralManager: 0x283a94aa0> can only accept this command while in the powered on state
[React] 今までこんなふうに書いていた
export const MyComponent: React.FC<{
propA: number;
children?: React.ReactNode;
}> = ({ propA, children }) => {...
こう書けることを知った。
export const MyComponent: React.FC<
React.PropsWithChildren<{
propA: number;
}> = ({ propA, children }) => {...
[React] act
について
- コンポーネントのレンダーが発生するコードは
act
で囲む- レンダーで発生する更新がすべて処理されて反映されることを保証する。
-
act
という名称は Arrange-Act-Assert パターンから来ている - 参考: テストのレシピ集 – React
- 問題は
act
で囲んだコードの副作用で非同期処理が走る場合- コールバック関数 ->
await (処理)
->setState
とか -
setState
まで呼ばれることを保証するわけではない- それはそう
- 気軽に
async/await
をしているとテストがしづらくなる-
act
の後にwaitFor
を書かないと動かないテスト
-
- コールバック関数 ->
- ただ、
await
を使わないといけないケース、使わないと無駄に複雑になるケースもある- 設計次第。コンポーネントの外側から見て、非同期的振る舞いを許容できるかどうか
どうしても、非同期処理が必要な場合は、act
の中で React Testing Library の waitForXXX
などを使って待つ
act {
doSomethingToUpdateState();
waitFor(() => ...);
}
ESLint に怒られる場合は wait を外に出す(または、testing-library/no-unnecessary-act
ルールの isStrict オプションを外す)。
SQL で IN に複数書けるの知らなかった。subquery で検索したい時に便利
select
id
from my_table
where
(foo, bar) in
(
select
foo,
min(bar) as bar
from
...
useSWRInfinite() を使って自動で最後のページまで読み込む方法。全件取得だと遅すぎたり、件数制限があるときに。今は以下のようにしている。
const {
data,
size,
setSize,
isValidating,
} = useSWRInfinite(
getKey,
fetcher,
{
// Disable unnecessary updates while loading.
revalidateFirstPage: false,
initialSize: 1,
}
);
// Read pages in order until it reaches the last end.
useEffect(() => {
if (data?.length === size) {
setSize(size + 1);
}
}, [data?.length, setSize, size]);
また、最初のページはできるだけ早く表示したい。getKey()
でページ番号に応じて件数を調整することもできるかもしれない(未検証)。今は Context を用意して、最初のページ(件数少なめ)を事前ローディングしておき、useSWRInfinite のデータが取得できるまではそちらを使うようにしている。
LSP 実装の E2E テストを TypeScript で書いている。
LSP クライアントを実装するときは、vscode-languageserver-node の
- vscode-jsonrpc
- vscode-languageserver-protocol
のふたつを使って実装するのが良さそう(最初は、vscode-languageclient を使おうとしたが、deprecated な vscode パッケージに依存していて面倒くさそうだった)。
import path from 'node:path';
import process from 'node:process';
import { spawn } from 'node:child_process';
import * as rpc from 'vscode-jsonrpc/node';
import { InitializeRequest, InitializeParams } from 'vscode-languageserver-protocol/node';
const serverPath = path.join('path', 'to', 'lsp-server');
const ls = spawn(serverPath, { cwd: undefined, env: {} });
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
const connection = rpc.createMessageConnection(
new rpc.StreamMessageReader(ls.stdout),
new rpc.StreamMessageWriter(ls.stdin)
);
connection.listen();
const params: InitializeParams = {
processId: process.pid,
rootUri: null,
capabilities: {},
};
connection.sendRequest(InitializeRequest.type, params);
Expo dev-client をビルドするときに Android.manifest を確認する方法。expo prebuild でコードを生成できる。ただし、このとき、eas.json で指定した環境変数を使うことができないので、マニュアルで指定する。
$ ENV=... expo prebuild --platform android
android/app/src/main/AndroidManifest.xml
にファイルが生成されている。ここで手動で修正することもできる。その後、いつも通り EAS Build でビルドする。
$ eas build --platform android --profile development-client
[Rust] Clippy の on/offって設定ファイルで出来ないのかなーと思ったら、以下のような Issue が立っていた。
[TypeScript] 定数文字列の配列から Union 型を作る
const TAGS = ["h1", "h2", "p"] as const;
type Tags = typeof TAGS[number]; // type Tags = "h1" | "h2" | "p"
[React Native] ScrollView の zoomScale を取得する方法。event 経由でしか取得できない?
Expo SDK 46 で React Native Reanimated 2.10.0 を動かす。
@babel/plugin-proposal-export-namespace-from
を依存に入れる。
$ npm i --save-dev @babel/plugin-proposal-export-namespace-from
babel.config.js
に以下を追加
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
...
'@babel/plugin-proposal-export-namespace-from',
'react-native-reanimated/plugin',
],
};
};
Union type を Swift で表現するには、Associated value を持つ Enumeration を使う。
enum SpecialToken {
case token(AddedToken)
case string(String)
}
しかし、使う側は毎回 Variant を指定しなければならない。
let specialTokens: [SpecialToken] = [.string("A"), .string("B")]
文字列で指定することが多いケースでは、以下のように書きたい。
let specialTokens: [SpecialToken] = ["A", "B"]
こういう暗黙的な変換が実現できないか調べたところ、ExpressibleByExtendedGraphemeClusterLiteral
プロトコルを実装すればいいらしい。
- Implicit conversion with auto constructors - Evolution / Pitches - Swift Forums
- Swift の文字列表現プロトコルまとめ - Qiita
enum SpecialToken: ExpressibleByStringLiteral {
case token(AddedToken)
case string(String)
init(stringLiteral value: String) {
self = .string(value)
}
}
これで実現したいことができるようになった。
Rust の Arc<RwLock<T>>
から mutable reference
を得る方法。
let mut m = model.model.write().unwrap();
if let ModelWrapper::BPE(b) = &mut *m {
self.trainer.read().unwrap().train(b)
} else {
panic!()
}
-
.write()
で WriteGuard を取得。mutable な変数で束縛 -
*m
で Guard を外し、 -
&mut
で参照を得る
SwiftPM では Package.swift
を宣言的に書くことができる。
dependencies: [
.product(name: "Tokenizers", package: "tokenizers-swift")
]),
ここで .product
と書いてあるのは Enum かと思ったが、ドキュメントを確認すると static method だった。
/// Creates a target dependency on a product from a package dependency.
///
/// - parameters:
/// - name: The name of the product.
/// - package: The name of the package.
/// - condition: A condition that limits the application of the target dependency. For example, only apply a
/// dependency for a specific platform.
static func product(
name: String,
package: String,
condition: TargetDependencyCondition? = nil
) -> Target.Dependency
このような型の省略を Type Omission というらしい。
宣言的な DSL を実装するための仕組みとして面白い。
Swift のコレクションの要素数やインデックスの型がなぜ、UInt
ではなく Int
なのか?
-
Why is the collection index and count an Int instead of UInt - Using Swift - Swift Forums
-
UInt
とInt
の変換を頻繁にやる必要があって面倒だからでは? -
Collection
のインデックスはComaprable
を実装していればInt
以外でもいいよ - 「明確にプラットフォームのアドレスサイズと同じ容量が必要なのでなければ、負数にならない、という理由だけで
UInt
を使うのではなくInt
が推奨される」Language Guide – The Basics
-
久々に Swift をちゃんとやり直したけど、非同期周りがめちゃ進化してた。async
/await
もあるし、特に Actor がとても良い
- [Swift] actor reentrancy のありがたみと注意点 - Qiita
- Actor はクラスのように定義でき、プロパティやメソッドを持てる
class …
をactor ...
にするだけ - Actor へのアクセスはすべて非同期になり、固有の実行キューで実行。ブロッキング/デッドロックしない
- 不適切な呼出はコンパイラがチェックしてくれる
- 「メインスレッドで実行する Actor」も作れる
- 普通「UI の処理はメインスレッドからしか呼んではいけない」のだけど、これまではここの制御が大変だった(特に非同期処理を多段で呼び出す場合)
angular - day.js timezone plugin method produce an invalid Date - Stack Overflow
- Unicode を扱う ICU のバージョンによって、JS エンジンが
Date.prototype.toLocaleString
で出力される文字列のスペースをU+202F
(narrow no-break space) にしてしまう - その結果、
new Date()
で変換できる文字列にならない - しかし、dayjs の timezone プラグインがこれらを使って実装されている
- そのため、
dayjs.tz
を通すと Invalid Date になってしまう
この問題を解決しようとしているけど、そもそも、
-
toLocaleString()
の吐き出す文字列が1/1/2022, 00:00:00
という形式 -
new Date()
で保証されている形式は ISO-8601 形式のみ- Date.parse() - JavaScript | MDN
- なので、そもそも dayjs の実装が良くない
nb で、毎回 nb sync
するのが面倒だし忘れるので、自動化したい。特定のフォルダ以下のファイルが更新されたコマンドを実行できるようにする。
まずは fswatch
を brew でインストールする
brew install fswatch
変更対象のパスなどは不要なので、以下のコマンドで ok (.git
などは無視しないと無限ループする)
fswatch -o ~/Documents/nb/home --exclude '/\.' | xargs -n1 -I{} nb sync
dependabot でセキュリティアップデートだけを有効にして、最新バージョンへの自動アップデートを無効にするには、dependabot.yml
ファイルで open-pull-requests-limit
を 0
にするのか...。若干わかりづらい。
改行と空白でレイアウトを調整しているテキストを React (JSX) にするのが意外と面倒
- remark-markdown で Markdown を JSX に変換 (一部、強調とかもサポートしたかった)
- そのままだと改行を反映するには Markdown の hard break syntax を使う必要があるので、remark-break プラグインを導入する
import ReactMarkdown from 'react-markdown';
import remarkBreaks from 'remark-breaks';
<ReactMarkdown remarkPlugins={[remarkBreaks]}>{text}</ReactMarkdown>
- ただ、これでも空白類による横揃えができない (行頭の空白類は削除される)。また、
も削除されてしまう(なんで?) - なので、Markdown の引用やリストをうまく使いつつ、レイアウトの調整はスタイルシートで行う