コンパイラかく語りき

import { Fun } from 'programming'

VSCode で Prettier の設定が無視されて効かない問題

tl;dr

./.prettierrc.json を作成する。

Prettier の設定が無視される

VSCodeformatOnSave 機能で Prettier の設定が効きませんでした。

Prettier 設定を workspace で行ったり、.prettierrc に書いたりしましたが、どちらも VSCode から無視される状態。

issue

prettier-vscode レポジトリにてこんな issue comment を発見。

.prettierrc.json works. prettierrc.js and prettierrc.config.js do not work.

https://github.com/prettier/prettier-vscode/issues/371#issuecomment-447757781

.prettierrc.json なら動くとのこと。試したところ、自分の環境では問題が解決しました。

【JavaScript, VSCode, Prettier】import 文を自動改行

td;dr

VSCode で Prettier の設定を行い、 import 文を自動改行する。

f:id:chuck0523:20190821220030g:plain

概要

ESLint/TSLint によって1行のコードの長さを制限することができます。

import 文もこの対象になりますが、auto fix が効かないので手動で修正することになり、やや面倒です。

これを自動修正するには、 Prettier を使います。

VSCode の自動フォーマット設定

⌘+, で設定画面を開き、prettier の自動フォーマットが効くようにします。

自分はファイル保存時にフォーマットしてほしいので、 editor.formatOnSave を有効にしました。

f:id:chuck0523:20190821213142p:plain

他には、ペースト時にフォーマット(editor.formatOnPaste)、タイピングごとにフォーマット(editor.formatOnType)もありますのでお好みで。

ちなみに、ESLint/TSLint を利用している場合は、以下の記事も参照ください。

VSCodeにおける自動フォーマットで整形が崩れる問題への対処法 - qiita.com

VSCode Prettier パッケージ

prettier-vscode をインストールします。

Prettier の改行行数を設定

プロジェクトルートに .prettierrc.json ファイルを作成し、 printWidth 設定を追加します。

{
  "printWidth": 140
}

これで、140字を超える行数に関しては、改行が挿入されるようになります。

ちなみに、.prettierrcprettierrc.js ではなく .prettierrc.json を使った理由については以下も参照ください。

VSCode で Prettier の設定が無視されて効かない問題 - chuckwebtips.hatenablog.com

VSCode で行番号カラムの幅を狭める

VSCode の行番号カラムの幅が広いと感じるのは自分だけでしょうか?

特にエディタ画面をスプリットすると、なおさらカラムの幅が気になります。

f:id:chuck0523:20190821204658p:plain

実はこのカラム、3つの要素から成り立っています。

左から順に、グリフマージン、行番号、折りたたみアイコンです。行番号と折りたたみ機能は利用しますが、グリフマージンは必要なかったのでその部分を消してしまうことに。

ちなみに、グリフマージンにはデバッグポイントを設定することができますよね。

f:id:chuck0523:20190821204757p:plain

このチェックを外します。 すると、グリフマージンの分だけカラムが狭まりました。

f:id:chuck0523:20190821205022p:plain

参考: change the width of the line number #48791 - github.com

【JavaScript】Vue.js で retweet / link リンク【Twitter】

概要

ツイートを埋め込むような web サービスを作る場合、リツイートとイイネのリンクを実装することがあるかもしれません。

そのリンク(正式名称はインテント)を Vue.js で作成する方法です。

実装

リンクの定義です。

// ツイート ID (いわゆる id_str)
const tweetId = "foo"

// イイネ インテント リンク
const likeUrl = `https://twitter.com/intent/like?tweet_id=${tweetId}`

// リツイート インテント リンク
const retweetUrl = `https://twitter.com/intent/retweet?tweet_id=${tweetId}`

マークアップです。

/* イイネ インテント */
<a :href="likeUrl">
     /* お好みでアイコンなど */
</a>

/* リツイート インテント */
<a :href="retweetUrl">
     /* お好みでアイコンなど */
</a>

上記の a タグをクリックすると、以下のようなリツイート確認画面が開きます。

f:id:chuck0523:20190819201948p:plain

参考 URL

