tettyはエモがわからない

Web Developerとしてフリーランスやスタートアップでモノづくり(開発)をしているtettyのぼやきが見れる場所。

全集中 Reactの呼吸 Relayの舞 『戯れ』

はじめに

こちらは『DMMグループ '20卒内定者 Advent Calendar 2019'』21日目の記事になります。

本記事は投稿主がNext(React)でGraphqlを使いたいが「apollo多いよね。relay使ってみたくない?」というお気持ちでreact-relayを用いた スターターキットを作り戯れる話 です。

GraphqlとRelayについて

Graphql

てってぃのモノづくり工房 - Graphql

TOPかっこいいですね。非常にクールです。

GraphqlはRESTの課題解決をしようぜというお気持ちから生まれた規格です。 解決したい課題としてはいくつかありますが、投稿主が使っていて感じている利点として 必要なリソースを1つのリクエストで得られるという点にあるのかなと。

おかげさまで幾度もフェッチせずに済み、必要なデータもコンポーネントごとにfragment(Graphqlクエリの断片化)を定義することでDX最高です。

Relay

てってぃのモノづくり工房 - Relay

Graphqlのクールさとは相まって、ポップなTOPが良いですね。

Relayは単一のGraphqlリクエストで結果を得られるようにしたり、Mutation(データの作成・更新)も簡易的に実行できるようサポートされているGraphqlクライアントです。ありがたい。

もちろん、Graphqlクライアントということで以下のようなメリットがあります。

  • コレクションのページネーション
  • データキャッシュ
  • Mutation後にキャッシュの一貫性保持

また、Relayには制限もあります。

  • 採用技術がReact or ReactNativeになる。
  • 導入するアプリケーション構造の自由度を下げる。
  • Relay Server Specificationの規約でクエリとレスポンスの冗長性が少し増す。

Apolloはよく聞くけど、Relayはあまり聞かないよね。

日本記事の検索("react graphql")

てってぃのモノづくり工房 - react graphql

アポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロアポロ。

海外記事の検索("how to use graphql with react")

てってぃのモノづくり工房 - how to use graphql with react

ようやく”Relay”の単語が出てきましたね!ちなみに、この記事の内容はApolloでした。

GraphqlクライアントにApolloが多い理由

単純な理由がいくつかあります。

  • Apolloは様々な環境で使用できるが、RelayはReactがメインである。
  • Apolloは軽量で柔軟なアプローチができるが、Relayは自由度が低い。
  • Apolloはとにかく資料が豊富でキャッチアップが早いが、Relayの資料は少ない。

Apolloはこれから学習するので詳細には語れませんが、細かい比較内容をそのうち記事化します。

現在ではVue.jsでもRelayを導入する個人制作のパッケージ(vue-relay)などがありますが、やはり公式としてはReactオンリーのようです。

スターターキットを作る

0. 前提

  • APIサーバにGraphqlが採用されている。

  • フロントエンド構成は以下を利用する。

    • TypeScript: ^3.7
    • Next: ^9.1
    • React
    • nodenv
    • react-relay
    • relay-runtime
    • relay-compiler
    • @graphql-codegen
    • @material-ui など

1. フロントエンド構成の説明

Next.js

今回、フロントエンドのフレームワークはZeit社のNext.jsを使用していきます。NextはReactを包含してSSRを可能とする素敵な技術ですが、仕事先のプロジェクトでがっつり使っていくということで今回はReact単体ではなくNextも含めて採用しました。

https://nextjs.org/

TypeScipt(3.7系)

Graphqlの型システムを利用するにはこれしかないでしょ!ということでいつものTypeScriptです。3.7系からOptional Chainingが利用できるという点も含めて最新を採用します。

www.typescriptlang.org

nodenv

言わずもがな、nodenv。 プロジェクトごとにnodeバージョンを切り替えます。

github.com

react-relay, relay-runtime, relay-compiler

ReactでRelayを使用するコアパッケージや、Graphqlのスキーマファイルから各コンポーネントに対応する型定義ファイルを自動生成してくれる方々です。

relay.dev

@graphql-codegen

GraphqlスキーマファイルからTSの型定義ファイルを生成することに利用します。便利。

