dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

follow us in feedly

[VSCode]htmlファイル編集中のみタグ移動ショートカットキーを有効にする(主にVSCodeVimユーザ向け)

メモ。
Vim には対応する括弧にカーソルを移動する % キーがあるが、
matchit.vim というプラグインを使うと、html の場合に < から > までではなく
対応するタグ ( <body> ~ </body> ) にジャンプできる。

f:id:dackdive:20181106212254g:plain

VSCode にも Vim エクステンション を入れて使っているが、これと同じ操作ができないか調べてみた。

まさにこれでした。

どうやら、 editor.emmet.action.matchTag というコマンドが標準でサポートされており、これにショートカットキーを割り当てればよさそう。

f:id:dackdive:20181106212729p:plain
editor.emmet.action.matchTag

ただ、問題点が1つ。
これをこのまま設定してしまうと、html 以外のファイルを開いているときに % で対応する括弧を移動するショートカットが効かなくなってしまう。

stack overflow だとバッティングを避けるために Cmd+% (Cmd+Shift+5) に割り当ててるみたいだけど、できれば元の Vim と同じように操作したい。

VSCode でショートカットキーを設定する際、 when というオプションがあるが、これで特定の拡張子のときだけ有効になるように制御できないものか。
と思い調べてみるとちゃんとあった。

How can I add a key binding for only certain file types?
Use the editorLangId context key in your when clause:

{ "key": "shift+alt+a",           "command": "editor.action.blockComment",
                                     "when": "editorTextFocus && editorLangId == csharp" },

指定可能な editorLangId のリストは
https://code.visualstudio.com/docs/languages/identifiers
にある。

というわけで、html ファイルを編集中のみ % を対応するタグの移動に割り当てるには、 keybindings.json に以下のように書けばいい。

{
    "key": "shift+5",
    "command": "editor.emmet.action.matchTag",
    "when": "editorLangId == html"
}


残念なところ

1個だけどうにもならなかった点があって。
Vim だと、 % と Visual Mode を組み合わせて

  • Shift+v で開始タグがある行を選択
  • そのまま % を押し、閉じタグまでを選択
  • d でまるっと削除

とかができたんだけど、VSCode ではできなかった。諦める。

Spring Frameworkに入門した

諸事情で Spring Framework を学ぼうと思い始めてみた。1日目。
これまでのプログラミング経験が

みたいな感じだったので、Java については基本的な構文は知ってるもののビルドツールなどのエコシステムすらよくわからない状態からスタート。


やったこと

Spring Framework の全体像を知る

まずは公式ドキュメントを読んでみる。
https://spring.io/

Projects のページを見るとわかるが、Framework といいつつ Spring ○○ と名のつくプロジェクトが大量にあり、関係性やどれから学ぶべきなのかさっぱりわからない。

またこういうのはたいてい Getting Started なるチュートリアルが提供されているものだが、
Guides を見ても一番最初に読むべきものがわからない。困った。

というわけで日本語の解説サイトを検索し、このあたりに目を通す。

どちらも最初の方しか読んでないが、テックスコアの

およびクラスメソッドさんの連載記事の 第1回第2回 あたりを読むと、おおよそ以下のようなことがわかる。

  • Spring Framework は多数のモジュール(プロジェクト)からなるフレームワークであり、コアとなる機能は DI コンテナ
  • DI コンテナ機能のイメージ
  • Spring を用いた DI の実現方法にはいくつかの種類がある。XML の設定ファイルを使う/アノテーションを使う など


Spring Boot の概要を知る

上に挙げたサイトではまだ、Spring Boot というプロジェクトが何を指しているのか見えてこない。
その割に公式サイトでは一番に出てくるので気になる。
というわけで今度は Spring Boot について調べ、以下のスライドにたどり着く。

これ見て Spring Boot が何たるかをなんとなく理解する。
特に

f:id:dackdive:20181102174621p:plain

この図を見て Spring Framework 本体との関係を理解。
後半の Spring Boot の構成要素については一旦スキップ。


Gradle を学ぶ

次は Spring 公式サイトの Guides にある一番基本っぽいチュートリアル
Getting Started · Building an Application with Spring Boot
をやろうと思ったんだけど、冒頭で Gradle に詳しくなければこのモジュールを参考にするよう促されたのでまずはこっち。