公式ドキュメント: Web Intents - developer.twitter.com

【Node.js】Mongoose の upsert で、 ドキュメント作成時のみ追加するフィールド

Mongoose の upsert 便利ですよね。正しくは、upsert: true オプションですね。ドキュメントが存在しなければ作成、存在すれば更新をしてくれます。

参考: How do I update/upsert a document in Mongoose? - stackoverflow.com

さてそんな upsert を行う時、作成時のみに何かしたいというケースがあります。例えば、 created_at のようなフィールドの作成でしょうか。一度作成されたら更新されたくない、といったフィールドです。

そんな時、 $setOnInsert が便利です。

以下、サンプルコードです。

import mongoose from '../libs/mongoose';

const { Schema } = mongoose;

// schema
const ThingSchema = new Schema({
  id: Number,
  name: String,
  created_at: Date,
  updated_at: Date,
});

const Thing = mongoose.model('Thing', ThingSchema);

Thing.upsertThing = (thingId, thingName) => Thing.findOneAndUpdate(
  // ドキュメント検索条件
  { id: thingId },

  // ドキュメントデータ
  {
    // 常に適用される
    $set: {
      id: thingId,
      name: thingName,
      updated_at: new Date(),
    },
    // 作成時にのみ適用される
    $setOnInsert: {
      created_at: new Date(),
    },
  },

  // オプション
  { new: true, upsert: true },
)

$setOnInsert - docs.mongodb.com

If an update operation with upsert: true results in an insert of a document, then $setOnInsert assigns the specified values to the fields in the document. If the update operation does not result in an insert, $setOnInsert does nothing.

【JavaScript】Google Optimize を React アプリケーションで使う

f:id:chuck0523:20190730222918p:plain

Google Optimize を React アプリケーションで使う

概要

AB テストツールである Google OptimizeReact.js で作るアプリケーションで使う。

React アプリケーション作成

簡単に React アプリケーション を作成できる create-react-app を使う。

$ npx create-react-app APPLICATION_NAME
$ cd APPLICATION_NAME
$ npm start

APPLICATION_NAME は何でもよい。

Optimize

アカウントが未作成の場合、 Google Optimize にて作成する。

アカウントを作成したら、コンテナを1つ作成し、名前をつける。

コンテナを作成したら、エクスペリエンスを作成する。

名前: My first experience
エディタページ: http://localhost.domain:3000

とする。名前は何でも良い。 Optimize はローカル環境で動かないので、hosts ファイルに編集を加える。

# /etc/hosts
127.0.0.1 localhost.domain

なぜ、ローカル環境で Optimize が動かないかの理由はこちら

エクスペリエンスを作成したら、パターン(variant) を作成する。 パターンのデフォルト名は パターン 1

パターンを作成したら、Google Analytic のプロパティと連携する。

f:id:chuck0523:20190730212730p:plain

連携ボタンは↑のような感じ。(2019/07/30時点)

連携が完了すると、以下のスニペットがモーダル表示される。

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_PROPERTY_ID></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', 'GA_PROPERTY_ID', { 'optimize_id': 'OPTIMIZE_ID'});
</script>

各 ID はぼかして書いていることに留意。

コードベースに戻り、上記のスニペットindex.html<head /> 内に貼り付ける。

Google Optimize のダッシュボードに戻り、メインの目標をページビュー数として設定する。

f:id:chuck0523:20190730214010p:plain

「設定」の項目までスクロールダウンして、「オプティマイズのインストール」を実行する。 この時、Chrome 拡張のインストールをオススメされる。

オプティマイズのインストール」が成功すると、遷移先にて以下のようなモーダルが表示される。

f:id:chuck0523:20190730214347p:plain

エクスペリエンスを実行する

ダッシュボードにて、「パターン1」を編集する。

編集ボタンを押すと、localhost.domain:3000 が表示され、編集可能状態に入る。

UI 要素に hover すると、以下のように選択される。

f:id:chuck0523:20190730214829p:plain

p タグ内のテキストを Google Optimize on React! のように編集する。 編集が完了したら、保存を選択して、ダッシュボードに戻る。

