React Native0.56アップデート(失敗した選択たちとその選択理由・正解の解説)
React Native本体のbreaking changeなアップデートを始めて自分で担当した。
その道中たくさんの選択ミスをした。
とても大変だったので2度と苦しまないよう
- 起こったこと
- それに対し行なったこと(またそれを行なった理由)
- 正解だった選択肢とその理由
の3点を時系列に記して行く。
第一の壁 アップデート方法
いつもはreact-native-git-upgradeを使っている。
起こったこと
ものすごく差分が出た。
それに対し行なったこと(またそれを行なった理由)
差分が多すぎる上に何が変更されたのか差分を見て理解できなかった。
アップデート作業を担当する以上、後々の不具合に備えて何が変更されたのかを自覚しておくべきなのでまずはchange logを読んでどんな変更が行われたのかを把握し、記事を検索して差分を見ながら手動で差分を当てるという手法を選択した。
ビルドして失敗する理由がライブラリのせいである可能性も考えると、原因の追求先を減らすためにまずは本体(react-nativeとreact)だけアップデートし、ビルドした上で動作確認してライブラリを1つずつ確認していこうという方針が安全だと自分では思った。
正解だった選択肢とその理由
react-native-git-upgradeを使うこと。
上記のやり方が間違っているというわけではないが、「アプリは動くが他のライブラリが互換性的に最適とはいえないバージョンのまま」という状態ができあがった。
当時かなり多くのライブラリが最新バージョンではなかった。
これを解消しようと他のタスクに「ライブラリのアップデート」というタスクを切ってあったので、今回は本当に安全にReact Native本体のバージョンだけをあげ、そこの安全確保ができた上で1つずつライブラリをあげる作業をすればいいやという考えで、その目的は果たしていたが、そこまで臆病にならずともreact-native-git-upgradeをした方が効率良いということだった。
また今回はreact-native-git-upgradeによるコンフリクト解消は一緒にみてもらった。
第二の壁 Babel
起こったこと
・Babelに必要なモジュールがなくてビルドできない。
・Babel7になったことでライブラリ内の構文がBabel7のルールに即していないと怒られてビルドできない。
// エラー内容 SyntaxError: A trailing comma is not permitted after the rest element // 該当箇所 // 1行で記述する際に末尾に","があると怒られる。 const {shouldUpdated, ...props, } = Props;
それに対し行なったこと(またそれを行なった理由)
・下記モジュールを追加した。
babel-preset-react-native v5.0.2 @babel/core v7.0.0-beta.47 babel/core v^7.0.0-bridge.0
・Babel7に対応してないライブラリも対応できるようになるとchange logにも紹介のあるbabel-bridgeの導入を試みた。
正解だった選択肢とその理由
babel-preset-react-nativeと@babel/coreに関してはchange logに記載があったがbabel-coreも必要だった。
しかもバーションは微妙に異なる。
なんとなくbabel/coreと@babel/coreはバージョンを揃えるもんだと思い込みもあって記事の通り両方v7.0.0-beta.47にしようとして「babel/coreはそんなバージョンないぞ」と怒られた。
npm outdate コマンドを打てばbabel/coreにv7.0.0-beta.47が本当にないことがわかったのでwantedのバージョン(v7.0.0-bridge.0)を指定したらうまくいった。
(しかしこれに関してはどこかのissueの回答にも両方v7.0.0-beta.47にしろと書いてあったのも頑なにそうだと思い込む一因となってしまった)
途中で気が付いたが上記のエラーが起きたライブラリのgithubをのぞいたらどれも構文(RN0.56)に対応してくれていた。
結論、該当ライブラリを最新にアップデートをするだけでよかった。
この時点でbabel-bridgeの必要は無くなったので導入しなかった。
そもそも先に各ライブラリがRN0.56に対応しているかどうかを調べるということをしていれば時間を浪費しなくてすんだ。
第二の壁 Jest
起こったこと
・jestが実行できない
・setTimeout関数を使うライブラリで`TypeError: _setter is not a function`エラー
・mockの方法が変わった
それに対し行なったこと(またそれを行なった理由)
ここは分担して主に他のメンバーにやってもらった。
正解だった選択肢とその理由
・jestが実行できない問題の解消
jestもBabel7の影響を受けているようでbabel/coreと@babel/coreを7のバージョンを使うこと、またReact Native内部のjestプロセッサーを使うようにすることで解消ができた。
具体的には下記のコードをpackage.jsonに追記した。
"jest": { "transform": { "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js" }, }
・setTimeout関数を使うライブラリで`TypeError: _setter is not a function`が多発
JavaScriptのコードはオブジェクトに格納されている必要があり、その格納するオブジェクトの最上位に位置する単一のオブジェクトはグローバルオブジェクトと呼ぶが、これはWebブラウザでの実装の場合「window」、Node.jsの場合は「global」がグローバルオブジェクトとなる。
setTimeoutを提供してくれているreact-timer-mixinの中でこのグローバルオブジェクトを設定する処理があった。
var GLOBAL = typeof window === 'undefined' ? global : window;
package.jsonに下記の記述をしていたためグローバルオブジェクトがwindowに設定されてしまっていた。
この記述をまるっと削除することで解決した。
"globals": { "window": { "navigator": { "userAgent": false } } },
・mockの方法が変わった
今まで1つのテストファイルの頭でmockを用意している箇所があった。
これが効かなくなったライブラリがいくつかある。
対応としてはテストファイルの頭でしていたmockをマニュアルモックとして__mocks__配下につくりなおした。
// 削除 jest.mock('shortid', () => ({ generate: () => 'shortID_mock', }));
@__mocks__/shortid.js
// 新規作成 export default { generate: () => 'shortID_mock', };
第三の壁 react-native-scrollable-tab-view
起こったこと
0.56アップデートとは直接関係なかったが番外編として。
自社のプロダクトでは様々なケースでreact-native-scrollable-tab-viewを利用している。
左側はタブビューが入れ子になる場合。
右側はタブビューの中にtextIputが大量にあり、抱えている情報が大量になる場合。
これらに対してreact-native-scrollable-tab-viewとreact-native-scrollable-tab-view-latestという元が同じライブラリから派生したライブラリを画面ごとに使い分けていた。
この2つのライブラリが両方ともbabel7の構文にひっかかって修正する必要があった。
それに対し行なったこと(またそれを行なった理由)
react-native-scrollable-tab-viewの本体が0.56対応して更新されていたのでアップデートが必要なことに加え、この期に統一を図ろうとした。
正解だった選択肢とその理由
実はこのreact-native-scrollable-tab-viewは入れ子にして使うことは非推奨であり、本体の方はそれを考慮したつくりになっていないためにタブビューを入れ子にするとandroidで不具合が起きる。
これに対応するためreact-native-scrollable-tab-viewはandroidで入れ子にしても動くような修正をした今まで使っていたライブラリを構文だけ修正して使う必要があり、textInputが大量にあって画面で抱えるデータが多くなる画面ではパフォーマンスが改善されている本家最新版を使った方が良いのでこれもまた今まで使っていたライブラリを構文だけ修正して使うことになった。
問題だったのは、担当した私が「なんでこれわかれてるんだっけ?」状態であったこと。
わからないのでひとまず本家最新にして動作確認するところからはじめたので不具合が生じたときにreact-nativeのバグなのか、react-native-scrollable-tab-viewのバグなのか、はたまた分けているライブラリの意図を自分が汲めていないのかでさんざん時間を使って調査・検証して頭を悩ませるはめになった。
ここでは正解というより、適切にコメントを書くことは大事という教訓を得た。
第四の壁 Twitter
起こったこと
・端末にTwitterアプリがない状態でTwitterサインインやログインができない
(以前はWebViewが開いてサインインもログインもできた。)
clang: error: no such file or directory: '/Users/MyUser/react-native-twitter-signin/Example/ios/build/Build/Products/Release-iphonesimulator/libRNTwitterSignIn.a'
それに対し行なったこと(またそれを行なった理由)
Twitterログインの問題は他のメンバーが担当。
アーカイブビルドに関しては古いキャッシュが残っているのだろうと下記キャッシュの削除を一通りおこなった。
・rm -rf node_modules/ ・rm -rf ios/Pods ・npm cache clean --force ・npm install ・pod install ・npm start -- --reset-cache ・XcodeでBuild Clean
正解だった選択肢とその理由
・端末にTwitterアプリがない状態でTwitterサインインやログインができない
これはアプリ側ではなく、開発者アカウントの設定の問題だった。
・iOSのアーカイブビルドのみfail
Xcode > Library配下のRNTwitterSignin.xcodeprojとPods.xcodeproj > Pods配下のTwitterCoreとTwitterKitがバッティングを起こしていた。
そもそもreact-native linkの挙動が変わったことが原因となる。
以前のreact-native linkではXcode > Library配下にRNTwitterSignin.xcodeprojが生成されていたのだが、いつからかPodsfileに下記記述が加わるようになった。
pod 'react-native-twitter-signin',: path => '../node_modules/react-native-twitter-signin'
以前他のネィティブモジュールでも似たようなことがあったのだが、xcodeprojとPodsのモジュールはどちらかがあればよいらしく、むしろ両方あるとどこかでバッティングを起こしビルドできないというケースを引き起こす。
README.mdにしたがって書かれている手順をそのまま実行してライブラリを入れているが、そこに書かれたことを行なってどうなればよいのか。
こうあるべきを知らないが故に解決できなかった。
クロスプラットフォーム開発をするということは両方のネィティブの知識がある程度ないとできないということを痛感した。
大きなアップデートがあった時は古いものは基本消したほうが良いというのと、キャッシュクリアだけではなく今後はxcodeprojとPods配下にも目を配るようにしたい。
第五の壁 Podfile.lock
起こったこと
忘れてしまったがなんかpod installがうまくいかなかった?
それに対し行なったこと(またそれを行なった理由)
ここらへんではもう心がおれかけていたのでまた特に考えずキャッシュクリアをしていた。
ただライブラリやモジュールのバージョンを変えない方が良いだろうとxx.lockファイル系は削除せずにライブラリやモジュールを取得し直した。
(これの前にpackage.jsonを削除して必要ないものまでアップデートしてしまってjestが失敗するということがっあったこともあって、絶対に.lockは消さない方が良いと思い込んでいた。)
正解だった選択肢とその理由
xxx.lockは他の開発メンバーがまったく同じ環境でライブラリをインストールできるために使うのであって、だれかのブランチを持ってきたときはもちろん消さずにlockファイルを頼りにinstallするべきだが、RNのアップデートでは古いバージョンをひきづっていることによる不具合の方が考えられるので削除して各種新しいバージョンで入れ直した方が良い。
RNアップデートは初めてということもあるがネィティブに詳しくなかったり過去の修正を記憶してないと大変。
他にも
・何度もやっているとnpm installが遅くなる
・./gradlew cleanBuildCacheがやたら時間かかる
・./gradlew cleanBuildCache後のAndroid Studioがビルドできるまでにやたらめった時間がかかる
など時間もかかるし1回の検証に時間かかる割に経験が少ない故に原因の追求先が多くて、あえて一人でやりきろうと抱え込むのは学習のコスパ悪かった気がする。
ただ次は3割増くらいにはうまくできるのではないか。
Gophercises vol.2 url redirect (YAML / middleware / http-headers)
学び
net/http
ServeMux
TTP要求マルチプレクサ(複数の信号を受け取り、1つの信号にして返却する)
各受信要求のURLと登録されたパターンのリストを照合し、URLに最も近いパターンのハンドラーを呼び出す。
React Native for Web / dom 関連の記事多読まとめ
記事一覧
- React NativeをWebに持ってくることの意味
- react-native-web と react-native-dom
- GitHub - vincentriemer/react-native-dom
- GitHub - necolas/react-native-web
- react-native-dom の何がすごいのか
- Web最新技術がてんこ盛りのreact-native-domから目が離せない
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
React Native for Web 概念関連記事
要点
・ブラウザはそもそもドキュメントビューアであり、アプリのランタイムではない。
・React Nativeはプラットフォームを限定しない抽象化を行なっているため、
react-native-windowsやreact-native-macosなど
様々なプラットフォームの抽象化を実現化できている。
まさに "learn once write anywhere"
・React Native for Webは、ネイティブアプリをブラウザ上で再現しようとしたものではなく、
React Nativeが提唱したGUIの抽象化を、Webアプリケーションの世界でも使えるようにした、
ブラウザ向けのReactコンポーネント&便利モジュール集ライブラリである。
・事例としてはTwitter Liteが挙げられる。
・React Nativeによって抽象化された「モバイルアプリ開発の当たり前」をブラウザの世界に持ち込んでくれる存在。
まとめ
もともとReact Natibeは "Webの技術であったReactをモバイルで使えるようにしたにもの" という発祥であったがゆえに "Webに回帰するなんておかしいのではないか" という意見もあった。
しかし、そもそもReact Nativeは様々なプラットフォームの抽象化を実現化したものであるのでWebでReact Nativeを動かそうという発想自体が理にかなったものであるということがわかり、技術を採用するにあたり不安を払拭することができた。
react-native-dom 概念関連記事
✒︎react-native-web と react-native-dom
要点
・react-native-domは既存のreact-nativeの仕組みを使ってさらに新しいプラットフォームをターゲットとする。
・react-native-webはreact-nativeという本来アプリを作るための基盤・コンポーネントを、Webでも動かせるようにしたもの。
..........理解できなかったので一旦本家リポジトリで各自の説明を読み比べてみる。
✒︎GitHub - react-native-dom
マルチスレッド
モバイルのReact Nativeと全く同じ仕組みでコンポーネントやロジックはweb workerによって実行される。
(web worker: JavaScriptにスレッド機能を提供することでWeb コンテンツがスクリプトをバックグラウンドのスレッドで実行可能にする手段)
モバイルのReact Nativeと同じレイアウトの挙動
yogaへのカスタムバインディングを使い、webアセンブリにコンパイルすることでネイティブでもwebでも同じレイアウトを実現させる。
(yoga: クロスプラットフォームを意識して作られてたレイアウトエンジン)
既存のReact Nativeと同じバンドラで構築されている
React Nativeで使用されているJavaScriptのbundlerであるMetroを使用しており、ネィティブとJSが別スレッドでJS関連(ホットローディングなど)のビルドは早く、開発者にとっても良い体験を提供する。
(bundler: gem同士の互換性を保ちながらパッケージの種類やバージョンを管理してくれる仕組みのこと)
DOMの循環
同じネイティブモジュールブリッジを使用することで... 既存のReact Nativeのモジュールを使うことができるよね。というようなことを言っているとおもうのだが曖昧。
react-native-domのinitial commitは2017年の6月。
発表されたのは2018年5月のReactEurope2018なので今の所大きな変更が起こる可能性は多いにある。
✒︎[GitHub - react-native-web
高品質のインターフェース
JavaScriptで高速かつ柔軟なWeb UIを作ることができる。
ネイティブ品質のインタラクション、複数の入力モード(タッチ、マウス、キーボード)のサポート、最適化されたベンダープレフィックススタイル、RTLレイアウトのビルトインサポート、組み込みのアクセシビリティ、React Dev Toolsとの統合を提供する。
Write once, render anywhere
既存のReact DOMコンポーネントと相互運用し、React Native APIの大半と互換性があるため、既存のコードを書き直すことなく、ネイティブおよびWeb用の新しいコンポーネントを開発できる。
導入事例は既にあり、TwitterをはじめUber、メジャーリーグサッカーやプレステなどが挙げられる。
ブラウザはChrome、Firefox、Edge、Safari 7以上、IE 10以上に対応。
要点
・react-native-domのすごいことはブリッジの仕組みにのっとって構築されているということ
ブリッジ
ブリッジとはJSとネィティブの処理を成立させるための仕組みであるが、実現する上でで課題となることが2つある。
ブリッジを実現する際の課題
1: リソースの競合
2: JSとネィティブ間のやりとりのすべての往復に伴う一定のオーバーヘッドがある
ブリッジを実現するためのポイント
これらを解消するために意識しないといけないポイントが2つ
1: JSとネィティブ間のやりとりを非同期で行うこと
2: JSとネィティブ間のやりとりによるスレッド間通信オーバーヘッドを最小限に抑えること
ブリッジの仕組み
・ブリッジはReact Nativeがもつ3つのスレッド(下記参照)の実行と、JSからネイティブ機能を呼び出す際、キューへのタスク追加、またネイティブからJS機能を呼び出す際にキューへのタスク追加を行う
スレッド1: シャドウキュー(コンポーネント再配置時に使用)
スレッド2: メインスレッド(UIKitが使用)
スレッド3: JSスレッド(開発者が書いたJSコードが動く。主にレンダリングを行う)
JavaScript スレッドとその他スレッドはそれぞれ機能呼び出しを媒介しているキューが存在し、キューに積まれたタスクを各スレッドが消化していって JS 層とネイティブ層の連携がなされている
・このブリッジ仕組みをreact-native-domではWeb Workerをスレッドとして使用することで実現
・Web Workerをスレッドとして使用することで実現
・レンダリングスレッドを Yoga の DOM 実装ベースで作っている
まとめ
私にとって魅力的にうつったのはコンポーネントベース開発の加速。
atomic design でいうところの atoms や molecules はプラットフォーム共通のコンポーネントを使い、OrganismsやTemplateのみ各プラットフォームにあわせて作成する。
これを実現するためにフォルダ構成を粒度単位でわけることを進めていきたいと考えた。
✒︎Web最新技術がてんこ盛りのreact-native-domから目が離せない
・React Native向けのUIライブラリや各種モジュールが、ブラウザ上でも動く
・React Nativeは UIスレッドと別にJavaScriptを実行するためのバックグラウンドスレッドを持っていて、react-natieve-domではWeb Workerの中でReactアプリケーションを動かす ことで、ブラウザ上であるにも関わらずReact Nativeの2スレッド制を完全再現している
・レイアウト計算をするYogaをWebで実現
Yoga: AndroidやiOSといったプラットフォーム上でFlexboxによるレイアウト計算する
まとめ
噛み砕けていないが、webのviewを完璧にネィティブのViewだとして扱わせることに成功しているということだと思う。
故にReact Nativeで使っていたモジュールが使えて、しかもWeb workerを使ってブラウザ上でマルチスレッドを実現しているので処理速度的にも問題ないものが出来上がっており、その内情はメンテ性などを顧みないかなりゴリ押しな面があるが大いに期待できるものである。
Gophercises vol.1 console quiz (csv / flag / Scanf)
つくったもの
CSVファイルから読み取ったクイズの問題データを整形して出力し、
コマンド上からユーザーの入力を受け取り正解数をカウントする。
github.com
使用したパッケージ
"encoding/csv"
csv - The Go Programming Language
"flag"
flag - The Go Programming Language
コマンドラインフラグの解析
"fmt"
fmt - The Go Programming Language
"os"
os - The Go Programming Language
"strings"
strings - The Go Programming Language
A Tour of Go vol.8 (Goroutines)
Goroutines
・軽量なスレッド
・goキーワードに続く関数を新しいスレッド(=goroutine)で実行する
・goroutineが実行されていてもmain()が終了するとプロセスが終了する点は注意
・Message-passing communication(各プロセスはメッセージを送り合い、内容は書き変わらない)
Channels
・goroutine 間でのメッセージパッシングをするためのもの
・メッセージの型を指定できる
・bufferで1度に扱えるメッセージの量を指定できる
・送信用チャネルはクローズする必要がある
・後述のselectで複数のチャネルから同時にメッセージを受信できる
// チャネルの生成 make(chan 要素数,キャパシティ) make(chan 要素型) // チャネルへの値の送受信 チャネル <- 送信する値 <- チャネル // バッファの指定 ch := make(chan int, 100)
// 第二引数で送信先であるチャネル(変数:c)を受け取る func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } // send sum to c c <- sum } func main() { s := []int{7, 2, 8, -9, 4, 0} // int型のチャネルを生成 c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) // receive from c x, y := <-c, <-c fmt.Println(x, y, x+y) }
Select
・複数ある case のいずれかが準備できるようになるまでブロックし、準備ができた case を実行
・複数の case の準備ができている場合、 case はランダムに選択される
func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { // 変数xの値をチャネルcを通して送信 case c <- x: x, y = y, x+y // チャネルquitが準備できたら"quit"を出力 case <-quit: fmt.Println("quit") return } } } func main() { // 1:チャネル作成 c := make(chan int) quit := make(chan int) go func() { // 2:0~9まで10回反復処理 for i := 0; i < 10; i++ { // 受信したメッセージを表示 fmt.Println(<-c) } // 送信用チャネルquitにメッセージ0を送信 quit <- 0 }() // フィボナッチ関数にチャネルを引数として渡して実行 fibonacci(c, quit) }
A Tour of Go vol.7(Interfaces/Type assertions)
Interfaces
・メソッドの型だけを定義した型
・オブジェクトの振る舞いを定義する
下記の例では Abserをインターフェースとする変数aに対し、Abserで定義しているメソッドAbs()を実装していない型の変数を代入しようとしてエラーになる。
// インターフェースAbserはメソッドAbsを振る舞いとして定義 type Abser interface { Abs() float64 } func main() { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} a = f // a MyFloat implements Abser -> 1 a = &v // a *Vertex implements Abser -> 2 // In the following line, v is a Vertex (not *Vertex) // and does NOT implement Abser. a = v fmt.Println(a.Abs()) } type MyFloat float64 // 1: MyFloatはメソッドabs()を持つ func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } type Vertex struct { X, Y float64 } // 2: *Vertex(ポインタ型)はメソッドabs()を持つ func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
インターフェースを実装する際はimplementsの表記は必要ない。
インターフェースのわかりやすい実装例
dev.classmethod.jp
nil
一旦離脱するがGoではnilは型を持つ
func main() { var i32 *int32 fmt.Println(i32 == nil) // true var arr []int fmt.Println(arr == nil) // true fmt.Println(compare(i32, arr)) // false }
基本的にnilは "==nil" がtrueになるが、インターフェースの時だけ型もnilでないと==nilはfalseとなる。
func main() { var arr []int fmt.Println(arr == nil) // true fmt.Println(reflect.ValueOf(arr).IsNil()) // true fmt.Println(reflect.TypeOf(arr)) // []int var arr2 interface{} = arr fmt.Println(arr2 == nil) // false fmt.Println(reflect.ValueOf(arr2).IsNil()) // true fmt.Println(reflect.TypeOf(arr2)) // []int var arr3 interface{} fmt.Println(arr3 == nil) // true fmt.Println(reflect.TypeOf(arr3)) // <nil> }
arr2はinterfaceとして扱っているため、型がnilでないので== nilはfalseとなる
isNilはあくまでBalueOfの下から値がnilだということを言いたいのか。
== nilは値に限らずnilをチェックしているのだろうが深堀はしない。
Type assertions
インターフェースはどんな型でも受け入れるので引数の型をインターフェース型にすればどんな型の値も受け取る関数をつくることができる。
しかし、この方法で受け渡された値は元の型がなにだったのかということを知ることができない。
この時に型アサーションを使えば動的に元の型がなんであるのかをチェックすることができる。
value, ok := <変数>.(<型>)
valueには型アサーション成功時に実際の値が格納され、2番目の変数okにはアサーションの成功否が格納される。
func main() { printIf(12) printIf("hello") printIf([]string{"cat", "dog"}) printIf([2]string{"hello", "world"}) } func printIf(src interface{}) { if value, ok := src.(int); ok { fmt.Printf("parameter is integer. [value: %d]\n", value) return } if value, ok := src.(string); ok { value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる fmt.Printf("parameter is string. [value: %s]\n", value) return } if value, ok := src.([]string); ok { value = append(value, "unknown") // 対象がsliceなのでAppendができる fmt.Printf("parameter is slice string. [value: %s]\n", value) return } fmt.Printf("parameter is unknown type. [valueType: %T]\n", src) }
Type switches
型アサーションと分岐を組み合わせた処理を手軽に記述することができる。
func printIf(src interface{}) { switch value := src.(type) { case int: fmt.Printf("parameter is integer. [value: %d]\n", value) case string: value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる fmt.Printf("parameter is string. [value: %s]\n", value) case []string: value = append(value, "<不明>") // 対象がsliceなのでAppendができる fmt.Printf("parameter is slice string. [value: %s]\n", value) default: fmt.Printf("parameter is unknown type. [valueType: %T]\n", src) } }
A Tour of Go vol.6 (Methods/Receiver)
Methods
Goはクラスの仕組みを持たないが型にメソッドを持つことができる。
メソッドは引数を
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X+v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }
Vertex型はレシーバ引数によってメソッドAbs()を持つ。
レシーバー: メソッドを呼び出される対象
クラスでメソッドを実装してきた身としてはメソッドありきでどの型に属すかを引数で決めるという順番はすごく慣れない。
レシーバーは値型もポインタ型も取ることができる。
ポインタ型
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { fmt.Println(v) //{30 40} return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale(f float64) { v.X = v.X * f //3*10=30 v.Y = v.Y * f //4*10=40 } func main() { v := Vertex{3, 4} v.Scale(10) fmt.Println(v.Abs()) }
ポインタ型ではmain関数で宣言したVertext変数を操作する
値型
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { fmt.Println(v) //{3 4} return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale(10) fmt.Println(v.Abs()) }
値型ではVertext変数のコピーを操作する
type Vertex struct { X, Y float64 } // メソッドがポインタレシーバ func (v *Vertex) Scale(f float64) { fmt.Println(v) v.X = v.X * f v.Y = v.Y * f fmt.Println(v) } func ScaleFunc(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} // vは値型 v.Scale(2) // ScaleFuncの第一引数はポインタ型なので&v ScaleFunc(&v, 10) p := &Vertex{4, 3} // pはポインタ型 p.Scale(3) // ScaleFuncの第一引数はポインタ型でpはもともとポインタ型 ScaleFunc(p, 8) fmt.Println(v, p) }
type Vertex struct { X, Y float64 } // 値型のレシーバー func (v Vertex) Abs() float64 { fmt.Println(v) return math.Sqrt(v.X*v.X + v.Y*v.Y) } func AbsFunc(v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) fmt.Println(AbsFunc(v)) // pはポインタ型 p := &Vertex{4, 3} // Absは値型のレシーバだけどポインタ型も受け取る fmt.Println(p.Abs()) fmt.Println(AbsFunc(*p)) }
主なポインタレシーバを使う2つの理由
- メソッドでレシーバが指す先の変数を変更させたい
- メソッドの呼び出し毎に変数のコピーを避けたい
レシーバーを値型/ポインタ型使い分ける考え
qiita.com