Gradle や Ant, Maven などの名前は聞いたことがあって、ビルドツールということは知っていた。
のでフロントエンドで言う webpack とか gulp 的な位置づけかなと思ってたんだけど、どうやら依存パッケージの管理もやるみたいなので npm/yarn ぽい役割も兼ねているのかな、ぐらいの理解。


公式ガイドの Getting Started · Building an Application with Spring Boot をやる

ようやく Spring Framework のサンプルコードを書いてみる。
このモジュールも別に「まずこれからやるべき」と明記されてるわけではなく、 https://spring.io/guides に並んでいる膨大なモジュールの中から一番基本っぽいものを選んだ。

結果、たしかに Spring を使って簡単なアプリケーションを作ることはできたが、おまじない的に出てくる各種アノテーションが何をやっているのかはさっぱりわからなかった。

一度 Spring Boot を使わずに素の Spring Framework を使ったコードも書いてみないと感覚がつかめないのかな...と感じる。


今回の学び

  • Spring Framework の概要を学んだ
  • Gradle の基礎的な内容を学んだ
    • Gradle はビルドツール。似たようなものに Ant, Maven がある
    • 依存パッケージの管理もやってくれる
  • Spring Boot を使ったアプリケーション構築チュートリアルを試した


次回やろうと思っているもの

Spring Guides の中ではタイトル的に一番とっつきやすそう&面白そうなので。ただし

  • 冒頭の Spring Data REST が Spring Framework 全体の中でどういう位置づけなのかわかっていない
  • Spring Boot を使うぽいので、結局またコアの仕組みの部分はブラックボックス化された状態から変わらなさそう

という懸念はある。


今回は見送った参考サイト

の README にリンクをまとめている。


参考になりそうな書籍

Spring Framework の日本語書籍、調べるといくつか出てくるがどれも Amazon のレビューがイマイチで買うの躊躇している。。。
おすすめがあれば教えてください。

なるべく出版年が新しめのもので探した。
この3冊の中だと、最初に挙げたものがやや難解だけど内容としては良さげ。

また最近の WEB+DB PRESS で Spring Boot 特集があったらしい。

WEB+DB PRESS Vol.106

WEB+DB PRESS Vol.106

会社にあるはずだから読んでみよう。

[Salesforce]Winter'19: Apex Replay Debuggerの使い方

昨日の Meetup の内容をブログにもまとめておきます。本当はこっちを事前に公開したかった。

なお Meetup で話したときの LT 資料はこちら。


Apex Replay Debugger とは

Winter'19 リリースノート:Apex Replay Debugger を使用してすべての組織を無料でデバッグ (正式リリース)

Apex Replay Debugger は VSCode拡張機能として使える Apex のデバッグ機能です。
Apex を実行した際に出力されるデバッグログファイルを元に、ログファイル出力時の状況を VSCode 上で再現(Replay)できます。

そのため、Apex をステップ実行したり、任意の行にブレークポイントを置いてその時点での変数の状態を確認することができます。


事前に必要なもの Prerequisites

Apex Replay Debugger for Visual Studio Code を参考に必要なものを事前にインストールしておきます。


セットアップ手順

プロジェクトフォルダを作成する

VSCode のコマンドパレット (Mac の場合 Cmd + Shift + P で開く) から

SFDX: Create Project
# または
SFDX: Create Project with Manifest

を実行してプロジェクトを作成します。

または、従来のディレクトリ構成 (src/ の下に package.xml を置き、Force.com Migration Tool などを使ってデプロイ) でも
sfdx-project.json を置きさえすればこの後の VSCode のコマンドを利用することは可能です。

`--src
     `--classes
     `--pages
     `--...
     `--package.xml
`--sfdx-project.json

(その場合、sfdx-project.jsonpath パラメータは force-app -> src にする必要あり)


起動構成ファイル (launch.json) を作成する

.vscode/launch.json ファイルを作成し、以下をコピペします。

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch Apex Replay Debugger",
      "type": "apex-replay",
      "request": "launch",
      "logFile": "${command:AskForLogFileName}",
      "stopOnEntry": true,
      "trace": true
    }
  ]
}

もし、プロジェクトごとに設定するのが面倒な場合、ユーザー設定に "launch": ... という設定を追加することでも可能のようです。