エクスペリエンスの実行準備が整ったので、「開始」ボタンを押す。 シークレットブラウザで localhost.domain:3000 を開くと、ABテストが実行されていることが確認できる。

自前実装する場合

これまでは、Google Optimize のダッシュボードからエクスペリエンスを作成した。

より複雑なテストには自前で実装することもできる。

エクスペリエンスを新規作成し、ダッシュボードの最下部にある「アクティベーションイベント」を選択。 すると、右側のモーダルメニューとして「評価するイベント」を選択することができる。

f:id:chuck0523:20190730215948p:plain

これを「カスタムイベント」に設定し、エクスペリエンスを開始する。

すると、コード側からパターン情報を取得できる。

  async componentDidMount() {
    if (window.dataLayer) {
      await window.dataLayer.push({ event: "optimize.activate" });
    }
    this.intervalId = setInterval(() => {
      if (window.google_optimize !== undefined) {
        const variant = window.google_optimize.get("EXPERIENCE_ID");
        this.setState({ variant });
        clearInterval(this.intervalId);
      }
    }, 200);
  }

variant には 10 と言ったデータが渡ってくる。

参考URL

How to Add Google Optimize A/B Testing to Your React App in 10 Lines of Code - medium.com

【JavaScript】forEach と for文 における配列イテレーション差異

forEach と for文 における配列イテレーション差異

解説

JavaScript において、 forEach と for 文の違いはいくつかあると思うのですが、今回は配列イテレーションの差異について書きます。

new Array(n) のような形で配列を作成すると、empty item が初期値として設定されます。

// 要素数10の配列を新規作成
const ary = new Array(10)

// 0番目と3番目の要素に文字列を設定
ary[0] = "foo"
ary[3] = "bar"
> console.log(ary)
[ 'foo', <2 empty items>, 'bar', <6 empty items> ]

この状態の配列をイテレーションすると、for文とforEachにおいて挙動に差異が生まれます。

for文が empty item まで含めてイテレーションするのに対して、

// 10回標準出力される
for (let i = 0; i < ary.length; i++) {
  console.log(ary[i])
}
// foo
// undefined
// undefined
// bar
// undefined
// undefined
// undefined
// undefined
// undefined
// undefined
// undefined

forEach では empty item を除いた要素がイテレーションされます。

// 2回照準出力される
ary.forEach((item) => {
  console.log(item)
})
// foo
// bar

URLs

自分用 CSS Grid チートシート

CSS グリッドレイアウトに関するチートシートです。自分用なので結構雑です。

developer.mozilla.org

用語

Grid container

グリッドコンテナ。グリッドレイアウトを敷くもの。

Grid item

グリッドコンテナに包含されるアイテム。

Grid column

レイアウト上の列。

Grid row

レイアウト上の行。

Grid track

グリッドトラック。レイアウト上の縦線と横線のこと。 4x4なレイアウトなら、+ のようなトラックが引かれる。

Grid area

グリッドエリア。任意のグリッドトラックに囲まれた領域。

Grid cell

グリッドセル。グリッドトラックをまたぐこともあるグリッドエリアとは異なり、単一のグリッド領域を指す。

プロパティ

コンテナ系

display

Grid レイアウトを使うために指定してなくてはいけないもの。

.container {
  // ブロックレベルのグリッドコンテナを敷く。
  display: grid; 

  // インラインレベルのグリッドコンテナを敷く。
  display: inline-grid: 
}

grid-template-columns, grid-template-rows

それぞれ、行と列のサイズを指定する。

.container {
  // 1列目が60px, 2列目が80pxになる
  grid-template-columns: 60px 80px; 

  // 1行目が40px, 2行目が4em, 3行目が40pxになる
  grid-template-rows: 40px 4em 40px; 
}

grid-template-area

名称付きグリッドエリアを指定する。

.container {
  // 左上4マスを"a", 中央下1マスを"b", 右下1マスを"c" と指定
  grid-template-areas: 
            "a a ."
            "a a ."
            ". b c";
}

grid 系の他のプロパティから利用可能。

grid-template

grid-template-columns, grid-template-rows, grid-template-areas のショートハンド。

