クマのブログ

つまづいたところ、学びを書いていきます

【オンラインだけでしか買えない?ウソです】オンライン以外でのマチュピチュ遺跡チケット購入方法

概要

  • オンライン以外でもマチュピチュ遺跡へのチケットは購入できる
    • マチュピチュのチケットはオンラインでしか購入できない」という情報はウソ
  • 但し、いくつか条件があるので注意

背景

悪天候でガッカリのマチュピチュ遺跡

想定読者

購入可能場所

場所はここ。マチュピチュ遺跡の最寄りの村「アグアスカリエンテス」の中心地にあるのでわかりやすいです

CENTRO CULTURAL MACHUPICCHU

  • 整理券配布開始時間…6:00
  • 閉館時間…18:00
  • ※閉館間際は訪れる人も少ないので整理券がなくてもそのままカウンターに並べます(ただし人気のサーキットや時間帯は売り切れている)

購入方法

いわゆる「整理券方式」。非常に簡単なのでざっくり流れの説明と注意点を添えます。

0, パスポートと現金を用意

  • クレカ非対応なので注意してください
  • 値段はオンラインと同じなので必要な金額を確認して購入場所へ向かってください

1, CENTRO CULTURAL MACHUPICCHUに行く

  • 建物の入り口に受付がいるのでパスポートを渡すと整理券を発行してもらえる
  • 整理券には自分のパスポートNo.と整理券番号、集合時間が書いてある
  • 記載されている時間に再度CENTRO CULTURAL MACHUPICCHUに来るように指示される
  • ※時間帯によってはそのまま案内されます

↓建物外観。写真右下あたりの警官っぽい人がいる場所が受付

2, 約束の時間にCENTRO CULTURAL MACHUPICCHUに行く

  • 先ほどの受付に整理券を渡すと行列に並ぶように指示される
  • 整理券番号順に並ぶ

↓整理券。なくさないように注意。"320"と書かれているのが整理券番号

3, チケットカウンターでチケットが購入

  • 行列の先にチケットカウンターがあるので希望のコースと日時を伝えるとチケットが購入できる

これでチケット購入完了です

注意点

  • チケットは販売日の翌日分しか販売されません。(例えば, 1月1日にチケットカウンターへ行っても1月2日のチケットしか販売されてない)
  • 一日当たりのチケット枚数は全コース・全日時併せて3000枚
    • 各コース+時間帯(例: サーキット1の6:00 - 7:00)の枚数はMAX30 - 50枚(※あくまで感覚値)
  • 比較的午前中のチケットから売切れていくので早めに整理券を発行してもらう必要がある
    • 筆者の実体験でいうと15:30に受付して整理券番号は320番。整理券番号1番から購入可能なのでこの時点でのチケットの在庫状況は以下の通り
      • サーキット1or2(王道):午前中はほぼ売り切れ、一番早くて11:00からの入場チケット10枚ほど残っている
      • サーキット3or4:うろ覚えですが午前中は残り少なく、午後はまだ30ほど販売されていた
    • また翌日10:30に開館時間と閉館時間確認した時は番号は2桁台でした。(受付に怪しまれたのかちゃんと見してくれませんでした)

      余談

  • 筆者はここで購入したチケットを実際に使ってマチュピチュ遺跡になんも問題なく入場できました
  • ココのチケットカウンターを管轄しているのはオンラインと同様でペルー政府らしく信用は問題ないです。
  • 海外の観光客は結構普通に知ってる場所らしいです。なので早めに整理券を発行することをお勧めします。
  • とはいっても何よりも一番いいのはやっぱりオンライン公式サイトで購入しておくことがいいです(笑)

最後に

僕と同じように「マチュピチュ遺跡が悪天候できれいに見えなかった…」という残念な思いをした方にはこの購入方法をぜひ知ってもらってリベンジ果たしてほしいです!悔いのないマチュピチュ遺跡観光を!

↓リベンジ達成したときの写真

はじめてのデザインパターン #1

はじめに

ちょこちょこ自分で機能を作ることはできてきたけど、設計がイケてなかったりして苦戦していた時に先輩に相談したところ「デザインパターンとか知っておくと損ないかもね」と教えてもらった。

とはいえ「デザインパターンって何?」状態だったので調べてみたのでメモ。

参考

自分がPHP使いということもあり、以下を参考にしようとした

PHPによるデザインパターン入門