f:id:dackdive:20181031003338p:plain

このあたりは VSCode のドキュメントが参考になります。
https://vscode-doc-jp.github.io/docs/userguide/debugging.html#起動構成


デバッグログレベルを FINEST にする

Replay Debugger を実行するためには、デバッグログレベルを

  • VISUALFORCE: FINER or FINEST
  • APEX_CODE: FINEST

に設定する必要があります。

これは、VSCode 上で

SFDX: Turn On Apex Debug Log for Replay Debugger

というコマンドを実行することで変更できます。


Replay Debugger の使い方


ソースコード上に Checkpoint を置く

処理を中断したりその時点での変数の状態を確認したい行にカーソルを置き、

SFDX: Toggle Checkpoint

を実行します。
行番号の左に赤いマークがつくのがわかります。これを Checkpoint と呼びます。

f:id:dackdive:20181031004307g:plain


Checkpoint を組織に反映させる

設置した Checkpoint は組織に反映させる必要があります。
事前に Scratch Org を作成したり、 sfdx force:auth:web:login などで認証を済ませておいてから

SFDX: Update Checkpoints in Org

を実行します。Output パネルに

Starting SFDX: Update Checkpoints in Org
SFDX: Update Checkpoints in Org, Step 1 of 6: Retrieving org information
SFDX: Update Checkpoints in Org, Step 2 of 6: Retrieving source and line information
SFDX: Update Checkpoints in Org, Step 3 of 6: Setting typeRefs for checkpoints
SFDX: Update Checkpoints in Org, Step 4 of 6: Clearing existing checkpoints
SFDX: Update Checkpoints in Org, Step 5 of 6: Uploading checkpoints
SFDX: Update Checkpoints in Org, Step 6 of 6: Confirming successful checkpoint creation
Ending SFDX: Update Checkpoints in Org

のように出力されれば成功です。

ちなみにこれは、開発者コンソールでエディタを開き、Checkpoint を設置したのと同じことをやっています。

f:id:dackdive:20181031004947p:plain


Apex を実行する

Checkpoint を設置した Apex コードを実行します。
実行にはいくつか方法がありますが、好きな方法で OK です。

  1. Visualforce ページなどで使われている Apex の場合、画面を直接操作する
  2. 該当の Apex のテストコードを実行する
  3. Execute Anonymous Window などで実行する

余談ですが特に VSCode を使った開発の場合、2 に関してはテストクラスを開くとメソッドの上に Run Test というリンクが表示されており、そこからメソッドごとにテストを実行させることができたり

f:id:dackdive:20181031005645p:plain

3 に関しては、適当なスクリプトファイルを作成し、開いた状態で

SFDX: Execute Anonymous Apex with Editor Contents

を実行すれば、開発者コンソールでの Open Execute Anonymous Window とほぼ同等のことを行えて便利です。


デバッグログファイルを取得する

Apex を実行したら、その時のログファイルをローカルにダウンロードします。
これについても VSCode 上で

SFDX: Get Apex Debug Logs...

を実行すると、新しい順にデバッグログの一覧が表示されるので、そこから選択するとダウンロードすることができます。

f:id:dackdive:20181031010027p:plain

ログは .sfdx/tools/debug/logs/ の下に保存されます。


デバッグログファイルから Replay Debugger を起動する

該当のログファイルを開いた状態で、コマンドパレットまたは右クリックして表示されるメニューから

SFDX: Launch Apex Replay Debugger with Current File

を実行します。
ログパネルが開き、またログファイルの1行目に黄色いカーソルが表示され、デバッグ用のメニューが表示されます。

f:id:dackdive:20181031010756p:plain


デバッグ:Apex をステップ実行したり、変数の中身を覗いてみる

この状態で |▶ ボタン (Continue: F5) を押すと、先ほどの Checkpoint まで処理が進み、該当行でストップしていることがわかります。
左側の VARIABLES ペインを見ると、この時点での変数に格納されている値を確認することができます。

f:id:dackdive:20181031011213p:plain

また、行番号の左のスペースをクリックすることで、 Checkpoint を設置した行以外の行にも Breakpoint を設置することができます。
次にデバッガを実行したときにはこの Breakpoint でも処理が中断されるようになります。
(Checkpoint と Breakpoint の違いは後述)