.container {
  grid-template: 
            "a a ." minmax(50px, auto)
            "a a ." 80px
            "b b c" auto / 2em 3em auto;
}

grid-column-gap, grid-row-gap

水平と垂直のグリッドトラックの幅を指定。

.container {
  // 列グリッドトラックの幅を10pxにする
  grid-column-gap: 10px;

  // 行グリッドトラックの幅を10pxにする
  grid-row-gap: 15px;
}

grid-gap

grid-column-gap, grid-row-gap のショートハンド。

.container {
  // グリッドトラックの幅を、列15px, 行10px で指定
  grid-gap: 15px 10px;
}

justify-items

グリッドアイテムの水平方向の寄せ方を指定。

.container {
  // 中央寄せ
  justify-items: center;
  
  // 始点寄せ(始点が左なら左寄せ)
  justify-items: start;

  // 終点寄せ(終点が右なら右寄せ)
  justify-items: end;

  // 他にも色々ある
}

align-items

グリッドアイテムの垂直方向の寄せ方を指定。

.container {
  // 中央寄せ
  align-items: center;
  
  // 始点寄せ(始点が左なら左寄せ)
  align-items: start;

  // 終点寄せ(終点が右なら右寄せ)
  align-items: end;

  // 他にも色々ある
}

place-items

justify-items と align-items のショートハンド。

.container {
  // 垂直方向には中央寄せ、水平方向には終点寄せ
  place-items: center end;

  // 両方中央寄せ
  place-items: center;
}

justify-content

グリッドコンテナ自体を水平方向にどのように寄せるか。

.container {
  // 水平方向に中央寄せ
  justify-content: center: 
}

justify-content, align-content, place-content

それぞれ、justify-items, align-items, place-content のグリッドコンテナ版。

grid-auto-columns, grid-auto-rows

(未完)

grid-auto-flow

位置指定のゆるいグリッドアイテムたちをどのように自動敷き詰めするか指定する。

.container {
  // (グリッドアイテムの順番を保ちつつ)水平方向で敷き詰め
  grid-auto-flow: row;

  // (グリッドアイテムの順番を保ちつつ)垂直方向で敷き詰め
  grid-auto-flow: column;

  // グリッドアイテムの順番を変えてでも敷き詰め
  grid-auto-flow: dense;
}

アイテム系

grid-column-start, grid-column-end, grid-row-start, grid-row-end

グリッドアイテムを、グリッドレイアウト上のどこに置くか指定する。

grid-column, grid-row

それぞれ、(grid-column-start + grid-column-end), (grid-row-start, grid-row-end) のショートハンド。

grid-area

グリッドアイテムに名前を与える。

.a-item {
  grid-area: a; // ".a-item" が4マス分のグリッドエリア "a" を占める
}

また、grid-column-start, grid-column-end, grid-row-start, grid-row-end のショートハンドでもある。

.item {
  grid-area: 1 / col4-start / last-line / 6
}

justify-self

グリッドアイテム自身の水平方向の寄せ方を指定。

.item {
  // 自身をグリッドセル内で中央寄せ
  justify-self: center;
}

align-self

グリッドアイテム自身の垂直方向の寄せ方を指定。

.item {
  // 自身をグリッドセル内で中央寄せ
  align-self: center;
}

place-self

justify-self, align-self のショートハンド。

.item {
  // 自身をグリッドセル内で、垂直方向に中央寄せ、水平方向に終点寄せ
  place-self: center end;
}

React v16.6.0で追加された lazy, memo そして contextType を試してみる

React.js v16.6.0 にて追加された新機能を試してみました。

reactjs.org

環境づくり

サクッと、create-react-app を利用しました。

create-react-app - github.com

$ npm -g install create-react-app

$ npx create-react-app my-app

$ cd my-app

$ yarn start

初めて create-react-app を利用したのですが、簡単すぎてビビりました…。React で何か試したい時は積極的に使っていきたいですね。

React.memo

プレゼンテーションコンポーネント(独自に state を保持したり、ライフサイクル関数を使うことなく、描画の行うだけのコンポーネント)を定義するする時は、クラスコンポーネントではなく SFC を利用しています。