graphql-code-generator.com

@material-ui

毎度お馴染み、Material Designのパッケージです。

material-ui.com

2. Next + TSの環境作成

ざっとこんな感じの構成を作りました。 尚、Nextはpagesディレクトリなど、Next規定のディレクトリやファイルを作成すればちゃんと認識してくれます。

次セクションでディレクトリやファイルをピックアップして解説します。

project/
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
│   ├── _app.tsx
│   ├── _document.tsx
│   ├── _error.tsx
│   └── index.tsx
├── public
│   ├── fonts
│   ├── icons
│   └── images
├── src
│   ├── components
│   ├── layouts
│   ├── lib
│   ├── styles
│   └── types
│ 
└── tsconfig.json

next.config.js

Nextの設定ファイルになります。Nuxtでいうnuxt.configのようなものになります。

  • Dotenv

環境を振り分けるケースがわりかし多かったので入れました。 注意点としては通常のDotenvではなくdotenv-webpackを使用することになる点です。

  • withSass

今回はsrc/stylesにスタイリングファイル(Sass)を分離し、CSS moduleとして各コンポーネントで利用したかったので使用しました。

const withSass = require('@zeit/next-sass');
const Dotenv = require('dotenv-webpack');
const path = require('path');

module.exports = withSass({
  cssModules: true,
  sassLoaderOptions: {
    includePaths: ["src/styles/**/*.sass"]
  },
  webpack: config => {
    config.plugins = config.plugins || [];

    config.plugins = [
      ...config.plugins,
      new Dotenv({
        path: path.join(__dirname, '.env'),
        systemvars: true
      })
    ];

    return config
  }
});

pages/

Nextがページとして認識するディレクトリです。 各ページファイルだけでなく、Next規定のファイル(_hogehoge.tsx)も設置します。

public/

アセットファイルを設置するディレクトリです。 以前はstaticでしたが、最近のバージョンではpublicに名を変えています。

src/components

コンポーネントを格納します。 Graphqlのfragment(断片化されたクエリ)を定義したコンポーネントなども含めるため、relay-compilerに読み取らせると各fragmentコンポーネントに必要な型定義が生成されます。 こんな感じ。

てってぃのモノづくり工房 -flagmentコンポーネント

src/layouts

ページのレイアウトファイルを格納しています。 headerやfooterをセットにしたファイルですね。

src/lib

material-uiのテーマファイルやRelayのコンフィグ、ユーティリティなどを格納します。

src/styles

CSS moduleとして使用するSassファイルを格納します。 ここの細かい構造はまた今度。

3. Graphqlスキーマの生成

Graphqlのエンドポイントからスキーマファイルを生成するためget-graphql-schemaをインストールします。

使用

インストール後、Graphqlエンドポイントへ向けてスキーマファイルを生成するためのコマンドを実行します。

> get-graphql-schema <endpoint_url> schema.graphql

するとsrc/types/schema.graphqlが1秒もかからずにファイルが生成され、以下のようになります。

type User implements Node {
  id: ID!
  user: User!
}

"""The connection type for History."""
type UserConnection {
  """A list of edges."""
  edges: [UserEdge]

  """Information to aid in pagination."""
  pageInfo: PageInfo!
  totalCount: Int!
}
...

// スキーマが続く。すごいぞ。

4. コードジェネレーターの導入

Graphqlスキーマファイルから型定義ファイルを自動生成するため@graphql-codegenを導入します。 ひとまず、この辺りのパッケージを入れておきました。

"@graphql-codegen/cli": "^1.9.1", // 必須
"@graphql-codegen/typescript": "^1.9.1", //必須
"@graphql-codegen/typescript-operations": "^1.9.1",
"@graphql-codegen/typescript-resolvers": "^1.9.1",

また、自動生成にあたってのyamlを定義しなければならないのですが、最初は特にこだわることもないかなと思いますので以下のようにシンプルな定義にします。

overwrite: true
schema: "schema.graphql"
generates:
  src/types/graphql.ts:
    plugins:
      - typescript
      - typescript-resolvers

使用

諸々が整った段階でコマンドを実行します。いざ、ジェネレート。