注意事項

今のところ以下の制約があるようです。

  • Checkpoint は最大5個まで。また有効期限は30分
  • 一度に実行できるのは1個のログファイルまで
    • そのため、非同期処理などのデバッグは注意が必要

詳細は Apex Replay Debugger の「Considerations」の項 を一読されることをおすすめします。


その他

Checkpoint と Breakpoint

SFDX: Toggle Checkpoint を使い、また組織にもデプロイした Checkpoint と、VSCode 上だけで定義した Breakpoint は、できること(得られる情報)が異なります。
「Set Breakpoints and Checkpoints」「Considerations」 あたりを読む限り、以下のような違いがあるようです。

For more information than line breakpoints provide, add checkpoints. You can set up to five checkpoints to get heap dumps when lines of code run. All local variables, static variables, and trigger context variables have better information at checkpoints. Trigger context variables don’t exist in logs and are available only at checkpoint locations.

トリガーのコンテキスト変数をはじめとして、Checkpoint の方が持っている情報が多いみたいです。

  • Long string variable values are truncated at breakpoints. At checkpoints, heap-dump-augmented variables have full strings.
  • When viewing a standard or custom object at a breakpoint, you can drill down only to the object’s immediate child variables (one level deep). At checkpoints, heap-dump-augmented variables have full drill-down to child standard objects, not only to immediate children.

Checkpoint の方が長い文字列も省略されなかったり、ネストしたオブジェクトの情報なども完全に保持しているらしい。


Apex Debugger との違い

名前がややこしいですが、VSCode拡張機能として Apex Debugger というのもあります。

ただ、こちらは

This extension enables VS Code to use the real-time Apex Debugger with your scratch orgs and to use ISV Customer Debugger with your subscribers’ sandbox orgs.

というところから対象組織は Scratch Org (および ISV 向けの機能については Sandbox Org) に限定されたり、

  • One Apex Debugger session is included with Performance Edition and Unlimited Edition orgs.
  • To purchase Apex Debugger sessions for Enterprise Edition orgs, or to purchase more sessions for orgs that already have allocated sessions, contact Salesforce.

から Performance Edition, Unlimited Edition 以外では有償のようです。

[Salesforce]UserRecordAccessでレコードの参照・編集権限をチェックする

こんなことやりたい

Apex で SOQL を実行してレコードを取得するとき、実行ユーザが編集権限のあるレコードだけ返したい。
このとき、レコードに対し参照権限だけあると SOQL では取得できてしまうため適宜フィルターする必要がある。


先にまとめ

UserRecordAccess オブジェクトというのがあるので、それを使いましょう。
UserRecordAccess | SOAP API 開発者ガイド | Salesforce Developers

UserRecordAccess には Has*Access ( *EditDelete など) という項目があるので、チェックしたい権限に応じた項目を使います。

また API バージョン 30.0 以降であれば UserRecordAccess は各 SObject のリレーション項目として取得できます。

例:SELECT Id, Name, Email, UserRecordAccess.HasEditAccess FROM Contact


方法

ほぼほぼこちらのテラスカイさんの記事にある通りです。感謝。
が、少々アップデートがあるみたいなので順を追って記載します。

ブログ記事にあるように、あるユーザが特定のレコードに対して参照・編集・削除などの権限を有しているかを判定するためのオブジェクトとして UserRecordAccess というオブジェクトがあります。

UserRecordAccess | SOAP API 開発者ガイド | Salesforce Developers

上のオブジェクト定義を見るとわかりますが、 UserRecordAccess には以下の項目があります。

  • 各種の権限があるかどうかのフラグ項目
    • HasReadAccess : 参照
    • HasEditAccess : 編集
    • HasDeleteAccess : 削除
    • ほか
  • UserId
  • RecordId

UserIdRecordId を WHERE 句に指定することで、「あるユーザが特定のレコードに対して権限を有しているか」を判断できるわけです。

なので、冒頭に記載したように「編集権限のあるレコードだけ返す」を実現するためには、こういったメソッドを用意すればいいでしょう。