とおもったら、廃版になってて著者の方が本内容をはてブに掲載してくれていたのでこちらでインプット

Do You PHP はてブロ

感謝です。

デザインパターンとは

ソフトウェアを何度も設計していると、あちらこちらで共通する機能や問題があることに気づくことがあります。...。経験を積んだ開発者であれば、「こういった問題を解決するには、こういう構造でプログラムを書けばうまくいく」といったノウハウを持っているものです。

このノウハウを、他の開発者が再利用できるようカタログ化したものがデザインパターンです。

一言でいうとオブジェクト指向プログラミングにおいて開発者がソフトウェア設計する上での「型」

記事内では

「設計のコツの虎の巻」

と表現されている。

メリット

じゃあ、「デザインパターン学んでどうなるのさ?」という話で行くといくつか列挙されていますが個人的には

  • 再利用性が向上する
  • 保守性が向上する

が大きいメリットだなと感じます。

再利用性が向上

クラスの一つ一つの独立性が担保される設計になるので、クラス同士が疎結合になり再利用性が高くなる

保守性が向上する

独立性が高くなるとクラスが持つ役割がしっかり分担されるので、急な仕様変更や機能追加の対応がしやすくなる

例えば、LaravelでDBから値を取得する際にAカラムではなくBカラムの値をwhereする、みたいな修正があった時に

// 当初仕様
$user = User::where('Aカラム', $A_value)->first();
↓
// 仕様変更
$user = User::where('Bカラム', $B_value)->first();

これが一か所のクラスのメソッドにまとまって(抽象化されて)いて、呼び出し側は全て抽象メソッドを呼び出していれば修正は一か所ですが、上記の記述をそのまま呼び出し側に書いていたら修正規模は100、200...箇所と膨大なものになる、という話。

デメリット

デメリットもいくつか掲載されていますが例えば

  • 開発者同士がデザインパターンを知らないとコミュニケーションコストが生まれる
  • 使いこなすまでに時間がかかる

など、物理的に仕方ないかな、ということが多いです。

ただ、

  • 無理にでもパターンを適用しようとする
  • パターンのクラス図をそのまま適用しようとする

あたりはデザインパターンを利用する上で注意点かなと思います。

冒頭に「デザインパターンは虎の巻」と記載されていても絶対的なものではなく、むしろやりすぎると逆にイケていない設計になる、ということも注意しておく必要あり、と学びました。

どんなものがあるか

とは言え、やはり同じプロダクトの中で開発者同士が共通の型を使って、再利用性の富んだ設計ができるようになるのは大きな魅力なので、どんなものがあるか見ていきたい。

※第1回なので超基礎のみにしています。

#1, テンプレートメソッドパターン

こんな時に使う

今目の前に以下のようなクラスが2つあるとします。

↓ListDisplayクラス

<?php
require_once 'AbstractDisplay.class.php';

class ListDisplay
{
  /**
     * 表示するデータ
     */
    private $data;

    /**
     * コンストラクタ
     * @param array 表示するデータ
     */
    public function __construct($data)
    {
        $this->data = $data;
    }

    /**
     * クライアントから呼び出されるメソッド
     */
    public function display()
    {
        $this->displayHeader();
        $this->displayBody();
        $this->displayFooter();
    }

    /**
     * ヘッダを表示する
     */
    protected function displayHeader()
    {
        echo '<dl>';
    }

    /**
     * ボディ(クライアントから渡された内容)を表示する
     */
    protected function displayBody()
    {
        foreach ($this->getData() as $key => $value) {
            echo '<dt>Item ' . $key . '</dt>';
            echo '<dd>' . $value . '</dd>';
        }
    }

    /**
     * フッタを表示する
     */
    protected function displayFooter()
    {
        echo '</dl>';
    }
}

↓TableDisplayクラス

<?php
require_once 'AbstractDisplay.class.php';

class TableDisplay
{
  /**
     * 表示するデータ
     */
    private $data;

    /**
     * コンストラクタ
     * @param array 表示するデータ
     */
    public function __construct($data)
    {
        $this->data = $data;
    }

    /**
     * クライアントから呼び出されるメソッド
     */
    public function display()
    {
        $this->displayHeader();
        $this->displayBody();
        $this->displayFooter();
    }

    /**
     * ヘッダを表示する
     */
    protected function displayHeader()
    {
        echo '<table border="1" cellpadding="2" cellspacing="2">';
    }

