Michi's Tech Blog

一人前のWebエンジニアを目指して

バニラJSでAjaxを実装してみよう!

こんにちは!
スマレジ テックファームのMichiです!

今回はタイトルの通り、バニラJS(生のJavaScript)でAjaxを実装してみます。

はじめに

作るもの

郵便番号を入力すると、対象の住所が表示される機能を作ります。Ajaxで実装しますので、[検索]ボタンを押しても画面全体は更新されず、住所の表示のみ変わるようにします。

郵便番号検索API

郵便番号検索には、日本郵便が公開している郵便番号検索APIを使用します。使い方はhttps://zipcloud.ibsnet.co.jp/api/searchの後にリクエストパラメータを追加し、GET送信するだけです。
試しに、https://zipcloud.ibsnet.co.jp/api/search?zipcode=1000001をブラウザで叩いてみましょう。すると、このようなJSONが返ってきます。

概要の説明は以上です。

バニラJSで実装してみよう!

完成形

まずはじめに、完成したコードをお見せします

index.html(<head>は省略)

  <body>
    <form>
      <label for="zip-code">郵便番号</label>
      <input id="zip-code" type="number" />
      <input id="btn" type="button" value="検索" />
    </form>
    <hr />
    <div id="address">
      <!-- ここに検索結果が表示される -->
    </div>
    <script src="script.js"></script>
  </body>

script.js

document.addEventListener('DOMContentLoaded', () => {
  /* [検索]ボタンクリック時に実行される処理 */
  document.getElementById('btn').addEventListener('click', () => {
    const address = document.getElementById('address')
    const xhr = new XMLHttpRequest()

    /* 非同期通信の処理を定義 */
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          const data = JSON.parse(xhr.responseText)
          if (data.results) {
            const result = data.results[0]
            address.textContent = result.address1 + result.address2 + result.address3
          } else {
            address.textContent = '該当の住所は存在しません。'
          }
        } else {
          address.textContent = 'サーバーエラーが発生しました。'
        }
      } else {
        address.textContent = '通信中…'
      }
    }

    /* サーバーとの非同期通信を開始 */
    xhr.open(
      'GET',
      `https://zipcloud.ibsnet.co.jp/api/search?zipcode=${encodeURIComponent(
        document.getElementById('zip-code').value,
      )}`,
      true,
    )
    xhr.send(null)
  })
})

解説

①[検索]ボタンクリック時の処理

document.addEventListener('DOMContentLoaded', () => {
  /* [検索]ボタンクリック時に実行される処理 */
  document.getElementById('btn').addEventListener('click', () => {
    const address = document.getElementById('address')
  // XMLHttpRequestオブジェクトの作成
    const xhr = new XMLHttpRequest()

検索ボタンをクリックしたとき、XMLHttpRequestオブジェクトを作成します。
XMLHttpRequestオブジェクトとは、非同期通信を管理するJavaScriptの組み込みオブジェクトです。「XMLHttpRequest」という名前であるにもかかわらず、通信に使用するデータ形式/プロトコルXML*1/HTTPだけでなく、JSONも利用できます。むしろ現在では、JSONを扱うケースがほとんどでしょう。
要するにXMLHttpRequestオブジェクトは、「クライアント・サーバー間の通信を担当するオブジェクト」だと覚えておきましょう。

②非同期通信の処理を定義する

    /* 非同期通信の処理を定義 */
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) { // 通信が完了したとき
        if (xhr.status === 200) { // 通信が成功したとき
          const data = JSON.parse(xhr.responseText)
          if (data.results) { // 住所が取得できた場合、表示する
            const result = data.results[0]
            address.textContent = result.address1 + result.address2 + result.address3
          } else { // 住所が取得できなかった場合には、エラーメッセージを表示
            address.textContent = '該当の住所は存在しません。'
          }
        } else { // 通信が失敗したとき
          address.textContent = 'サーバーエラーが発生しました。'
        }
      } else { // 通信が完了する前
        address.textContent = '通信中…'
      }
    }

XMLHttpRequestオブジェクトのonreadystatechangeプロパティで、非同期通信の開始から終了までに実行する処理を定義します。onreadystatechangeは、通信の状態が変化したタイミングで呼び出されるイベントハンドラーです。
処理の内容は、フロー図で表すと以下のようになります。

③サーバーとの非同期通信を開始

    /* サーバーとの非同期通信を開始 */
    // HTTPリクエストを初期化
    xhr.open(
      'GET',
      `https://zipcloud.ibsnet.co.jp/api/search?zipcode=${encodeURIComponent(
        document.getElementById('zip-code').value,
      )}`,
      true,
    )
    // HTTPリクエストを送信
    xhr.send()
  })
})