> graphql-codegen --config codegen.yml

するとスキーマからsrc/types/graphql.tsが生成され、スキーマの型定義ファイルとして使用できるようになります。このような感じ。openAPIと同様ですね。

import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
export type Maybe<T> = T | null;
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type RequireFields<T, K extends keyof T> = { [X in Exclude<keyof T, K>]?: T[X] } & { [P in K]-?: NonNullable<T[P]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string,
  String: string,
  Boolean: boolean,
  Int: number,
  Float: number,
  /** An ISO 8601-encoded datetime */
  ISO8601DateTime: any,
};

...

//型定義が続く。強いぞジェネレータ。

5. RelayのEnvironmentファイル作成

クライアント-Graphqlエンドポイント間での通信やレスポンスをキャッシュするためのストアなど、Relayをフルに使っていくために定義をする必要があります。

import {Environment, Network, RecordSource, Store} from 'relay-runtime';

export const fetchQuery = (operation: any, variables: any) => {
  return fetch(process.env.RELAY_ENDPOINT as string, {
    method: 'POST',
    body: JSON.stringify({
      query: operation.text,
      variables,
    }),
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
  }).then((response: Response) => {
    if (response.ok) {
      return Promise.reject(response.statusText);
    }
    return response.json()
  });
};

const environment = new Environment({
  network: Network.create(fetchQuery),
  store: new Store(new RecordSource())
});

export default environment;

ここで重要なのが以下の2つ。

  • NetWork

  • Store

    • レスポンスを格納しキャッシュ、ローカルステートとして利用できるようにしてくれる。

6. Envファイル作成と完成

最後に、エンドポイントは様々なのでenvファイルで対応しておきましょう。

RELAY_ENDPOINT="http://localhost:xxxx/graphql"

以上でキットが完成しました。 そして、このキットを用いることでGraphql + Reactの開発を開始することができます。やったね。

感想

環境構築から既にしんどい思いをしたので、プロジェクトの立ち上がりがそこそこ遅いです。ですので、やるならばしっかりとスターターキットを作るべきだと感じました。

問題は土台のメンテナンスコストが負担になる点でしょうか。 環境構築の時点で非常に多くのパッケージを入れたかと思います。

また、Apolloも使ってみてどちらを採用するのか検討していきたいと思いますが、その前にせっかくRelayのキットを作ったので、 次回はキットを用いてコンポーネントを実装していく記事を書いていきたいと思います。

ではまた。

参考リポジトリ

本日のスターターはこちら。 まだ作っただけですので、READMEやexampleなど整備していきます。

github.com  

その他

Githubが公式でGraphqlのお試しを用意しているので、触ってみたい方はぜひ。 Docsをクリックすると叩けるクエリなどが表示されます。(これ、なんと自分らのクライアントでもできてしまうんです。)

developer.github.com

あ、クエリの叩き方はご自分で勉強してくださいね。

graphql.org

Vue3到来記念。Vueのアレって結局なんだっけシリーズ。〜render関数 編〜

はじめに

こちらは『DMMグループ '20卒内定者 Advent Calendar 2019』8日目の記事になります。

初アドカレ&初個人ブログということで気楽に発信していきたいと思います。

実装のみを見たい方は以下「render関数でIconコンポーネントを作る(準備編)」からご覧ください。

ご挨拶

日頃よりお世話になっております。tettyです。

今回は私の自己紹介+αですが、大体の自己紹介はもう書いてしまったんですよね...。

tetty.hateblo.jp

というわけで、今回は自己紹介のうち 技術・業務的なこと にフォーカスすることにしましょう。

自己紹介

はい。てってぃと申します。 自己紹介にはあまり興味がなく、早く主題に移りたいので簡単に。

1. Web Developerとして活動

  • Web Developerを目指してかれこれ3年ほど頑張っている。
  • メイン1社、お手伝い2社に技術者として関わる。

2. 立ち上げ開発・プロダクト検証がお好き

  • 好みof好み。テックを経て世界を知る=自己満足感。
  • 主にフロントエンドの技術選定から構築・実装まで行う。
  • サーバサイドを担当することはもちろんある。

