One Step Ahead

プログラミングやエンジニアリング全般について書いていきます

VSCode拡張を自作する。~ viewに対するtitle menuの設定 ~

はじめに


これは前回の『VSCode拡張を自作する。~ activitybarにオリジナルのアイコンを表示する。~』の続きというか、おまけ記事になります。
activitybarにオリジナルアイコンを設定する方法や、一連の作り方を知りたい方は以前の記事から読んでみてください!

commandの設定


今回は前回記事を参考に、予めactivitybarにオリジナルアイコンを表示させ、viewの表示ができるところまで用意してあります。
今日はここから、タイトル通りviewにmenuを追加していきます。
(contributesは以下の通り。)

// package.json "contributes"部分のみ抜粋
{
  "contributes": {
    "views": {
      "sample-container": [
        {
          "id": "custom-container-id-1",
          "name": "Custom view1"
        },
        {
          "id": "custom-container-id-2",
          "name": "Custom view2"
        }
      ]
    },
    "viewsContainers": {
      "activitybar": [
        {
          "id": "sample-container",
          "title": "Sample",
          "icon": "./logo.svg"
        }
      ]
    }
  }
}

まずは、commandsを設定していきます。
commandsも今までと同様に、"contributes"の中に設定していきます。
設定項目は下記の通りです。

  • command : 登録するコマンドの識別子
  • title : UI上で表示されるコマンド名
  • icon : (省略可) アイコンでコマンドを表現したい場合に設定します。
  • category : (省略可) アイコンをカテゴリ分けすることができます。(コマンドパレットにGo: Install/Update Toolsと表示される場合のGoの部分に当たります。)
  • enablement : (省略可)コマンドを有効にします。

今回は、view titleにReloadボタンを表示させたいので、iconまで設定します。

{
  "contributes": {
    // ↓↓ 今回設定部分 ↓↓
    "commands" :[
      {
        "command": "sampleExt.reload-command",
        "title": "Reload",
        "category": "SampleExt",
        "icon": "$(refresh)"
      }
    ],
    // ↑↑ 今回設定部分 ↑↑
    "views": {
      "sample-container": [
        {
          "id": "custom-container-id-1",
          "name": "Custom view1"
        },
        {
          "id": "custom-container-id-2",
          "name": "Custom view2"
        }
      ]
    },
    "viewsContainers": {
      "activitybar": [
        {
          "id": "sample-container",
          "title": "Sample",
          "icon": "./logo.svg"
        }
      ]
    }
  }
}

icon部分で設定している$(refresh)はVSCodeに元々用意されているアイコンを参照しています。
他にどんなアイコンがあるのか気になる方は、公式サイトを見てみましょう!

commandの設定はこれで完了です。


次は先ほど、設定したコマンドをmenuに登録していきます。
menuもcontributesから設定が可能です。

{
  "contributes": {
    "commands" :[
      {
        "command": "sampleExt.reload-command",
        "title": "Reload",
        "category": "SampleExt",
        "icon": "$(refresh)"
      }
    ],
    // ~ 省略 ~
    "menus": {
      "view/title": [
        {
          "command": "sampleExt.reload-command"
        }
      ]
    }
  }
}

今回はtitle menuを設定したいので、view/titleを選択します。
ここで設定可能な項目は、下記の通りです。

  • command : commandsタグ配下に設定したコマンドの識別子
  • alt : (省略可) 代替えコマンドの設定。代替えコマンドを設定する場合は予め、commandsに代替えのコマンドを登録しておく必要があります。
  • group : (省略可)メニュー項目をグループ分けできます。(navigationという特殊なグループも存在しますが、これは後述します。)
  • when : (省略可) 特定の条件に応じて、メニュー表示を制御したい場合に使用します。

UI表示だけ設定するのであれば、これでOKです。
実際に表示すると下記のようにメニューが表示されていることが分かります。

f:id:EaE:20210127223606p:plain

登録したコマンドに関しては、コマンドパレットからも確認することができます。

f:id:EaE:20210127223656p:plain

ただ、ここで物足りないのは、title menuが折りたたまれてしまっている部分です。
せっかくアイコンも設定しているのに、ChromeのReloadボタンのようなアイコンが表示されていません。