準備は整ったので、実際にサーバーへリクエストを投げます。 まずはopenメソッドでリクエストを初期化します。
openメソッドの引数には

  1. HTTPメソッド(GET、POSTなど)
  2. アクセス先のURL
  3. 非同期通信かどうか(デフォルトはtrueなので、明示的に宣言しなくてもOK)

を取ります。
第2引数のURLには、マルチバイト文字や予約文字が含まれている場合に備えて、encodeURIComponentメソッドでエンコード処理をしておきましょう。

リクエストを準備できたら、最後にsendメソッドでリクエストを送信します。sendメソッドの引数には、POST通信時のみリクエスト本体を指定できます。今回はGETなので、引数の指定はしません(値が設定されていない場合、既定値の null が使用される)。

完成

これにて完成です!実際に動かしてみましょう。

フォームに郵便番号を入力し、[検索]ボタンをクリックすると、「通信中…」の文字が表示されます。

その後、再び表示が変化し、該当の住所が表示されます。

画面は一度も更新されることなく、住所欄の表示のみが変化していますね!
他にも、「実在しない住所を入力したパターン」や、「ソースのURLを間違ったものにして、サーバーエラーを発生させる」などして、期待通りの挙動になっているか試してみましょう。

Vue.jsで実装してみる

本題とは逸れますが、Vue.jsとaxiosを使って全く同じ機能を実装すると、こうなります。

<template>
  <form>
    <label for="zip-code">郵便番号</label>
    <input id="zip-code" type="number" v-model="zipCode" />
    <input id="btn" type="button" value="検索" @click="fetchAddress" />
  </form>
  <hr />
  <div id="address">{{ address }}</div>
</template>

<script>
import { ref } from 'vue'
import axios from 'axios'

export default {
  setup() {
    const zipCode = ref()
    const address = ref()

    /* 非同期通信の処理を定義 */
    const fetchAddress = () => {
      // 通信が完了する前
      address.value = '通信中…'
      axios
        // HTTPリクエストを送信
        .get('https://zipcloud.ibsnet.co.jp/api/search', { params: { zipcode: zipCode.value } })
        // 通信が成功したとき
        .then((response) => {
          if (response.data.results) {
            // 住所が取得できた場合、表示する
            const result = response.data.results[0]
            address.value = result.address1 + result.address2 + result.address3
          } else {
            // 住所が取得できなかった場合には、エラーメッセージを表示
            address.value = '該当の住所は存在しません。'
          }
        })
        // 通信が失敗したとき
        .catch(() => (address.value = 'サーバーエラーが発生しました。'))
    }

    return {
      zipCode,
      address,
      fetchAddress,
    }
  },
}
</script>

やってみて、「あれ?意外とバニラJSと行数変わらなくね?」と思いました。
これは単純に操作するDOMや発火イベントが少ないからでしょう。操作するDOMやイベントが増えると、バニラJSではそのたびにDOMを取得するスクリプトを記述する必要があります。一方、Vue.jsでは手動でDOMを取得する必要はなく、テンプレートの中に埋め込まれた{{ address }} が値の変更を検知して、よしなに表示を切り替えてくれます。

このあたりの話は下記の記事が詳しいので、一読されることをおすすめします。

runteq.jp

まとめ

AjaxをあえてバニラJSで実装することで、フレームワークの裏側の処理が理解できた気がします。 こんな面倒くさいDOM操作をすべて手動でやってたのかと思うと、昔のプログラマーはすごいな思いました。
僕はもう二度とやりたくないです。

*1:「eXtensible Markup Language」の略。データのやり取りや設定ファイル書くときなどに使われる。

【JavaScript】Proxyってなんすか?

こんにちは!
スマレジ テックファームのMichiです!

突然ですが、Vue.jsのコードでreactiveなオブジェクトをconsole.logしたらこんな値が返ってきます。

「このProxyってやつ、なんすか?」とずっと思っていたので、調べてみることにしました!

Proxyとは?

公式ドキュメントでは ...

MDNのドキュメントにはこのように記載があります。

Proxy オブジェクトにより別なオブジェクトのプロキシを作成することができ、そのオブジェクトの基本的な操作を傍受したり再定義したりすることができます。

Proxy - JavaScript | MDN

「なんのこっちゃ???」って感じですよね。これで理解できるなら苦労せんて。
というわけで、もう少し嚙み砕いて説明してみます。

ひと言で説明すると...

Proxyとは「元のオブジェクトの動作をカスタマイズするためのオブジェクト」です。
例を見てみましょう。

// 元のオブジェクト
const data = { red: '赤色', yellow: '黄色'}

