20201128のJavaScriptに関する記事は30件です。

【React】環境構築から起動まで

はじめに

React を習得するまでの軌跡をメモっていく備忘録的な記事です。
https://qiita.com/u_query/items/51b4140a450ee5d51dcc の続きです

component を表示してみる

create-react-appで作ったデフォルトのファイルは中身は全部消して一から作ります。

index.js
import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
  return (
    <p>Hello, World</p>
  )
};

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

表示成功!

スクリーンショット 2020-11-28 23.43.07.png

まとめ

とりあえずファイルの先頭で以下の2行を書けば React が動く。

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

ReactDOM.renderメソッドを使って第一引数にコンポーネント、第二引数にdocument.getElementById('root')を入れる。

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

コンポーネントは以下のように定義をする。
returnの中に JSX を書いていき、ブラウザにはこの部分が表示される。

const App = () => {
  return (
    <p>Hello, World</p>
  )
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】component を作る

はじめに

React を習得するまでの軌跡をメモっていく備忘録的な記事です。
https://qiita.com/u_query/items/51b4140a450ee5d51dcc の続きです

component を表示してみる

create-react-appで作ったデフォルトのファイルは中身は全部消して一から作ります。

index.js
import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
  return (
    <p>Hello, World</p>
  )
};

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

スクリーンショット 2020-11-28 23.43.07.png

表示成功

まとめ

とりあえずファイルの先頭で以下の2行を書けば React が動く。

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

ReactDOM.renderメソッドを使って第一引数にコンポーネント、第二引数にdocument.getElementById('root')を入れる。

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

コンポーネントは以下のように定義をする。
returnの中に JSX を書いていき、ブラウザにはこの部分が表示される。

const App = () => {
  return (
    <p>Hello, World</p>
  )
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiita記事のコメント欄を内容直下に移動するユーザースクリプト

コメント欄を記事内容直下に移動し、スクロールの手間を減らします。
greasemonkeytampermonkey でご利用ください。

ページ読み込み直後に一瞬カクつくのは仕様です。(ある程度スクロールしないとコメントが表示されないため、最下部までスクロールさせて戻しています)

コメントの確認を目的としていたので、移動後のコメント入力欄が問題なく動作するかは未確認です。

// ==UserScript==
// @name         Qiita記事のコメント欄移動
// @version      1.0
// @match        *://qiita.com/*/items/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const scrollX = window.scrollX;
    const scrollY = window.scrollY;
    scrollTo(0, document.getElementsByTagName('body')[0].scrollHeight);
    setTimeout(function() { scrollTo(scrollX, scrollY); }, 50);

    const mo = new MutationObserver(function() {
        const comment = document.getElementById('comments');
        const userInfo = document.getElementsByClassName('ai-Container');
        if (comment && userInfo.length) {
            userInfo[0].parentElement.insertBefore(comment, userInfo[0]);
            mo.disconnect();
        }
    });
    mo.observe(document.getElementsByClassName('p-items')[0], {childList: true, subtree: true});
})();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】環境構築から起動まで

はじめに

React を習得するまでの軌跡をメモっていく備忘録的な記事です。

環境構築

yarn は installしておく

$ yarn global add create-react-app

React アプリに必要なファイル群を作成

$ create-react-app sample_app

アプリの起動

sample_app $ yarn start

localhost:3000 にアクセスすると...

スクリーンショット 2020-11-28 23.32.40.png

表示成功

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FizzBuzz(通常 / 再帰 / クラス)

はじめに

なんか書き殴りたくなったので書きます。

プログラム始めたばかりの時は、

「オブジェクト指向…??」

「再帰???」

みたいな印象だったのですが、気付いたら書けるようになってました。嬉しい。

備忘録的に残しておきます。
壮大なもの書こうかと思ったんですが、初心に帰ってFizzBuzzで

補足

①この記事では「これがFizzBuzzだ」みたいな話はしません
定義は下記に記しますがその定義があっているかどうかは議論しません。

②再帰的な書き方だったりクラスの書き方はもっと良い方法あると思います。
そんな時は是非マサカリが欲しいです。

定義
・1~100を順番に表示する
・3と5の倍数の場合[FizzBuzz]と表示する
・3の倍数の場合[Fizz]と表示する
・5の倍数の場合[Buzz]と表示する

通常ver

for (let i = 1; i <= 100; i++) {
  const Fizz = 3;
  const Buzz = 5;
  if (i % (Fizz * Buzz) === 0) {
    console.log(i, "FizzBuzz");
  } else if (i % Fizz === 0) {
    console.log(i, "Fizz");
  } else if (i % Buzz === 0) {
    console.log(i, "Buzz");
  } else {
    console.log(i);
  }
}

まぁ他にも書き方あると思いますがとりあえず次。

再帰ver

const FizzBuzz = (i, lastNum) => {
  const Fizz = 3;
  const Buzz = 5;
  if (i % (Fizz * Buzz) === 0) console.log(i, "FizzBuzz");
  if (i % Fizz === 0) console.log(i, "Fizz");
  if (i % Buzz === 0) console.log(i, "Buzz");
  if (i === lastNum) return;
  console.log(i);
  return FizzBuzz((i += 1), lastNum);
};

FizzBuzz(1, 100);

書いてから気付いたんですがFizzBuzzが再帰に向いてない気がする。
なんか書いてて再帰の良さみたいなものを表現できなかった…

クラスver

class CheckNumbers {
  constructor(i, Fizz, Buzz) {
    this.i = i;
    this.Fizz = Fizz;
    this.Buzz = Buzz;
  }

  main() {
    this.checkFizzBuzz();
    if (this.i === 100) return;
    return this.main((this.i += 1));
  }

  checkFizzBuzz() {
    if (this.i % (this.Fizz * this.Buzz) === 0) console.log(this.i, "FizzBuzz");
    if (this.i % this.Fizz === 0) console.log(this.i, "Fizz");
    if (this.i % this.Buzz === 0) console.log(this.i, "Buzz");
    if (this.i !== 100) console.log(this.i);
  }
}

new CheckNumbers(1, 3, 5).main();

なんかもっとよく書こうと書いてるうちに飽きてしまった…

もっとええ書き方あるで!っていう場合は是非教えてくださいm_ _m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.js、Firestore、axiosでデータの投稿日時表示、ソート機能実装

この記事の概要

↓以前書いた記事で作った掲示板アプリの追加実装を行います。
Nuxt.js、Firebase、axiosでパパッと掲示板!

※※※
前回からの続きになっております
以下、適宜axiosメソッドに渡しているURLのYOUR_PROJECT_IDを自分のプロジェクトIDに置き換える必要があることに注意してください。
YOUR_PROJECT_IDのままだと以下のエラーが出ます。
スクリーンショット 2020-11-24 20.39.13.png


追加するのは

・投稿日時の表示
・投稿日時順にソート

目標物

demo

demo

追加で導入するもの

moment (時刻フォーマットの整形用)
lodash (ソート用)

moment

ターミナル
$ npm install --save moment
pages/index.vue
<script>
import moment from 'moment';

lodash

導入方法は以下参照ください。
webpack プラグインを追加するには?

nuxt.config.js
import webpack from 'webpack'
export default {

  //・・・・・・省略

  // Build Configuration (https://go.nuxtjs.dev/config-build)
  build: {
    plugins: [
      new webpack.ProvidePlugin({
        _: 'lodash'
      })
    ]
  }
}

投稿日時の表示

送るデータにcreatedを追加

pages/index.vue
<script>
//・・・・・・・・・・・省略
  methods: {
    submitPosts() {
      this.$axios
        .$post(
          'https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/posts',
          {
            fields: {
              name: {
                stringValue: this.name
              },
              comment: {
                stringValue: this.comment
              },
//------------↓ここから--------------
              created: {
                timestampValue: new Date()
              }
//------------↑ここまで--------------
            }
          }
        )
        .then(() => {
          this.name = '';
          this.comment = '';
          this.getPosts();
        });
//・・・・・・・・・・・省略
</script>

そして表示

pages/index.vue
<template>

      <p>名前:{{post.fields.name.stringValue}}</p>
      <br>
      <p>コメント:{{post.fields.comment.stringValue}}</p>
      <br>
<!-----↓ここから-------------------------------------------------------->
      <p>投稿日時:{{post.fields.created.timestampValue}}</p>
      <br>
<!-----↑ここまで-------------------------------------------------------->
    </div>
  </div>
</template>

今こんな感じです。

スクリーンショット 2020-11-28 19.57.35.png

あまり見慣れないフォーマットなのでわかりやすく整形します。


filterを使ってフォーマットを整形してあげましょう。
さらにここでmomentを使用しています。

pages/index.vue
<script>
//・・・・・・・・・・・省略
    getPosts() {
      this.$axios.$get(
        "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/posts"
      )
      .then(res => {
        this.posts = res.documents;
      });
    }
  },
//------------↓ここから--------------
  filters: {
    dateFilter(date) {
      return moment(date).format('YYYY/MM/DD HH:mm:ss');
    }
  }
//------------↑ここまで--------------
};
</script>
pages/index.vue
<template>
<!-----省略-------------------------->

      <p>投稿日時:{{post.fields.created.timestampValue | dateFilter}}</p>

<!-----省略--------------------------->
</template>


こうなったらOKです。
スクリーンショット 2020-11-28 20.08.25.png


投稿日時でソートさせる