3. UXエンジニアもまた目標

  • これもまた目指して頑張っている。
  • 20代半ばくらいにUXうるさいおじさんになりたい。
  • 日々自分の作ったプロダクトにクレームを入れている。

4. 最近の活動

  • Next.js + TypeScript + Rails5 + Graphqlでプロダクト開発
    • 直近の目標はExpress + GraphqlでBFFを作ること。難しい。
    • 楽しい👏
  • Nuxt.js + TypeScript + PWA(SW)+ FireStoreで卒業制作を開発
    • KVSとJS→TSへの移行がとてもつらみ。
    • ネイティブアプリのUIやアニメーションの実装にチャレンジしている。難しい。
    • 楽しい👏
  • Rails5 + S3 + Lambdaでスタートアップのプロダクト開発
    • PM業と開発、ドメイン知識のインプット、接待を並行でやることがとてもつらみ。
    • 楽しい👏
  • DMM.comへの内定承諾について
    • 一瞬すぎたのでお話しすることがあまりなく。
    • 別記事でまとめたいと思います。

本題:render関数ってなんだっけ?

Vue.jsも少しばかり時間が経ってきたのかな?と思うこの頃ですが、ご存知の方よりも知らない(使ったことない)という方が多いであろう render関数 についてお話します。

1. そもそも

まずrender関数が目に入るのは main.jsですが、それがこちら。

// main.js
new Vue({
  render: h => h(App)
}).$mount("#app");

@vue/cliでVueプロジェクトをインストールすると作成されるものになりますが、この中に記述されている render: h => h(App) って何?他で使うタイミングあるの?

2. render関数

私がVueを使い始めたのは1年と少し前。「は〜。renderはAppコンポーネントを描画する?関数なんだな〜。h?なにこれ???HOCのh???」という第一印象になりました。

こちら、render関数を追っていくことで答えを見つけることができます。

// render関数
render?(this: undefined, createElement: CreateElement, context: RenderContext<Props>): VNode | VNode[];

// hのやつ
export interface CreateElement {
  (tag?: string | Component<any, any, any, any> | AsyncComponent<any, any, any, any> | (() => Component), children?: VNodeChildren): VNode;
  (tag?: string | Component<any, any, any, any> | AsyncComponent<any, any, any, any> | (() => Component), data?: VNodeData, children?: VNodeChildren): VNode;
}

型の通りですが「h」なる存在はcreateElementのこと。

htmlタグもしくはVueコンポーネントなどを渡すと仮装DOMを返却してくれる関数だとわかります。(ちなみにhはhyperscriptの略だそう。)

3. 過去の過ち

render関数についてまとめようと思った動機ですが、自分の過去リポジトリを漁っていてとんでもないものを見つけてしまったことにあります。それがこちら。

<template>
  <div>
    <IconAdd v-if="name === 'add'" />
    <IconAlert v-if="name === 'alert'" />
    <IconBlock v-if="name === 'block'" />
    <IconFace v-if="name === 'face'" />
    <IconFile v-if="name === 'file'" />
    <IconLock v-if="name === 'lock'" />
    <IconPerson v-if="name === 'person'" />
    <IconTime v-if="name === 'time'" />
    ...

「う゛!!!!!!!!!!!!!!!!!!!」

何がとんでもないのかよくお分かりかと思います。 これでは、選ばれなかったアイコン達がHTML自体にコメントとしてレンダリングされてしまいますね。パフォーマンスもよくありません。

さて、この暗黒物質を綺麗にしていきましょう。

render関数でIconコンポーネントを作る(準備編)

0.環境

  • node: v11.10.0
  • @vue/cli: v4.1.1
  • IDE: WebStorm 2019.2
  • PC: macOS Mojave v10.14.4
  • その他詳しくはこちら

1. ファイル構成

Iconコンポーネントを作成する際の構成は以下でいきます。

components
├── Example.vue
├── atoms
│   └── IconTextField.vue
└── icon
    ├── Icon.js
    ├── icons
    │   ├── IconCheckbox.vue
    │   ├── IconDoneOutline.vue
    │   ├── IconFace.vue
    │   ├── IconLock.vue
    │   ├── IconMoreHoriz.vue
    │   └── index.js
    └── types.js