public List<Contact> getEditableContacts() {
    Map<Id, Contact> contactMap = new Map<Id, Contact>([
        SELECT Id, Name, Email FROM Contact
    ]);
                                                         
    List<UserRecordAccess> accesses = [
        SELECT
            RecordId, HasEditAccess
        FROM
            UserRecordAccess
        WHERE
            UserId = :UserInfo.getUserId()
        AND
            RecordId IN :contactMap.keySet()
    ];
                                                         
    List<Contact> result = new List<Contact>();
    for (UserRecordAccess access : accesses) {
        if (access.HasEditAccess) {
            result.add(contactMap.get(access.RecordId));
        }
    }
    return result;
}


制限事項

というわけで便利なオブジェクトですが、制限事項がいくつかあります。
↑のテラスカイさんのブログ記事でも述べられていますが、個人的に気になったものを挙げると

  • RecordId は単一または複数のレコードを WHERE 句に指定できるが、 UserId は単一のみ
    • UserId IN *** とすると Error: Can filter on only UserId = [single ID], either RecordId = [single ID] or RecordId IN [list of IDs], and Has*Access = true というエラーが出る
  • 一度に取得できるレコードは 200 件までで、SOQL の結果が 200 件を超えると QueryException が発生する

特に後者の制限が厳しいですね。。。


より良い方法

制限事項を回避できる何か良い方法はないのかと SOAP API 開発者ガイド を最後まで読むと、

API バージョン 30.0 以降では、UserRecordAccess はレコードの外部キーになります。このオブジェクトをルックアップまたは外部キーとして使用する場合は、UserId または RecordId 項目を検索条件に使用したり、指定したりすることはできません。前記のサンプルクエリは、次のように実行できます。
SELECT Id, Name, UserRecordAccess.HasReadAccess, UserRecordAccess.HasTransferAccess, UserRecordAccess.MaxAccessLevel FROM Account

お! UserRecordAccessAPI バージョン 30.0 以降だと任意の SObject のリレーション項目として一緒に取得できるようです。
なので、先ほどのサンプルは以下のように書き直すことができます。

/** Since API version 30.0 **/
public List<Contact> getEditableContacts() {
    List<Contact> contacts = [
        SELECT
            Id, Name, Email,
            UserRecordAccess.HasEditAccess
        FROM
            Contact
    ];
    List<Contact> result = new List<Contact>();
    for (Contact contact : contacts) {
        System.debug('Contact [' + contact.Name + '] is editable?: ' + contact.UserRecordAccess.HasEditAccess);
        if (contact.UserRecordAccess.HasEditAccess) {
            result.add(contact);
        }
    }
    return result;
}

これにより、制限事項のうち「一度に取得できるレコードは 200 件まで」は回避できました。よかった。

ただし、 UserRecordAccess.Has*Access は WHERE 句に直接は指定できず、以下のエラーになります。

Error: You cannot filter on UserRecordAccess when used in a relationship


注意事項

Task, Event など一部の標準オブジェクトについては、UserRecordAccess を項目として取得する方法は使えないようです。

UserRecordAccess オブジェクトへのクエリが一度に 200 件までという制限があることなど、内部的にパフォーマンスに影響ありそうなことをやってそうな香りがしますね。
実際に使うときはパフォーマンス面での影響を少し意識しなければ。


サンプルコード

https://github.com/zaki-yama/salesforce-samples/tree/master/user-record-access

[Salesforce]ワークフローの時間ベースのアクションについて

今さらながら時間ベースのワークフローを使うことがあったのでメモ。

時間ベースのワークフローとは

f:id:dackdive:20180817101627p:plain

ここのこと。

時間ベースのワークフローで何ができる?

時間ベースじゃない方のワークフローアクションだと、ルール条件に一致した場合レコードの作成または編集直後にアクションが実行される。
これに対し、時間ベースのアクションは文字通り、アクションの実行を将来のある時点に予約しておくことができる。

具体的なユースケースとして、たとえば
「金額が1000万円以上の大型商談について、完了予定日の7日前になった時点でクローズされていなければ、商談所有者にリマインドメールを送信する」
といったことができる。


将来のある時点、は何を基準に決定される?

レコードの作成日やルールが適用された日だけでなく、対象のオブジェクトの日付項目の値を基準にアクション実行のタイミングを制御することもできる。

タイミングの制御は基準となる項目の日付から数えて◯日前(後)、といった指定方法になる。