現状はこんな感じです。
これをソートさせて行きます。

スクリーンショット 2020-11-28 20.13.30.png

lodash_.orderBy関数を使います

第一引数にソートしたい配列もしくはオブジェクト。
第二引数に値を渡します。

pages/index.vue
<script>
//・・・・・・・・・・・省略
    getPosts() {
      this.$axios.$get(
        "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/posts"
      )
      .then(res => {
//-----------------------↓ここ--------------
        this.posts = _.orderBy(res.documents, 'fields.created.timestampValue');
      });
    }
  },
  filters: {
    dateFilter(date) {
      return moment(date).format('YYYY/MM/DD HH:mm:ss');
    }
  }
};
</script>

スクリーンショット 2020-11-28 20.17.22.png


OKです。
最後に昇順、降順を切り替えられる様にします。

昇順、降順の切り替え

pages/index.vue
<template>
  <div>
    <h1>掲示板!</h1>
    <br>名前
    <div>
      <input type="text" v-model="name">
    </div>コメント
    <div>
      <textarea v-model="comment"></textarea>
    </div>
    <br>
    <button @click="submitPosts">投稿する</button>
    <br>
    <br>
    <h2>投稿一覧</h2>
<!-----↓ここから-------------------------------------------------------->
    <select v-model="selected" @change="getPosts">
      <option>新しい順</option>
      <option>古い順</option>
    </select>
<!-----↑ここまで-------------------------------------------------------->


<!-----省略-------------------------->


</template>

<script>
import moment from 'moment';
export default {
  data() {
    return {
      name: '',
      comment: '',
      posts: [],
//--------↓ここ--------------
      selected: '新しい順'
    };
  },


//・・・・・・・・・・・省略
</script>

まずプルダウンを用意します
「新しい順」←→「古い順」と切り替わるごとにselectedにその値が入ります、
selectedのデフォルトの値は「新しい順」としておきます。
さらに切り替わるごとにgetPostsメソッドを呼びリストレンダリングをします。


pages/index.vue
<script>
//・・・・・・・・・・・省略
    getPosts() {
      console.log('getPost')
      this.$axios
        .$get(
          'https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/posts'
        )
        .then(res => {
          switch (this.selected) {
            case '新しい順':
              this.posts = _.orderBy(res.documents, 'fields.created.timestampValue');
              break;
            case '古い順':
              this.posts = _.orderBy(res.documents, 'fields.created.timestampValue', 'desc');
              break;
          }
        });
    }
  },
//・・・・・・・・・・・省略
</script>

_.orderByの第三引数に'desc'を渡すことで降順になります。
switch文でselectedの値が「新しい順」なら昇順、「古い順」なら降順とします。

※第三引数に'asc'を渡すことで昇順を指定することもできます。

完成!

pages/index.vue
<template>
  <div>
    <h1>掲示板!</h1>
    <br>名前
    <div>
      <input type="text" v-model="name">
    </div>コメント
    <div>
      <textarea v-model="comment"></textarea>
    </div>
    <br>
    <button @click="submitPosts">投稿する</button>
    <br>
    <br>
    <h2>投稿一覧</h2>
    <select v-model="selected" @change="getPosts">
      <option>新しい順</option>
      <option>古い順</option>
    </select>
    <br>
    <div v-for="post in posts" :key="post.id">
      <hr>
      <br>
      <p>名前:{{post.fields.name.stringValue}}</p>
      <br>
      <p>コメント:{{post.fields.comment.stringValue}}</p>
      <br>
      <p>投稿日時:{{post.fields.created.timestampValue | dateFilter}}</p>
      <br>
    </div>
  </div>
</template>

<script>
import moment from 'moment';
export default {
  data() {
    return {
      name: '',
      comment: '',
      posts: [],
      selected: '新しい順'
    };
  },
  created() {
    this.getPosts();
  },
  methods: {
    submitPosts() {
      this.$axios
        .$post(
          'https://firestore.googleapis.com/v1/projects/vue-test-df33d/databases/(default)/documents/posts',
          {
            fields: {
              name: {
                stringValue: this.name
              },
              comment: {
                stringValue: this.comment
              },
              created: {
                timestampValue: new Date()
              }
            }
          }
        )
        .then(() => {
          this.name = '';
          this.comment = '';
          this.getPosts();
        });
    },
    getPosts() {
      this.$axios
        .$get(
          'https://firestore.googleapis.com/v1/projects/vue-test-df33d/databases/(default)/documents/posts'
        )
        .then(res => {
          switch (this.selected) {
            case '新しい順':
              this.posts = _.orderBy(res.documents, 'fields.created.timestampValue');
              break;
            case '古い順':
              this.posts = _.orderBy(res.documents, 'fields.created.timestampValue', 'desc');
              break;
          }
        });
    }
  },
  filters: {
    dateFilter(date) {
      return moment(date).format('YYYY/MM/DD HH:mm:ss');
    }
  }
};
</script>

お疲れさまでした!
最終的に記事上部の目標物の様になっていればOKです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フロントエンドやるなら入れておくべきESlintってなに?

プログラミング勉強日記

2020年11月28日
昨日の記事でPrettierについて扱ったが、Prettierと合わせて使用することのできるESlintについて紹介する。

ESlintとは

 ESlint(読み方:「イーエスリント」)は、JavaScriptやTypeScriptなどの静的解析ツールである。ESlintを導入することで、単純な構文エラーやプロジェクト固有のコーディング規約を定義することができる。厳密なルールを定義することで、複数人で開発する場合でもシステム全体のコードの一貫性を維持することができる。

ESlintの特徴

  • 自由に多くのルールを設定できる
  • 独自ルールを作成するAPI
  • 固有のライブラリー、フレームワーク、および実践のルールを持つ多数のプラグイン
  • ES6、ES7、JSXの内蔵サポート
  • 迅速に開始できるように、推奨ルールだけでなくサードパーティの設定利用が可能
  • Sublime、Vim、JetBrainsの製品およびVisual Studio Codeなどの、複数のエディタやIDEとの統合が可能

(参考文献:JSプログラマーのイラッとする「クセ」はESLintを導入して対処しよう)

ESlintの導入方法

1. ESlintを使ってTypeScriptを解析するためのライブラリをインストールする

 nodeのルートディレクトリに移動して、eslint, @typescript-eslint/parser, @typescript-eslint/eslint-plugin, eslint-plugin-reactの4つをインストールする。

コマンドプロンプト
npm install --save eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react

2. ファイルを作成して設定する

 eslintrc.jsonファイルを作成して、以下のように設定する。