    /**
     * ボディ(クライアントから渡された内容)を表示する
     */
    protected function displayBody()
    {
        foreach ($this->getData() as $key => $value) {
            echo '<tr>';
            echo '<th>' . $key . '</th>';
            echo '<td>' . $value . '</td>';
            echo '</tr>';
        }
    }

    /**
     * フッタを表示する
     */
    protected function displayFooter()
    {
        echo '</table>';
    }
}

↓利用側(クライアント)

<?php
require_once 'ListDisplay.class.php';
require_once 'TableDisplay.class.php';

$data = array('Design Pattern',
              'Gang of Four',
              'Template Method Sample1',
              'Template Method Sample2');

$display1 = new ListDisplay($data);
$display2 = new TableDisplay($data);

$display1->display();
echo '<hr>';
$display2->display();

TableDisplayクラスとListDisplayクラスを見てあなたは思います。

「なんだかすごく似ている処理をしているぞ。。。」

この時に使うのがテンプレートメソッド。

使い方

  • 複数の似た処理のクラスの抽象化クラスを1個作る
  • 抽象クラスには似た処理のクラス(具象クラス)でやってほしい処理のabstractでの定義と共通の処理を定義
  • 抽象クラスをexxtendsした似た処理のクラス(具象クラス)では、abstractでのメソッドの具体的な処理の実装を定義
  • 利用側(クライアント)で、具象クラス経由で抽象クラスの共通処理を呼び出す

言葉で話してもわかりにくいのでソースコードにて

修正後

↓AbstractDisplayクラス(新規作成) ここに共通の処理と具象クラスで処理してほしいメソッドを定義

<?php
/**
 * AbstractClassクラスに相当する
 */
abstract class AbstractDisplay
{
    /**
     * 表示するデータ。下記コンストラクタで値が格納される
     */
    private $data;

    /**
     * コンストラクタ。実際にインスタンス化するのは具象クラスなので、具象クラスをnewしたら呼ばれる
     * @param array 表示するデータ
     */
    public function __construct($data)
    {
        $this->data = $data;
    }

    /**
     * template methodに相当する(共通処理)
     */
    public function display()
    {
        $this->displayHeader();
        $this->displayBody();
        $this->displayFooter();
    }

    /**
     * データを取得する(共通処理)
     */
    public function getData()
    {
        return $this->data;
    }

    // 具象クラスで実装するメソッドたち
    /**
     * ヘッダを表示する
     * サブクラスに実装を任せる抽象メソッド
     */
    protected abstract function displayHeader();

    /**
     * ボディ(クライアントから渡された内容)を表示する
     * サブクラスに実装を任せる抽象メソッド
     */
    protected abstract function displayBody();

    /**
     * フッタを表示する
     * サブクラスに実装を任せる抽象メソッド
     */
    protected abstract function displayFooter();

}

↓ListDisplayクラス

データをリストで表示するための処理を書く

<?php
require_once 'AbstractDisplay.class.php';

/**
 * 具象クラスに相当する
 */
class ListDisplay extends AbstractDisplay
{
    // 抽象クラスで実装することを強制したabstractで定義したメソッドたち(これがないとエラーになる)

    /**
     * ヘッダを表示する
     */
    protected function displayHeader()
    {
        echo '<dl>';
    }

    /**
     * ボディ(クライアントから渡された内容)を表示する
     */
    protected function displayBody()
    {
        foreach ($this->getData() as $key => $value) {
            echo '<dt>Item ' . $key . '</dt>';
            echo '<dd>' . $value . '</dd>';
        }
    }

    /**
     * フッタを表示する
     */
    protected function displayFooter()
    {
        echo '</dl>';
    }
}

↓TableDisplayクラス

データをテーブルで表示するための処理を書く

<?php
require_once 'AbstractDisplay.class.php';

/**
 * ConcreteClassクラスに相当する
 */
class TableDisplay extends AbstractDisplay
{
        // 抽象クラスで実装することを強制したabstractで定義したメソッドたち(これがないとエラーになる)

    /**
     * ヘッダを表示する
     */
    protected function displayHeader()
    {
        echo '<table border="1" cellpadding="2" cellspacing="2">';
    }

    /**
     * ボディ(クライアントから渡された内容)を表示する
     */
    protected function displayBody()
    {
        foreach ($this->getData() as $key => $value) {
            echo '<tr>';
            echo '<th>' . $key . '</th>';
            echo '<td>' . $value . '</td>';
            echo '</tr>';
        }
    }