f:id:dackdive:20180817102652p:plain

↑では例として、商談オブジェクトのレコードに対し、「完了予定日」の7日前に実行されるアクションを設定している。


アクションが予約されたことをどうやって確認するの?

LEXであれば 環境>監視>時間ベースのワークフロー から、現在セットされている時間ベースのアクションを確認できる。

f:id:dackdive:20180817103443p:plain


アクションは一度予約されたら必ず実行される?

NO。
一度ルール条件に合致してアクションがセットされても、その後レコードが更新されてルール条件を満たさなくなれば、アクションは実行されない。

冒頭の例でいうと、

  • レコード作成時は金額1000万以上だったが、その後1000万未満になった
  • 完了予定日の7日前より以前に商談がロストした/クローズした

場合はリマインドメールは送信されない。

参考:FAQ - 時間ベースのワークフロー

キューの待機中のアクションは、必ず起動されますか?
いいえ。時間ベースのアクションは、プロセスまたはワークフロールールのルール条件が “false” と評価される限り、ワークフローキューに留まります。ルールが評価されたときに、レコードがルール条件に一致しなくなると、Salesforce はそのレコードについてキューにある時間ベースのアクションを削除します。


評価条件が「作成されたとき」か「作成されたとき、およびその後基準を満たすように編集されたとき」で挙動にどういう違いがあるの?

これは時間ベースじゃないアクションと同じ。
「作成されたとき」の場合はレコード作成時しか評価しないので、たとえば冒頭の例でいうと

  • 一度ロストした商談を再度 Prospecting に戻した(その時点ではまだ完了予定日の7日前より以前だった)

という操作をした場合も、リマインドメールの対象になるわけではない。

参考:FAQ - 時間ベースのワークフロー

レコードの待機中のアクションをキューに戻すことはできますか?
はい。レコードが更新され、評価条件をレコードが [作成されたとき、およびその後基準を満たすように編集されたとき] に設定した場合は、自動的にキューに戻されます (レコードは、入力された条件を以前に満たしていない必要があります)。

※これは余談だが、この直後にある例は日本語記事だとなぜか間違っている(1個前のと同じ例になっている)


プロセスビルダーでも同じことできないの?

できますね。「スケジュール済みアクション」という名前みたい。

f:id:dackdive:20180817105810p:plain

注意点として、プロセスの開始条件を「レコードを作成または編集したとき」にしている場合、スケジュール済みアクションを使えるようにするには

「レコードに指定の変更が行われた場合にのみアクションを実行しますか?」

にチェックを入れる必要がある。


リファレンス

Pipenvで仮想環境をプロジェクトディレクトリの下に作る(PIPENV_VENV_IN_PROJECT)

ちょいメモ。

久しぶりに Python を書くにあたって環境構築する際、 2018年のPythonプロジェクトのはじめかた - Qiita を見て Pipenv を使ってみた。

普通に pipenv shell で仮想環境を作成すると

 ~/workspace/Python/pipenv-sandbox $ pipenv shell
Creating a virtualenv for this project…
Using /Users/yamazaki/.pyenv/versions/3.6.5/bin/python3.6m (3.6.5) to create virtualenv…
⠋Running virtualenv with interpreter /Users/yamazaki/.pyenv/versions/3.6.5/bin/python3.6m
Using base prefix '/Users/yamazaki/.pyenv/versions/3.6.5'
New python executable in /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx/bin/python3.6m
Also creating executable in /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx
Spawning environment shell (/bin/zsh). Use 'exit' to leave.
. /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx/bin/activate
 ~/workspace/Python/pipenv-sandbox $ . /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx/bin/activate
(pipenv-sandbox-9fl74ZVx)  ~/workspace/Python/pipenv-sandbox $

というように、プロジェクトディレクトリとは別のグローバルな場所( ~/.local/share/virtualenvs/ )に仮想環境が作られる。

これを、

$ virtualenv venv

したときと同じように、各プロジェクトディレクトリの下に作成したい。

Configuration With Environment Variables を読むと

  • PIPENV_VENV_IN_PROJECT — If set, use .venv in your project directory instead of the global virtualenv manager pew.

とあり、 .zshrc

export PIPENV_VENV_IN_PROJECT=true