.eslintrc.json
{
  "parser": "@typescript-eslint/parser", 
  "extends": [
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true // Allows for the parsing of JSX
    }
  },
  "rules": {
    "react/prop-types": "off",
    "@typescript-eslint/no-empty-interface": 0
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

3. package.jsonにscriptを追加する

 package.jsonにESLintが実行できるようにscriptsタグに以下のコマンドを追加する。今回は拡張子が.ts.tsxのみを対象に解析する。

package.json
"lint": "eslint . --ext .ts,.tsx"

4. ESlintの実行

ターミナル
npm run lint

PrettierとESlintが連携してフォーマットを適用させる場合

 Prettierはリントツールと組み合わせて利用することができるので、すでにESlintを使用してるプロジェクトでも使用することができる。
 PrettierとESlintと組み合わせて使用するためには、prettier-eslintとprettier-eslint-cliが必要になる。

 ※Prettierの導入方法はこちらの記事で扱っている。

1. インストールする

 Google JavaScript Style Guideに従ってeslint-config-googleをインストールする。

コマンドプロンプト
npm install -D prettier-eslint prettier-eslint-cli eslint-config-google

2. ESlintの設定をする

 プロジェクトフォルダのルートに、.eslintrc.jsonというファイルを作成して以下の内容を記述する。(Google JavaScript Style Guideに準拠したECMAScript 2018の仕様でリントチェックを行うように定義)

..eslintrc.json
{
  "extends": ["google"],
  "parserOptions": {
    "ecmaVersion": 2018
  }
}

 次に、pakage.jsonに定義したscriptをprettier-eslintを使用したコマンドに変更する。

package.json
{
  "scripts": {
    "format": "prettier-eslint --write 'src/**/*.js'"
  },
  "devDependencies": {
    "eslint-config-google": "^0.13.0",
    "prettier-eslint": "^8.8.2",
    "prettier-eslint-cli": "^4.7.1"
  }
}

3. 実行する

 コマンドプロンプトから以下のコマンドを実行して、Prettierで直されたコードがESlintに渡されてESlintの整形も適応される。

コマンドプロンプト
npm run format

参考文献

Next.jsを勉強してみる その⑤ 〜ESLintを導入する編〜
Prettierの導入方法
フロントエンド開発で必須のコード整形ツール

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポートフォリオにiframeタグでyoutubeの動画を組み込むときのすゝめ

本記事の概要

初投稿になります!
本記事は、ポートフォリオにyoutube動画を組み込んだ時におきた問題についての記事になります。

問題点

ポートフォリオにyoutube動画を大量に組み込んだ際に、下記問題点が起こりました。

  • 埋め込みyoutubeの動画の含まれるHTMLの読み込みと表示が非常に遅い
  • リクエストが途中で途切れる・一瞬で遮断される
  • コンソールでエラーがまれに発生する

埋め込みタグのコードは下記になります。

<div class="youtube">
  <iframe src="https://www.youtube.com/embed/<%= @video_id %>" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>

htmlにyoutubeの動画を埋め込む際にiframeタグを使うことになります。
youtubeの公式リファレンス

しかし、iframeタグを用いるとhtmlのレンダリングを行う際、全体のページを読み込んだ後に、iframeタグ(動画)の読み込むを行うようです。
作成したポートフォリオでは、大量のyoutube動画の読み込みをする必要があったので、見事に、問題に直面しました。

改善策

<div class="youtube">
  <iframe data-src="https://www.youtube.com/embed/<%= @video_id %>" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>

まずiframeタグのsrc属性でyoutubeURLを読み込んでおりましたが、data-src属性にすることで、htmlのレンダリングで、読み込むをすることなく、JavaScriptコードやCSSで使用できる追加データを格納することができます。

つまり、iframeタグ内の属性の読み込みをスルーするかつ、データは、一旦避難しておくことでHTMLの読み込みと表示が、スムーズにいくということです。(間違っていたらご指摘ください)

次にJavaScript(jQuery)の記述です。

$(function(){
  $('.youtube').each(function() {
    let iframe = $(this).children('iframe');
    let url = iframe.attr('data-src');
    let id = url.match(/[\/?=]([a-zA-Z0-9_-]{11})[&\?]?/)[1];
    iframe.before('<img src="http://img.youtube.com/vi/'+id+'/mqdefault.jpg" />').remove();
    $(this).on('click', function() {
      $(this).after('<div class="youtube"><iframe src="https://www.youtube.com/embed/'+id+'" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>').remove();
    });
  });
}); 

こちらでは、data-src属性で格納したyoutubeURLを、src属性に置き換える動作を加えています。 
5・6行目の記述が重要です。
5行目では、変数idに、youtubeURLに含まれるvideo_id(動画情報)を正規表現とmatchメソッドを使って、引っ張り出しています。
6行目では、取得した動画情報を変数idとして、imgタグに代入します。ここでbeforeメソッドでiframeタグに表示されるのは、youtubeのサムネイル画像になります。 
HTMLで動画を読みこむ代わりに、動画のサムネイルをとりあえず表示することになります。

サムネイル画像をクリックアクションで、再度iframeタグを読み込み、負荷なくレンダリングがスムーズに行われました。

ポートフォリオの一部例です。
当初、HTMLの読み込みに30秒〜1分ほどかかる時もありましたが、数秒程度に改善されました。

musclegram.gif

ポートフォリオサイト

参考になりますと幸いです。
以上になります!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者が「とりあえずturbolinks消す」に抗ってみた

PF作成において、これまでJS関連でエラーが起きたらとりあえず消してきた
「turbolinks」と何とか仲良くなりたいと思い、実装した内容を備忘録として記述します。

概要

そもそも、turbolinksとは何なのか?

https://coffeecups.blue/turbolinks/

こちらの記事が参考になりますが、本当に簡潔に言ってしまえば、
ページ推移を高速にする機能、と言えます。rails4以降は標準装備されているようです。

一方で、使うことによるデメリットも多く、私が勉強した教材の中でもとりあえず作動しないようにしておくという冷待遇を受けていた悲しい機能です。それでも、PF作成において画像が重いページ等もあり、全体的な動作は早い方がよくね?って思ったのでこの度何とか消さずに実装できないか抗ってみました。実際1秒ぐらい違います。

※大前提として、勉強まだ10週間の初学者が作っているPF内でのことなので、動的な機能がてんこ盛りの複雑なアプリではもっと色々設定しなきゃならないと思われます。

実装

まず、rails6においてturbolinksは

app/javascript/application.js 
require("turbolinks").start()

と記載されています。消したい場合はこれをコメントアウトするわけです。

turbolinksが作動することで起こるデメリット

①JSのイベントハンドラーに修正を加えないと動作しなくなってしまう。
通常使われるload、DOMContentloaded、window.onLoad、等の記述では動作しない。

②戻るボタンでページを戻ろうとすると不具合が生じる。

解決方法

①イベントハンドラーをturbolinks仕様に変更する。

・turbolinks:load ページの読み込み時
・turbolinks:click turbolinksが有効なaタグのクリック

等になります。また、自身の実装では特にライブラリを使用している部分において一部うまく起動しない場合もありました。やむなく、そのページだけturbolinksを作動させない設定をしました。指定したいページのリンク下にdata: {"turbolinks" => false}を記載します。

<%= link_to '表示名', パス, data: {"turbolinks" => false}%>'
<a data-turbolinks="false" href=""></a>

②ブラウザバックやプレビュー用のキャッシュを止める
turbolinksで戻るボタンでページ推移すると、描画したものがページ遷移直前にキャッシュされる仕様で二重キャッシュになり動作を妨げます。

<meta name="turbolinks-cache-control" content="no-cache">

これでOKです。より詳細に設定するのであれば、turbolinks:before-cacheなどのイベントハンドラーを使いこなし、設定することができるようですが、私の作成しているような静的アプリでは上記で問題ありませんでした。

まだまだ奥が深いturbolinksですが、今後もなるべく冷遇しないように仲良くやっていきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでfor文を使う

したかったこと

JSX内で表の列をfor文を用いることで、繰り返し処理する。
理由)デザインを変える時に、一つ一つ変更するのが面倒だった。