    /**
     * フッタを表示する
     */
    protected function displayFooter()
    {
        echo '</table>';
    }
}

↓利用側(クライアント)・・・変更なし

<?php
require_once 'ListDisplay.class.php';
require_once 'TableDisplay.class.php';

$data = array('Design Pattern',
              'Gang of Four',
              'Template Method Sample1',
              'Template Method Sample2');

$display1 = new ListDisplay($data);
$display2 = new TableDisplay($data);

$display1->display();
echo '<hr>';
$display2->display();
  • やっているのは「継承」を使った実装
  • 重複部分を抽象クラスに実装することで、実際にインスタンス化する具象クラスの記述量を減らすことができ、拡張性もUP
    • 似たような処理(ただ文字列を羅列するだけの処理など)が欲しくなったときに同じように具象クラスのメソッドを実装してあげればいい
  • 呼び出し側に影響が出ないので、呼び出し側は抽象クラスのメソッドを使っていることを意識しなくていいのは嬉しい

おわりに

  • Google検索では「デザインパターン 古い」などと予測検索出てきますが、僕みたいなイケていない設計をしてしまうペーペーにとってはいい「虎の巻」になるなと思っている。
  • 実際にこれを実務で使いましたが、かなり良かった。
    • 今回は6つの具象クラスの共通部分を抽象クラスに移行できたので具象クラスが非常にわかりやすく再利用性の高さも実感しました。
  • スイマセン、意外と1パターンでも盛沢山だったので、続きは次回に回します。。。

update/save, create/makeの違い

update/saveについて

update

差分を見ずにUPDATEのSQLを発行する

Customer::where('id', 1)
          ->update(['name' => $request->name]);
  • ここで$request->nameが既存のDBの内容と同じだった場合でもUPDATEのSQLが発行され、クエリが実行されることになる
  • updated_atがカラムにあれば現在時刻に更新する

save

差分を見てUPDATEのSQLを発行する

$customer = Customer::find(1)
$customer->name = $request->name;
$customer->save();
  • ここで$request->nameが既存のDBの内容と同じだった場合、差分がないことを検知しUPDATEのSQLは発行されず、クエリが実行されない
  • updated_atカラムも更新はされない

<補足>saveのお供「fill」

やっていることはモデルのプロパティの変更を一括で出来てしまう

public function create(UserControllerRequest $request)
{
    $user = $this->user;

    $user->name = $request->name;
  $user->password = $request->password;

    $user->save();
}

例えば、こんなコードを

public function create(UserControllerRequest $request)
{
    $this->user->fill($request->all())->save();
}

fillを使ってスッキリ書ける、というメリットがある。

saveとの相性がいいので、頻出する。

所感

余計なクエリを発行しなくていいので、updateよりもfill&saveが主流ではないかなと思いました。

※実際にアサインしているプロジェクトもfill, saveが主流

update/saveの参考記事

  • saveとupdateの違い

https://tech1313.hatenablog.com/entry/2020/10/21/155947

  • fillについて

https://zenn.dev/naonao70/articles/a8abba2b170c31

ありがとうございます。

create/makeについて

create

 Model::create() に該当。INSERTのSQLが発行され、クエリが実行される

インスタンスの作成 → 属性の代入 → データの保存を一気通貫

make

 new Model() に該当。INSERTのSQLは発行されない。

インスタンスの作成 → 属性の代入 まで実行

所感

要はDBに保存しているか、せずにモデルのインスタンス化だけしているかの違い

create/makeの参考記事

  • createとmakeについて

https://qiita.com/nunulk/items/06370af1594a10faa749#eloquent-model-factory-とは

  • 特にcreateについて

https://katsusand.dev/posts/laravel-save-data-db/

ありがとうございます。

例外のカスタム~reportable, renderable~

reportable

https://readouble.com/laravel/8.x/ja/errors.html#the-exception-handler

クロージャ―で処理をしたい時とかに使う

※以下記事の感じ。実際に使ってないけど、例外投げられた時にslackへnotifyする、みたいな

https://tsyama.hatenablog.com/entry/laravel-slack-notification

renderable

https://readouble.com/laravel/8.x/ja/errors.html#rendering-exceptions

  • デフォルトではmessageやstatus code、traceが返されるが、これを変換する場合に使う
    • ex. 作成した例外クラスで404を返したいときとか