を追加したところ、プロジェクトディレクトリの下に仮想環境が作成されるようになった。

 ~/workspace/Python/pipenv-sandbox $ pipenv shell
Creating a virtualenv for this project…
Using /Users/yamazaki/.pyenv/versions/3.6.5/bin/python3.6m (3.6.5) to create virtualenv…
⠋Running virtualenv with interpreter /Users/yamazaki/.pyenv/versions/3.6.5/bin/python3.6m
Using base prefix '/Users/yamazaki/.pyenv/versions/3.6.5'
New python executable in /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv/bin/python3.6m
Also creating executable in /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv
Spawning environment shell (/bin/zsh). Use 'exit' to leave.
. /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv/bin/activate
 ~/workspace/Python/pipenv-sandbox $ . /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv/bin/activate
(pipenv-sandbox) ~/workspace/Python/pipenv-sandbox $ 

# .venv ディレクトリが作られている
(pipenv-sandbox) ~/workspace/Python/pipenv-sandbox $ ls -al
total 8
drwxr-xr-x  4 yamazaki  staff  136  5 16 02:33 ./
drwxr-xr-x  8 yamazaki  staff  272  5 16 02:08 ../
drwxr-xr-x  5 yamazaki  staff  170  5 16 02:33 .venv/
-rw-r--r--  1 yamazaki  staff  138  5 16 02:08 Pipfile

.venv というディレクトリ名は変更できないのかな。
個人的にはこちらの方が好みだけど、両者にメリットデメリットあるのかは不明。

ちなみに

仮想環境が作られた場所を確認するには --venv オプションを使う。

(pipenv-sandbox-9fl74ZVx)  ~/workspace/Python/pipenv-sandbox $ pipenv --venv
/Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx

React 16.3.0で追加されたStrictModeコンポーネントについて

2018-04-01のJS: TypeScript 2.8、React 16.3.0、TensorFlow.js - JSer.info を読んで。

React 16.3.0 から StrictMode コンポーネントというものが追加されたらしい。
公式ドキュメントを読んでみます。


StrictMode とは

StrictMode はアプリの潜在的な問題を検出するために追加されたコンポーネントコンポーネントだが Fragment などと同じく UI として画面に表示されるものはない。
<StrictMode>...</StrictMode> で囲まれた子孫コンポーネントに対し、いくつかのチェックを行う。

また development モードでのみ動作し、 production build 時には影響を与えない。


ScrictMode がチェックしてくれること

今のところ以下。今後のリリースで機能は追加予定とのこと(Additional functionality will be added with future releases of React.)

  1. 安全でないライフサイクルメソッドの使用(Identifying components with unsafe lifecycles)
  2. レガシーな string ref の使用(Warning about legacy string ref API usage)
  3. 予期せぬ副作用の検出(Detecting unexpected side effects)
    • (検出するために、特定のライフサイクルメソッドを二度実行する)


1. 安全でないライフサイクルメソッドの使用

背景として、v16.3.0 以降は非同期レンダリングなどのサポートのために一部のライフサイクルメソッド

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

が今後削除予定となった。
参考:Update on Async Rendering - React Blog (こっちはまだ読んでない)

<StrictMode> の子孫コンポーネントでこれらのライフサイクルメソッドを使用しているものがあれば、ブラウザのコンソールで warning が出力される。

(サンプル)

import React, { Component, StrictMode } from 'react';

class UnsafeComponent extends Component {
  componentWillMount() {
    console.log('componentWillMount');
  }

  componentWillReceiveProps(props) {
    console.log('componentWillReceiveProps');
  }

  render() {
    return <div>Unsafe Component</div>;
  }
}

export default function App() {
  return (
    <StrictMode>
      <UnsafeComponent />
    </StrictMode>
  );
}

(結果) f:id:dackdive:20180404015423p:plain

Warning: Unsafe lifecycle methods were found within a strict-mode tree:
    in App
    in AppContainer

componentWillMount: Please update the following components to use componentDidMount instead: UnsafeComponent

componentWillReceiveProps: Please update the following components to use static getDerivedStateFromProps instead: UnsafeComponent

Learn more about this warning here:
https://fb.me/react-strict-mode-warnings

該当のコンポーネント名と使っているライフサイクルメソッドが表示されている。