こういった場合は、先ほど少しだけ紹介した、navigationという特殊なグループを設定することで解決ができます。 navigationは文字通り、Navメニュー専用のグループでこれを設定することで、アイコンが表示されるようになります。

{
  "contributes": {
    "commands" :[
      {
        "command": "sampleExt.reload-command",
        "title": "Reload",
        "category": "SampleExt",
        "icon": "$(refresh)"
      }
    ],
    // ~ 省略 ~
    "menus": {
      "view/title": [
        {
          // 特殊なグループ"navigation"を指定
          "command": "sampleExt.reload-command",
          "group": "navigation"
        }
      ]
    }
  }
}

これを実際に確認してみると....

f:id:EaE:20210127224507p:plain

今度はReloadアイコンが表示されているのを確認できました。

おまけ


navigationグループを活用することで、Explorerのようなtitle menuを簡単につくることができます。

  "contributes": {
    "commands" :[
      {
        "command": "sampleExt.1addFile-command",
        "title": "Add File",
        "category": "SampleExt",
        "icon": "$(file-add)"
      },
      {
        "command": "sampleExt.2addFolder-command",
        "title": "Add Folder",
        "category": "SampleExt",
        "icon": "$(file-directory-create)"
      },
      {
        "command": "sampleExt.3reload-command",
        "title": "Reload",
        "category": "SampleExt",
        "icon": "$(refresh)"
      },
      {
        "command": "sampleExt.4collapse-command",
        "title": "Collapse All",
        "category": "SampleExt",
        "icon": "$(collapse-all)"
      }
    ],,
    // ~ 省略 ~
    "menus": {
      "view/title": [
        {
          "command": "sampleExt.1addFile-command",
          "group": "navigation"
        },
        {
          "command": "sampleExt.2addFolder-command",
          "group": "navigation"
        },
        {
          "command": "sampleExt.3reload-command",
          "group": "navigation"
        },
        {
          "command": "sampleExt.4collapse-command",
          "group": "navigation"
        }
      ]
    }
  }
}

f:id:EaE:20210127225824p:plain

ちなみに、sampleExt.1addFile-command, sampleExt.4collapse-commandのようにコマンドの先頭部分に数字を入れているのは、コマンドの表示順を調整するためです。
title menuの表示はあくまでcommand名でソートされるため、順番を調整するためには名前つけを意識的に行うか、先頭に数値を入れるなどして調整する必要があります。

まとめ


  • title menuを表示するためにはまずcommandを定義する。
  • 定義したコマンドをmenus配下のview/titleに設定する。
  • アイコンを表示したい場合は特殊グループnavigationを設定する。

ここまではUI表示を定義しただけに過ぎないので、次こそは実際のコマンドを定義する部分に触れていきましょう。

関連記事


  1. VSCode拡張を自作する。~ activitybarにオリジナルのアイコンを表示する。~

Visual Studioで『Custom Project Templates』の作成

はじめに


Visual Studioには、元々コンソール アプリ(.NET Core)ASP.NET Core Web アプリケーションなど色々なProject Templateが用意されています。
既存のTemplateに加えて、『Custom Project Templates』を追加することもできるため、開発チームや自分用に予め用意しておけば、非常に開発が捗ります!!

今日は『Custom Project Templates』の作り方についてまとめていきます。

検証環境


  • Windows10 Pro
  • Visual Studio 2019

上記の環境で、検証を行っています。

Templateの作り方


知らないと何となく難しそうなTemplate作成ですが、比較的簡単に作成することができます。主なTemplateの作り方は、次の2通りです。

  1. 既存ProjectからExportする。
  2. .vstemplateファイルを自作する。

特に1. 既存ProjectからExportする。は非常に簡単で、開発で特定のProjectをテンプレート化したいという場合は、ボタンをクリックするだけでほぼ終わりです。

個人的には、ほとんどの場合が1の方法で作成しています。
なんでも良いので、適当なProjectをExportしてから、カスタマイズした方が.vstemplateファイルのタグを一から入力する必要がなくて、楽だからです。

既存ProjectからTemplateのExport


さっそく、既存ProjectからTemplateを作成していきます。
Visual Studioを開いたら、まずメニューのプロジェクト(P)を選択します。
プロジェクトを選択するとテンプレートのエクスポート(E)があるはずなので、それを選択します。