import React from 'react';

// ふつーの SFC
const Hello = () => {
  return (
    <div>Hello React v16.6!</div>
  )
}

しかし、最近業務で引っかかった問題として、SFC で不要な再レンダリングが起こっていました。

レンダリングの原因は、空配列であった state に空配列を再代入していたことでした。値は変わっていないのに、参照が変わっていたため、SFC が props に更新ありと判定していました。

参考リンク: optimizing-performance - reactjs.org

そこで、PureComponent への書き換えを行いました。

PureComponent は props を shallow 比較するのでパフォーマンス向上に寄与しました。

React.memo を使うと、SFC を PureComponent に書き換える事なく同様のことが実現できます。

参考リンク: React.memo - reactjs.org

import React from 'react';

// ふつーの SFC
const Hello = (props) => {
  return (
    <div>Hello React v16.6!</div>
  )
}

// props を shallow 比較するコンポーネント
const PureHello = React.memo(Hello)

クラスへの書き換えの手間がかからなくて良さげです。

React.Suspense と React.lazy

ここで、API フェッチを行い、そして待ち合わせを行うようなコンポーネントのケースを考えてみます。 従来ならこのように書いていました。

import React, { Component } from 'react';

// 与えられた Todo を表示する
const Todo = (props) => {
  if(props.isLoading) {
    return <div>Loading...</div>
  }
  return (
    <div>{ props.todo.title }</div>
  )
}

// Todo のフェッチを行い、フェッチ状況も管理する
class App extends Component {
  state = {
    isLoading: false,
    todo: null
  }

  componentDidMount() {
    this.setState({ isLoading: true })

    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(res => res.json())
      .then(json => {
        this.setState({ isLoading: false, todo: json })
      })
  }
  
  render() {
    return (
        <Todo isLoading={this.state.isLoading} todo={this.state.todo} />
    );
  }
}

React.Suspense を使って書くと以下のようになります。(多分、細部では違った処理になっているかもしれません)

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

const Todo = (props) => {
  if(todo === null) {
    throw new Promise((resolve, reject) => {
      fetch(`https://jsonplaceholder.typicode.com/todos/${props.id}`)
        .then(res => res.json())
        .then(json => {
          todo = json
          resolve()
        })
        .catch(e => {
          console.warn("Not fetched yet")
          reject()
        })
    })
  }

  return (
    <div>{ todo.title }</div>
  )
}

class App extends Component {
  render() {
    return (
      {/* Suspense にフェッチ完了までのリンダリングを引き受けさせる*/}
      <Suspense fallback={<div>Loading...</div>}>
        <Todos id={1} />
      </Suspense>
    );
  }
}

参考URL: React 16.6で追加されたReact.Suspenseについて - qiita.com

さらに、この Suspense ですが、React.lazy を使った dynamic import によって Code splitting を噛ませることもできるようです。

import React, { Component, Suspense, lazy } from 'react';

const Todo = lazy(() => import('path/to/Todo.jsx'))

class App extends Component {
  render() {
    return (
      <Suspense fallback={<div>Loading...</div>}>
        <Todo id={1} />
      </Suspense>
    );
  }
}

export default App;

SSRではまだ使えないようですが…。

参考URL:

static contextType

Context は以前から実装されていた機能ですが(実はあんまり使ったこと無い)、 クラスの static 変数として context にアクセスできるようになったみたいです。

import React, { Component } from 'react';

const MyContext = React.createContext("default value");

class MyClass extends React.Component {
  static contextType = MyContext;

  render() {
    console.log(this.context) // "default value"
    return (
      <div>{ this.context }</div>
    )
  }
}

参考URL: static contextType - reactjs.org

static getDerivedStateFromError()

従来の componentDidCatch に加えて、 getDerivedStateFromError が追加されました。

componentDidCatch の問題点は、SSR に対応していないことでした。 getDerivedStateFromError は将来的には SSR 対応されるようなので、ユニバーサルなアプリケーションには役立ちそうです。

参考URL: static getDerivedStateFromError()

以上、 React v16.6 で追加された機能について試したり、調べたりしてみました。