2. アイコンに使いたいSVGMaterial Designから拝借してきます。

material.io

3. SVGをVueコンポーネント化します。

// icon/icons/IconLock.vue

<template>
  <svg
    xmlns="http://www.w3.org/2000/svg"
    :width="size"
    :height="size"
    :viewBox="viewBox"
  >
    <path d="M0 0h24v24H0z" fill="none" />
    <path
      :style="style"
      d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"
    />
  </svg>
</template>

<script>
import { iconColors, typeColor } from "../types";

export default {
  name: "IconLock",
  props: {
    size: {
      type: Number,
      default: 24
    },
    color: {
      type: String,
      default: "primary",
      validator: v => typeColor.includes(v)
    }
  },
  methods: {
    getIconColor() {
      return iconColors[this.color] || iconColors.primary;
    }
  },
  computed: {
    style() {
      return { fill: `${this.getIconColor(this.color)}` };
    },
    viewBox() {
      return `0 0 ${this.size} ${this.size}`;
    }
  }
};
</script>

4. 作成したアイコン達をまとめます。

// icon/icons/index.js

import IconFace from "./IconFace";
import IconDoneOutline from "./IconDoneOutline";
import IconMoreHoriz from "./IconMoreHoriz";
import IconCheckbox from "./IconCheckbox";
import IconLock from "./IconLock";

export default {
  checkbox: IconCheckbox,
  doneOutline: IconDoneOutline,
  face: IconFace,
  moreHoriz: IconMoreHoriz,
  lock: IconLock
};

5. Iconコンポーネントに必要な属性値やカラーを用意します。

// icon/types.js

export const iconColors = {
  primary: "#2196F3",
  secondary: "#607D8B",
  danger: "#f44336",
  warning: "#FF9800"
};

export const typeColor = ["primary", "secondary", "danger", "warning"];

export const typeIcon = ["face", "checkbox", "doneOutline", "moreHoriz", "lock"];

render関数でIconコンポーネントを作る(実装編)

突然ですが、完成形です。(効果音)

// icon/Icon.js

import { typeColor, typeIcon } from "./types";
import icon from "./icons";

export default {
  name: "Icon",
  props: {
    color: {
      type: String,
      default: "primary",
      validator: v => typeColor.includes(v)
    },
    name: {
      type: String,
      default: "face",
      validator: v => typeIcon.includes(v)
    },
    size: {
      type: Number,
      default: 24
    }
  },
  methods: {
    getIcon() {
      return icon[this.name] || icon.checkbox;
    }
  },
  render(h) {
    const IconComponent = this.getIcon();
    return h(
      "i",
      {
        class: "Icon"
      },
      [
        h(IconComponent, {
          attrs: {
            size: this.size,
            color: this.color
          }
        })
      ]
    );
  }
};

templateもstyleも綺麗さっぱり無くなりました。

そして、例の暗黒物質も目に入ることが無くなりましたね。嬉しい!!!

解説

コンポーネント定義

h(
  "i",
  {
    class: "Icon"
  },
  [
    h(IconComponent, {
      attrs: {
        size: this.size,
        color: this.color
      }
    })
  ]
);

ついにhがでてきました。ここでは、実際にレンダリングされるコンポーネントとその属性やdirectionなどを定義していきます。

使用できるパラメータに関してはこちらを参照してください。

ちなみにtemplateを使った場合の比較は以下の通りです。

<template>
  <i class="Icon">
    <IconComponent :size="size" :color="color" />
  </i>
</template>

Iconコンポーネントの取得

getIcon() {
  return icon[this.name] || icon.checkbox;
}

コードの通りなのですが、主に暗黒物質を綺麗にした箇所がこちらになります。 アイコンリストを辞書化しているので、相応のアイコン名をkeyにとして各アイコンを取得することができます。

render関数では使用するcomponentsを定義する必要がありませんので、こういったシンプルな書き方ができるのです。

完成形

Vue3到来記念。Vueのアレって結局なんだっけシリーズ。render関数編

おまけ

せっかくIconコンポーネントを作成したので、もっと実用化してみましょう。