f:id:EaE:20210125211649p:plain

テンプレートのエクスポート(E)を選択すると出力メニューが開くので、ガイドに沿って進めていきます。

f:id:EaE:20210125211738p:plain

Project Templateを作成する場合は、プロジェクト テンプレート(R)にチェックが入っていることを確認して、出力対象のProjectを選択します。
今回のサンプルには、TargetProjectしか存在しないので、それを選択します。

f:id:EaE:20210125211941p:plain

テンプレート名は新規プロジェクト追加時に表示されるテンプレート名のことで、検索にも使用するので分かりやすい名前を付けておきましょう。
また、.NET Frameworkなど特定のフレームワークに依存する場合は、(.NET Framework)など識別できるようにしておくのがポイントです。

後は完了をクリックすれば、出力は完了です。 出力したテンプレートは、下記のフォルダに出力されます。

%USERPROFILE%\Documents\Visual Studio 2019\My Exported Templates

f:id:EaE:20210125212620p:plain

.zipファイルが確認できれば、出力は完了です。

オリジナルのテンプレートを配置する


完成したProject Templateを配置していきます。オリジナルで作成したものに関しては、User用のフォルダへ配置する必要があります。
(ちなみに、既存ProjectからExportした場合は自動的にUser用フォルダにも出力されます。)

%USERPROFILE%\Documents\Visual Studio 2019\Templates\ProjectTemplates

のフォルダ直下にTemplateを配置していきますが、ここでポイントがあります。
配置する際には、フォルダを.zip圧縮する必要があります。

f:id:EaE:20210125213114p:plain

(展開する必要はありません。)

加えて重要なポイントとして、圧縮したファイルのRootに.vstemplateファイルが含まれている必要があります。

f:id:EaE:20210125213228p:plain

先ほど、作成したTemplateにも.vstemplateファイルが含まれているのが確認できます。 これで準備完了です。

Error回避Tips


1. User用フォルダへの配置忘れ

ちなみにMy Exported Templates側の.zipファイルを修正して、User用フォルダに配置しない。というミスが良くあります。 あくまで、Templates\ProjectTemplates配下に配置したものが読み取られるので注意しましょう。

2. 圧縮方法を間違え

先ほど、.zipファイルにする必要があるといいましたが、ここにもポイントがあります。 自作したTemplateをフォルダごと圧縮してしまうと、.zipファイルはフォルダが入れ子の状態になってしまいます。 あくまで、Rootに.vstemplateファイルが含まれている必要があるので注意しましょう。
(圧縮したいファイルやフォルダを全選択した状態で、送るから圧縮すれば、入れ子にはなりません。)

自作Templateから新規Project作成


それでは最後に、自作TemplateからProjectを作成してみます。 新しいプロジェクトの追加から、先ほどの"SampleExportTemplate (.NET Core)"を検索します。

f:id:EaE:20210125215222p:plain

先ほどの自作Templateが確認できましたね!
後はプロジェクトを作成するだけです。

f:id:EaE:20210125215334p:plain

対象にしたTargetProjectと同じ構成のProjectが作成されたことが確認できました。

既存のテンプレート保存場所


Templateを作成するのであれば、既存Templateに一通り目を通しておくと良いと思います。
下記のフォルダに配置されていますが、くれぐれも壊さないように注意しましょう!!

%ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\ProjectTemplates\<Language>\<Locale ID>

まとめ


  • 自作Templateを作るのは意外に簡単。特に既存のProjectからのExport。
  • 自作Templateを配置する際にミスが多いので注意する。

これだけでも十分に便利ですが、今回の方法だと少し不便な部分があります。
例えば、新規プロジェクト作成の際に入力したプロジェクト名に応じて、ファイル名を変更したい場合などは単にExportするだけでは対応ができません。
次は.vstemplateを編集して、より便利なTemplateを作成していきます。

VSCode拡張を自作する。~ activitybarにオリジナルのアイコンを表示する。~

はじめに


activitybarに自作拡張機能のアイコンを表示するところで躓いたので、備忘録として残しておきます。

activitybarってどこ?