// Proxyで元のオブジェクトをカスタマイズする
const proxy = new Proxy(data, {
  get(target, prop) {
    return 'hello'
  }
})

console.log(data.red) // 結果:赤色
console.log(proxy.red) // 結果:hello

元のオブジェクトdata.redには赤色という値が格納されているにもかかわらず、Proxyオブジェクトでラップしたproxy.redを呼び出すと、元のオブジェクトには存在しないhelloという文字列が返ってきます。これが上で書いた「元のオブジェクトの動作をカスタマイズする」ということです。
ちなみに、Proxyは直訳すると「代理」という意味になります。元のオブジェクトの代わりに、処理を「代理」で行ってくれているというわけですね。

使い方

基本構文

new Proxy(target, handler)

引数

  1. target: 動作をカスタマイズされる側のオブジェクト
  2. handler: ターゲットの動作を定義するためのオブジェクト

Proxyオブジェクトはnew演算子で初期化し呼び出します。第1引数にターゲット、 第2引数にはハンドラーを取ります。ハンドラーで定義できるメソッドは決まっており、具体的には以下の表の通りとなります。

引用:https://ja.javascript.info/proxy

このハンドラーで定義できるメソッドのことをトラップと呼称したりもします。今回はこの中でも代表的なトラップである、getsetについて見ていきます。

getトラップ

最初の例でも出てきましたが、getはプロパティが取得されたときに呼び出されるトラップです。

基本構文

get(target, prop, receiver) {
  // プロパティを取得したときに実行する処理
}

引数

  1. target: 元のオブジェクト(new Proxyの第一引数と同じ)
  2. prop: オブジェクトのプロパティ名
  3. receiver: proxyオブジェクト自身(今回は使わない)

戻り値

  • 任意の値

具体例
以下は、存在しないプロパティを取得しようとした際エラーとなる実装です。

const data = { red: "赤色", yellow: "黄色" }

const proxy = new Proxy(data, {
  get(target, prop) {
    if (prop in target) {
      return target.prop
    } else {
      throw new Error("存在しないプロパティです")
    }
  }
})

console.log(proxy.blue) // 結果:存在しないプロパティです

setトラップ

setはプロパティを設定するときに呼び出されるトラップです。

基本構文

set(target, prop, value, receiver) {
  // プロパティを設定するときに実行する処理
}

引数

  1. target: 元のオブジェクト(new Proxyの第一引数と同じ)
  2. prop: オブジェクトのプロパティ名
  3. value: 新たに設定するプロパティの値
  4. receiver: proxyオブジェクト自身(今回は使わない)

戻り値

  • 真偽値(設定が成功するとtrue、失敗するとfalseを返す)

具体例
以下は、文字列以外をプロパティに設定しようとした際エラーとなる実装です。

const data = { red: "赤色", yellow: "黄色" }

const proxy = new Proxy(data, {
  set(target, prop, value) {
    if (typeof value !== "string") {
      throw new Error("設定できるのは文字列のみです")
      return false
    } else {
      target.prop = value
      return true
    }
  }
})

proxy.red = 1 // 結果:設定できるのは文字列のみです

他にも様々なトラップが存在します。気になる方は、公式ドキュメントなどで調べてみるとよいでしょう。

ユースケース

Proxyを使ったユースケースには、様々なものが考えられます。以下はその例です。

  • デフォルト値の取得 ... 存在しないオブジェクトのプロパティを取得しようとした時にデフォルトの値を返す。
  • 大文字⇔小文字の変換 ... プロパティの取得・設定時に大文字/小文字のどちらか一方へ変換することによって、大文字/小文字の区別をなくす。
  • バリデーション ... プロパティの設定時にバリデーションを設定して、不正な値をはじく。

とは言いつつ、実際に自分でProxyオブジェクトを作成する機会はあまりないでしょう。一番多いのは、外部ライブラリやフレームワークの中で使用されているパターンです。
冒頭でも書いたように、Vue.jsのreactiveオブジェクトをconsole.logするとProxyオブジェクトが返されます。これは、 Vue.js におけるリアクティブ性は Proxy オブジェクトにより実現されているからです。

まとめ

JavaScriptのProxyについて解説しました。
自分でProxyを使う機会はなかなかないかもしれませんが、フレームワークからProxyが返ってきたときに「なにこれ!?」ってならないように、勉強していきたいですね。

【JavaScript】インポート/エクスポートルールの整理

こんにちは!
スマレジ テックファームのMichiです!

JavaScriptのインポートとエクスポートのルールってたくさんあってややこしいですよね...。
というわけで、今回の記事で整理していきたいと思います。

基本形

個別にエクスポートする