2. レガシーな string ref の使用

ref を使ってコンポーネントを参照するための方法はこれまで2通りあって

  1. ref="input" のように文字列で指定する
  2. ref={(element) => this.input = element} のように callback 関数で指定する

このうち 1 の文字列で指定する方にはいくつか問題があったらしく、ドキュメントでも 2 の方法を推奨していた。
<StrictMode> の子孫コンポーネントで 1 の string ref を使っている箇所があると、こちらも同様にブラウザのコンソールでwarning が出る。

(サンプル)

import React, { Component, StrictMode } from 'react';

class LegacyRef extends Component {
  handleClick = () => {
    const name = this.refs.name.value;
    console.log('LegacyRef', name);
  };

  render() {
    return (
      <div>
        <input ref="name" />
        <button onClick={this.handleClick}>click me</button>
      </div>
    );
  }
}

class NewRef extends Component {
  constructor(props) {
    super(props);

    this.nameRef = React.createRef();
  }

  handleClick = () => {
    const name = this.nameRef.current.value;
    console.log('NewRef', name);
  };

  render() {
    return (
      <div>
        <input ref={this.nameRef} />
        <button onClick={this.handleClick}>click me</button>
      </div>
    );
  }
}

export default function App() {
  return (
    <StrictMode>
      <LegacyRef />
      <NewRef />
    </StrictMode>
  );
}

(結果) f:id:dackdive:20180404021000p:plain

Warning: A string ref, "name", has been found within a strict mode tree. String refs are a source of potential bugs and should be avoided. We recommend using createRef() instead.

    in div (created by LegacyRef)
    in LegacyRef (created by App)
    in App
    in AppContainer

Learn more about using refs safely here:
https://fb.me/react-strict-mode-string-ref


余談: createRef() を使った新しい ref の方法

上のサンプルで、 NewRef コンポーネントがやっているのは v16.3.0 から追加された新しい ref の実装方法で、 createRef() という関数を使う。
従来の string ref のような書き方で、かつ string ref のときの問題点は解決されている。らしい。

参考:React v16.3.0: New lifecycles and context API - React BlogcreateRef API の項

なお createRef() の導入後も 2 の callback を使った方法はサポートされるので、置き換える必要はない。


3. 予期せぬ副作用の検出

背景として、React の動作時には大きく2つのフェーズがある。

  • render フェーズ:DOM に適用する必要のある変更を決定する。 render メソッドが呼ばれ、結果を直前の render の結果と比較する
  • commit フェーズ:React がすべての変更を DOM に適用する。 componentDidMountcomponentDidUpdate などのライフサイクルメソッドもこのフェーズで呼ばれる

一般に commit フェーズは速いが render は遅い。そのため、非同期レンダリングによってレンダリング処理を複数の小さな処理に分割し、ブラウザをブロックしないように停止と再開をしながらレンダリングを行う。
これにより、commit 前に render フェーズのライフサイクルメソッドが複数回実行される可能性が生じる。
(このあたりはよくわかっていない)

ので、これらのライフサイクルメソッドに副作用がないことが重要となる。
これらのライフサイクルメソッドとは具体的には以下。

  • Class コンポーネントconstructor メソッド
  • render
  • setState の第一引数に関数を渡したときの関数(updater と呼ぶらしい)
  • getDerivedStateFromProps (v16.3.0 から componentWillReceiveProps の代替として追加)

ただ、これらのメソッドに副作用がないことを自動的に検出することは難しいため、StrictMode ではこれらのメソッドを2回ずつ実行する。

(サンプル)

class SideEffect extends Component {
  constructor(props) {
    super(props);

    this.state = { count: 0 };
    console.log('SideEffect constructor');
  }

  increment = () => {
    this.setState((prevState, props) => {
      console.log('SideEffect updater', prevState);
      return {
        count: prevState.count + 1,
      };
    });
  };

  render() {
    console.log('SideEffect render');
    return <div onClick={this.increment}>{this.state.count}</div>;
  }
}

export default function App() {
  return (
    <StrictMode>
      <SideEffect />
    </StrictMode>
  );
}

(結果)

f:id:dackdive:20180404023839p:plain


コード

一応上げておく。

https://github.com/zaki-yama/react-strict-mode-example