今回対象になっている『activitybar』は下記の写真のオレンジ色の部分です。
Exploer, Docker拡張といったアイコンが表示されている場所のことを指します。

20210124210037

ここに自作のアイコンを表示させていきます。

(VSCodeのUI各部名称が分からないという方は公式サイトを確認しましょう!!)

activitybarにビューコンテナを登録する


まず、activitybarに自作のアイコンを表示させるためには、ビューコンテナに自作のアイコンの設定を登録する必要があります。
登録するために、contributes.viewsContainers.activitybarに対して、コンテナの登録を行います。

設定が必要なプロパティは下の3つです。

  • id : 作成するコンテナの識別子
  • title : 表示される名前
  • icon : 設定するアイコン(package.jsonを基準とする相対パスになります。)
// package.json
{
  "contributes": {
    "viewsContainers": {
      "activitybar": [
        {
          "id": "sample-container",
          "title": "Sample",
          "icon": "./logo.svg"
        }
      ]
    }
  }
}

idは、Viewに対してコンテナを登録する際に使用します。
titleはViewに描画された際、アイコンに対してマウスカーソルをhoverさせると画面に表示されたりします。
iconとして表示されるイメージは、50×40四方のブロックの中央に、24×24のサイズで描画されます。推奨ファイルは.svgですが、画像ファイルの種類は問いません。
(画像ファイルの種類は問わない。そうですが、私は.svg以外で試したことはありません。)

これで、コンテナの登録が完了です。
次は登録したコンテナをViewに表示させる作業です。

viewsに自作のコンテナを登録する


コンテナの登録はcontributes.viewsに対して行います。
先に登録済みの、package.jsonを載せておきます。

// package.json
{
  "contributes": {
    "views": {
      /* ここで先ほど登録したコンテナのidを指定する。 */
      "sample-container": [
        // viewの登録はObject配列になっているため、複数登録可能。
        {
          "id": "custom-container-id-1",
          "name": "custom-container-id-1-name"
        }
      ]
    },
    "viewsContainers": {
      "activitybar": [
        {
          "id": "sample-container",
          "title": "Sample",
          "icon": "./logo.svg"
        }
      ]
    }
  }
}

contributes.viewsには、すでに組み込みとして"explorer", "debug", "scm"などが用意されていますが、今回は自作のコンテナなので先ほど登録したidを使用します。
コンテナを設定したら、次にコンテナ起動時(アイコンクリック時)に表示されるviewを定義していきます。
ここで、必須にとなるプロパティはid, nameです。

  • id : view識別子
  • name : 表示名
  • type : (省略可)treewebviewのどちからを選択可
  • icon : (省略可)このアイコンは、Viewの名前が表示できない場合に代わりに表示されます。
  • visibility : (省略可)collapsed, visible, hiddenから選択できます。(ただし、これは初期設定で、その後はユーザー設定に依存する。)
  • when : (省略可)viewの表示条件を設定できます。
  • contextualTitle : (省略可)viewがデフォルトのビューコンテナから移動され、追加のコンテキストが必要な場合に使用されます

Jsonの構造からわかる通り、view定義はコンテナの配下に複数定義することができます。

↑でアイコンの設定は完了しているため、これをDebugすると自作アイコンと登録したviewが表示されます。 f:id:EaE:20210124215125p:plain

実験として、viewを2つ定義した場合も見てみましょう。

// package.json
{
  "contributes": {
    "views": {
      "sample-container": [
        // view 1つ目
        {
          "id": "custom-container-id-1",
          "name": "custom-container-id-1-name"
        },
        // view 2つ目
        {
          "id": "custom-container-id-2",
          "name": "custom-container-id-2-name"
        }
      ]
    },
    "viewsContainers": {
      "activitybar": [
        {
          "id": "sample-container",
          "title": "Sample",
          "icon": "./logo.svg"
        }
      ]
    }
  }
}

↑の状態で、Debugを開始すると2つのViewを確認することができます。 f:id:EaE:20210124215521p:plain

これで自作アイコンの表示は完了です。
ただ、あくまでも『自作アイコンの表示』までなので、この状態では何の機能もありません。ただ、『アイコンをクリックできるだけ。』の状態です。
実際に使えるものにしていくためには、viewに応じた機能をactivateする必要があります。