以下はIconコンポーネントを用いたTextFieldコンポーネントですが、 エラー時はアイコンごとカラーが変化するようになっています。みたいな。

// atoms/IconTextField.vue

<template>
  <div class="IconTextField" :style="styles.root">
    <Icon
      :color="attr.icon"
      :size="20"
      name="lock"
      class="IconTextField__icon"
    />
    <input
      :id="`${name}-TextField`"
      :class="classes.input"
      :name="name"
      :value="value"
      :placeholder="placeholder"
      type="text"
      @input="onInput"
    />
  </div>
</template>

<script>
import Icon from "../icon/Icon";

export default {
  name: "IconTextField",
  components: { Icon },
  props: {
    name: {
      type: String,
      default: "input"
    },
    value: {
      type: String,
      default: ""
    },
    placeholder: {
      type: String,
      default: "placeholder"
    },
    fullWidth: {
      type: Boolean,
      default: false
    },
    error: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    onInput(e) {
      if (e) {
        this.$emit("input", e.target.value);
      }
    }
  },
  // Optional Chaining使いてぇ〜〜〜。
  computed: {
    styles() {
      return {
        root: {
          width: this.fullWidth ? "100%" : null
        }
      };
    },
    classes() {
      return {
        input: this.error
          ? "IconTextField__input--error"
          : "IconTextField__input"
      };
    },
    attr() {
      return {
        icon: this.error ? "danger" : "secondary"
      };
    }
  }
};
</script>

完成形

Vue3到来記念。Vueのアレって結局なんだっけシリーズ。render関数編

おわり

render関数、使ってみたくなりましたか?

もしそう思っていただけると記事にした甲斐があるので嬉しいです。

フロントエンドの開発経験としてはまだまだ若いですが、これから定期的に発信させていただければと思います。最後までご覧いただきありがとうございました!

ご意見や改善点などありましたら、ぜひいただけると嬉しさの極みです。

今回のサンプルリポジトリ

github.com

投稿者について

tetty.hateblo.jp

ハローtetty活躍の場!

ご挨拶

どうも、tetty(ててぃ)です。 tettyの由来は本名+トッティ からです。どうでもいいですね。

さて、初のアドベントカレンダー参加をしたということもありますが、ブログを書く余裕もじわじわとできてきたので、これから色々と書き留めていきます。

また、当ブログは投稿者のモノづくり記録を主な目的として運営していきますが、訪問していただいた方にも何かインプットがあれば幸いです。

投稿者について

tettyです。 おそらく日本で最大規模の某専門学校に通っている21歳♂です。 ちなみに、某情報系国立大学の推薦を蹴った罪を背負って生きています。

やっていること

主にスタートアップベンチャー数社で色々やらせていただいてます。

  • 外部CTO
  • PM
  • 立ち上げ開発
  • パートナー開発(フロントエンド中心)などなど
Q > ...ちゃんと学校行ってる?
A > 行ってます。でも最近はやばいです。

投稿者はクリエイティブであることに命を注いでいるので、スケジュールが詰まっていようが結果良ければ全て良しなのです。良いモノ作って天に召されたい。

もう少し詳しく!

投稿者の仕事場を並べておきます。

noFRAME schools, inc.

  • 外部CTOやPMなどやってます(エンジニア募集中だよ)

https://www.noframeschools.com/www.noframeschools.com

株式会社プレックス

  • フロントエンドの選定・構築・開発・コードレビューなど

plex.co.jp

Nzigen, Inc.

  • 技術者としての基礎を叩き込んでいただいた。
  • 主にフロントエンドのパートナー開発。

nzigen.com

SNSもやってるよ

twitter.com

  • wantedly(更新しばらくしてないので許して欲しい)

www.wantedly.com

好み

  • 美味しいご飯
  • お酒(煙たいウイスキー・ワイン・ビール)
  • ソシャゲ(GvG系大好きマン)
  • 猫ちゃん(あ〜好き。尊いよね。)

終わりに

雰囲気はわかっていただけましたでしょうか? 自分のことについては、何かの節目で更新していこうと思っています。 ではでは、以後よろしくお願いいたします!