S3基本概念の理解

はじめに

LaravelでS3にある画像データを処理する機能を実装しようとなった際につまづき、自力で解決しきれなかったので、特に圧倒的不足を感じたS3の知識を一通り確認

参考

AWS公式Doc(https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/Welcome.html

機能

ストレージクラス

S3 Standard, S3 標準 – IA, S3 Glacier Instant Retrieval...などユースケースに応じていろんな機能がある

アクセス管理

S3ブロックパブリックアクセス

一般ネットワークからのアクセスを拒否。オブジェクトごとにURLが付与されるが、URL踏んでもアクセスできないようになる

アクセスコントロールリスト(ACL

バケット・オブジェクトへの書き込み、読み込みの許可を管理

…などなど

データ処理

S3内のデータに変更があった時、イベントを発生させたり。

ストレージのログ記録とモニタリング

請求金額が上限行きそうなときのアラート発生させたり

分析とインサイト

保存されているデータの状況に応じて、コストの最適化をしてくれたり。

Amazon S3 の仕組み

これが一番知りたかった。

これなしではサーバーサイドでS3内のデータを操作するのは難しいと思う

Bucket

  • オブジェクト(保存するデータ)のコンテナ(集合体)
    • RDBでいうテーブル
  • 全オブジェクトはバケット内に保存される
  • バケットをどのリージョンに置くか設定できる

Object

  • 基本エンティティ
    • 要は保存されるデータ(.pngとか.pdfとか.txt)
    • RDBでいうレコード
  • オブジェクトデータとメタデータで構成
    • メタデータContent-Typeなどの標準HTTPメタデータも含まれる
      • だから、ダウンロードの時もContent-Typeを指定してあげればダウンロードできたりWEBサーバーに保存できたりするのか
  • キーとバージョンIDによって一意に特定
    • バケット、オブジェクトキー、およびオプションでバージョン IDの組み合わせで一意に特定

Keys

  • オブジェクトの固有の識別子

S3バージョニング

  • 同じバケット内で同一のオブジェクト(複数のバリアント)を保持する時に使用
  • 障害対応に有効

バージョンID

  • バージョニング有効時にオブジェクトに付与されるID

バケットポリシー

  • IAMごとに、バケットとそのオブジェクトへのアクセスを制限(認可)

アクセスコントロールリスト (ACL)

  • 個々のバケットとオブジェクトへの読み取り・書き込みの制限ができる
  • 直近はあまり使用がないらしい
    • IAMで制御できちゃうからかな?
    • サーバーサイド側でその制御(他のユーザーのマイページ内の画像にアクセス、など)ができてしまうからかな?

Amazon S3 の最新のユースケースの大部分では ACL を使用する必要がなくなっており、オブジェクトごとに個別にアクセスを制御する必要がある特例を除き、ACL を無効にすることをお勧めします。

S3 アクセスポイント

  • arn:aws:s3:***みたいなやつ
  • S3 オブジェクトのオペレーション (GetObject や PutObject など) を実行するために使用

ライフサイクルフックの違いによるレンダリングのタイミング~img src編③~

はじめに

前回(~img src編②~)の続き

前回はv-ifの条件をurl(親のプロパティ)にしていたが、今回はchildUrl(子のdataプロパティ)にする

話すこと

前回と同じで、以下コードを例にどのライフサイクルフックをつかったら、どんな挙動を示すのかを順にみていく

やりたいこと

コンポーネントで定義するimgタグのsrc属性の内容に応じて表示する画像を変更する

OKな時

f:id:kuma_kuma0121:20220226221223p:plain

NGな時

f:id:kuma_kuma0121:20220226220839p:plain

ソースコード

親コンポ―ネント

<template>
  <div>
    <ImageChildComponent
      :url="url"
      @replace="replaceUrl"
    ></ImageChildComponent>
  </div>
</template>

<script>
import ImageChildComponent from './ImageChildComponent.vue'

export default {
  components: {
    ImageChildComponent
  },
  data() {
    return {
      url: '',
    }
  },
  methods: {
    replaceUrl(newUrl) {
      console.log(newUrl);
      this.url = newUrl;
    },
  }
}
</script>

コンポーネントでやっていることは前回と変わらない

コンポーネント

<template>
  <div>
    <!-- OK時の画像 -->
        <div
      v-if="childUrl" // 変更
    >
      <h2>OK!!!</h2>
      <img
        :src="childUrl"
        width=200
      >
    </div>
    <!-- NG時の画像 -->
    <div
      v-else
    >
      <h2>NG...</h2>
      <img
        src="../assets/no-image.png"
        width=200
      >
    </div>
  </div>
</template>

<script>
export default {
  props: {
    url: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      childUrl: '',
    }
  },
    beforeCreate() {
    console.log('beforeCreate: ', this.childUrl);
    // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    // this.childUrl = newUrl;
  },
  created() {
    console.log('created: ', this.childUrl);
    // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    // this.childUrl = newUrl;
  },
  beforeMount() {
    console.log('beforeMount: ', this.childUrl);
    // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    // this.childUrl = newUrl;
    // console.log('beforeMount: ', this.childUrl);
  },
  mounted() {
    console.log('mounted: ', this.childUrl);
    // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    // this.childUrl = newUrl;
    // console.log('mounted: ', this.childUrl);
  },
  methods: {
    getUrl() {
      console.log('methods(getUrl): ', this.childUrl);
      // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
      // this.childUrl = newUrl;
      // return this.childUrl;
    }
  }
}
</script>

<template>でやっていることは基本的には一緒ですが、変化点は以下

<script>でやっていることは前回と全く一緒

懸賞方法

前回同様、コメントアウトしてある箇所を随時コメントインして実行していく

デフォルト

ブラウザ

f:id:kuma_kuma0121:20220226220839p:plain

コンソール

f:id:kuma_kuma0121:20220226221655p:plain

  • 今回はエラーがでずにchildUrlがundifiedになっている…

ギモン

  • beforeCreateの時点ではdata()は実行されていないから、前回同様「childUrlなんてない!」って怒られてもいいと思うけど、なんで今回undefinedが定義されているんだろう…

beforeCreated

...
beforeCreate() {
    console.log('beforeCreate: ', this.childUrl);
    const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    this.childUrl = newUrl;
},
...

ブラウザ

デフォルトと同じなので割愛

コンソール

コチラもデフォルトと同じなので割愛

デフォルトでchildUrlを認識しているのであれば、beforeCreateでchildUrlにnewUrl代入すればDOMに反映されると思ったが、違った

created

created() {
    console.log('created: ', this.childUrl);
    const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    this.childUrl = newUrl;
    console.log('created: ', this.childUrl);
},

ブラウザ

f:id:kuma_kuma0121:20220226221223p:plain

コンソール

f:id:kuma_kuma0121:20220226221757p:plain

今までで一番Vueライフサイクルのイメージ通りの挙動した

consoleもcreatedでchildUrlにnewUrlを定義した後は、全てconsole.logでnewUrlの値が出力できている

beforeMount

beforeMount() {
        console.log('beforeMount: ', this.childUrl);
    const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    this.childUrl = newUrl;
    console.log('beforeMount: ', this.childUrl);
},

ブラウザ

createdと同じ

コンソール

f:id:kuma_kuma0121:20220226221842p:plain

こちらも想定通り

boforeMount以降はnewUrlが表示されている

mounted

mounted() {
        console.log('mounted: ', this.childUrl);
    const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    this.childUrl = newUrl;
    console.log('mounted: ', this.childUrl);
},

ブラウザ

created, beforeMouteと同じ

コンソール

f:id:kuma_kuma0121:20220226221911p:plain

mountedでnewUrlをchildUrlに代入しているので、mountedでdataが変更されたタイミングでre-renderingされ、mountedでconsoleに表示される

methods

ここで想定するのは以下のようなソースコード

<template>
        <div
      v-if="url"
    >
      <h2>OK!!!</h2>
      <img
        :src="getUrl()" // 変更
        width=200
      >
    </div>
        ...
</template>

<script>
export default {
    ...,
    methods: {
        getUrl() {
                console.log('methods(getUrl): ', this.childUrl);
          const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
          this.childUrl = newUrl;
          return this.childUrl;
      }
        ...
}
</script>

ブラウザ

f:id:kuma_kuma0121:20220226220839p:plain

コンソール

f:id:kuma_kuma0121:20220226222015p:plain

  • v-ifで表示childUrlがnullなので、常にno-imageが表示される

getUrlをv-ifで呼び出した場合

     
        <div
      v-if="getUrl()"  <!-- 変更 -->
    >
      <h2>OK!!!</h2>
      <img
        :src="getUrl()"
        width=200
      >
    </div>

ブラウザ

f:id:kuma_kuma0121:20220226221223p:plain

コンソール

f:id:kuma_kuma0121:20220226222058p:plain

前回同様で

  • mountedまでは1回めのDOM生成時(v-if, imgのsrc属性)のgetUrl呼び出し時のconsole
  • mounted以降は2回目のDOM生成時のgetUrl呼び出し時のconsole

の出力結果

参考記事

https://jp.vuejs.org/v2/guide/instance.html#ライフサイクルダイアグラム

所見

  • createdでdataプロパティの値を変更するとそれ以降のライフサイクルフックでも変更後の値が取得できる点から、一番Vueのライフサイクル通りの挙動をしていた
  • 前回同様、最後のgetUrの例はライフサイクルを知るのにかなりわかりやすかった
  • imgのsrcの変遷を追うことでだいぶ理解はできたと思う

ライフサイクルフックの違いによるレンダリングのタイミング~img src編②~

はじめに

前回~imgのsrc編①~の続き

今回はv-ifの値が子コンポーネントで定義したdataプロパティだった時はどうなるのか、といいう確認がしたい

いまだに画面描画時に処理をしたい時にbeforeCreate, created, beforeMount, mountedの内どれを使えばいいのかの判断も曖昧なので、そんな自分の一助になればと思った

加えてmethodsでsrcの値をreturnするパターンはどうなのか、ということも確認

話すこと

前回と同じで、以下コードを例にどのライフサイクルフックをつかったら、どんな挙動を示すのかを順にみていく

やりたいこと

コンポーネントで定義するimgタグのsrc属性の内容に応じて表示する画像を変更する

OKな時

f:id:kuma_kuma0121:20220223214525p:plain

NGな時

f:id:kuma_kuma0121:20220223214455p:plain

ソースコード

親コンポ―ネント

<template>
  <div>
    <ImageChildComponent
      :url="url"
      @replace="replaceUrl"
    ></ImageChildComponent>
  </div>
</template>

<script>
import ImageChildComponent from './ImageChildComponent.vue'

export default {
  components: {
    ImageChildComponent
  },
  data() {
    return {
      url: '',
    }
  },
  methods: {
    replaceUrl(newUrl) {
      console.log(newUrl);
      this.url = newUrl;
    },
  }
}
</script>

コンポーネントでやっていることは前回と変わらない

コンポーネント

<template>
  <div>
    <!-- OK時の画像 -->
    <div
      v-if="url"
    >
      <h2>OK!!!</h2>
      <img
        :src="childUrl"
        width=200
      >
    </div>
    <!-- NG時の画像 -->
    <div
      v-else
    >
      <h2>NG...</h2>
      <img
        src="../assets/no-image.png"
        width=200
      >
    </div>
  </div>
</template>

<script>
export default {
  props: {
    url: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      childUrl: '',
    }
  },
beforeCreate() {
    console.log('beforeCreate: ', this.childUrl);
    // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    // this.childUrl = newUrl;
  },
  created() {
    console.log('created: ', this.childUrl);
    // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    // this.childUrl = newUrl;
  },
  beforeMount() {
    console.log('beforeMount: ', this.childUrl);
    // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    // this.childUrl = newUrl;
    // console.log('beforeMount: ', this.childUrl);
  },
  mounted() {
    console.log('mounted: ', this.childUrl);
    // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    // this.childUrl = newUrl;
    // console.log('mounted: ', this.childUrl);
  },
  methods: {
    getUrl() {
      console.log('methods(getUrl): ', this.childUrl);
      // const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
      // this.childUrl = newUrl;
      // return this.childUrl;
    }
  }
}
</script>

<template>でやっていることは基本的には一緒ですが、変化点は以下

  • 表示するimgのsrc属性の値が子コンポーネント特有のdataプロパティchildUrlになっている

<script>でやっていることも基本一緒ですが、変化点は以下

  • コンポーネント特有のプロパティchildUrlを定義
  • 各ライフサイクルフックでchildUrlnewUrlを代入
  • consoleで出力するのはchildUrl

検証方法

手順として前回同様、コメントアウトしてある箇所を随時コメントインして実行していく

デフォルト

ブラウザ

f:id:kuma_kuma0121:20220226220839p:plain

コンソール

f:id:kuma_kuma0121:20220226220956p:plain

前回同様、エラーが2つ。理由も同じなので割愛

beforeCreated

...
beforeCreate() {
  console.log('beforeCreate: ', this.url);
  const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
  this.childUrl = newUrl;
},
...

ブラウザ

デフォルトと同じなので割愛

コンソール

コチラもデフォルトと同じなので割愛

ここまでも前回同様

※beforeCreatedに console.log('beforeCreate: ', this.url); を書いていると常にエラーがでてくるので、これ以降はこの処理をコメントアウトする

created

created() {
    console.log('created: ', this.url);
  const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
  this.childUrl = newUrl;
  console.log('created: ', this.url);
},

ブラウザ

beforeCreateと同じ

コンソール

f:id:kuma_kuma0121:20220226221048p:plain

今回は何も表示されない

恐らくv-ifでurlがnullなので、v-else側に入っているからだと思う

そして、再インスタンス化(前回同様console.logをcreatedに追加)しても結果は変わらない

結果は割愛

created() {
    console.log('created: ', this.url);
    const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    this.$emit("replace", newUrl);
        // インスタンス化するために追加
    console.log('created: ', this.url);
},

ギモン

多分上記推測であってるはず

今回はv-elseに入って、前回はv-if側に入った点からみると

一度templateの描画、インスタンス化→urlの値が変化(前回)→再描画、インスタンス

という流れだろうか

ただこれで行くと、前回各ライフサイクルフックでthis.urlの内容が表示されなかった点はつじつまが合わない

beforeMount

beforeMount() {
    console.log('beforeMount: ', this.url);
    const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    this.childUrl = newUrl;
    console.log('beforeMount: ', this.url);
},

ブラウザ

createdと同じ

コンソール

createdと同じ

ギモン

createdと同じ

mounted

mounted() {
    console.log('mounted: ', this.url);
    const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    this.$emit("replace", newUrl);
},

ブラウザ

created, beforeMouteと同じ

コンソール

created, beforeMouteと同じ

ギモン

created, beforeMouteと同じ

methods

ここで想定するのは以下のようなソースコード

<template>
        <div
      v-if="url"
    >
      <h2>OK!!!</h2>
      <img
        :src="getUrl()" // 変更
        width=200
      >
    </div>
        ...
</template>

<script>
export default {
    ...,
    methods: {
    getUrl() {
      console.log('methods(getUrl): ', this.url);
      const newUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
      this.childUrl = newUrl;
      return this.childUrl;
  }
    ...
}
</script>

ブラウザ

created, beforeMoute, mountedと同じ

コンソール

created, beforeMoute, mountedと同じ

getUrlをv-ifで呼び出した場合

     
        <div
      v-if="getUrl()"  <!-- 変更 -->
    >
      <h2>OK!!!</h2>
      <img
        :src="getUrl()"
        width=200
      >
    </div>

ブラウザ

f:id:kuma_kuma0121:20220226221223p:plain

コンソール

f:id:kuma_kuma0121:20220226221250p:plain

ここで初めてブラウザに表示

コンソールの流れからして、

  • beforeMountまではDOMは生成されないためgetUrlが呼ばれないためchildUrlはnull
  • beforeMount以降はDOMが生成され、getUrlが呼ばれる
    • console3行目:1回目のDOM生成時のv-ifでのgetUrl。初回のconsole.logなので、childUrlには何も入っていない
    • console4行目:1回目のDOM生成時のimg内のgetUrl。既にchildUrlが変更されているので、newUrlが出力
    • console5行目:1回目のDOM生成が終わり、dataが変更されているので、mountedが呼び出される。childUrlはgetUrlで設定したnewUrlが入っている
    • console6, 7行目:それぞれ2回目のv-if, img内のgertUrl呼び出し時のconsole.log。1回目のDOM生成時に設定したchildUrlがそのまま引き継がれている

参考記事

https://jp.vuejs.org/v2/guide/instance.html#ライフサイクルダイアグラム

所見

  • 今回はv-ifの条件がnullでスタートしたので、ほぼno-imageの表示であまり変化が負えなかった
  • とは言え、最後のgetUrの例はライフサイクルを知るのにかなりわかりやすかった
  • いくつかVueのライフサイクルとは想定外の挙動をしている、、、
  • 次はdataをv-ifにした上で、ライフサイクルを追ってみる