まとめ


  • contributes.viewsContainers.activitybarに対して自作コンテナを登録する。
  • 自作のコンテナをcontributes.viewsに登録する。
  • コンテナを登録する際、viewは複数定義が可能。

VSCode拡張機能『MyCheatsheet for VS Code』 をリリースしました。

はじめに


タイトル通り、『MyCheatsheet for VS Code』 をリリースしました。
リリースしたと言っても今日というわけではなく、昨年10月にこっそりとリリースしています。 ただ、ひっそりリリースしたはいいものの、作った経緯とかをどこにも書いていなかったので、今日はご紹介交えてそこら辺を書いていきたいです。

Githubはこちら
Marketplaceはこちら

MyCheatsheet for VS Codeって何?


『Cheatsheet』はカンニングペーパーのことで、カンニング前提の社会人にとっては強い味方ですw
使用頻度の少ないコマンドや、特殊な文法などをまとめておくと検索の手間が減り非常に便利です。
実際にVSCode拡張機能でもいくつかのCheatsheetが公開されており、『Git Cheatsheet』や『Vue Cheatsheet』などがあります。

上のようにCheatsheetをダウンロードして参照するという拡張機能はすでにありましたが、自分でCheatsheetを作るという拡張機能はなかったので、自分で作ろうと思い立って作成したのが、『MyCheatsheet for VS Code』です。

機能紹介


この拡張機能は...

  • ワークスペースのRootに.cheatsheet.mdファイルが存在する場合。
  • ユーザーホームディレクトリに.cheatsheetsフォルダが存在する場合。

のいずれかで機能します。

『ワークスペースのRootに.cheatsheet.mdファイルが存在する場合』というのはイメージしやすいかなと思いますが、『ユーザーホームディレクトリに.cheatsheetsフォルダが存在する場合』というのがイメージしにくいかと思います。

端的に言えば、C:\Users\myName\cheatsheetsが存在している場合。このようなケースで機能が有効になります。(Linux, Macであれば、~/.cheatsheetsフォルダが存在している場合です。) 加えてWikiにも記載しましたが、フォルダ構造が重要です。

## windows
C:\Users\myName\.cheatsheets
 |
 |-- golang
 |     |-- .cheatsheet.md
 |
 |-- typescript
       |-- .cheatsheet.md

or 

## Linux or mac
~/.cheatsheets
 |
 |-- golang
 |     |-- .cheatsheet.md
 |
 |-- typescript
       |-- .cheatsheet.md

上のように、フォルダ名直下に.cheatsheet.mdファイルが必要になります。

Cheatsheet参照機能

show demo Ctrl + Shift + P(Win, Linux) もしくはCmd + Shift + P(Mac)でコマンドパレットを開き、Show Cheatsheetコマンドを検索します。
ワークスペースのRootに.cheatsheet.mdファイルが存在する場合は、下のように表示されます。

show wk-ch

ホームディレクトリにフォルダが存在する場合には、下記のように表示されます。

show wk-ch

後は、参照したいシートを選択すればOKです。

Global Cheatsheet初期化機能

Ctrl + Shift + P(Win, Linux) もしくはCmd + Shift + P(Mac)でコマンドパレットを開き、Init GlobalCheatsheetsDirectoryコマンドを検索します。
これはホームディレクトリ初期化コマンドで、ホームディレクトリに.cheatsheetsディレクトリが存在しない場合は、ディレクトリを作成します。

Cheatsheet追加機能

add demo Ctrl + Shift + P(Win, Linux) もしくはCmd + Shift + P(Mac)でコマンドパレットを開き、Add Cheatsheetコマンドを検索します。
コマンドを選択すると、workspaceglobalの2つの選択肢が表示されます。
.cheatsheetディレクトリが存在しない場合は、globalは表示されません。

workspaceは文字通り、現在VSCodeで開いているワークスペースのRoot直下にシートを作成できます。 リポジトリでシートを共有したい場合などを想定しています。

次にglobalですが、これがHomeディレクトリ直下のシートになります。 globalコマンド選択後に、テキストボックスが表示されるので作成したいシートの名前を入力すればシートが作成されます。

Cheatsheet編集機能