解決前のコード
<tr>
  <th style={{fontWeight: 'bold',fontSize:40}}>1set</th>
  <th><input style={{height:52, width:80,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>kg</span></th>
  <th><input style={{height:52, width:70,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}></span></th>
</tr>
<tr>
  <th style={{fontWeight: 'bold',fontSize:40}}>2set</th>
  <th><input style={{height:52, width:80,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>kg</span></th>
  <th><input style={{height:52, width:70,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}></span></th>
</tr>
<tr>
  <th style={{fontWeight: 'bold',fontSize:40}}>3set</th>
  <th><input style={{height:52, width:80,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>kg</span></th>
  <th><input style={{height:52, width:70,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}></span></th>
</tr>

解決策

・即時関数を使う。
・for文のループで出力された列を作成したリストにプッシュし、最後にリストを返す。(これをしないでfor文内で値を返すと、1ループ目で値を返してしまいました。)

解決後のコード
{(() =>{
  const item=[];
  for(let i=1; i <5; i++) {
    item.push(
    <tr>
      <th style={{fontWeight: 'bold',fontSize:40}}>{i}set</th>
      <th><input style={{height:52, width:80,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>kg</span></th>
      <th><input style={{height:52, width:70,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}></span></th>
    </tr>
    )
  }
  return(item)
})()}

結果

スクリーンショット 2020-11-28 17.44.47.png

参考にした記事 - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの関数の様々な利用方法

はじめに

脱初心者にむけてアウトプットをしていこうと思って記事を書いております。
私は、まだ現場に出たことのない完全にど素人です。
間違ったことがありましたら、ぜひコメントいただけると幸いです。

この記事の目的

JavaScriptの関数についてわからない方に向けて、わかりやすいようにまとめております。私自身、初心者で同じ境遇の方々もいると思うので、そういう方にむけてわかりやすく解説できたらと思っています。

関数にはいろいろな使われ方があり、今回がそのうちの3つを紹介しようと思います。

サブルーチンとしての関数

サブルーチンとしての関数とは繰り返しおこなわる機能をまとめ、それに名前をつけて参照するだけでその機能を実行可能にしてくれるもの。
つまり、特定のタスクを行うための「アルゴリズム」をひとまとめのユニットにする目的で使われる。

例えば
const year = new Date().getFullYear();  //年(西暦)を得る
if(year % 4 !== 0) console.log(`${year}年は閏年ではない。`)
else if(year % 100 !== 0) console.log(`${year}年は閏年である.`)
else if(year % 400 !== 0) console.log(`${year}年は閏年ではない。`)
else console.log(`${year}年は閏年である。`);

このような閏年を判断するアルゴリズムがあり、
この文を何回も使用する機会があった場合、何度もこの記述を書くことは可読性を下げることになります。
また仕様変更があった場合に、記述した全ての箇所で、修正をしなくてはなりません。
こういったことを避けるために、「サブルーチン」として使う方法です。

function printLeapYearStatus() {
  const year = new Date().getFullYear();  //年(西暦)を得る
  if(year % 4 !== 0) console.log(`${year}年は閏年ではない。`)
  else if(year % 100 !== 0) console.log(`${year}年は閏年である.`)
  else if(year % 400 !== 0) console.log(`${year}年は閏年ではない。`)
  else console.log(`${year}年は閏年である。`);
}

printYearLeapStatus();

こうすることで、関数を呼び出すだけで済みますし、変更したい場合も、一つのサブルーチンの内容を変えるだけで済むので、圧倒的に楽なのです。

値を返すサブルーチンとしての関数

一つ目に紹介したサブルーチンとしての関数は実は経験を積むことで使うことが少なくなっていくらしい、、、
そして、その次の段階として「値を返すサブルーチン」としての関数です

例えば
function isCurrentYearLeapYear() {//今年は閏年か?
  const year = new Date().getFullYear();
  if(year % 4 !== 0) return false;
  else if(year % 100 !== 0) return true;
  else if(year % 400 !== 0) return false;
  else return true;
}

console.log(isCurrentYearLeapYear());  //実行結果:true
(2020年は閏年なので)

この文章に変えたとして、
先のサブルーチンとしての使われ方との違いは。
1. HTMLで出力できる
2. ファイルに書き出せる
3. if文で条件判定ができる
4. 他の処理のために今年が閏年かどうか知れる
他にもメリットがありと思います。
つまり、いろいろ汎用性がある値として返すサブルーチンの方が役に立つ時が多いいということです。

純粋関数

純粋関数とは、同じ入力に対しては同じ出力を返します。
副作用を持たない、つまりプログラムの状態を変えない。
使うことのメリットとは、テストが簡単で理解が容易で可搬性が上がることです。

いろいろ調べると、純粋関数でいろいろな議論がされているみたいなので、私にとってまだまだ早い領域だと気付きました。

ただ僕なりの解釈として、関数と純粋関数の違いについては、
constとletの違いと似てる!と感じました。
constは定数でletは変数、使用する目的は、そのうち値が変わりそうなものにはletを、constは今後値が変わらないものに使うので、
そういう意味ではこれらの違いとあんまり違わないのかなと思っています。

最後に、

まだまだ難しいことがあるなと感じました,
この記事を見ていただいて、間違っていると感じたら、ぜひコメントいただければと思います。

最後までご覧いただいありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptにおけるCookieにデータを保存・参照する方法

Cookieにデータを保存する方法

document.cookie = 'id=1';

Cookieのデータを参照する方法

alert(document.cookie);

サンプルコード

index.html
<section class="cookie">
    <h2>クッキー</h2>
    <button class="btnSave">保存する</button>
    <button class="btnRead">読み出す</button>
</section>
main.js
// 「保存する」ボタンをクリックしたとき
document.querySelector('.btnSave').addEventListener('click', () => {
    // クッキーを保存する
    document.cookie = 'id=1';
    document.cookie = 'age=30';
    document.cookie = `name=${encodeURIComponent('あいうえお')}`;
})

// 「読み出す」ボタンをクリックしたとき
document.querySelector('.btnRead').addEventListener('click', () => {
    // クッキーを読み出す
    const obj = convertCookieToObject(document.cookie);
    console.log(obj);
    //  オブジェクトを JSON 文字列に変換して表示する
    alert(JSON.stringify(obj, null, ' '))
})

/*
    クッキーをObjectに変換する。
    @param cookies クッキー文字列
    @return 連想配列
*/
function convertCookieToObject(cookies) {
    const cookieItem = cookies.split(';');
    const obj = {};
    cookieItem.forEach((item) => {
        // 「=」で分解
        const element = item.split('=');
        // キーを取得
        const key = element[0].trim();
        // バリューを取得
        const value = decodeURIComponent(element[1]);
        // 保存
        obj[key] = value;
    });
    return obj;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Progate JavaScript V

前回のIVと比べたら比較的簡単、ボーナスセクションでした。
インポート、エクスポートという言葉はゲームをインストールするときに体験したことがあったので、イメージしやすい分野でした。
ただ、すごい不思議な概念ですよね。誰が発案したのかとかを考えると面白いというか趣深いというか・・。
パッケージの部分はプログラミングの壮大さを感じましたね!
世界中の人が作ったモノをインポートして、自分のコードをカスタマイズできる機能が魅力的に感じました。
JavaScriptが奥深いというのは、こういった部分があるからなのでしょうか・・。
まだまだ勉強してもし足りない気分にもなりました(´꒳`)。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuejsの学習のまとめ

Vuejsの学習のまとめ

目次
1. Vuejs2でのthisの使い方
2. 上記の親ファイルのコード
3. データ渡しのメソッドについて
4. props, $emitをEventManagement.vueに書く

Vuejs2でのthisの使い方

<template>
<div>
<p>Good({{ halfNum }})</p>
<button @click="increment">+3</button>
</div>
</template>

methods: {
    increment() {
      this.$emit("my-click", this.number + 3);
}
  }
};

上記の親ファイルのコード

v-bindまたは:{{オブジェクト名}}を記述する。

テンプレートにイベントを登録する

<template>
<div>
  <GoodHeader></GoodHeader>
  <p>{{ number}}</p> 
  <GoodNum :number="number" v-on:my-click="number = $event"></GoodNum> 
//$eventとnumberをレンダリング
  <GoodNum :number="number" ></GoodNum>
</div>
</template>

データ渡しのメソッドについて

親から子へのデータの受け渡しで、$eventとして、登録でき
テンプレートを作成する。

  methods: {
    increment() {
      this.$emit("my-click", this.number + 3);
    }
  } 
};

$emitを子から親へデータの受け渡しを設定する

親側のコード

<template>
<div>
  <keep-alive>
    <component :is="currentComponent"></component>
  </keep-alive>
  <div>
   <h2>イベント管理</h2>
   <EventManagement v-model="eventData.title"></EventManagement> 
</template>
export default {
  data() {
    return {
      currentComponent: "sample"
      eventData: {
        title:"入力内容"
      }
    };
  },
  components: {
    sample,
    EventManagement
}

props, $emitをEventManagement.vueに書く

親ファイルのテンプレートのコードを省略できる。

<template>
    <div>
        <label for="title">Theme</label>
        <input
         id="title"
         type="text"
        :value="value"
        @input="$emit('input',$event.target.value)" 
        >
        <pre>{{ value }}</pre>
    </div>
</template>

<script>
export default {
    props: ["value"]
};
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptで電卓を作ってみた

今回はJavascriptで電卓を作ってみた

参考にしたサイトはこちらである

https://techacademy.jp/magazine/21139

最初は自力で実装してみようとやってみたが最初からこけたので断念した

具体的につまずいたのは
・画面に2桁以上の数字を表記することが出来ない
変数を変える事で1桁であれば入れ替え可能だったが連続して数字を打ち込むことが出来なかった
・四則演算の記号を表示することが出来ない
(何とも初歩的な物.....)

さすがに1.2週間勉強してオセロゲームを作ったら簡単には出来るようにはならなかった(笑)

今回JavaScriptの記述はこれだけだった

var log = document.getElementById("log");
function edit(elem) {
    log.value = log.value + elem.value;
}
function calc() {
    log.value = new Function("return "+log.value)();
}

とても短い、、、
今回注目して考えたのは「this・elem.value」と「new Function」である

htmlの方はこんな感じだ

<body>
    <div id="dentaku">
        <input value="" action="" name="input" id="log">
        <table>
            <div>
                <button input="button" id="AC" value="" onclick="edit(this)">AC</button>
                <button input="button" id="+/-" value="" onclick="edit(this)">+/-</button>
                <button input="button" id="%" value="%" onclick="edit(this)">%</button>
                <button input="button" id="÷" value="/" onclick="edit(this)">÷</button>
            </div>
            <div>
                <button input="button" id="7" value="7" onclick="edit(this)">7</button>
                <button input="button" id="8" value="8" onclick="edit(this)">8</button>
                <button input="button" id="9" value="9" onclick="edit(this)">9</button>
                <button input="button" id="×" value="×" onclick="edit(this)">×</button>
            </div>
            <div>
                <button input="button" id="4" value="4" onclick="edit(this)">4</button>
                <button input="button" id="5" value="5" onclick="edit(this)">5</button>
                <button input="button" id="6" value="6" onclick="edit(this)">6</button>
                <button input="button" id="-" value="-" onclick="edit(this)">-</button>
            </div>
            <div>
                <button input="button" id="1" value="1" onclick="edit(this)" >1</button>
                <button input="button" id="2" value="2" onclick="edit(this)">2</button>
                <button input="button" id="3" onclick="edit(this)">3</button>
                <button input="button" id="+" value='+' onclick="edit(this)">+</button>
            </div>
            <div>
                <button input="button" id="">0</button>
                <button input="button" id="">0</button>
                <button input="button" id="">.</button>
                <button input="button"  value="=" onclick="calc()">=</button>
            </tr>
        </table>
    </div>
    <script src="./main.js"></script>
</body>

this・elem.valueについて

今回はonclickの引数にthisを入れた。
この時、基本的にthisはクリックの対象を要素として持ってくる
それからthisで取得した要素をelemとしてelem.valueでクリックした数字を入れる事が出来る

また今回はhtmlでonclick="edit(this)"と指定したが
onclick="edit(this.value)"としたらthisのvauleを指定することが出来
jsの方では
log.value = log.value + elem;
と書いても同じように作動させることが出来る。

参考記事
https://www.sejuku.net/blog/26119
https://style.potepan.com/articles/21691.html
https://tacamy.hatenablog.com/entry/2013/01/06/224718

new function

これに似た関数でeval関数というのがあるがセキュリティ面などで問題がある.
どちらも文字列から関数を作るにはとても役に立つ。
またnewFunctionは無名関数に当たり、ここでlog.valueの計算を実行して
inputタグに出力するのである。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

expressでOpenAPI仕様のAPIを実装するときのTips

要点

  1. express-openapiを使おう (openapi-generatorではなく)
  2. security handlerの実装には、シンプルにOpenAPIのinitializeメソッドに引数securityHandlersを渡すのが良い。(openapi-security-handlerは、必要ない)

express-openapiを使おう (openapi-generatorではなく)

OpenAPI仕様のYAMLファイルを Swagger Editor (https://editor.swagger.io/) などを用いて作った後、openapi-generator (https://github.com/OpenAPITools/openapi-generator) を使ってスケルトンを作れそう...と思うのですが、openapi-generatorの出力するNode.js向けのスケルトンは、あまり良い感じではないです。

むしろexpress-openapi (https://www.npmjs.com/package/express-openapi) の方が、使う機能だけ使って書くように考えられているので、やりたいことに集中できるように思います。

security handlerの実装には、シンプルにOpenAPIのinitializeメソッドに引数securityHandlersを渡すのが良い。(openapi-security-handlerは、必要ない)

APIサーバを実装するとき、ほとんどの場合、セキュリティのことも考えることになると思います。

このとき、express-openapiのHighlightsを見ると、こういうことが記述されています。

Leverages security definitions for security management.
* See openapi-security-handler

openapi-security-handlerを使う必要があるのかな...と感じますが、必要ありません。

express-openapiでセキィリティハンドラを実装するときは、initializeメソッドの引数secuurityHandlersを渡すのが正解です。

initialize({
  apiDoc: apiDoc,
  app: app,
  securityHandlers: {
    keyScheme: function(req, scopes) {
      return Promise.resolve(true);
    }
  }
});

reqはexpressでおなじみのもので、scopesには、以下のようにAPIの定義のsecurityでスキーマ別に定義するスコープ配列が渡ってきます。

security:
- keyScheme: [admin]

セキュリティハンドラの戻り値は、即値でbooleanを返すか、booleanを返すPromiseとします。

trueが認証OKで、falseがNGです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでCSSファイルを切り替える

JavaScriptを利用して、読み込むCSSファイルを切り替えます。

コード

HTMLにはlinkタグを全て記述しておきます。

<link rel="stylesheet" href="1.css" data-css="css1">
<link rel="stylesheet" href="2.css" data-css="css2" disabled>
<link rel="stylesheet" href="3.css" data-css="css3" disabled>

data-css属性に名前を付けておく。この名前で切り替えます。
最初に無効化しておきたいファイルにはdisabled属性を付けておく。

function change_css(name){
    for(const el of document.querySelectorAll('link[data-css]')){
        el.disabled = el.dataset.css !== name
    }
}

この関数に読み込みたいファイルの名前(css2等)を渡せば、ファイルが切り替わります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Progate JavaScript IV

段々難しくなってきました。
一つ一つの要素の難易度はそこまで高くないと感じておりますが、絵面が違うマトリョーシカを自分で作り上げてるイメージ・・。
コンストラクター、継承、オーバーライド、全てが効率よく回す為にはどうしたらよいのかという考えのもと定義されているような感じがします。
その部分を感じながら勉強するのは楽しいですね!日常生活にも活かせそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

学生時代に初めてアプリをリリースして学んだこと10選

はじめに

学生時代にサービスをリリース・運用するために必要な知識がゼロの状態で、アプリをリリースして設計や運用に関する多くの事を学んだので、その知見を共有します。
※当時の状況を説明しながら学んだことを書いていくので、結論だけ知りたい方はまとめをご覧ください。

想定する読者

この記事は、以下のような方々に向けた記事になります。

  • これから個人開発を始めてみたいけど、どうやって進めたらよいかわからない
  • サービスの運用経験がなくて個人開発の自信がない
  • サービスを開発→リリース→運用するにあたって大切な事を学びたい

アプリを作ろうと思ったきっかけ

私は大学2年生までは、特にプログラミングが楽しいと思うわけでもなく、アルバイトやサークル、授業に取り組み、よくある大学生活を送っていました。
大学3年生から研究室配属があり、当時はHTML, CSSを用いてまともにウェブサイトすら作れないようなスペックだったので、自身の技術力に危機感を覚えていたこともあり、厳しいといわれていた研究室を志望し、そこに配属されました。

配属された研究室では、年中何かしらの開発イベントや勉強会が開催されていたので、取り合えず参加してみました。
参加したプロジェクトは学園祭に向けた展示用のシステムを作成するプロジェクトでした。
そこで、ほとんど先輩にフォローされながらでも、システムを完成させたときの達成感や、学園祭に来てくださった人々に展示物を使ってもらえることの喜びを覚え、システム開発が楽しいと感じました。

プロジェクトが無事に終了したところで、自分でも何か作ってみたい!と考え、趣味のツーリングをより楽しくできるものはないか、と考えた末に道の駅巡りアプリを作ることにしました。

アプリ開発を決心したときの筆者のスペック

  • HTML, CSS, JavaScript, jQueryを使ってレスポンシブサイトの作成ができた
  • Vue.jsを独学で学び始めて2カ月ほど
  • Firebaseの存在は知っていた(使ったことはない)
  • アプリ開発、リリースの知見ゼロ
  • サービスの運用経験ゼロ

作成したアプリの概要

アプリの主な機能は、以下の通りです。
- 位置情報をもとに、道の駅に訪れたらスタンプを押せる機能
- 道の駅の訪問達成率をもとにしたランキング機能
- 一定の条件を満たすと獲得できるトロフィー機能

より詳しい機能についてはアプリページをご覧下さい。

ウェブアプリケーションで作ってみた

アプリ開発を決心した当初のスペックは、HTML, CSS, JavaScriptしか書けなかった為、ウェブアプリケーションとして作ることにしました。
バックエンド経験はゼロだったので、当時はまだ使ったことがないFirebaseを用いて開発することにしました。

わからないことばかりで何度も躓いては調べて、を繰り返し、ひとまずメイン機能である位置情報を元に道の駅を登録する機能が完成しました。

学び1:利用データの取得方法は設計時にはっきりとさせておく

この時点での学びとして、データを集めることの大変さを学びました。
作成する道の駅巡りアプリでは、位置情報をもとに道の駅の登録有効範囲かどうかを判定するため、日本全国道の駅の緯度経度が必要になります。
私は「このご時世だしインターネット上にデータが公開されているだろう」、と謎の自信を持っていましたが、そんなうまい話がある訳もなく、道の駅の緯度経度一覧は公開されていませんでした。

当時は日本全国道の駅の数は約1100駅(正確には覚えていませんが、1100駅は超えていました)ほどで、GoogleMapなどから道の駅の緯度経度情報を手動で調べるには時間がかかりすぎます。
アプリの機能を考えているときは、特にデータの取得方法などは考えずに、自分が欲しい機能だけを考えていたため、いざ作るとなったときにデータ集めがボトルネックになることを痛感しました。

以上の経験から、アプリ設計時には、利用したいデータを集めることが可能かどうかや、利用するデータの取得方法、を予め考えておく必要があることを学びました。

余談ですが、データの収集方法の1つにスクレイピングがあります。PythonならSeleniumが、JavaScriptならPuppeteerが有名でしょうか。
これらいずれかのライブラリを使えるようになっておくと、人生得する機会が多くなるかもしれません。

学び2:リリースするだけでは使われない(そもそも見つけられない)

紆余曲折ありましたが、時間をかけて道の駅のスタンプを押す機能と、達成率に応じたランキング機能が完成したところで、ウェブアプリケーションを公開しました。

Firebaseにホスティングして2カ月ほど経過しましたが、全く使われません。
それもそのはずで、SEO施策は行っておらず、Twitter等のSNSでの宣伝もしていない状態でしたので、使われるはずもありません。
唯一、数名の知人に使ってもらっていましたが、身内専用アプリと化していました(笑)

ここでの学びは、個人開発では、作ってリリースするだけではなく使ってもらうための施策が重要、ということです。
ウェブならSEO、アプリならASOをしっかりと考えないと、世の中に認知されないアプリケーションとなってしまいます。
こういうときにSNSのフォロワーがたくさんいると、一定数の方々に認知してもらうことができるので、SNSフォロワーはとても貴重な資産だということも実感しました。

学び3:維持費とモチベーションについて

ウェブアプリケーションとして公開したは良いものの、全く使われなければ話になりません。

ここで私はふと、維持費について考えました。
ウェブアプリケーションの一般的な維持費といえば、サーバ代とドメイン代ですね。

前者のサーバ代はFirebaseの太っ腹無料枠のおかげで0円です。
サービスローンチ時点で初速を出せない個人開発者にとって、この無料枠はとても大きな存在です。全く使われないアプリケーションに毎月少額でもお金を払い続けるのはモチベーション低下につながりますからね。

後者のドメイン代ですが、よくある〇〇.comを取ろうとすると大体月1,000円程度かかります。全く使われないアプリケーションに月1,000円支払い続けるのは、やはりモチベーションが続きません。
ウェブアプリケーションでは、ドメインを取らなければSEOの観点から不利とされていますが、ドメインを取ったからといって、一気にSEOが改善されるわけではありません。
SEOでは、サイトの被リンク数やコンテンツの質など、さまざまな条件が必要です。
もちろん広告費を払ってしまえばSEOは全て解決ですが、学生の私にそんな予算はありませんでした。

以上の経験から得た学びは、

  • 使われないサービスにお金を払い続けると、金銭的な問題よりもモチベーション的な問題で続かない
  • 使われるサービスにするためにはある程度お金が必要だが、お金をかけたからといって使われるとは限らない

モバイルアプリケーションに方向転換

ウェブアプリケーションとしてリリース後、サービスが使われない日々を過ごしながら、私はモバイルアプリケーションに方向転換することにしました。
もともと、道の駅まで行って登録する、というアプリの特性上スマートフォンでの利用をメインとしているため、ウェブよりもアプリのほうが使い勝手が良いです。(一応PWAにしていましたが、iOSユーザにはなんちゃってPWAしか提供できないのが辛かったです)
そこで、モバイルアプリケーションについて調べていると、どうやらウェブ技術でスマホアプリが作れるハイブリッドアプリなるものがあるようです。
しかも、ハイブリッドアプリはiOS, Androidにも対応可能です。
さらに、私はAndroidスマートフォンを使っているので、Google PalyStoreについて調べてみたところ、なんとアカウント登録時に$25支払うだけでその後はアプリをリリースし放題でした。
買い切り$25は、月額1,000円のドメイン費用と比べて格段にコストを抑えることができます。

ここで私はモバイルアプリケーションに方向転換することにしました。
ウェブアプリケーションで作った際にFirebase × JavaScript だとライフサイクルがないため、ユーザ認証が完了するまで特定のDOMをレンダリングしない、等の処理が面倒だったので、ライフサイクルがあるJavaScriptフレームワークを使うことにしました。
幸いハイブリッドアプリを作成するフレームワークであるCordovaは、モダンJavaScriptフレームワークであるReact, Vue.js, Angularのいずれもサポートしていました。
当時勉強中だったこともあり、Vue.js × Cordova で開発することにしました。
ウェブアプリケーションとしてリリースしていたソースコードを一部流用しつつ、ひたすら調べながら Vue.js × Cordova に作り変えました。

学び4:公式ドキュメントは絶対に読むべき

開発初心者あるあるだと思いますが、公式ドキュメントがわかりにくいor英語で読めないという理由から、Qiita等のやってみた記事をたくさん読んで解決方法を探していました。
そこで示されていた解決方法を実施してみても、うまく動きません。エラーメッセージでググってStackOverflowにたどり着いたり、かなり苦戦して開発をしていました。

これはCordovaに慣れてきた今だからわかることなのですが、当時のエラーは利用ライブラリのバージョンの違いによるビルドエラーや、旧バージョンでの解決方法を試していた等の失敗がほとんどでした。
Cordova は比較的古い技術なので、インターネット上には様々なバージョンのやってみた記事がたくさんあります。私は、自身が現在扱っているバージョンと異なるバージョンの解決策をひたすら探していたので、解決までにかなりの時間を要してしまいました。

以上の経験から得た学びは

  • 公式ドキュメントは必ず最初に読む。よくわからなくても、他のやってみた系の記事を読んだあとに、再度公式ドキュメントを見るとわかるときがある
  • どうしても公式ドキュメントの内容が分からない場合は、できるだけ最新のやってみた記事を参照する。その際、Google検索オプションを駆使してできるだけ目的にあった記事に絞込む
    • 2020年以前の記事: before:2020
    • 2019年以降の記事: after:2019
    • 特定のキーワードを除外: -除外キーワード
  • 検索時は自分が利用しているライブラリのバージョンを意識する

ようやくリリース

モバイルアプリ移行に苦労しつつも、なんとか形にすることができたので、Google PalyStoreにリリースすることができました。
アプリストアにリリースすると、1日に1人~2人にインストールしてもらえました。ウェブアプリケーションとして公開した際は誰にも利用してもらえなかったので、毎日数人にインストールされている様子を眺めて喜んでいました。
その後は1日のインストール数が徐々に伸びていき、リリースして10カ月経った現在のインストール数は約2,000になりました。

学び5:データベース操作にクエリは必須

リリース後順調にユーザー数が増えてきて、300人程度になった辺りからアプリの動作が遅くなってきました。
原因はデータベースからの値の取得に時間がかかっていることでした。

初めてFirebaseを触った私は、「Firebase Firestore 値 取得」等で調べてたどり着いた公式ページや、やってみた記事を参考に、データベースからの値取得ロジックを実装していました。
イメージとしては以下のような状態です(経験のある方はこれはいかにヤバいコードかがわかると思います)

Firestore(DB)の構造
users: {
  ユーザID: {
    name: "山田太郎",
    その他ユーザ情報10件ほど
  },
  ユーザID: {
    name: "山田花子",
    その他ユーザ情報10件ほど
  },
  (以下、300件前後)
}
Firestoreの値取得
// アプリ起動時
const users = {}
const db = firebase.firestore()
db.collection("users").get()
.then(snapshot => {
  snapshot.forEach(doc => {
    users[doc.id] = doc.data()
  })
})

Firebaseについて詳しくない方向けに解説すると、アプリ起動時にデータベースに保存されているユーザー情報をすべて取得する処理です。

これだとユーザーが増えるたびにデータベースから取得するデータ量が増え、取得に時間がかかってしまいます。
開発経験のある方からすると、こんな当たり前のこと…と思われるかもしれませんが、当時学生の私にとって、これすらも考慮することができませんでした。

幸い、ユーザ情報に最終ログイン日時を持たせていたので、急遽ユーザー情報の取得を、
自身の最終ログイン日よりも後にログインしたユーザのみ取得し、端末に保存した全ユーザーデータを上書きする
という処理に修正しました。
つまり更新があったものだけを取得している状態です。
こうすることで、アプリの動作速度は無事に改善しました。

以上の経験から得た学びは

  • 今後増えていくデータは必ずクエリによる差分取得が可能なデータ構造にする
    • 例)更新日や最終ログイン日などを持たせて差分取得可能にする

学び6:メンテナンスモードは必ず実装すべき

アプリはリリースして終わりという訳ではなく、機能追加や機能改善を行っていくと思います。
その際に出てくる問題の1つとして、DB構造が実現したい機能にあっていないことが挙げられます。
設計当初は想定していなかった機能追加や、DB設計がよくなかったのでDB構造を変更したい、といったシーンで困るのが、リリースタイミングです。

皆さんご存知の通り、モバイルアプリケーションは端末にインストールして利用するものです。
そのため、処理を変更した際はストアにアップデートを配信し、ユーザーにアプリをアップデートしてもらう必要があります。
仮にいくつかのバージョンが混在する状態でDB構造を変更すると、古いアプリバージョンの利用者は、過去のDB構造を参照しようとするので、DB構造を変更した時点でエラーとなってしまいます。
つまり、DB構造変更時はユーザーがアプリを使えなくする必要があります。

メンテナンスモードを実装することで、一時的にアプリを利用不可にし、DB構造を切り替えた後にアップデートを配信し、メンテナンス解除と同時にアプリのアップデートをユーザに促すことで、DB構造の切り替えを行うことができます。
また、DB構造を変更する際は旧バージョンのDB構造はそのままにし、新しいDB構造を追加してそちらを参照させることで、万が一旧バージョンを参照したユーザーが居ても、エラーを出さずに済みます。
さらに、新バージョンで問題があった際は、速やかに参照先を旧バージョンへ戻せばよいので、切り替えによって起きた予期せぬ不具合にも対応することができます。

以上の経験から得た学びは

  • DB構造切替や予期せぬ不具合のために、メンテナンスモードは必ず実装する
  • DB構造を切り替える際は、旧バージョンを残しつつ、新しいテーブル構造を作成すると良い
    • hoge/v1/fugahoge/v2/fuga や、 hoge1/fugahoge2/fuga といったように作ると切り替えがスムーズになる

学び7:そのデータはDBで持たせるべきか、ローカル(端末)で持たせるべきかを吟味する

次に値を保持する場所についてです。
当たり前ですが、DBを利用するには料金がかかります。
Firebaseであれば読み取り回数や書き込み回数、保存容量などで料金が発生します。
そのため、可能な限りDBで保持しなくても良いデータはローカルに持たせたいですよね。

私はリリースしたアプリでイベントを開催した際に、イベントで得たチケットの枚数をローカル(ユーザーのスマートフォン端末)に保存していました。
他のユーザーに獲得したチケット枚数を共有する訳でもなかったので、ローカルで保持しようと考えたからです。

ある時、ユーザーから「イベントチケットが消えてしまいました」とお問い合わせがありました。
そこで調査しようと思ったのですが、該当ユーザーのチケットの獲得状況はローカルで保持していたので、こちらから調べることができない状態でした。
システムエラーはログに出力するように設定していたのですが、該当ユーザーのエラーログは見当たらず、調査が難しい状況でした。
ユーザーとのやり取りの末、原因は2つのスマートフォンで1つのアカウントにログインしていたため、ローカル(端末)に保存していたチケットの枚数が共有されなかったことだとわかりました。

DBで保持しなくても良いデータとは

以上の経験から、私は、以下のいずれも満たさないものが、DBで保持しなくても良いデータだと考えています。
※これはアプリの特徴や人によって考え方が変わってくるところだと思うので、あくまで一個人の意見として見て頂けると幸いです。

  • 他のユーザに共有する可能性があるデータ
    • 例)道の駅の訪問達成率、ユーザー名などのプロフィール情報、アイコン画像
  • 不具合が発生した際に、調査に必要なデータ
    • 例)ログイン日時、フレンド申請状態管理データ、イベントやランキングスコア
  • 複数のスマートフォンで同一アカウントにログインした際に、データの差異が発生する可能性があるデータかつ、差異が生じた際に不都合があるデータ
    • 例)イベントやランキングスコア、ユーザのフレンド情報

上記を満たさなければ、ローカルにのみ保存して良いデータだと私は考えています。
上記のルールを満たさないデータの具体例を考えてみると、以下のようなデータが挙げられます

  • お知らせの既読、未読情報
  • アプリのUIカスタマイズ機能
    • テーマカラー、フォントサイズなど
  • チャットメッセージの既読、未読情報
  • 表示速度高速化のためにキャッシュデータ

学び8:エラーログに含めるべき情報

アプリ開発に限らず、サービスにはバグがつきものです。バグが起こらないシステムなどありません。
そこで大切なのが、バグが起きた際にいかに速やかに原因を突き止め、解決するか、です。

以前、ユーザーから「アプリが起動できません」とお問い合わせがきました。
これだけでは、どういった状況でアプリが起動できないのか、起動できないというのはスプラッシュスクリーン(アプリの起動画面)が表示されないのか、スプラッシュスクリーンは表示されるが、その後のページがロードされないのか、等考えられる可能性は無数にあります。
また、手元の環境でエラーを再現したいのですが、該当ユーザーがどのバージョンを利用しているのか、機種やOSは何か、などあまりにも情報が不足していて、再現が難しい状況でした。
当時はシステムのエラーログのみを出力していたので、ユーザーのとやり取りで機種名やOSのバージョン、アプリのバージョンなど、様々な情報をヒアリングしました。
結果的に無事に解決することができたのですが、解決までに数日を要してしまい、ユーザーに迷惑をかけてしまいました。

以上の経験から得た学びは、エラーログには、最低限以下の情報を出力すべきだと考えています。

  • ユーザID
  • アプリバージョン
  • 端末情報(OS、機種など)
  • エラー発生時刻
  • エラーメッセージ
  • その他、エラー発生時特有の情報
    • 例)エラーが発生した際の変数の中身、どこまで処理が進んで、どこで不具合が発生したか、など

学び9:開発環境と本番環境のデータ量は同じにする

ユーザー数も順調に増え、本番環境で機能開発をするのが怖くなってきたので、開発環境を作りました(本来は初めから用意しておくべきですが……)
開発環境を作成してからは、開発環境をメインに機能開発や修正を行い、問題がなければ本番環境にリリース、という流れで開発を行っていました。

ある日、いつものように開発環境での動作確認は問題なかったので、本番環境にリリースしました。
すると、開発環境ではうまくいった処理が、本番環境だとうまくいかない、という事態が発生しました。

どうしてこのような事態になってしまったかというと、原因は開発環境と本番環境のデータ量の違いにありました。
本番環境では日々ユーザーが増え、ユーザー数が1,000人ほど居ましたが、私が用意した開発環境はユーザー数が15人ほどしか登録されていませんでした。
ユーザー数増加に伴い、処理に時間がかかり、開発環境ではうまくいっていた非同期処理が、本番環境では実行時間の増加によりタイミングがずれてしまいました。

以上の経験から得た学びは、開発環境と本番環境のデータ量、データの種類は同じにするということです。

学び10:リリースタイミングはよく考える

個人開発者の方は、日々隙間時間を見つけて開発をしていると思います。
ひたすら開発を続けている人も居るかもしれませんが、それでも1泊2日の旅行にいったり、24時間以上開発ができない状況になることはあると思います。

私がリリースしたアプリは、実際に道の駅に訪れて登録する、という特性上、1週間のうちに土日で最も利用されます。
幸いFirebaseはオートスケーリングなどは勝手にやってくれるので、こちら側が管理する必要はありません。

私はとある金曜日に新機能をリリースをしたのですが、次の日から1泊2日の旅行の予定がありました。
というのも、新機能を自ら使ってみようと考えていたからです。
新機能の動作確認も問題なかったので、後日リリースした機能を使って楽しくツーリングをしていました。
すると、ユーザーからのお問い合わせでランキングの変動がされなくなっていることが発覚しました。
原因を探して修正したくても、1泊2日のツーリング真っただ中のため、PCもなければ帰宅するにも宿を予約しています。
仕方なくユーザーからのお問い合わせには、数日後に対応したのですが、お問い合わせがあった瞬間から楽しいツーリング中でもお問い合わせの内容が頭から離れませんでした。

以上の経験から得た学びは、リリースタイミングはよく考えるということです。
どんなにテストをしたとしても、予期せぬ処理にバグが発生することもあります。なのでリリース時は、リリース後数日はバグが起きてもすぐに直せるような状況を作ってからリリースすると良いと思います。

まとめ

学生時代に初めてアプリをリリースして学んだこと10選のまとめです。

  • 利用データの取得方法は設計時にはっきりとさせておく
    • アプリ設計時には、利用したいデータを集めることが可能か、利用するデータの取得方法、を予め考えておく
  • リリースするだけでは使われない(そもそも見つけられない)
    • 個人開発では、作ってリリースするだけではなく、使ってもらうための施策が重要
  • 維持費とモチベーションについて
    • 使われないサービスにお金を払い続けると、金銭的な問題よりもモチベーション的な問題で続かない
  • 公式ドキュメントは絶対に読むべき
    • やってみた系の記事はわかりやすいが、バージョンの違いによって動かなくなることが多々あるので、公式ドキュメントは必ず最初に読む。よくわからなくても、他のやってみた系の記事を読んだあとに、再度公式ドキュメントを見るとわかるときがある
  • データベース操作にクエリは必須
    • 今後増えていくデータは必ずクエリによる差分取得が可能なデータ構造にする
  • メンテナンスモードは必ず実装すべき
    • DB構造切り替え作業や予期せぬ不具合への対策として、メンテナンスモードは必ず実装する
    • DB構造を切り替える際は、旧バージョンを残しつつ、新しいテーブル構造を作成すると良い
  • そのデータはDBで持たせるべきか、ローカル(端末)で持たせるべきかを吟味する
  • エラーログに含めるべき情報を考える
    • ユーザID
    • アプリバージョン
    • 端末情報(OS、機種など)
    • エラー発生時刻
    • エラーメッセージ
    • その他、エラー発生時特有の情報
  • 開発環境と本番環境のデータ量は同じにする
  • リリースタイミングはよく考える
    • リリース時は特に、リリース後数日はバグが起きてもすぐに直せるような状況を作ると良い

以上が私が学生時代に初めてアプリをリリースして学んだこと10選でした。
経験を積んだエンジニアの方々からすると、どれも当たり前の内容かもしれませんが、これから個人開発を始める方など、開発経験の少ない方のお役に立てば幸いです。
読んでいただき、ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】APIのエラーハンドリングをより良く書きたい。

当初の書き方

APIと通信を行い、ステータスコードでエラー画面を表示したり、
成功のトーストを表示したりなどをする状況で、

「エラー処理は try-catch だな...」と思っていた自分は
当初は以下のように書いておりました。

let response

try {
  // API通信
  response = await this.$api.post({ ... })
} catch (error) {
  response = error.response
}

if (response.status === 201) {
  // 成功時の処理
} else {
  // 失敗時の処理
}

この書き方でも問題は無いのですが、
letを使うのはちょっと...という感じがしてしまいます。


改善

そこで、API通信が非同期処理であり、戻り値はPromiseであることに着目すると、
以下のように書き換えられます。

const response = await this.$api.post({ ... }).catch((error) => {
  return error.response
})

if (response.status === 201) {
  // 成功時の処理
} else {
  // 失敗時の処理
}



これでletを使わずに書くことができて、より良いコードになりました。
(ちなみにPromiseについてはこちらのサイトで勉強したのですが、すごく良かったです。)


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Progate JavaScriptIII

スクリーンショット 2020-11-28 13.36.24.png

return文難しいですね・・。
これを使うとコードがスッキリして扱いやすくなるのでしょうけど、少し複雑。。
現存してる知識で無理矢理数値は合わせましたが、return文の深い理解がもう少し必要だと感じました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Progate JavaScript III

スクリーンショット 2020-11-28 13.36.24.png

return文難しいですね・・。
これを使うとコードがスッキリして扱いやすくなるのでしょうけど、少し複雑。。
現存してる知識で無理矢理数値は合わせましたが、return文の深い理解がもう少し必要だと感じました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの関数はオブジェクトに過ぎないのでプロパティを持たせることができる

百聞は一見に如かず

function hello() {
  return console.log(`${ hello.message } ${ hello.user }.`)
}
hello.message = 'hello'
hello.user = 'momo'
hello() // -> hello momo.
console.log(hello) // -> { [Function: hello] message: 'hello', user: 'momo' }

面白いですね。
なおhello.nameには関数名(hello)が格納されている。

初めはhello.userではなく、hello.nameで記述したけれど関数名が表示されて、なるほどっと思った。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで加速度センサーひろってみた

加速度センサーを拾ってみた

window.addEventListener("devicemotion", ハンドラとなる関数);
で拾えます。(使えるかどうかは、使用しているデバイスとブラウザに依存します。)

image.png

  • 赤 ・・・ event.accelerationIncludingGravity.x の値
  • 緑 ・・・ event.accelerationIncludingGravity.y の値
  • 青 ・・・ event.accelerationIncludingGravity.z の値
  • 白 ・・・ 上記をベクトルとみなしたときの長さ。(それぞれを2乗して和を取って平方根を取ったもの)

CodePen

(※センサを搭載していないデバイスで閲覧している場合、何も面白くないものが表示されていると思います。)

センサを搭載してても、Qiita記事への埋め込みだとセンサが取れないっぽい。
CodePenのサイトから直接閲覧する必要あり。

スマホでCodePenのサイトを直接開いて、スマホを上下方向などに動かすと波形が振れると思います。(壊さないようにお気をつけください。)

See the Pen g-Sensor by kob58im (@kob58im) on CodePen.

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Progate JavaScript II

Progate
・JavaScriptII

繰り返し、配列、オブジェクト等
上記の内容でしたが、こちらもPHPと類似点が多い印象でした。
バッククォーテーションを用いたテンプレートリテラルは忘れてしまいがちなので意識していきたいです。
while文、for文、swich文はほぼPHPと同じ。
配列とオブジェクトは初感覚でした。
応用問題は、変数(定数).プロパティ を用いた処理を忘れずに行いたいです。
ただ、基礎の組み合わせなので難しくはないですね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ProgateのJavaScript PHPとの類似点及び反省点

Progate
・JavaScriptII

繰り返し、配列、オブジェクト等
上記の内容でしたが、こちらもPHPと類似点が多い印象でした。
バッククォーテーションを用いたテンプレートリテラルは忘れてしまいがちなので意識していきたいです。
while文、for文、swich文はほぼPHPと同じ。
配列とオブジェクトは初感覚でした。
応用問題は、変数(定数).プロパティ を用いた処理を忘れずに行いたいです。
ただ、基礎の組み合わせなので難しくはないですね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 基礎

JavaScriptとは

プログラミング言語の1つで主にクライアントサイド(WEBブラウザ上)で力を発揮します。
現在、JavaScriptは多くののWebアプリケーション開発現場で用いられているほど人気のある言語です。また、世界で最も使用されている言語だと言われています。

JavaScriptの主な役割

・ ページ遷移無しで画面の表示を切り替えられる
・ 画面を更新をせずに、サーバーと通信できること

クライアント側のぺージに動きを付けることで、ページ遷移なしで画面の表示を切り替えられる。
その結果、ユーザーはストレスなく様々な情報を得ることができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

電卓作成

今回は完全自作でiphoneの純正電卓アプリを作っていく

まずはフローチャートであらすじとシナリオを書く

image.png

すごく大雑把だけど、ここから詳しく書いていく
こんな感じ
image.png

現状での完成イメージはこんな感じ
けど、全て実装できないのでまずは単純に=ボタンで計算をするところまでを実装する

前回のように2次元配列で実装したいが今回は何も見ず最低限度の検索だけで作りたいのでhtmlで書き出す

早速挫折した
onclickを使う事が出来なかった(笑)
とりあえずボタンを押したらポップアップを表示させることでonclickが働いてるかを見る

https://techacademy.jp/magazine/15062

これからは普通に調べながらやっていく(笑)

次はクリックさせたら変数にそれを反映させる

onclick内に変数を操作する

変数をhtmlに表示する

https://www.sejuku.net/blog/88958

テキストボックスから値を取得

https://techacademy.jp/magazine/21069

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react-hook-formでサーバーサイドで行ったバリデーション結果がコントロール操作時にクリアされる

react-hook-form v6.10 ~ 6.11.5 までで確認した挙動です。

やろうとしたこと

react-hook-formを使用して情報登録用のフォームを開発していたが、
仕様上フォームの入力内容バリデーションはAPIを呼び出してサーバーサイドで実施しなければいけなかった。

理由は単純で、フォームの入力内容がバリデーションを通った場合は入力項目を表示する確認画面を表示し、
バリデーションエラーがあった場合にはフォームの再入力を促すという画面構成だったからだった。

  +------+      
  | Form |<---+
  +------+    |
     | Submit |
     +--------+  フォームが submit されたらバリデーションAPIで検証。
     |           検証OKなら確認画面へ、バリデーションエラーがあった場合は
     |           バリデーションエラーメッセージを表示したフォーム画面を再表示する。
     v
+---------+
| Confirm |
+---------+
    | Submit  確認画面で submit されたら登録完了。
    |
    ~

react-hook-formのuseForm()が返すsetError()でサーバー側のエラーをセットできるので利用していたが、
エラーがあったコントロールを操作しようとすると対応するバリデーションエラーメッセージが消えてしまった。

バリデーションエラーメッセージはフォームの入力中に何度でも確認できるよう、
submit されない限り表示し続けるという要件だったので、入力を再開した時点でメッセージが消えるのは喜ばしい挙動ではなかった。

対応

「コントロールの入力が開始された時点で対応するバリデーションエラーをリセットするべきである」というのが
react-hook-formの考えの様だった: https://github.com/react-hook-form/react-hook-form/issues/1881

react-hook-formにはフロントエンドサイドでフォーム入力中にインタラクティブなバリデーションを行う機能があり、
APIはこの機能をベースに設計されている。

入力を再開してバリデーションエラーが無くなったらエラーメッセージは非表示にするという方針のようだ。

『サーバーでバリデーションした結果を表示し続けたい』というような時は、
バリデーションエラーメッセージをsetError()などで管理しないで独自のstateとして管理するしかないようだったので
以下のようなerrorssetErrorに近いAPIを自作した。

import { useCallback, useState } from "react"
import type { UseFormMethods } from "react-hook-form"

// react-hook-form の setError はジェネリクスに渡された型に
// Array, object が含まれていると setError の第1引数にあらゆる型を受け入れるようになっている。
// あらゆる型を受け入れると Typescript の型チェックが働かなくなる。
// 強制的に型チェックを有効にするために与えられた型のプロパティ名に対して 
// unknown を持つダミータイプを提供する事で setError の型推論を強制的に復活させる。
type ForwardPropertyName<T> = { [K in keyof T]: unknown };

type SetPersistError<T> = UseFormMethods<ForwardPropertyName<T>>["setError"];

type ErrorsType<T> = UseFormMethods<T>["errors"];

type UsePersistErrorResult<T> = {
  persistErrors: ErrorsType<T>;

  setPersistError: SetPersistError<T>;
};

/**
 * コンポーネントがアンマウントされるまではエラー情報を保持する
 * react-hook-form の{@code useForm}が返す{@code errors}, {@code setErrors}と
 * 同等のオブジェクトと関数を返す。
 *
 * react-hook-form v6 の{@code errors}はフォームコントロールが変化すると
 * 全てのエラー情報を消去してしまう。
 * バックエンドでバリデーションした結果を永続的に表示し続けたい場合は、独自のエラー管理を行わなければいけない。
 * この関数はコンポーネントがアンマウントされるまではエラー情報を保持する
 * {@code persistErrors}と{@code setPersistError}を返す。
 * この2つは{@code errors}と{@code setErrors}と同じように使用できる。
 *
 * See: https://github.com/react-hook-form/react-hook-form/issues/1881
 */
const usePersistError = <T>(): UsePersistErrorResult<T> => {
  const [persistError, setPersistError] = useState<ErrorsType<T>>({});

  const setPersistErrorTyped: SetPersistError<T> = useCallback(
    (name, error) => setPersistError((prev) => ({ ...prev, [name]: error })),
    [setPersistError]
  );

  return { persistErrors: persistError, setPersistError: setPersistErrorTyped };
};

export { usePersistError };
export type { SetPersistError, ErrorsType };

使い方はusePersistError(){persistErrors, setPersistError}を取り出し、
サーバーサイドバリデーションの結果はsetPersistError()を呼び出して追加する。
persistErrorsのプロパティにセットされているバリデーションエラー情報を画面に表示するという形になった。

react-hook-formのドキュメントに記載されているサンプルコード元にすると以下のような使い方になる。

バリデーションエラーメッセージはhandleSubmitのコールバックなどで手動でクリアすることになる。

codesandbox

import React from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
import { usePersistError } from "./usePersistError";

import "./styles.css";

interface IFormInputs {
  firstName: string;
  lastName: string;
  age: string;
  website: string;
}

function App() {
  const { register, handleSubmit } = useForm<IFormInputs>();
  const { persistErrors, setPersistError } = usePersistError<IFormInputs>();

  const onSubmit = (data: IFormInputs) => {
    // Clear error
    setPersistError("firstName", {});
    setPersistError("lastName", {});
    setPersistError("age", {});
    setPersistError("website", {});

    alert(JSON.stringify(data));

    if (!data.firstName) {
      setPersistError("firstName", { message: "firstName is missing." });
    }
    if (!data.lastName) {
      setPersistError("lastName", { message: "lastName is missing." });
    }
    if (!data.age) {
      setPersistError("age", { message: "age is missing." });
    }
    if (!data.website) {
      setPersistError("website", { message: "website is missing." });
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>First Name</label>
        <input type="text" name="firstName" ref={register} />
        {persistErrors.firstName?.message && (
          <p>{persistErrors.firstName.message}</p>
        )}
      </div>
      <div style={{ marginBottom: 10 }}>
        <label>Last Name</label>
        <input type="text" name="lastName" ref={register} />
        {persistErrors.lastName?.message && (
          <p>{persistErrors.lastName.message}</p>
        )}
      </div>
      <div>
        <label>Age</label>
        <input type="text" name="age" ref={register} />
        {persistErrors.age?.message && <p>{persistErrors.age.message}</p>}
      </div>
      <div>
        <label>Website</label>
        <input type="text" name="website" ref={register} />
        {persistErrors.website?.message && (
          <p>{persistErrors.website.message}</p>
        )}
      </div>
      <input type="submit" />
    </form>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Progate JavaScript I

Progate
・JavaScriptⅠ

PHPの記述と似ていると感じました。
変数の定義の仕方が
Java→let **
PHP →$ ***
PHPをはじめに学んでいた為、すんなりと終えることができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む