変数、関数、クラスのぞれぞれは、宣言前にexportをつけることで、個別にエクスポートすることができます。

input.js

// 変数のエクスポート
export const name = "Michi";

// 関数のエクスポート
export function hello() {
  console.log("Hello");
}

// クラスのエクスポート
export class Member {
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  getName() {
    return this.lastName + " " + this.firstName;
  }
}

まとめてエクスポートする

ブラケット{}を使用することで、まとめてエクスポートすることができます。

input.js

const name = 'Michi'

function hello() {
  console.log('Hello')
}

class Member {
  constructor(lastName, firstName) {
    this.lastName = lastName
    this.firstName = firstName
  }

  getName() {
    return this.lastName + ' ' + this.firstName
  }
}

// まとめてエクスポート
export { name, hello, Member }

インポートする

エクスポートしたそれぞれの変数、関数、クラスは、import宣言をすることによって、別モジュールで使用できます。
インポートするものは、ブラケット{}の中に羅列します。

output.js

// input.jsからインポート
import { name, hello, Member } from './input.js'

console.log(name) // 結果:Michi
hello() // 結果:Hello
const member1 = new Member('山田', '太郎')
console.log(member1.getName()) // 結果:山田 太郎

一括でインポートする

インポートするものの量が多い場合は、import * as <obj>という構文で、オブジェクトとして一括インポートできます。
それらを使用したいときは、<obj>.<呼び出したい変数・関数・クラス>という形で呼び出します。

output.js

// オブジェクトとして一括インポート
import * as input from './input.js'

console.log(input.name) // 結果:Michi
input.hello() // 結果:Hello
const member1 = new input.Member('山田', '太郎')
console.log(member1.getName()) // 結果:山田 太郎

ただし、この形はあまり推奨しません。 理由は、

  • 呼び出し時のコードが長くなるから:console.log(name)console.log(input.name)
  • インポートしたものが、いつどこで使われているのか分かりにくいから

import { name, hello, Member } from './input.js'のように、明示的にインポートする方がおススメです。

別名を使う

asを利用することで、別名でエクスポート/インポートできます。

別名エクスポート

input.js

class Member {
  constructor(lastName, firstName) {
    this.lastName = lastName
    this.firstName = firstName
  }

  getName() {
    return this.lastName + ' ' + this.firstName
  }
}

// 別名でエクスポート
export { Member as Person }

output.js

import { Person } from './input.js'

const member1 = new Person('山田', '太郎')
console.log(member1.getName()) // 結果:山田 太郎

別名インポート

input.js

export class Member {
  constructor(lastName, firstName) {
    this.lastName = lastName
    this.firstName = firstName
  }

  getName() {
    return this.lastName + ' ' + this.firstName
  }
}

output.js

import { Member as Person } from './input.js'

const member1 = new Person('山田', '太郎')
console.log(member1.getName()) // 結果:山田 太郎

デフォルトエクスポート

デフォルトエクスポートとは

モジュールの中にエクスポートするものが1つしかない場合は、デフォルトエクスポートを使用することができます。
使い方はexport宣言の後に、defaultを付けるだけです。

member.js

// デフォルトエクスポート
export default class {
  constructor(lastName, firstName) {
    this.lastName = lastName
    this.firstName = firstName
  }

  getName() {
    return this.lastName + ' ' + this.firstName
  }
}

この場合、エクスポートする関数/クラスに名前を付ける必要はありません。
なぜなら、エクスポートされるものは1つしかないので、名前で識別する必要ながないためです。

デフォルトエクスポートをインポートする場合は、インポート側で変数(仮の名前)を付けてあげる必要があります。
またこの場合、ブラケット{}は不要です。

output.js

// Memberという変数を付ける
import Member from './member.js'

const member1 = new Member('山田', '太郎')
console.log(member1.getName()) // 結果:山田 太郎

デフォルトエクスポート時のファイル名

ところで先ほど、input側のファイルを以前までの例と違い、member.jsという名前に変えていたのにお気づきでしょうか?
これはコードの一貫性を保つために、インポートされた変数はファイル名に対応するべきという規則があるからです。例えば、input.jsというファイルからデフォルトエクスポートされた場合、インポート側(output.js)からはどんな名前でもインポートできます。

output.js

// どんな名前でもインポートできる
import input from './input.js'
import Member from './input.js'
import hoge from './input.js'

しかし、インポートの変数名とファイル名が乖離していると、余計な混乱が生じます。ですので、この場合はエクスポートされたモジュールの内容を表すmemberという名前で、変数名とファイル名を統一するべきです。

まとめ