edit demo Ctrl + Shift + P(Win, Linux) もしくはCmd + Shift + P(Mac)でコマンドパレットを開き、Edit Cheatsheetコマンドを検索します。
参照と同じ形式で、シートの候補が表示されるので、対象のシートを選択することでシートの編集を行うことができます。

今後の機能追加予定


今後のUpdate予定としては、

  • Gitと連携してリポジトリ機能を付けていくこと
  • GithubへのPush、Cloneをサポート

が目標になるかなと思っています。
人によっては、PCを複数台使っている人もいると思うので、PCへの簡単な共有を考えるとリポジトリ作成が必須になるかなと思っています。

まとめ


  • 自作のCheatsheetを作成できるVSCode用の拡張機能。
  • リポジトリ管理ができるような機能を追加予定。
  • Githubとの連携もできたらいいなぁと模索中。

今後の情報はこの記事のUpdateと更新情報記事でやっていきます!

golangのinit関数は複数回定義できる

はじめに


そのまんまタイトル通りの内容ですw
Github で いくつかgolangのリポジトリをごそごそしている時に、init関数が1ファイルに対して、複数回定義されているのを発見しました。
Packageの初期化をするための関数という認識だったので、勝手に『1回だけの定義なんだろうな。』と思っていました。お恥ずかしい。
復習も含めて、再度init関数について調べなおしてみました。

init関数の役割


init関数は特殊な関数で、Packageの初期化を行う際に機能します。

package main

import (
    "fmt"
)

var message string

func init() {
    message = "Hello world"
}

func main() {
    fmt.Println(message)
}

main関数実行前の初期化として作用します。

init関数の呼び出し


init関数は、PackageをImportした際に呼び出されます。 あくまで、『importした際』に呼び出されるのがポイントです。
これを検証するために、下記のような構造のソースコードを用意しました。

src --- main.go
     |
     | --- lib1
     |      | --- lib.go
     |
     | --- lib2
            | --- lib.go
// /src/lib1/lib.go
package lib1

import "fmt"

var libName = "lib1"

func init() {
    fmt.Printf("%s init called!!\r\n", libName)
}

func Func() {
    fmt.Printf("%s Func Done.\r\n", libName)
}
// /src/lib2/lib.go
package lib2

import "fmt"

var libName = "lib2"

func init() {
    fmt.Printf("%s init called!!\r\n", libName)
}

func Func() {
    fmt.Printf("%s Func Done.\r\n", libName)
}

Packageに相当する2つのファイルでは、それぞれinit関数を定義しており、呼び出しのタイミングでConsole上に文字を出力しています。

/src/main.go
package main

import (
    "fmt"

    "github.com/atEaE/go-sample/lib2"
    "github.com/atEaE/go-sample/lib1"
)

func init() {
    fmt.Println("main called")
}

func main() {
    lib1.Func()
    lib2.Func()
    fmt.Println("Hello world!")
}

main.goでは、2つのPackageを参照し、それぞれmain関数の中でPackageの関数を呼び出しています。 ここでは呼び出し順を確認するために、下記ような順番でソースコードを記載しています。

  1. lib2をImportする.
  2. lib1をImportする.
  3. lib1内のFunc関数を呼び出す。
  4. lib2内のFunc関数を呼び出す。

※ VSCode でgofmtなどをしていると保存の際に自動的に並び順がフォーマットされてしまうので、あえてメモ帳などを使って編集しています。

なんとなく(私だけかも知れないが...)、main関数内の記載順に呼びされそうな感じがしますが、上のコードを実行するとimport順にinit関数が呼びされることが確認できます。

C:\workspace\sample\go-sample> go run main.go
lib2 init called!!
lib1 init called!!
main called
lib1 Func Done.
lib2 Func Done.
Hello world!

加えて、呼び出し元であるmain.goinit関数はimport終了後に呼びされているのが確認できます。

init関数の定義


init関数は1ファイルに限らず、Package内で複数回定義ができます。
これについては、公式のドキュメント内の『Package initialization』の項に記載がありました。

Multiple such functions may be defined per package, even within a single source file.
--- このような関数(init関数)は、1つのソースファイル内であっても、パッケージごとに複数定義することができます。--- The Go Programming Language Specification

これも検証用のソースコードを用意しました。

src --- main.go
     |
     | --- lib
     |      | --- lib1.go
            | --- lib2.go
// /src/lib/lib1.go
package lib

import "fmt"

var multiPle1 string

func init() {
    multiPle1 = "lib1 init called"
}

func init() {
    fmt.Printf("%s\r\n", multiPle1)
}

func Hello() {
    fmt.Print("Hello, ")
}
// /src/lib/lib2.go
package lib

import "fmt"

var multiPle2 string

func init() {
    multiPle2 = "lib2 init called"
}

func init() {
    fmt.Printf("%s\r\n", multiPle2)
}

func World() {
    fmt.Print("World!!")
}

libPackage全体でみると4回のinit関数が定義されており、各ファイルで2回ずつinit関数を定義しています。

// /src/main.go
package main

import "github.com/atEaE/go-sample/lib"

func main() {
    lib.Hello()
    lib.World()
}

このコードの事項結果は下記のようになります。

C:\workspace\sample\go-sample> go run main.go
lib1 init called
lib2 init called
Hello, World!!

初期化処理が煩雑な場合などは、initを分割した方がメンテナンスが高くなりそうですね。

まとめ


  • init関数はimport時に呼び出される。
  • init関数は1ファイルに限らず, Package毎に複数回定義できる。

React Effect Hooksの覚え書き(基本)

はじめに


Effect Hooks はReact16.8から導入された機能です。
Function Component に対して、Class ComponentのcomponentDidMountcomponentWillUnmountなどに相当する処理を実装する場合に使用します。

useEffectを使うたびにcomponentDiMountcomponentWillUnmountの使い分けについて調べている気がしたので、いい加減まとめておきます。

React Component の Lifecycle


React の Lifecycleは大まかにMount, Update, Unmountのサイクルに大別されます。 f:id:EaE:20210118150715p:plain

Mountサイクル

ComponentのインスタンスがDOMに挿入される際のサイクルになります。

Updateサイクル

PropsState の変更によって再レンダリングが発生する場合のサイクルになります。

Unmountサイクル

Componentが DOM から削除されるときのサイクルになります。

各VersionにおけるLifecycleを確認する場合には、公式で紹介されているこちらのサイトが便利です。

Effect Hooksが与える副作用


これについては、公式サイトに名言されています。

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

--- Reactクラスのライフサイクルメソッドに慣れている方は、useEffect HookをcomponentDidMount、componentDidUpdate、componentWillUnmountを組み合わせたものと考えることができます。---

Class components における componentDidMountcomponentDidUpdatecomponentWillUnmountの役割を担うことができます。

公式サイトのサンプルコードを参考に、 componentDidMountcomponentDidUpdatecomponentWillUnmountを実装したClassとFunction Componentを↓に記載します。
やっていることは少し奇妙ですが、ButtonをClickすることでドキュメントタイトルを切り替えています。

import React from 'react';
import ReactDOM from 'react-dom';

interface Props {}
interface State {
  count: number;
}
class ClassComponent extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      count: 1
    }
  }

  componentDidMount() {
    document.title = `Called MountFunc!!`;
  }

  componentDidUpdate() {
    document.title = `Called UpdateFunc!! Cnt = ${this.state.count}`;
  }

  componentWillUnmount() {
    console.log("Called UnmountFunc");
  }

  setCount() {
    this.setState({ count: this.state.count + 1});
  }

  render() {
    return (
      <div>
        <h1>Counter</h1>
        <button onClick={_ => this.setCount()}>Click</button>
      </div>);
  }
}

ReactDOM.render(
  <React.StrictMode>
    <ClassComponent />
  </React.StrictMode>,
  document.getElementById('root')
);

Class Componentの場合は、componentDidMountcomponentDidUpdatecomponentWillUnmountをそれぞれ実装することで、Lifecycleに応じて処理を実行することができます。

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

interface Props {}

function FunctionComponent(props: Props) {
  const [count, setCount] = useState(1);
  const initState = 1;

  useEffect(() => {
     // componentDidMount &  componentDidUpdate
    if (initState === count) {
      // componentDidMount専用
      document.title = `Called MountFunc!!`;
    } else {
      // componentDidUpdate専用
      document.title = `Called UpdateFunc!! Cnt = ${count}`;
    }   

    // 戻り値として、componentWillUnmount
    return console.log("Called UnmountFunc");
  }) 

  return (
    <div>
      <h1>Counter</h1>
      <button onClick={_ => setCount(count + 1)}>Click</button>
    </div>
  );
}