JavaScriptのインポート/エクスポートルールについて解説しました。
JavaScriptは明示的にexportを書かないといけないのがやっかいですよね。Pythonみたいにimportだけで済めばいいんだけどなあ。

JavaScriptのオブジェクト指向を完全に理解する

こんにちは!
スマレジ テックファームのMichiです!

今回の記事は「JavaScriptを完全に理解する」シリーズのオブジェクト指向編です。

JavaScriptのオブジェクトとは?

他のオブジェクト指向言語では、「オブジェクト=クラス(から生成されるインスタンス)」のことを指します。 一方、JavaScriptでオブジェクトというと、{ key: value }のようないわゆる連想配列を思い浮かべる方が多いと思います。
JavaScriptでは、連想配列とオブジェクトは同一のもの」であり、その時々の使い方や文脈によって言い方を変えているに過ぎません。
そのため、本来のJavaScriptにクラスの概念は存在しないのですが、ES2015からはクラス構文が導入され、他のクラスベースのオブジェクト指向言語と同じような感覚で書けるようになりました。(ちなみに、クラスベースのオブジェクト指向に対して、JavaScriptオブジェクト指向はプロトタイプベースと呼ばれるらしい。)

本記事ではES2015に倣い、クラス構文でのオブジェクト指向を解説します。

クラスを定義する

前述のように、ES2015からは他の言語と同じように、class クラス名でクラスを定義することができるようになりました。
インスタンス変数はconstructor関数の中で定義します。尚、この関数名はconstructorで固定です。

class Member {
  // コンストラクター
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  // メソッド
  getName() {
    return this.lastName + ' ' + this.firstName;
  }
}

const member = new Member('山田', '太郎');
console.log(member.getName()); // 結果:山田 太郎

ゲッターとセッター

get構文を使用することで、読み取り専用のプロパティ(ゲッター)を定義できます。
先ほどのgetName関数をゲッターを使って定義したものが下のコードになります。

class Member {
  // コンストラクター
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  // ゲッター
  get fullName() {
    return this.lastName + ' ' + this.firstName;
  }
}

const member = new Member('山田', '太郎');
console.log(member.fullName); // 結果:山田 太郎

尚、ゲッターで定義したプロパティは、下記のように代入して上書きすることはできません。

member.fullName = '佐藤 次郎';
console.log(member.fullName); // 結果:山田 太郎

プロパティを上書きするためにはset構文を使用して、セッターを定義します。

class Member {
  // コンストラクター
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  // ゲッター
  get fullName() {
    return this.lastName + ' ' + this.firstName;
  }

 // セッター
  set fullName(value) {
    [this.lastName, this.firstName] = value.split(" ");
  }
}

const member = new Member('太郎', '山田');
member.fullName = '佐藤 次郎';
console.log(member.fullName); // 結果:佐藤 次郎

静的プロパティ/メソッドを定義する

staticキーワードをプロパティ/メソッド宣言の頭に付けることで、静的プロパティ/メソッドを定義することができます。
静的プロパティ/メソッドは、「インスタンス化されていないクラスから直接呼び出せるプロパティ/メソッド」です。

class Area {
  static PI = 3.14;
  static getCircle(radius) {
    return radius ** 2 * this.PI;
  }
}

// インスタンス化せずに、クラスから直接呼び出している
console.log(Area.PI) // 結果:3.14
console.log(Area.getCircle(2)) // 結果:12.56

サンプルのように、同じクラス内の静的メソッドまたはプロパティを静的メソッドから呼び出すには、 thisキーワードを使います。

既存のクラスを継承する

既存のクラスを継承する場合は、extendsキーワードを利用します。

// 親クラス
class Member {
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  getName() {
    return this.lastName + " " + this.firstName;
  }
}

// 子クラス(Memberクラスを継承)
class BusinessMember extends Member {
  work() {
    return this.getName() + "は働いています。";
  }
}

const businessMember = new BusinessMember('山田', '太郎');
console.log(businessMember.getName()); // 結果:山田 太郎
console.log(businessMember.work()); // 結果:山田 太郎は働いています。

親クラスのメソッドをオーバーライドする

親クラスで定義されたメソッド/コンストラクターは、子クラスで上書き(オーバーライド)することもできます。

// 親クラス
class Member {
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  getName() {
    return this.lastName + " " + this.firstName;
  }
}

// 子クラス(Memberクラスを継承)
class BusinessMember extends Member {
  // 引数にclazzを追加
  constructor(lastName, firstName, clazz) {
    super(lastName, firstName);
    this.clazz = clazz;
  }

  // 役職込みの名前を返すよう修正
  getName() {
    return super.getName() + '/役職:' + this.clazz;
  }
}