ReactDOM.render(
  <React.StrictMode>
    <FunctionComponent />
  </React.StrictMode>,
  document.getElementById('root')
);

Function Componentの場合は、useEffectだけでcomponentDidMountcomponentDidUpdatecomponentWillUnmountを表現する必要があります。
componentDidMountcomponentDidUpdateはuseEffectに引き渡す関数本体に処理を記載することで表現できます。 今回は、MountサイクルとUpdateサイクルを分けて表現したかったので、初期ステータスと比較して呼び出す処理を変更しています。

componentWillUnmountは、useEffectの戻り値として、関数を返却することで表現することができます。

単純に使うだけであればこれだけOKですが、レンダリング毎のuseEffectの抑制や、Mount & Unmount時のみの実行などチューニングを行う場合には、より複雑になるのでそれはまた次の機会にします。

まとめ


  • useEffectを使うことで、関数コンポーネントでもcomponentDidMountcomponentDidUpdatecomponentWillUnmountに相応する作用をもたらすことができる。
  • useEffectを使うことで、より細かなチューニングも行うことができる。

参考・引用


意外と大切な開発初期のVSCode準備作業

はじめに


開発初期の準備作業は意外と大変です。
特にプロジェクトに応じてチームが流動的な変わっていく場合には、常に同じような環境で開発できるとは限りません。

『Aのプロジェクトはすんなり開発にJoinできる!』
『Bのプロジェクトは初動が遅いなぁ。』

なんてことが普通にあります。

今日は『開発初期におけるVSCodeの準備作業』と題して、簡単なTipsまとめです。

共有したい設定情報はリポジトリに含める


.vscodeフォルダ配下の設定は、Workspace毎に適用されるため、
.vscode/settings.jsonに予めエディタの設定情報を記載してコミットしておくことで、Formatterの設定や、文字コード、Whitespaceのトリミングなどの設定を共有することができます。

{
  "editor.tabSize": 2,
  "editor.insertSpaces": true,
  "editor.renderWhitespace": "boundary",
  "editor.rulers": [140],
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "files.encoding": "utf8",
  "files.trimTrailingWhitespace": true,
  "files.insertFinalNewline": true,
  "files.eol": "\n",
  "search.exclude": {
    "public/**": true,
    "node_modules/**": true
  }
}

ファイルのEncodingやEOLの設定をあらかじめ入れておくことによって、エディタ最適化の時間を短縮することができます。
これだけでもある程度は効果的ですが、特定の拡張機能に対する設定を共有したい場合は別の設定情報も合わせて加える必要があります。

推奨拡張機能を全体で共有する


特定の拡張機能に依存した設定を全体で共有したい場合、推奨拡張をREADME.mdに記載する場合が多いかと思いますが、.vscode/extensions.jsonファイルを使えば、推奨拡張のインストールを開発者に促すことができます。

{
  "recommendations": [
      "dbaeumer.vscode-eslint",
      "esbenp.prettier-vscode"
  ],
  "unwantedRecommendations": []
}

recommendationsには推奨される拡張機能を。
何らかの理由により推奨されない拡張機能がある場合には、unwantedRecommendationsに拡張機能名を記載します。 後は、Cloneしたソースコードを開けば拡張機能のインストールを促すINFOが表示されます。

f:id:EaE:20210114210419p:plain

INFOが表示されない場合は、Ctrl + Shift + Pでコマンドレットを開き、Show Recommended Extensionsを入力することで、推奨機能の確認を行うことができます。

まとめ


  • .vscode/settings.jsonをリポジトリにコミットすれば、設定情報は共有できる。
  • 拡張機能に依存した設定を共有する場合は、.vscode/extensions.jsonも合わせて使うと良い。

初期開発に関わる作業は、特定の人しかやらないことが多いので意外と属人化しやすい部分だなと思っています。 小さなことですが、スムーズな開発にはこういった小さな気づかいや工夫が大切ですね。