const businessMember = new BusinessMember('山田', '太郎', '課長');
console.log(businessMember.getName()); // 結果:山田 太郎/役職:課長

まとめ

JavaScirptのオブジェクト指向について解説しました。
JavaScriptオブジェクト指向は特殊でとっつきにくいイメージがありましたが、ES2015で実装されたクラス構文を使用すれば、他の言語と同じ感覚で書けるので、大きな混乱もなく理解することができたと思います。

今さら「Gitflow」の復習

こんにちは!
スマレジ テックファームのMichiです!

今回はGitflowの復習がてら、記事を書きます。

今更Gitflowを学ぶ理由

「エンジニア歴9カ月目で今さらGitflowの復習?」と思われるかもしれませんが、これにはちょっとした事情があります。
エンジニアに転職して最初の半年は、先輩と2人で新規開発案件を担当していました。 メンバーが2人しかいないのでGitの運用も超適当で、特にブランチなども切らずに同じ場所で作業していました。

それでも、

僕「先輩、ここコンフリクトしたんですけどどうしたらいいですか?」
先輩「あー、じゃあそっちの変更に合わせといて」
僕「了解です!」

みたいな感じで、お互いに確認すればよかったんで、その時は特に問題なかったんですね。
で、最近になって、メンバーが何十人もいる大きなプロジェクトにアサインされたんですが、ここで問題発生。

僕「やべえ、Gitflow全部忘れた🙄」

入社前にちゃんとGitflowの勉強もしたんですけどね…。半年も触らなければ人間、見事に忘れるものです(笑)
そういうわけで、ここでしっかりGitflowを復習して、忘れないように身に着けたいと思います!

Gitflowとは?

Gitflowとはチーム開発でGitを運用する際のルールのようなものです。
Gitflowでは目的に応じて複数のブランチを派生させ、最終的にそれらをマージ(合体)させることでプロダクト開発をしていきます。

Gitflow 概念図

Gitflowで使用するブランチ

main(master)

すべての原型となるブランチです。
masterというのは古い言い方で、今はmainと呼称するらしいです。プロダクトをリリースするときはこのブランチからリリースされます。このブランチ上で作業を行うことはありません。

develop

mainから派生した開発用のブランチです。
実際の開発作業はここからfeatureブランチを切って行うので、このブランチ上で作業を行うことはありません。

feature

developから派生する機能の追加・修正用ブランチです。
各機能の担当者はdevelopから各々のfeatrueブランチを切り、作業が終わればその内容をdevelopへマージします。尚、このブランチを直接mainへマージしてはいけません。

release

同じく、developから派生するプロダクトリリース用のブランチです。
リリース予定日が近づくと、developからreleaseブランチを切り、リリース準備(総合試験やバグ修正)を行います。リリース準備が終了すると、その内容はmainへマージされた後本番環境へデプロイされます。また、developへもマージされます。

hotfix

mainから派生する緊急対応用(バグ修正など)のブランチです。
このブランチのみ、mainから分岐します。作業が終われば、mainとともにdevelopへ修正内容をマージします。

実際の流れ

今回はプロジェクトの一メンバーとして、Gitflowを用いて開発する際の流れを解説します。
尚、プロジェクトにはすでにmasterdevelopブランチが存在するものとします。

1. developブランチを最新の状態にする

まずは大元となるdevelopブランチを最新の状態にします。

git checkout develop
git pull origin develop

2. featureブランチを切る

最新状態のdevelopからfeatureブランチを作成します。
ブランチ名はチームによりますが、例えばfeature/{#チケット番号}-{作業内容}のような規則で命名します。

git checkout -b feature/#123456-gitflow-test

3. featureブランチで作業を行う

作成したfeatureブランチで作業を行います。
作業内容は適宜コミットしましょう。

git add .
git commit -m "ADD:Gitflow test"

※次の項で述べますが、developブランチの変更を取り込む際にgit rebaseを使う場合は、ここでリモートリポジトリにプッシュしないように!

4. developブランチの変更を取り込む

最新のdevelopブランチを、現在作業中のfeatureブランチに取り込みます。
これは自分の作業中に、他のメンバーがdevelopに変更を加えた場合、自分の開発内容とコンフリクトを起こす可能性があるためです。

まず、developブランチに移動して、最新状態のリモートリポジトリを取り込みます。

git checkout develop
git pull origin develop

次にfeatureブランチに戻り、最新にしたdevelopを取り込みます。
ここでdevelopを取り込むには、mergereabaseを使う2種類の方法があります。

a. mergeを使用する方法

mergefeatureの先頭に、developブランチを合体させるコマンドです。

git checkout feature/#123456-gitflow-test
git merge develop

■特徴

  • featureブランチの先頭にマージコミットを作成する
  • 非破壊的な操作であるため、既存のブランチが変更されることはない
  • featureで長く開発を続けていると、開発機能とは直接関係ないマージコミットが増え続けてしまう

mergeのイメージ図(Bitbucketの公式ドキュメントより)

b. rebaseを使用する方法

一方、rebaseはコミットを新しく作り直すことによって、履歴を1本の線にするコマンドです。

git checkout feature/#123456-gitflow-test
git rebase develop

■特徴

  • コミットを作り直し、developブランチの先頭にfeatureの履歴をつなげる
  • 破壊的な変更であるため、ルールを守らないとチーム全体に影響を及ぼすことがある
  • マージコミットは作成されないため、履歴が直線的で見やすい

rebaseのイメージ図(Bitbucketの公式ドキュメントより)

ここで注意しなければならないのが、先ほども言ったようにrebaseする前にローカルの変更をリモートリポジトリ へプッシュしてはいけない」ということです。 rebaseはコミットを作り直すので、プッシュする前にrabaseを行うとローカルとリモートの内容に差異が発生し、コンフリクトしてしまいます。こうなるともう強制プッシュするしかないのですが、チームによってはGitHub/Labの設定によって強制プッシュが禁止されている場合もありますので十分気を付けましょう。

尚、mergeを使用するか、rebaseを使用するかはチームの方針によって変わってきます。わからない場合は、コマンドを打つ前に一度、ほかのチームメンバーに聞いてみるのがいいでしょう。

5. リモートリポジトリへプッシする

ここまで出来たら、ローカルのfeatureブランチの変更内容をリモートリポジトリへpushします。

git push origin feature/#123456-gitflow-test

6. マージ/プルリクエストを発行する

developブランチに対して、マージリクエスト(GitLab)、もしくはプルリクエスト(GitHub)を発行します。

7. レビューしてもらう

内容をレビューしてもらい、OKならリクエストが承認され、featureブランチがdevelopへマージされます。
指摘事項がある場合はコードを修正し、3. 以降の過程をやり直します。

まとめ

Gitflowの概要と実践について、サクッとではありますが解説しました。
「developブランチの変更を取り込む」 の箇所は、私も実務に入る前はmergeを使う方法しか知らなかったので、びっくりしました。(基本的にrebaeは使ったらアカンっていう認識のコマンドやった…)
初めて使う時は怖すぎて、先輩に「ほんまにやっていいんですか!?」って何回も聞いた記憶があります(笑)

参考

www.atlassian.com

www.atlassian.com

今年一年の目標

こんにちは!
スマレジ テックファームのMichiです!

新年明けましておめでとうございます。
さて、今回は新年1本目の記事ということで、今年一年の目標を語っていきます。

今年の目標

1. エンジニアとして独り立ちする

今年最大の目標は兎にも角にもこれです。
やはり、まだまだ周りの方々にサポートしていただきながら業務を進めらている状況なので、一刻も早く独り立ちしたいという思いが強いです。
何をもって独り立ちしていると判断するかは難しいですが、現在は先輩と一緒に案件入れてもらっている状態なので、「仮にその先輩がいなくなっても一人で契約を取れる」ことを目標にします。

2. ブログを毎週更新する

昨年11月からはじめたこのブログですが、今年も毎週更新で積極的にアウトプットしていきたいです。
ていうか、毎週書かないと会社からブログ手当貰えないし。

3. 副業をはじめる

今年は副業にもチャレンジしてみたいなと思っています。
ただ、現段階では実力もキャパシティも足りていないので、今年後半くらいからスタートできればなという感じです。 前半はこれまで通り、技術のキャッチアップに集中したいです。

4. 週3回ジムに行く

ここからはプライベートの目標です。
まずは健康面。筋トレ頑張ります。 昨年も週1~2 回ペースでは行っていたのですが、現状ほぼリモートワークなので圧倒的に運動量が足りていないです。
なので、今年は頑張って週3回を目標にします。良い仕事をするためにも、まずは健康な体があってこそですからね。

5. 禁酒する

禁酒します!
とは言っても、あくまで自宅の中限定です。お誘いがあれば外で飲むのは全然OKなので、どしどし誘っちゃってください!←
禁酒しようと思ったきっかけは、時間がもったいないからです。お酒を飲むとその後一日何もできなくなっちゃうので。あとシンプルに、お酒は一人よりみんなで飲む方が楽しい!
自宅で飲む分の時間を自己研鑽や副業に費やして、2023年はガンガン成長していきたいです。

6. 引っ越す

これはその時の状況にもよりけりなのですが、大阪市内へ引っ越したいなと思っています。
現住の神戸市もいい街なのですが、やはり関西のIT業界の中心は大阪なので、これからのWebエンジニアとしてのキャリアを考えても、大阪にいた方が何かと便利かなと思います。
但し、最近は神戸のITエンジニアコミュニティも活発になってきていて、自分自身そこでの繋がりも増えているので、大変悩みどころです。

7. 仕事以外の楽しみを見つける

昨年はエンジニア転職1年目ということもあって、ほぼ仕事(プライベートでのキャッチアップ含めて)しかしていた記憶がないです。
なので、今年は仕事以外にもちゃんと楽しみを持とうと思っています。先ほども言いましたが、現状ほぼリモートワークなのでちゃんとON/OFFしないと、どこかでいきなりメンタルが死にます(笑)
とりあえずは読書を始めてみようと思って、先日数年ぶりに本屋で小説を購入しました。


どれくらい達成できたかは、来年の目標の時に。
というわけで、本年もよろしくお願いいたします!

JavaScriptの関数を完全に理解する②

こんにちは!
スマレジ テックファームのMichiです!

この記事は「JavaScriptの関数を完全に理解する①」の続きです。
前回で書ききれなかったJavaScriptの関数について整理していきます。

引数のデフォルト値

下記のように、引数にデフォルト値を持たせることができます。
呼び出し側の実引数に何も指定しない場合は仮引数のデフォルト値がそのまま使用され、呼び出し側で実引数を指定した場合はその指定された値が引数として使用されます。

function getTriangle(base = 1, height = 1) {
  return base * height / 2
}

console.log(getTriangle()) // 結果:0.5
console.log(getTriangle(2, 5)) // 結果:5

可変長引数

仮引数の前に...(ピリオド3個)を付与することで、可変長引数となります。
可変長引数とは、あらかじめ個数が決まっていない引数のことで、JavaScriptでは渡された任意個数の引数を配列としてまとめて受け取ることができます。

function sum(...nums) {
  let result = 0
  for (let num of nums) {
    result += num
  }
  return result
}

console.log(sum(1, 3, 5, 7, 9)) // 結果:25

受け取った引数numsはArrayオブジェクトとして代入されるので、通常の配列と同じようにpushpopメソッドで操作可能です。

また、...演算子は実引数側でも使用できます。

function sum(num1, num2, num3) {
  return num1 + num2 + num3
}

const num_list = [1, 3, 5]
console.log(sum(...num_list)) // 結果:9

名前付き引数

名前付き引数とは、呼び出し時に名前を明示的に指定できる引数のことです。
名前付き引数を用いることで、以下のようなメリットがあります。

  • 引数が多くなっても、コードの意味が分かりやすい
  • 省略可能な引数をスマートに表現できる
  • 引数の順番を自由に変更できる
function getTriangle({base, height}) {
  return base * height / 2 
}

console.log(getTriangle({base: 5, height: 4})) // 結果:10

同じようにして、引数に渡したオブジェクトから特定のプロパティのみを取り出すこともできます。

function show({name}) {
  console.log(name)
}

const member = {
  id: 'Y0001',
  name: '山田太郎',
  address: 't_yamada@example.com'
}

show(member) // 結果:山田太郎

なお、これらはすべて分割代入と呼ばれるテクニックの応用です。 分割代入についてご存じない方は、まずこちらのドキュメントを読むことをおすすめします。 developer.mozilla.org

複数の戻り値を返したい

「関数から複数の値を返したい」という場合には、配列/オブジェクトとして値を1つにまとめたうえで返す必要があります。
例えば以下は、与えられた任意個数の数値に対して、それぞれ最大値と最小値を求める関数の例です。

function getMaxMin(...nums) {
  return [Math.max(...nums), Math.min(...nums)]
}

const [max, min] = getMaxMin(10, 35, -5, 78, 0)
console.log(max) // 結果:78
console.log(min) // 結果:-5

これも分割代入の応用で、変数max, minにそれぞれ関数の返り値を代入しています。

もちろん、以下のようにしても構いません。

function getMaxMin(...nums) {
  return [Math.max(...nums), Math.min(...nums)]
}

const result = getMaxMin(10, 35, -5, 78, 0)
console.log(result) // 結果:[78, -5]

ただし、配列で丸ごと返り値を取得すると、使用する際result[0]のように記述する必要があり、何を表しているか分かりにくいため、基本的には分割代入を利用するほうをおすすめします。

まとめ

二回に渡ってJavaScriptの関数について整理しました。
JavaScriptの関数を完全に理解するには、分割代入を理解できるかがカギだと思います。 (このへんも時間があれば記事にしたい)