20190704のJavaScriptに関する記事は28件です。

jQueryがそのページに読み込まれているか確認する方法

こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。

jQueryがそのページに読み込まれているか確認する方法

実は、jQueryを読み込むと、グローバルにjQuryという関数が宣言されています。これは$と同じ関数です。
すなわち、以下の式がtrueであればjQueryが読み込まれていると分かります。
typeof jQuery === "function"

この記事が参考になれば幸いです。

はてなブックマーク・Pocketはこちらから

はてなブックマークに追加
Pocketに追加

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

Vue.jsでQiita簡易クライアントを作成

こんにちは
普段はバックエンドがメインなのですが、フロントエンドの勉強がてらVue.jsをやってみます

開発環境

画面遷移しないのでVue Routerは使用しません

成果物

Qiita APIを利用してタグ検索できます

ソースコード

src/main.js
import Vue from "vue";
import App from "./App.vue";
import store from "./store";

import BootstrapVue from "bootstrap-vue";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";

Vue.use(BootstrapVue);

Vue.config.productionTip = false;

new Vue({
  store,
  render: h => h(App)
}).$mount("#app");
src/App.vue
<template>
  <div id="app">
    <qiita-form />
    <qiita-content />
  </div>
</template>

<script>
import Form from "./components/Form.vue";
import Content from "./components/Content.vue";

export default {
  name: "app",

  components: {
    "qiita-form": Form,
    "qiita-content": Content
  }
};
</script>

ストア

src/store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const fetchItmes = async value => {
  const response = await fetch(
    `https://qiita.com/api/v2/tags/${value}/items?page=1&per_page=20`,
    { mode: "cors" }
  );
  return response.json();
};

export default new Vuex.Store({
  state: {
    selected: null,
    items: []
  },

  getters: {
    selected: state => state.selected,
    items: state => state.items
  },

  mutations: {
    selected(state, selected) {
      state.selected = selected;
    },
    items(state, items) {
      state.items = items;
    }
  },

  actions: {
    onChange({ commit }, value) {
      commit("selected", value);
    },
    async search({ commit, state }) {
      commit("items", []);
      const items = await fetchItmes(state.selected);
      commit("items", items);
    }
  }
});

コンポーネント

src/components/Form.vue
<template>
  <div class="sticky-top">
    <b-card>
      <b-form-group
        label="Qiitaくらいあんと: タグを付けた日時の降順で20件取得します"
      >
        <b-form-select :options="options" v-model="selected">
          <template slot="first">
            <option :value="null" disabled>-- 選択してください --</option>
          </template>
        </b-form-select>
      </b-form-group>
      <b-button @click="search" :disabled="isDisabled" variant="info" block
        >検索</b-button
      >
    </b-card>
  </div>
</template>

<script>
export default {
  name: "Form",

  computed: {
    selected: {
      get() {
        return this.$store.getters.selected;
      },
      set(value) {
        this.$store.dispatch("onChange", value);
      }
    },
    isDisabled() {
      return this.$store.getters.selected === null;
    },
    options: () => [
      { value: "javascript", text: "JavaScript" },
      { value: "typescript", text: "TypeScript" },
      { value: "elm", text: "Elm" }
    ]
  },

  methods: {
    search() {
      this.$store.dispatch("search");
    }
  }
};
</script>
src/components/Content.vue
<template>
  <div>
    <b-list-group v-for="item in items" :key="item.id">
      <qiita-item :item="item" />
    </b-list-group>
  </div>
</template>

<script>
import Item from "./Item.vue";

export default {
  name: "Content",

  components: {
    "qiita-item": Item
  },

  computed: {
    items() {
      return this.$store.getters.items.filter(item => !item.private);
    }
  }
};
</script>
src/components/Item.vue
<template>
  <b-list-group-item>
    <div>
      <b-link :href="item.url" target="_blank">{{ item.title }}</b-link>
      <small>{{ updateAt }}</small>
    </div>
    <div>
      <span v-for="tag in item.tags" :key="tag.name">
        <b-badge variant="info" pill>{{ tag.name }}</b-badge>
      </span>
    </div>
    <div>
      <small :id="userName">{{ `by ${userName}` }}</small>
    </div>
  </b-list-group-item>
</template>

<script>
export default {
  name: "Item",

  props: {
    item: {
      url: String,
      title: String,
      updated_at: String,
      tags: Array,
      user: {
        name: String,
        id: String
      }
    }
  },

  computed: {
    updateAt() {
      const date = new Date(this.item.updated_at);
      return ` (最終更新日: ${date.toLocaleDateString()})`;
    },
    userName() {
      return this.item.user.name || `@${this.item.user.id}`;
    }
  }
};
</script>

ソースコード全体
https://github.com/akthrms/qiita-client

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

WordPressでjsやcssを読み込むときにキャッシュを更新させる対策

概要

WordPress開発をしているとき、jsやcssを編集したのに表示が変わっていないぞという経験は、ありませんか?

wp_enqueue_script()やwp_enqueue_style()関数で、jsとcssファイル編集後にキャッシュを更新させる対策を紹介します。

ブラウザキャッシュとは

chromeやfirefoxなどの多くのブラウザには、Webページの表示高速化を目的にキャッシュという機能があります。
このキャッシュは、Webページにアクセスした時、ブラウザで見える内容のデータ(js、css等)をキャッシュして表示を速くします。

便利な機能ですが、jsやcssを編集してあと、Webページを更新してみると、表示が変わっていないことがあります。
これは、ブラウザにキャッシュされた古いjsとcssを読み込んでいるためです。

キャッシュを更新して最新のページを表示させる対策として、以下があります。

①スーパーリロードをする。

スーパーリロードとは、ブラウザのキャッシュを無視してWebページの最新のデータを読み込む機能です。
これによって、キャッシュが上書きされ、最新のWebページが表示されます。

ただ、サイトを閲覧している人誰もがスーパーリロードを知っているとは限らないし、jsやcssが変わっていることを知りません。
誰もが最新の状態でWebページを見ることができるように開発者側が配慮する必要があります。
そのため、この方法は好ましくありません。

chromeスーパーリロードのショートカット

// Windows版
Shift + F5

// Mac版
Cmd + Shift + R

②クエリパラメータにタイムスタンプを指定する。

クエリパラメータにタイムスタンプを指定することで、プラウザが別のファイルとして認識され、最新のjsとcssが読み込まれます。

タイムスタンプの指定例

// js
<script type="text/javascript" src="example.js?date=20190101120000"></script>
// css
<link rel="stylesheet" type="text/css" href="style.css?date=20190101120000">

パラメータ名は、特に決まりはありません。書かなくても動作します。
今回は、分かりやすいようにパラメータ名をdateにしています。
あとは、jsとcssファイル更新日時のタイムスタンプを動的に変えてやればOKです。

WordPressでブラウザキャッシュ対策

WordPressでも簡単にキャッシュ対策できます。
jsとcssを読み込むwp_enqueue_script()やwp_enqueue_style()関数には、第4引数にバージョンを指定するパラメータが用意されています。

バージョンを指定しない場合、デフォルトで設定されるWordPressのバージョンが指定されるようです。
そのため、jsとcssを編集しても、WordPressをアップデートされない限りは、最新のjsとcssが読み込まれないことになります。
バージョンにタイムスタンプを指定することで、上記で紹介したクエリパラメータにタイムスタンプを指定する対策と同様のことが可能です。

タイムスタンプの指定例

// js
wp_enqueue_script( 'example',
    plugins_url( '/js/example.js', __FILE__ ),
    array(),
    date_i18n( 'YmdHis', filemtime( plugins_url( '/js/example.js', __FILE__ ) ) )
);

// css
wp_enqueue_style( 'style',
    plugins_url( '/css/style.css', __FILE__ ),
    array(),
    date_i18n( 'YmdHis', filemtime( plugins_url( '/css/style.css', __FILE__ ) ) )
);

filemtime関数でファイルの更新日時のタイムスタンプを取得しています。

ちなみに、以下方法でも可能のようです。

こちらの方法では、date以外にデフォルトで設定されるWordPressのバージョンのクエリパラメータも追加で付与されるようです。
dateのクエリパラメータだけを表示させたい場合は、上記の方法が良いです。

// js
wp_enqueue_script( 'example?date=' . date_i18n( 'YmdHis', filemtime( plugins_url( '/js/example.js', __FILE__ ) ) ),
    plugins_url( '/js/example.js', __FILE__ )
);

// css
wp_enqueue_style( 'style?date=' . date_i18n( 'YmdHis', filemtime( plugins_url( '/css/style.css', __FILE__ ) ) ),
    plugins_url( '/css/style.css', __FILE__ )
);

参考URL(WordPress Codex)

wp_enqueue_script関数

https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/wp_enqueue_script

wp_enqueue_style関数

https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/wp_enqueue_style

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

redux-form (5) - Initialize From State

redux-form (1) - Simple Form Example
redux-form (2) - Synchronous Validation Example
redux-form (3) - Field-Level Validation Example
redux-form (4) - Submit Validation Example
redux-form (5) - Initialize From State


ReactでForm componentを作るときに、とても便利なredux-formの説明です。

redux-formの概説についてはまず以下の記事を参考にしてください。

redux-form (1) - Simple Form Example

Initialize From State

Initialize From State - Getting Started With redux-form

redux-formでは、decorated form component(wrapped component)のform stateを初期化するために、initialValuesを用います。initialValuesは、例えばサーバから取得した値を、React Redux の connect()を使って、mapStateToPropsでRedux state をinitialValues propに変換して、decorated form componentに渡すようにします。

initialValuesでformを初期化できるのは、デフォルトで1回のみです。

decorated form componentはreset() propを受け取りますが、reset()を実行することでformをinitialValuesに戻すことができます。

以下が、reducerとaction creatorのソースです。

src/account.js
// Quack! This is a duck. https://github.com/erikras/ducks-modular-redux
const LOAD = 'redux-form-examples/account/LOAD'

const reducer = (state = {}, action) => {
  switch (action.type) {
    case LOAD:
      return {
        data: action.data
      }
    default:
      return state
  }
}

/**
 * Simulates data loaded into this reducer from somewhere
 */
export const load = data => ({ type: LOAD, data })

export default reducer

以下がredux-formのサンプルです。ポイントの説明を下にまとめてあります。

src/InitializeFromStateForm.js
import React from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm } from 'redux-form'
import { load as loadAccount } from './account'

const data = {
  // used to populate "account" reducer when "Load" is clicked
  firstName: 'Jane',
  lastName: 'Doe',
  age: '42',
  anniversaryDate: '2018-08-22',
  sex: 'female',
  employed: true,
  favoriteColor: 'Blue',
  bio: 'Born to write amazing Redux code.'
}

const colors = ['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', 'Violet']

let InitializeFromStateForm = props => {
  const { handleSubmit, load, pristine, reset, submitting } = props
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <button type="button" onClick={() => load(data)}>
          Load Account
        </button>
      </div>
      <div>
        <label>First Name</label>
        <div>
          <Field
            name="firstName"
            component="input"
            type="text"
            placeholder="First Name"
          />
        </div>
      </div>
      <div>
        <label>Last Name</label>
        <div>
          <Field
            name="lastName"
            component="input"
            type="text"
            placeholder="Last Name"
          />
        </div>
      </div>
      <div>
        <label>Age</label>
        <div>
          <Field name="age" component="input" type="number" placeholder="Age" />
        </div>
      </div>
      <div>
        <label>Anniversary Date</label>
        <div>
          <Field name="anniversaryDate" component="input" type="date" />
        </div>
      </div>
      <div>
        <label>Sex</label>
        <div>
          <label>
            <Field name="sex" component="input" type="radio" value="male" />{' '}
            Male
          </label>
          <label>
            <Field name="sex" component="input" type="radio" value="female" />{' '}
            Female
          </label>
        </div>
      </div>
      <div>
        <label>Favorite Color</label>
        <div>
          <Field name="favoriteColor" component="select">
            <option value="">Select a color...</option>
            {colors.map(colorOption => (
              <option value={colorOption} key={colorOption}>
                {colorOption}
              </option>
            ))}
          </Field>
        </div>
      </div>
      <div>
        <label htmlFor="employed">Employed</label>
        <div>
          <Field
            name="employed"
            id="employed"
            component="input"
            type="checkbox"
          />
        </div>
      </div>
      <div>
        <label>Bio</label>
        <div>
          <Field name="bio" component="textarea" />
        </div>
      </div>
      <div>
        <button type="submit" disabled={pristine || submitting}>
          Submit
        </button>
        <button type="button" disabled={pristine || submitting} onClick={reset}>
          Undo Changes
        </button>
      </div>
    </form>
  )
}

// Decorate with reduxForm(). It will read the initialValues prop provided by connect()
InitializeFromStateForm = reduxForm({
  form: 'initializeFromState' // a unique identifier for this form
})(InitializeFromStateForm)

// You have to connect() to any reducers that you wish to connect to yourself
InitializeFromStateForm = connect(
  state => ({
    initialValues: state.account.data // pull initial values from account reducer
  }),
  { load: loadAccount } // bind account loading action creator
)(InitializeFromStateForm)

export default InitializeFromStateForm

React-Reduxのconnect()関数について少し説明します。

mapStateToPropsinitialValues propが設定されています。initialValues は reduxForm が処理します。initialValues は { field1: 'value1', field2: 'value2' } の形をしており、componentWillMount() において form を初期化します。
initialValuesはここの例のようにpropで受け渡す以外に、reduxForm() のconfig parameterで受け渡す方法もあります。

mapDispatchToPropsobject を指定することができます。この場合、objectの各fieldがaction creatorとなり、 React-Redux は dispatchをaction creatorにbindします。
connect() -React Redux

InitializeFromStateForm = connect(
  state => ({          // mapStateToProps
    initialValues: state.account.data 
  }),
  { load: loadAccount } // mapDispatchToProps
)(InitializeFromStateForm)

以下はindex.jsですが、オリジナルのものを最小化してあります。

src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, combineReducers } from 'redux'
import { reducer as reduxFormReducer } from 'redux-form'
import account from './account'

const dest = document.getElementById('content')

const reducer = combineReducers({
  account,
  form: reduxFormReducer // mounted under "form"
})
const store = createStore(reducer)

const showResults = values =>
  new Promise(resolve => {
    setTimeout(() => {
      // simulate server latency
      window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`)
      resolve()
    }, 500)
  })

let render = () => {
  const InitializeFromStateForm = require('./InitializeFromStateForm').default
  ReactDOM.hydrate(
    <Provider store={store}>
      <h2>Form</h2>
      <InitializeFromStateForm onSubmit={showResults} />
    </Provider>,
    dest
  )
}

render()

実行画面

初期画面です

image.png

「Load Account」ボタンでformを初期化します。最初の一回目だけ有効です。初期状態を変更してから、再度初期状態に戻すためには、UndoChangesボタンでreset()を発行する必要があります。

image.png

submitボタンを押します。

image.png

今回は以上です。

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

jQueryで表示非表示活性非活性する方法

表示になっているものを非表示にする

$(要素).hide();
$(要素).toggle(false);

非表示になっているものを表示する

$(要素).show();
$(要素).toggle(true);

実行されるたびに表示と非表示を切り替える

$(要素).toggle();

活性化

$(要素).prop('disabled', false);
// disabled属性を削除する
$(要素).removeAttr('disabled');

非活性化

$(要素).prop('disabled', true);

注意1 : disabled属性のある要素はサーバへ送られない

注意2 : attr()は使わない

  • attrで活性非活性の切り替えは古いjQueryならできるらしいけど、ソースにあるとわかりにくいから使わない方が良いと思う
  • attr(属性, 値)で、属性に値を設定する
    • $(要素).attr('disabled', true); は、disabled属性の値をtrueにするということ
    • $(要素).attr('disabled', false); は、disabled属性の値をfalseにするということ
  • disabled属性は「存在すれば非活性」「存在しなければ活性」なので属性の値はどうでもいい
/** これは全部非活性になる. */
<input type="text" disabled>
<input type="text" disabled="disabled">
<input type="text" disabled="true">
<input type="text" disabled="false">

出展

他のことを知りたい

JavaScriptの実装に役立つリンク集 - Qiita
JavaScriptでファイルの入出力をしたい時に役立つリンク集 - Qiita
JavaScriptでいろいろ取得したい時に見てみるリンク集 - Qiita

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

JavaScriptでいろいろ取得したい時に見てみるリンク集

要素のidを取得したい

/** jQuery. */
let id = $(要素名).attr('id');
// onClickイベントなどで
let id = $(this).attr('id');

ラジオボタンの選択値を取得したい

/** 普通. */
let radios = document.getElementsByName("name属性");
let selected;
for (let i = 0; i < radios.length; i++){
    if(radios[i].checked){
        selected = radios[i].value;
        break;
    }
}
/** jQuery. */
let selected = $('input[name="name属性"]:checked').val();

input type="hidden"の値を取得したい

/** jQuery. */
// name属性指定
let a = $('input:hidden[name="name属性"]').val();
// id属性指定
let d = $('#id属性').val();

家族要素を取得したい

jQueryを使って要素を取得したい

jQueryを使わないで要素を取得したい

他のことを知りたい

JavaScriptの実装に役立つリンク集 - Qiita
JavaScriptでファイルの入出力をしたい時に役立つリンク集 - Qiita
jQueryで表示非表示活性非活性する方法 - Qiita

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

Ajaxのフワッと理解

注意事項

この記事は、「ぶっちゃけ下書きが溜まってきて処分したかったけど消すのはもったいない」という筆者の気まぐれで作成された記事である。
某5才児教育番組みたいなノリの記事なので真剣にAjaxを学びたい人は回れ右でお願いします。
この記事を読んで得られるものは、ふわっとしたAjaxの理解だけです。

Ajaxってなに?

javascriptでWebページを再読込(ページ更新処理)せずにページの内容を変更する技術のこと。

名前の由来は「Asynchronous JAvascript + Xml」の頭文字を取ったもの。

そもそもAjaxを使うと何ができるの?

画面をポチポチしても、画面が固まらなくなるよ!

何で画面をポチポチすると固まるの?

画面をポチポチすると、新しい画面のデータを取りに行くから、戻ってくるまで画面が固まるんだよ!

じゃあ何でAjaxだと固まらないの?

見えないところで新しい画面のデータを取りに行って、今の画面にこっそり渡してるから固まらないんだよ!

ちょっとだけまじめに同期と非同期のお話

  • 同期とは(画面が固まる方)
    画面の更新処理(HTTPリクエスト)をサーバーに送信すると、サーバーから応答(HTTPレスポンス)が返ってくるまで処理を待機する(=画面操作ができなくなる)こと。
  • 非同期とは(画面が固まらない方)
    画面の更新処理(HTTPリクエスト)をサーバーに送信しても、サーバーからの応答(HTTPレスポンス)の有無にかかわらず処理を継続する(=待機中も画面操作ができる)こと。

Ajaxでできること(めっちゃざっくり)

Google検索のサジェスト、FXのチャートとかなんかリアルタイムで動いてるやつ。

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

AWSマネジメントコンソールの色をアカウントによって変更するChrome拡張機能を作った(実装編)

はじめに

先日、AWSマネジメントコンソールの色をアカウントによって変更するChrome拡張機能を作成してリリースしました。
機能や使い方はこちらの記事をご覧下さい。
AWSマネジメントコンソールの色をアカウントによって変更するChrome拡張機能を作った(紹介編)

実装詳細

オプション画面はjqueryを使用して値を取得しています。
input要素を適当に配置しただけのHTMLですので特記する事はありません。

AWSのサイトで起動するスクリプトはJavaScriptです。

ソースはこちらに
https://github.com/sh-nakayama/AWS_COLOR_CHANGER

苦労した点

オプション画面で入力した値を保存して、AWSのマネコンのサイトで起動するJavaScriptに読み込ませるのに苦労しました。
最初はオプション画面で入力した値をlocalstrageに保存していましたが、
localStorageはページごとに保存するデータが分かれている事が判明、読み書きは同じ場所でないと値が共有できません。つまりAWSのマネコンのサイトで起動するJavaScriptではlocalStorageに保存した値が読めません。
回避方法はあるみたいですが、
https://mae.chab.in/archives/2861

苦労した点の対応

今回はchrome.storageを使用することで対応しました。

下記の記事を参考に作成しました。
https://easyramble.com/chrome-storage-set-and-get.html
https://qiita.com/shimutaya/items/e8835d6ce794ef6c73cf

終わりに

意外と簡単にリリースまでこぎつけました。
実は最も苦労した点はmanifestファイルかもしれません。スペルミスや、インデントミスでかなり時間を取られました:sweat_smile:

参考にさせていただいた記事

https://qiita.com/otchy/items/e29bf2e377c5b7ddc2f7

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

Google App ScriptでDriveAppを使わずに、自動でファイルダウンロードする機能を作った

概要

GASでJsonのダウンロードができれば良いなと思っていたが、調べていると大体、一旦Google Driveに入れろということが書いてありました。
しかし、いろいろ面倒なのでなにか他に手段が無いかと調べていたところ、GASでHTMLを表示できるということなので、DataURL使えばいいじゃんと思ったので、やってみました。
GASの設定など、TypeScriptでの環境構築などは他でいろいろ紹介されていると思うので、そちらを参照してください。
今回紹介するのは単純にダウンロード部分です。

GAS上でのプロジェクトの構成

以下のようになっているとします。

.
├── json.gas
├── main.gas
└── download.html

中身

HTMLを表示するための関数とJsonを作成する用の関数です。
webpackでトランスパイルされて、json.gas になる想定です。そのため、globalで定義します。

json.ts
declare let global: any;

global.body = "" // HTML上で参照される変数

function() { // この関数がJson文字列を作成すると仮定する。この中でspread sheetを読み込むでもよしdocumentを読み込むでもよし
    return JSON.stringify({
        firstName: "john",
        lastName: "satoh"
    }, null, 2);
}

function jsonDownloadDialog() {
    global.body = createJson();
    const name = "jsonの中身"; // ダイアログの名前

    var output = HtmlService.createTemplateFromFile('download'); //同じディレクトリにあるHTMLを参照している
    var html = output.evaluate().setHeight(420).setWidth(600);
    var ui = SpreadsheetApp.getUi();

    ui.showModalDialog(html, name);
}
global.jsonDownloadDialog = jsonDownloadDialog;

こちらは「JSON」→「ダウンロード」というメニューを用意するためのスクリプトです。
こちらはwebpackに含まれない想定です。(globalに定義しても呼ばれなかったので別にしました。

main.js
function onOpen() {
    var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    var entries = [{
        name : "ダウンロード",
        functionName : "jsonDownloadDialog" // globalに定義されているjson.gasの関数を参照している
    }];
    spreadsheet.addMenu("JSON", entries);
}

こちら作成したJsonを表示し、さらに自動的にファイルをダウンロードするスクリプトが入っているHTMLです。
#jsonの中身をFileとしてObjectURLにしている感じです。

download.html
<!DOCTYPE html>
<html lang="jp">
<head>
    <base target="_top">
    <script>
        window.addEventListener('load', function() {
            console.log(document.querySelector("#json").textContent)
            var link = document.createElement('a');
            link.download = 'data.json'; // ファイル名
            var blob = new Blob([document.querySelector("#json").textContent], {type: 'text/plain'});
            link.href = window.URL.createObjectURL(blob);
            link.text = "もう一回ダウンロード";
            document.querySelector("#download_area").appendChild(link);
            link.click(); // aタグを叩く
        });
    </script>
</head>
<body>
    <pre id="json"><?
output.append(body);
        ?></pre>
    <div id="download_area">
    </div>
</body>
</html>

出来上がり

スクリーンショット 2019-07-04 19.37.48.png

まとめ

GAS面白い。
claspでhtmlがあげられるのを知りませんでしたし、メニューを追加できるのも知りませんでした。。。
これなら本当に小さいアプリケーションは作れてしまいますね。。。

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

v-forを使う時、配列に初期値があると初めから画面に表示されてしまう問題

vueでtodoリストを作る際に、v-forを使い配列の中身を順に表示させようとしたのですが、ボタン要素を作る為にhtmlに<button>を記述したところ、初期の状態からボタン要素が見えてしまっている不備があった為、少し躓いたので解決策を記載します。

html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">   
</head>
<body>
    <div id="app">
        <h1>ToDoList</h1>
        <table>
            <thead>
              <tr>
                <th>コメント</th>
                <th>状態</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="todo in todos" :key="todo.value">
                <td>{{ todo.item }}</td>
                <td><button @click="">{{ todo.state }}</button></td>
            </tr>
            </tbody>
          </table>
          <input type="text" v-model="newItem">
          <button @click="addItem">追加</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="script.js"></script>
</body>
</html>

ここは問題なしです。

javascript(vue) 不備があったコード。

let vm = new Vue({
     el: '#app',
     data: {
          newItem: '',
          todos: [{
               item: this.newItem,
               state: ''
          }]
     },
     methods: {
          //タスクを追加する関数
          addItem: function () {
               this.todos.push({item: this.newItem, state: '作業中'})
          }
     }
});

このコードだと、htmlに初めからボタン要素が見えてしまいます。
じゃあhtmlの<button>を消して、javascriptの関数内でボタン要素を作り、それを配列todosにpushしよう!
と思ったのですが、vueには生のjavascriptでお世話になったdocument.createElementに相当するものは無いようなので、少し躓いてしまいました。

難しく考えていましたが解決策は非常にシンプルでした。

解決したコード

let vm = new Vue({
     el: '#app',
     data: {
          newItem: '',
          todos: []
     },
     methods: {
          //タスクを追加する関数
          addItem: function () {
               this.todos.push({item: this.newItem, state: '作業中'})
          }
     }
});

【解決策】
配列todosを空にするだけです。

【原因】
htmlに初めからボタン要素が表示されていた原因は、配列todosのボタン要素になるプロパティstateに初期値を与えてしまったせいです。
ですので配列todosを空にし、関数内でpushする時にプロパティを作ってあげれば解決です。
(プロパティitemは今回のボタン要素の表示には関係ありませんが、同じ関数内で一緒にpushしてあげた方がスマートで保守性と可読性も上がると思います。)


以上です。
よく考えれば当たり前なのに理解不足もあり少し時間を取られたので記載しておきました。
補足や訂正などありましたら、ぜひご教授いただければ嬉しいです。
最後まで見ていただきありがとうございます。

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

v-forを使う時、配列に初期値があると初めから画面に表示されてしまう

vueでtodoリストを作る際に、v-forを使い配列の中身を順に表示させようとしたのですが、ボタン要素を作る為にhtmlに<button>を記述したところ、初期の状態からボタン要素が見えてしまっている不備があった為、少し躓いたので解決策を記載します。

html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">   
</head>
<body>
    <div id="app">
        <h1>ToDoList</h1>
        <table>
            <thead>
              <tr>
                <th>コメント</th>
                <th>状態</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="todo in todos" :key="todo.value">
                <td>{{ todo.item }}</td>
                <td><button @click="">{{ todo.state }}</button></td>
            </tr>
            </tbody>
          </table>
          <input type="text" v-model="newItem">
          <button @click="addItem">追加</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="script.js"></script>
</body>
</html>

ここは問題なしです。

javascript(vue) 不備があったコード。

let vm = new Vue({
     el: '#app',
     data: {
          newItem: '',
          todos: [{
               item: this.newItem,
               state: ''
          }]
     },
     methods: {
          //タスクを追加する関数
          addItem: function () {
               this.todos.push({item: this.newItem, state: '作業中'})
          }
     }
});

このコードだと、htmlに初めからボタン要素が見えてしまいます。
じゃあhtmlの<button>を消して、javascriptの関数内でボタン要素を作り、それを配列todosにpushしよう!
と思ったのですが、vueには生のjavascriptでお世話になったdocument.createElementに相当するものは無いようなので、少し躓いてしまいました。

難しく考えていましたが解決策は非常にシンプルでした。

解決したコード

let vm = new Vue({
     el: '#app',
     data: {
          newItem: '',
          todos: []
     },
     methods: {
          //タスクを追加する関数
          addItem: function () {
               this.todos.push({item: this.newItem, state: '作業中'})
          }
     }
});

【解決策】
配列todosを空にするだけです。

【原因】
htmlに初めからボタン要素が表示されていた原因は、配列todosのボタン要素になるプロパティstateに初期値を与えてしまったせいです。
ですので配列todosを空にし、関数内でpushする時にプロパティを作ってあげれば解決です。
(プロパティitemは今回のボタン要素の表示には関係ありませんが、同じ関数内で一緒にpushしてあげた方がスマートで保守性と可読性も上がると思います。)


以上です。
よく考えれば当たり前なのに理解不足もあり少し時間を取られたので記載しておきました。
補足や訂正などありましたら、ぜひご教授いただければ嬉しいです。
最後まで見ていただきありがとうございます。

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

axiosでネットワークエラー・タイムアウトエラーをハンドルする

axiosでネットワークエラー・タイムアウトエラーをハンドルする場合、以下のように判定可能です。

axios.interceptors.response.use(
  function(error) {
      //タイムアウトの場合
      const isTimeout = error.code === "ECONNABORTED";
      if (isTimeout) {
        //do something
        return;
      }

      //ネットワークエラーの場合
      if (!error.response) {
        //do something
        return;
      }
  }
);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS Webアプリ】ホワイトリストを使ってWebページのアクセス制御する

はじめに

Google Apps Scriptを使えばウェブアプリケーションを簡単に作成できる。
Gsuiteのユーザーの場合、[アプリケーションにアクセスできるユーザー]をGsuiteユーザー内で公開できるが、「全員」に公開した場合細かな アクセス制御 がGUIからできないので、それを簡単に実装するコード

スクリーンショット 2019-07-04 16.21.01.png

やり方

前準備

シート1にアクセス権限を管理するデータを入力する。
statusokのユーザのみがアクセスできる。
スクリーンショット 2019-07-04 16.46.52.png

ソースコード

スプレッドシートの[ツール]→[スクリプトエディタ]を開き以下のようにファイルを作成する。
スクリーンショット 2019-07-04 16.26.05.png

index.htmlは表示したいHTMLファイル
error.htmlはアクセス権限の内ユーザーに対するHTMLファイル

※HTMLファイルの中身は自由に変えてもらって大丈夫です。

index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
   <h1>こんにちは世界!</h1>
  </body>
</html>
error.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
   <h1>アクセス権限のないユーザーです。</h1>
  </body>
</html>
コード.gs
//現在ログインしているユーザーのメールアドレスを取得
LOGIN_USER = Session.getActiveUser().getEmail();

function doGet(e) {
  //ログインしているユーザーのメールアドレスを使ってアクセス権を確認
  if(getUserData(LOGIN_USER).status == 'ok'){
    htmlOutput = HtmlService.createTemplateFromFile("index").evaluate();
  }else{
    htmlOutput = HtmlService.createTemplateFromFile("error").evaluate();
  }

  htmlOutput
  .setTitle('メールアドレスでアクセス権限してみる')
  .addMetaTag('viewport', 'width=device-width, initial-scale=1')

  return htmlOutput
}

//シート名を指定してシートを取得
function getSheetData(sheetName) {
  var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
  var rows = sheet.getDataRange().getValues();
  var keys = rows.splice(0, 1)[0];
  return rows.map(function(row) {
    var obj = {};
    row.map(function(item, index) {
      obj[String(keys[index])] = String(item);
    });
    return obj;
  });
}

//メールアドレスをしていしてユーザーデータを返す
function getUserData(email) {
  var user_data = getSheetData('シート1');

  var user = user_data.filter(function(value) {
    return email == value.email;
  });

  if (user[0] != undefined) {
    return user[0];
  }

  Logger.log('そのメールアドレスに対応するユーザーは存在しません。');
  return {status: 'ng'};
}

[公開]→[ウェブアプリケーションとして導入]を押して、承認画面がいろいろ出てくるので、承認してから
アプリケーションにアクセスできるユーザー全員にすると使えます!
もちろんGsuite内のユーザーの中で更にアクセス制御・制限したい場合でも使えます。

スクリーンショット 2019-07-04 16.35.57.png

お疲れ様です

うまく行けば以下のように表示されるはずです!

うまくいかない場合は アプリケーションのURL末尾が/execになっているか確認してください。 /dev の場合はテストモードなのでスクリプトエディタにアクセスできるユーザーではないとアクセスできません。
スクリーンショット 2019-07-04 16.39.21.png
スクリーンショット 2019-07-04 16.38.41.png

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

YouTubeで再生範囲指定したプレイリスト的なものを作った

Why use IFrame API

初めは、Data APIのplaylistItemsを使おうと思ってたんです。そしたら、すでに廃止済み(予定?)だったらしいです。(そうなら、Referenceから消てくれ~)
それで、いろいろ探した結果IFrame APIにたどり着きました。

つくったもの

AZKi Live Looper

推しのラジオのライブパートをループするページです。
動画下のボタンで何回目のラジオか選べます。
それだけ!

プレイヤーの設定

メインとなるjsは公式から4.までほぼコピペです。プレイヤーのサイズとかは適宜設定してください。

それと、動画のIDと開始時間、終了時間が必要なので次のようなjsonファイルを用意しました。

LiveData.json
{
    "contents":[
        {
            "video":"VIDEO_ID",
            "start":"2000",
            "end":"3060"
        },
        以下同様に作っていく
    ]
}

読み込みはjQuery(参考ページ)で

player.js
var liveData = [];
$.getJSON('./LiveData.json', function (data) {
    liveData = data['contents'];
});

次に、公式3.を以下のように書き換えました。

LiveData.js
var player;
var count = 0;

function onYouTubeIframeAPIReady() {
    player = new YT.Player('player', {
        videoId: liveData[count].video,
        //変更箇所
        playerVars: {
            start: liveData[count].start,
            end: liveData[count].end
        },
        events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
        }
    });
}

変更点は、playerVarsを加えたことで、この中で各パラメータが設定できるのでstart(開始時間)とend(終了時間)を設定しました。両方とも動画の頭からの秒数を文字列で指定します。
これでとりあえず最初の動画が表示出来ます。

次の動画の読み込み

プレイヤーで動画の再生が終了したときに次の動画を読み込めるように、次のようなonPlayerStateChangeを定義します。

player.js
var done = false;
function onPlayerStateChange(event) {
    if (event.data == 0 && !done) {
        count += 1;
        done = true;
        if (!(count < liveData.length)) {
            count = 0;
        }

        player.loadVideoById({
            videoId: liveData[count].video,
            startSeconds: Number(liveData[count].start),
            endSeconds: Number(liveData[count].end)
        });
    } else
    if (event.data == 1 && done) {
        done = false;
    }
}

onPlayerStateChangeはプレイヤーの再生ステータスが変化したときに実行され、プレイヤーが停止したときにcountをインクリメントしてloadVideoByIdで次の動画を読み込み再生します。

変数doneは動画再生中はfalse、次の動画の読み込み中にtrueとなります。これがないとcountが2回インクリメントされてしまったりします。

これでお気に入り部分の垂れ流しができました。

動画選択ボタン

オプションです。
ボタンを作って

index.html
<input type="button" value="#1" onclick="setLive(0)">

こんな感じで、loadVideoByIdで動画をロードさせるだけ

player.js
var setLive = function (Num) {
    count = Num;
    player.loadVideoById({
        videoId: liveData[count].video,
        startSeconds: Number(liveData[count].start),
        endSeconds: Number(liveData[count].end)
    });

ソースコード

https://github.com/Tkg-tamagohan/AZKiLiveLooper

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

今更だけどfind()とchildren()の違いについて

もう使われなくなってきたjQueryですが、コードを読むときにやたらjQueryが使われている&Reactを用いた開発でもjQueryと共存ができるのでさきほど勉強をはじめました。
そこでfind()とchildren()が似てたので、使い分けについて説明していきます。

find()とchildren()の違いとはズバリ

小要素を取得できる階層の違いです。
find()は小要素すべてを取得でき、children()は1階層下の要素のみ取得できます。

HTMLが以下のときに

<div id="セレクタ">
  <a>あいうえお</a>
  <div>
    <a>かきくけこ</a>
  </div>
</div>

find()の場合

$('セレクタ').find('a');

で、親要素のセレクタ内にある小要素すべて<a>を取得します。
なので、あいうえおとかきくけこ両方取得できます。

children()の場合

$('セレクタ').children('a');

で、親要素のセレクタ内にある1階層下の小要素<a>のみ取得します。
なので、あいうえおのみ取得できます。

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

Laravel MixのIE11対応

LaravelMixのextensionにlaravel-mix-polyfilltというものがあり、簡単に使えたので書いておきます。

https://laravel-mix.com/extensions/polyfill

laravel-mix-polyfillのインストール

npm install laravel-mix-polyfill --save-dev

laravel-mix-polyfillを使うための設定

let mix = require('laravel-mix');

require('laravel-mix-polyfill');

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css')
   .polyfill({
      enabled: true,
      useBuiltIns: "usage",
      targets: {"firefox": "50", "ie": 11}
   });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JS]連想配列に任意の値と等しい要素があれば、それらのインデックスを全て取ってくるサンプル~flatMap編~

いきなり結論コード

See the Pen 配列の一致インデックス取得flatMap版 by riotam (@riotam4) on CodePen.

解説

キモになる部分
      return this.users.flatMap((v,k) => v.gender === '男' ? k : [])

実際に処理を行なっているのは、↑この部分です。
this.usersにデータが入っていて、それをflatMapしています。
基本的には関連記事のmap版と似ているのですが、1つだけ簡単に書ける部分があります。

map版との違い

map版だとこんな感じ
      let idx = []
      this.users.map((v,k) => v.gender == '男' ? idx.push(k) : [])
      return idx

ズバリ、map版ではidx変数を宣言してそこにpushしているのですが、これが自体がいらなくなります。

逆に結論から説明すると、

flatMap版
      return this.users.flatMap((v,k) => v.gender === '男' ? k : [])

これを

mapに書き換えたら…
      return this.users.map((v,k) => v.gender === '男' ? k : [])
      // => [ [], 1, 2 ]

こうなっちゃいます。
[]''にしたら[ '', 1, 2 ]と、
[]nullにしたら[ null, 1, 2 ]と、返されちゃいます。
mapはたとえnullでも、何かしらの値を入れた配列を作りますが、
対してflatMapはfalsyな値なら無しにしてくれます。便利。

最後に

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

関連記事

同じ挙動を、mapメソッドで作ったのがこちら

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

[JS]連想配列に任意の要素があれば、それらのインデックスを全て取ってくるサンプル~map編~

いきなり結論コード

See the Pen 配列の一致インデックス取得map版 by riotam (@riotam4) on CodePen.

解説

キモになる部分
      let idx = []
      this.users.map((v,k) => v.gender == '男' ? idx.push(k) : [])
      return idx

まず、idxという変数を用意。
this.usersにデータが入っているので、それらをmapします。

mapメソッドの引数は、関数型で書く必要があり、アロー関数と三項演算子でワンライナーにしてしまってます。
要は、this.usersをv(value)とk(key)にして順番に呼び出す。
→そのvのgenderキーが'男'と等しければ、idx変数にpushしていく。そうじゃなければ、という感じ。

ちなみに

[]部分はnull''0など、falsyなものなら、挙動に問題はありません。

最後に

さいごまでありがとうございました。

関連記事

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

[JS]連想配列に任意の値と等しい要素があれば、それらのインデックスを全て取ってくるサンプル~map編~

いきなり結論コード

See the Pen 配列の一致インデックス取得map版 by riotam (@riotam4) on CodePen.

解説

キモになる部分
      let idx = []
      this.users.map((v,k) => v.gender == '男' ? idx.push(k) : [])
      return idx

まず、idxという変数を用意。
this.usersにデータが入っているので、それらをmapします。

mapメソッドの引数は、関数型で書く必要があり、アロー関数と三項演算子でワンライナーにしてしまってます。
要は、this.usersをv(value)とk(key)にして順番に呼び出す。
→そのvのgenderキーが'男'と等しければ、idx変数にpushしていく。そうじゃなければ、という感じ。

ちなみに

[]部分はnull''0など、falsyなものなら、挙動に問題はありません。

最後に

さいごまでありがとうございました。

関連記事

同じ挙動のものをflatMapで作ってみました。こちら

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

[JS]連想配列の任意要素に一致するインデックスを取得する際、findIndex関数だと1つしか取得できない件

今回したいこと

    users:[
      {name:'花子', age:'10', sex:'女'},
      {name:'太郎', age:'11', sex:'男'},
      {name:'次郎', age:'12', sex:'男'},
    ],

このデータから、sex='男'のuserのインデックスを取得したい。
つまり、1と2が変えればOK。

findIndexを使ったサンプル

See the Pen findIndexサンプル by riotam (@riotam4) on CodePen.

ご覧の通り、男は次郎くんもいるのですが、帰ってくるインデックスは1…つまり、太郎君しか認識できません。
これは、よくない。

fileterを使ったサンプル

See the Pen filterサンプル by riotam (@riotam4) on CodePen.

今度はfilterを使ってみました。
次郎君も認識できますが、インデックスを返してくれないので、微妙。

結論

  • findIndexは要素1つしか取れない
  • 2つ目以降の一致したインデックスは無視されるので注意
  • 全て取りたいなら、forEach等で回して、一致するインデックスを1つずつ配列にpushしていくパターンが早そう # 追記 Twitterで募集してみたが、やはり一発で全インデックスを取得できるメソッドはなさそうな。 みなさま、貴重なご意見ありがとうございました。

結局、map,flatMap,reduce等を使って作るのが良いとのこと。
また、サンプルコードつくります!
ありがとうございました。

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

[JS]連想配列に任意の値と等しい要素があるとき、findIndex関数だとインデックスが1つしか取得できない件

今回したいこと

    users:[
      {name:'花子', age:'10', sex:'女'},
      {name:'太郎', age:'11', sex:'男'},
      {name:'次郎', age:'12', sex:'男'},
    ],

このデータから、sex='男'のuserのインデックスを取得したい。
つまり、1と2が変えればOK。

findIndexを使ったけど1つしか取れない

See the Pen findIndexサンプル by riotam (@riotam4) on CodePen.

ご覧の通り、男は次郎くんもいるのですが、帰ってくるインデックスは1…つまり、太郎君しか認識できません。
これは、よくない。

fileterを使ったけど、インデックスが取れない

See the Pen filterサンプル by riotam (@riotam4) on CodePen.

今度はfilterを使ってみました。
次郎君も認識できますが、インデックスを返してくれないので、微妙。

結論

  • findIndexは要素1つしか取れない
  • 2つ目以降の一致したインデックスは無視されるので注意
  • 全て取りたいなら、forEach等で回して、一致するインデックスを1つずつ配列にpushしていくパターンが早そう

追記

Twitterで募集してみたが、やはり一発で全インデックスを取得できるメソッドはなさそうな。
みなさま、貴重なご意見ありがとうございました。

結局、map,flatMap,reduce等を使って作る感じとのこと。
ということで、サンプルコードつくって関連記事に貼ってますので、参考にどうぞ。
ありがとうございました。

関連記事

mapメソッドで作ってみました。こちら
flatMapメソッドで作ってみました。こちら

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

【React】最小限のreact-router実装で仕組みを理解する

概要

react-routerの使い方を理解するために最小限の実装をする

成果物

out.gif

手順

  1. Reactの新規プロジェクト作成
  2. react-routerをインストール
  3. srcディレクトリの中身を削除
  4. index.jsとApp.jsを作成

1. Reactの新規プロジェクト作成

$ npx create-react-app hogehoge

2. react-routerをインストール

$ cd hogehoge
$ npm install react-router

3. srcディレクトリの中身を削除

$ rm -rf ./src/*

4. index.jsとApp.jsを作成

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

ReactDOM.render(
    <AppRouter />,
    document.getElementById('root')
);
App.js
import React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

function Index(){
    return <h2>Home page is here!</h2>
}

function About(){
    return <h2>This is About page.</h2>
}

function Users(){
    return <h2>This is User page.</h2>
}

function AppRouter(){
    return(
        <Router>
            <div>
                <nav>
                    <ul>
                        <li>
                            <Link to='/'>Home</Link>
                        </li>
                        <li>
                            <Link to='/about/'>About</Link>
                        </li>
                        <li>
                            <Link to='/users/'>Users</Link>
                        </li>
                    </ul>
                </nav>
                <Route path='/' exact component={Index} />
                <Route path='/about/' component={About} />
                <Route path='/users/' component={Users} />
            </div>
        </Router>
    );
}

export default AppRouter;

Happy Hacking :sunglasses: !

参考

https://reacttraining.com/react-router/web/guides/quick-start

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

Vue.js・Nuxt.js ハマりポイントまとめ

Vue.js や Nuxt.js で開発していてハマったポイントを雑多にまとめています。(随時追加予定)

props を子コンポーネントにそのまま流したい

具体的な例をあげると、UI コンポーネントをラップしたコンポーネントを作りたい場合などです。

こういうときは、$attrs を使うとよいです。

https://isoppp.com/note/2018-12-16/what-is-vue-attrs/

computed は型アノテーションが必須

明らかに推論できるような場合でも、型アノテーションが必須です。

記述していない場合はエディタがエラーを表示します。

computed: {
  fullName(): string { // 明らかに string を返すが、アノテーションは必須
    return `${firstName} ${lastName}`;
  }
}

正しく Vue コンポーネントを記述しているのに何かエラーになる場合、これが原因のことが多いです。

リロードした時だけ動かない

具体的な例をあげると、「リロードした時だけ動かない。他のページから遷移してきた場合は動く。」のようなケースです。

これは、SSR と CSR に起因しているケースが多いです。

サーバーサイド(クライアントサイド)でしか実行してはいけない関数を実行してしまっていないか確認しましょう。例えば、サーバーサイドで window オブジェクトにアクセスしているとかです。

テーブル周りでエラーになる

theadtbody を書けば直ることがあります。

Vue では、自動的にこれらを補完するため、SSR と CSR で DOM が一致せず、エラーになります。

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

JavaScriptで正規表現を使って対象文字列が意図した文字列か判定する方法

例えば6桁の半角英数字のみTrueにしたい場合、

const hoge = 'abc123';
const hogeRegExp = new RegExp('^[a-zA-Z0-9]{6}$');
if(hogeRegExp.test(hoge)){
   console.log('true');
}else{
   console.log('false');
}
//trueが表示される
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WSL2のコロコロ変わるIPをMyDNSで何とかする

1.WSL2のIPは起動するごとに変わる気まぐれさん

 Windows10Build18922に搭載されているWSL2はお試しバージョンということだけあって、ネットワークに難があります。その最たるものは、起動するごとにプライベートIPが変化する部分です。これではsshやhttp接続の時に、面倒くさいことこの上ない状態となります。

 IPを固定する方法を試行錯誤してみたのですが、ホストOS側の仮想ブリッジが再作成されてネットワークそのものが変えられてしまうため、小手先でどうにかなる問題ではありませんでした。

 ということで対応策を考えた結果、ドメインと関連付ければ良いという結論に至りました。

2.そうだ、MyDNSを利用しよう

 まずは、MyDNSでアカウントを作成し、ドメインを一つ設定します。ローカルで使うだけなので、無料の中から好きなものを選びましょう。

3.プログラムのインストール

 お察しの通り、まっとうな方法ではプライベートIPを登録することは出来ないので、WSL上でnodeを入れてゴニョゴニョします。

sudo apt install nodejs npm

 wsl2mydnsをnpmからインストールします。これがゴニョゴニョするプログラムです。

sudo npm -g  wsl2mydns

 これで必要なもののインストールは完了です。

4.Windows側でbatを作成

wsl_start2.bat
@echo off
start wsl wsl2mydns MyDNS-ID MyDNS-PASS
pause

 WSL2を使うときに、このbatをWindowから実行すればOKです。IPはMyDNSに通知され、ドメインとプライベートIPが結びつきます。

5.仕組み

 MyDNSに通常の方法でIPを通知すると、自分の使っているネットワークのグローバルIPが設定されてしまいます。ということで、プログラムでWSL2上のeth0のIPを通知するようになっています。

 ソースコードはGitHubに入れてあります。TypeScriptを使いました。

6.困ったこと

 Windowsからビルドしたものをnpmに登録すると、コマンドとして実行させるための以下の行、

#!/usr/bin/env node

ここの改行コードに文句を言われ、コマンドが正常に実行できない状態になりました。おかげで何度もnpmに再アップする羽目になりました。

7.作成に使ったTypeScript

 思い立ったが吉日で、さくっと作りました。MyDNSの操作ライブラリは以前から作ってあったので、DirectIP登録の部分だけ追加するだけで済みました。やはりTypeScriptを使うと、素のJavaScriptでやるよりも、プログラミングが楽です。

 こちらにTypeScriptの入門ネタを作り始めました。時間のあるときに内容を書き足していきたいと思っています。ちなみにそのシステム自体も、フロントエンドとバックエンド両方ともTypeScriptで出来ています。

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

React HooksのみでWebアプリを作りました! ( 参考にどうぞ ! )

この記事について

私が作ったWebアプリをオープンソース化したので、そのWebアプリがどのような形で作られているかを解説した記事です。

オープンソース化した理由としては、これからReact Hooksを導入する人Webアプリを作ってみたい人などの参考になればと思いオープンソース化しました。

作ったもの

画像投稿ができるWebアプリです。

デモページ

デモページ

ソースコード

製作期間

  • 三か月ほど

実装した機能

  • 認証機能
  • 画像投稿機能
  • いいね機能
  • フォロー機能
  • 通知機能

使用したものなど

  • React
  • React-Router
  • dayjs
  • TypeScript
  • Firebase Authentication
  • Firebase FireStore
  • Firebase Storage
  • Firebase Hosting

なぜ作ったのか?

前回の記事でJavaScriptで使えるフレームワークを作成したのですが、記事を投稿した後、中々上手い実装などが浮かばず、行き詰ってしまいました。
この状況から何とか抜け出そうと考えた結果、Webアプリを作ってみようと思い、出来たのが今回のWebアプリです。

解説

ここからは、Webアプリがどのように出来ているかを解説したいと思います。

フォルダー構成について

│
├─node_modules
├─public
└─src
  ├─assets
  | └─icons
  ├─components
  │  ├─atoms
  │  ├─modules
  │  ├─molecules
  │  ├─organisms
  │  ├─pages
  │  └─templates
  ├─logics
  │  ├─actions
  │  ├─reducer
  │  └─util
  |    └─uses
  └─tests

フォルダーのみ表示してます

解説は、srcフォルダー内のみにします。

src/assets

画像などを保存するフォルダーです。

src/components

Reactコンポーネントを記述したファイルを入れるフォルダーです。
src/componentsのフォルダー構成は、Atomic Designに基づいて以下のようにコンポーネントのファイルを分割してます。

components/atoms

一番小さいコンポーネントの単位です。
React Hooksを使って無いコンポーネントのみを入れるようにします。

atomsのコンポーネント例
atomsのコンポーネント例
import React from "react";
import styles from "./LoadingBar.module.scss";

export const LoadingBar: React.FC<{ isLoading: boolean }> = ({ isLoading }) => {
  return isLoading ? <div className={styles.loading_bar} /> : null;
};


components/molecules

atomsの次に小さいコンポーネントの単位です。
こちらも、atomsと同じくReact Hooksを使ってないコンポーネントのみを入れますが、
atomsとの違いは、要素数がatomsより多いコンポーネントになります。( 要素数がだいたい3~4個以上 )

moleculesのコンポーネント例
moleculesのコンポーネント例
import React, { FormEvent } from "react";
import styles from "./SearchInput.module.scss";

interface SearchInputProps extends React.Props<{}> {
  maxWidth?: number;
  placeholder?: string;
  onSubmit: (inputValue: string) => void;
}

export const SearchInput: React.FC<SearchInputProps> = ({
  onSubmit,
  maxWidth,
  placeholder = "キーワードを検索"
}) => {
  const submit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    onSubmit((e.currentTarget.search as HTMLInputElement).value);
  };

  return (
    <form
      className={styles.search__input}
      style={{ maxWidth }}
      onSubmit={submit}
    >
      <div className={styles.search_icon} />

      <input
        type="text"
        name="search"
        autoComplete="off"
        placeholder={placeholder}
      />
    </form>
  );
};


components/organisms

コンポーネントの大きさ関係なくReact Hooksを使っていれば問答無用でこちらのフォルダーに入れられます。

organismsのコンポーネント例
organismsのコンポーネント例
import React from "react";
import { useUsers } from "../../../logics/util/uses/users";
import { useUtil } from "../../../logics/util/uses/util";
import { User } from "../../../types";
import { Button } from "../../atoms/Button";

interface FollowButtonProps extends React.Props<{}> {
  uid: User["id"];
  className?: string;
}

export const FollowButton: React.FC<FollowButtonProps> = React.memo(
  ({ uid, className }) => {
    const { alerts } = useUtil();
    const { isFollowee, current_user, followUser, unfollowUser } = useUsers();
    const isFollowed = isFollowee(uid);
    const currentUserId = current_user ? current_user.id : "";
    const onClick = () => {
      if (uid && currentUserId && currentUserId !== uid) {
        if (isFollowed) {
          unfollowUser(uid);
        } else {
          followUser(uid);
        }
      } else if (currentUserId) {
        alerts.follow_user.warn();
      } else {
        alerts.required_login.warn();
      }
    };

    return (
      <Button
        disabled={!uid}
        className={className}
        color={isFollowed ? "red" : "blue"}
        size="small"
        onClick={onClick}
      >
        {isFollowed ? "フォローを解除" : "フォローする"}
      </Button>
    );
  }
);


components/pages

一つのページを構成するコンポーネントを入れるフォルダーです。

pagesのコンポーネント例
pagesのコンポーネント例
import React from "react";
import { RouteComponentProps } from "react-router";
import { Board } from "../../atoms/Board";
import { BoardItem, NotifyBoardItem } from "../../atoms/BoardItem";
import { DashboardTemplate } from "../../templates/DashboardTemplate";
import styles from "./Dashboard.module.scss";
import { useDashboard } from "./use";

export const Dashboard: React.FC<RouteComponentProps> = () => {
  const { notifies, finished, getNotifies } = useDashboard();

  return (
    <DashboardTemplate>
      <main className={styles.dashboard}>
        <Board headerLabel="通知一覧" minWidth={500}>
          {notifies.map(notify => (
            <NotifyBoardItem key={`notify-item-${notify.id}`} notify={notify} />
          ))}

          {finished ? null : (
            <BoardItem
              className={styles.link}
              label="さらに10件の通知を表示"
              onClick={getNotifies}
            />
          )}
        </Board>
      </main>
    </DashboardTemplate>
  );
};


components/templates

pagesでの共通部分を抜き出して、再利用できるようにしたコンポーネントを入れるフォルダーです。

templatesのコンポーネント例
templatesのコンポーネント例
import React, { useEffect } from "react";
import useReactRouter from "use-react-router";
import { GlobalHeader } from "../../organisms/GlobalHeader";
import styles from "./PageTemplate.module.scss";

interface PageTemplateProps extends React.Props<{}> {
  title?: string;
  background?: string;
  className?: string;
}

export const PageTemplate: React.FC<PageTemplateProps> = ({
  children,
  className,
  background
}) => {
  const { location } = useReactRouter();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [location.pathname]);

  return (
    <div className={styles.page} style={{ background }}>
      <GlobalHeader />

      <div className={`${styles.body} ${className || ""}`}>{children}</div>

      <div className={styles.footer}>
        <p>©︎ 2019 uttk</p>
      </div>
    </div>
  );
};


components/modules

上記のコンポーネントの分割に当てはまらないコンポーネントを入れるフォルダーです。

modulesのコンポーネント例
modulesのコンポーネント例
import React, { useEffect, useState } from "react";
import { IconButton } from "../../atoms/Button";
import styles from "./Alerts.module.scss";

/**
 * 長いので省略
 */

interface AlertProps extends React.Props<{}> {}

export const Alert: React.FC<AlertProps> = React.memo(() => {
  const [alerts, setAlert] = useState<AlertElement[]>([]);
  const len = alerts.length;

  AlertsMg.elements = alerts;
  AlertsMg.dispatch = (elements: AlertElement[]) => {
    setAlert(elements);
  };

  return (
    <div>
      {alerts.map((alert, i) => (
        <AlertBar
          key={alert.id}
          top={(len - i) * 64}
          alert={alert}
          displayTime={3000}
        />
      ))}
    </div>
  );
});

interface AlertBarProps extends React.Props<{}> {
  top: number;
  alert: AlertElement;
  displayTime: number;
}

export const AlertBar: React.FC<AlertBarProps> = React.memo(
  ({ top, alert, displayTime }) => {
    const [style, setStyle] = useState(alert.style);

    useEffect(() => {
      let timeoutId = setTimeout(() => {
        setStyle(`${style} ${styles.alert_show}`);

        timeoutId = setTimeout(() => {
          setStyle(alert.style);
          timeoutId = setTimeout(alert.onClose, 300);
        }, displayTime);
      }, 0);

      return () => clearTimeout(timeoutId);
    }, []);

    return (
      <li className={style} style={{ top }}>
        <p>{alert.message}</p>
        <IconButton
          size={16}
          icon="close_white"
          color="transparent"
          className={styles.close_btn}
          onClick={alert.onClose}
        />
      </li>
    );
  }
);


src/logics

ロジック部分の処理を書いたファイルを格納するフォルダーです。

logics/actions

通信処理などをする関数を定義したファイルを入れるフォルダーです。

logics/reducer

後述するuseClutchで使うreducerを定義したファイルを入れるフォルダーです。

logics/util

actionsreducerには当てはまらないものをここに入れます。

logics/util/uses

このフォルダーには、コンポーネントで使うReact HooksカスタムHooksを定義したファイルを入れます。

コンポーネントについて

コンポーネントは、以下のフォルダー構成になります。

コンポーネントのフォルダー構成
/ComponentName
├─ index.tsx                  <- JSXを書くファイル
├─ ComponentName.moduel.scss  <- index.tsxのスタイルを書くファイル (フォルダーと同じ名前にする)
└─ use.ts                     <- index.tsxで使うコールバックや変数を定義するファイル

index.tsxに描画部分を書き、ロジックなどはuse.tsに書くことによって、
描画部分はより描画に専念して書けますし、ロジックも同じように書けます。
修正する時も、色々とやりやすかったりします。
今回のWebアプリではpagesのみこのようにしていますが、後々は全部のコンポーネントをこの構成したいです。

ComponentName.module.scssは、index.tsx

index.tsx
import styles from "./ComponentName.module.scss"

のようにするためです。

State管理について

このWebアプリでは、Reactは使っていますが、Redux使って無いので工夫する必要があります。

useClutch

このWebアプリでは、useClutchというカスタムHooksを作成しました。
以下が、データフローになります。

data-flow.png

useClutchがやっていることは単純で、Promiseを返すreducerを実行して、awaitして結果を受け取ると、
内部でsetStateをして描画を更新します。
こうすることで、reducer内で非同期処理が出来て処理を一か所にまとめることができます。

clutchの使用例
type Action = { type: "increment" } | { type: "decrement" };

interface StoreType {
  counter: number;
}

const state : StoreType  = {
  counter: 0
};

const sleep = (t:number) => new Promise(r => setTimeout(r,t,t));

const reducer = async (state:number, action:Action) : Promise<StoreType> => {
  switch(action.type){
    case "increment":
      await sleep(5000);
      return state + 1;

    case "decrement":
      await sleep(5000);
      return state - 1;

    default:
      return state;
  }
}

const App : React.FC = () => {
  const clutch = useClutch(reducer, store);
  const increment = () => clutch.dispatch("increment", { type: "increment" }).catch(console.error);
  const decrement = () => clutch.dispatch("decrement", { type: "decrement" }).catch(console.error);

  // 複数のactionを繋げることもできます。
 // 描画更新は、すべてのactionが終了したときに発生します。
  const add = () => clutch.pipe(
    "test",
    state => ({ type: "increment" }),
    state => state.counter > 10 ? null : ({ type: "increment" }) // 前のactionの結果を受け取って10以下ならさらにincrementする
  ).catch(console.error)

  return (
    <div>
      <p>カウント : {clutch.counter}<p>
      <button onClick={increment}>カウントアップ</button>
      <button onClick={decrement}>カウントアップ</button>
      <button onClick={add}>1足して10以下ならさらに1足す</button>
    </div>
  );
}

デザインについて

デザインは勉強の意味も込めて自分でやりましたが、ダサいので参考にはならないと思います。

しかしあれですね、デザイナーさんは凄いですね!
私もデザインについてもっと勉強しないといけないと感じました。( 特にAdobe。。。 )

React HooksとTypeScriptは相性がいい!

React HooksTypeScriptを使って開発しましたが、これが予想以上に良かったです!
React Hooksで、コンポーネントの記述量を減らして、その減らした分をTypeScriptに使うことで、
従来の記述量と同じくらいか少ないのに、型安全で、より設計しやすいコーディングをすることができました。
これから開発する際には、TypeScriptを使ったほうがよさそうですね。

また、React Hooksは柔軟に組み合わせることができるので、複雑な処理も関数に閉じ込めることができます。
上記で紹介したuseClutchカスタムHooksで実装してます。

useClutchのソースコード
useClutch.ts
import { useRef, useState } from "react";

type Reducer<S, A> = (preState: S, action: A) => Promise<S>;

type GetStoreType<R extends Reducer<any, any>> = R extends Reducer<infer S, any>
  ? S
  : any;

type GetActionType<R extends Reducer<any, any>> = R extends Reducer<
  any,
  infer A
>
  ? A
  : any;

type ActionCreator<S, A> = (preState: S) => A | null;

type RequestStatus = "start" | "success" | "cancel" | "error";

type ListenRequestCallback = (request: string, status: RequestStatus) => void;

type CancelCallback = () => void;

export interface Clutch<StoreType, ActionType> {
  state: StoreType;
  request: <T>(
    req: string,
    promiseCreator: () => Promise<T>
  ) => Promise<T | null>;
  cancelRequest: (request: string) => boolean;
  listenRequest: (cb: ListenRequestCallback) => () => void;
  isLoading: (request?: string) => boolean;
  pipe: (
    request: string,
    ...funcs: Array<ActionCreator<StoreType, ActionType>>
  ) => Promise<void>;
  dispatch: (request: string, action: ActionType) => Promise<void>;
}

export const useClutch = <R extends Reducer<any, any>>(
  asyncReducer: R,
  initializeState: GetStoreType<R>
): Clutch<GetStoreType<R>, GetActionType<R>> => {
  type StoreType = GetStoreType<R>;
  type ActionType = GetActionType<R>;

  // 描画を更新するためのState
  const [pureState, setState] = useState<StoreType>(initializeState);

  const {
    progressCancel, 
    progressPromise, 
    listenCallbacks,
    notifyRequest,
    resolveAsync,
    updateState, 
    clutch
  } = useRef({
    // 実行中のPromise処理をキャンセルするコールバックを保持するMap
    progressCancel: new Map<string, CancelCallback>(),

    // 実行中のPromiseインスタンスを保持するMap
    progressPromise: new Map<string, Promise<any>>(),

    // 非同期処理状況をlistenする関数を保持するSet
    listenCallbacks: new Set<ListenRequestCallback>(),

    // 非同期処理の状態をlistenCallbackに伝える関数
    notifyRequest: (request: string, status: RequestStatus) => {
      listenCallbacks.forEach(cb => cb(request, status));
    },

    // 引数に渡されたPromiseを実行しawaitして結果を返す
    resolveAsync: <T>(
      request: string,
      cb: () => Promise<T>
    ): Promise<T | null> => {
      const promise = progressPromise.get(request);

      if (promise) {
        return promise as Promise<T | null>;
      }

      notifyRequest(request, "start");

      const cancelCallback = new Promise<"cancel">(re => {
        progressCancel.set(request, () => re("cancel"));
      });

      const progress = Promise.race([cb(), cancelCallback])
        .then(result => {
          notifyRequest(request, result === "cancel" ? "cancel" : "success");

          progressCancel.delete(request);
          progressPromise.delete(request);

          return result === "cancel" ? null : result;
        })
        .catch(e => {
          notifyRequest(request, "error");

          progressCancel.delete(request);
          progressPromise.delete(request);

          return Promise.reject(e);
        });

      progressPromise.set(request, progress);

      return progress;
    },

    // stateを更新する関数
    updateState: async (
      request: string,
      oldState: StoreType,
      promiseCreator: (oldState: StoreType) => Promise<StoreType>
    ) => {
      const newState = await resolveAsync(request, () =>
        promiseCreator(oldState)
      );
      const updated: Partial<StoreType> = {};

      if (newState) {
        for (const key in oldState) {
          if (oldState[key] !== newState[key]) {
            updated[key] = newState[key];
          }
        }

        if (Object.keys(updated).length > 0) {
          clutch.state = { ...clutch.state, ...updated };
          setState(clutch.state);
        }
      }
    },

    // Propsに渡されるclutchオブジェクト
    clutch: {
      state: pureState,

      // 処理が実行中かのフラグを返す関数
      isLoading: (request?: string): boolean => {
        if (request) {
          return progressPromise.has(request);
        }

        return !!progressPromise.size;
      },

      // reducerを通さないPromiseを監視するようにする
      request: <T>(
        req: string,
        promiseCreator: () => Promise<T>
      ): Promise<T | null> => {
        return resolveAsync<T>(req, promiseCreator);
      },

      // 実行中のPromiseを中断する
      cancelRequest: (request: string): boolean => {
        const cancel = progressCancel.get(request);

        if (cancel) {
          cancel();
          return true;
        } else {
          return false;
        }
      },

      // listenCallbackを設定する関数
      listenRequest: (cb: ListenRequestCallback): (() => void) => {
        listenCallbacks.add(cb);
        return () => listenCallbacks.delete(cb);
      },

      // 引数に渡されたActionCreatorの順番で、reducerを実行する
      pipe: async (
        request: string,
        ...funcs: Array<ActionCreator<StoreType, ActionType>>
      ): Promise<void> => {
        const promiseCreator = async (oldState: StoreType) => {
          for (const fn of funcs) {
            const action = fn(oldState);

            if (action) {
              oldState = await asyncReducer(oldState, action);
            }
          }

          return oldState;
        };

        await updateState(request, { ...clutch.state }, promiseCreator);
      },

      // action&payloadをreducerに渡して実行する
      dispatch: async (request: string, action: ActionType): Promise<void> => {
        const promiseCreator = (oldState: StoreType) => {
          return asyncReducer(oldState, action);
        };

        await updateState(request, { ...clutch.state }, promiseCreator);
      }
    }
  }).current;

  return clutch;
};


使った技術の感想

React & React Hooks

Reactは以前から使っていたので色々とやり易かったですが、React Hooksによってより使いやすくなりました。
例えば、上で説明したpagesのコンポーネントのindex.tsxの中身は、以下のようになりました。

pages/Home/index.tsx
import React from "react";
import { RouteComponentProps } from "react-router";
import { Button } from "../../atoms/Button";
import { Icon } from "../../atoms/Icon";
import { PageTemplate } from "../../templates/PageTemplate";
import styles from "./Home.module.scss";
import { useHome } from "./use";

export const Home: React.FC<RouteComponentProps> = () => {
  const { isLogin, isCheked, login, goToTrend } = useHome();

  return (
    <PageTemplate className={styles.page}>
      <div className={styles.home}>
        <div className={styles.title}>
          <div className={styles.wrapper}>
            <h1 className={styles.title_label}>NIJINOWA</h1>

            <div className={styles.wrapper}>
              <p>
                <big>NIJINOWA</big> は二次創作を投稿できるサービスです
              </p>
            </div>

            <div className={styles.container}>
              <div className={styles.wrapper}>
                <Button color="blue" onClick={goToTrend}>
                  トレンドを見る
                </Button>
              </div>

              <div className={styles.wrapper}>
                <Button disabled={!isCheked} onClick={login}>
                  {isLogin
                    ? "ダッシュボードに移動"
                    : "Googleアカウントでログイン"}
                </Button>
              </div>

              <div className={styles.wrapper}>
                <a
                  href="https://twitter.com/uttk8128"
                  target="_blank"
                  className={styles.twitter_btn}
                >
                  <Icon icon="twitter_white" size={32} />
                  製作者のTwitter
                </a>
              </div>
            </div>
          </div>
        </div>
      </div>
    </PageTemplate>
  );
};

見てわかるように、ロジックの処理が書かれていないことが分かります。
これは、useHome()というカスタムHooksにロジック部分を書いているためです。
以下がuseHome()の実際のソースコードになります

pages/Home/use.ts
import { useEffect, useState } from "react";
import useReactRouter from "use-react-router";
import { useAuth } from "../../../logics/util/uses/auth";

export const useHome = () => {
  const { history } = useReactRouter();
  const { isLogin, isCheked, loginCheck, loginWithGoogle } = useAuth();
  const [isPush, setPush] = useState(false);
  const login = () => {
    if (isLogin) {
      history.push("/dashboard");
    } else {
      setPush(true);
      loginWithGoogle();
    }
  };

  useEffect(() => {
    if (!isCheked) {
      loginCheck();
    } else if (isLogin && isPush) {
      history.push("/dashboard");
    }
  }, [history, isCheked, isPush, isLogin]);

  return {
    isLogin,
    isCheked,
    login,
    goToTrend: () => history.push("/trends")
  };
};

こうやって、ViewLogicを分けて書けるようになったのも、React Hooksによるところが大きいかと思います。
また、ファイルを分けているので関心の分離といった観点からもいいのではないでしょうか。

TypeScript

TypeScriptを使うことによって、コンポーネントやカスタムHooksに型情報を追加し、より扱い易いものを作ることができます。
また、上記でも言ったように、本当にReact HooksTypeScriptの相性は良く、
TypeScriptの記述量の多さに嫌気が差す方もいるかとは思いますが、React Hooksがコンポーネントの記述量を減らしてくれているので、
実質今までの記述量とあまり変わらないです。むしろ少なくなっている方です。
さらに、同じ記述量でもTypeScriptの方が型安全ですし、VSCodeなどのTypeScriptをサポートしている
エディタを使えば入力補完も効いて、素晴らしい開発者体験が得られます。
React Hooksを導入する・しているなら、TypeScriptも導入することをお勧めします。

Firebase

Firebaseは、小さいサービスなどを作るのに本当に便利です。
今回のWebアプリでは、Auth,FireStore,Storage,Hostingを使いました。
今回の開発では、特にFireStoreのデータ構造とreducerによる処理の分割が、相性がとてもよかったです。

例えば、userドキュメントfollowerサブコレクションがあったとき、followerreducerを作れば、
それだけで、followerの取得や変更などがアプリに追加できます。
これは、後からデータを追加する際にも同じような感じでアプリを拡張できます。

つまり何が言いたいのかというと、FireStoreコレクションごとにreducerを作ることにより、
コレクションを新しく追加したとき、新しくreducerを作って、Webアプリに機能を追加し、
編集の際も、そのreducerのみを変更すればいいのです。

また、ログイン機能を簡単に入れれたり、サイトを簡単にデプロイ出来たりとFirebaseは凄さに恐縮してしまいます。

作ってみた感想

実は今回Webアプリ製作はフレームワークの行き詰まりの解消のためでもあったので、
普通に作るのではなく、二つの制限をかけて作りました。

制限
1. React.jsで作るが、Classコンポーネントは使わない
2. なるべく、他のnpmモジュールに頼らず自分で作る

上記の制限の中で作ることにより、より設計やデータの流れについて考えるようになり色々とフレームワークのアイディアが見えてきたので、Webアプリを作ってよかったと思いました。

あと、やっぱりモノづくりは楽しい!

後書き

今回のWebアプリを作るきっかけになった、前回の記事で紹介したフレームワークですが、
前回の記事いいね!などしてくれた方たちには大変申し訳ないですが、
このまま開発してもよくならないように感じたので、開発するのは終わりにしたいと思います。
しかし、また別のフレームワークを企画しています!

最後まで、読んでいただきありがとうございます。
何か気になることなどがあれば、お気軽にコメントください!
あと、最近Twitter始めたのでそちらでも大丈夫です!
それではまた?

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

Reactコンポーネントのライフサイクル

React で使われているコンポーネントは、プロパティやステートの変化を受けて状態を変化します。最終的にぺージから破棄されるまでの間をコンポーネントのライフサイクルと言われます。

Reactでは、ライフサイクルの辺ごと、様々なメソッドが呼びだれています。
これにより、コンポーネントの表示や破棄の各タイミングで、独自の振る舞いを実装することができます。

以下がライフサイクル時に呼ばれるメソッド群です。

componentWillMount : コンポーネントの描画の直前に呼びばれる。
componentDidMount :コンポーネントの描画の直後に呼ばれる。
componentWillUnmount :コンポーネントの解放のタイミングで呼ばれる。
componentWillReceiveProps:プロパティを受け取る直前に呼ばれる。
shouldComponentUpdate コンポーネントの更新の可否を決定する
componentWillUpdate コンポーネントの更新の直前に呼ばれる。
componentDidUpdate コンポーネントの更新の直後に呼ばれる。

このうちは、componentWillMount :は、コンストラクタで代用可能であり、非推奨とされているらしいです。
コンポーネントのライフサイクルは大きく2つに分類され例外の処理をハンドリングすることもできます。実行例は追々。
コンポーネントのMount(生成および破棄)
コンポーネントのUpdate(更新)

コンポーネントのErrorHandling (例外発生)

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

JS 【JavaScript初心者向け】Generative Art With JavaScript 受講

はじめに

表題通り受講予定
前日の知識詰め込み用でもある。
1 https://gist.github.com/x-hack-git/70c9c99ee33b8c5d1495b409d01849b9
2 https://x-hack.connpass.com/event/136668/

事前準備

参考noteより抜粋

https://note.mu/kiwatchi1991/n/n9be787993ab0
https://note.mu/pocho_chonsuke/n/n6ab154f59100


API

線を描くと宣言します。: ctx.beginPath();
書き始める場所を指定します。: ctx.moveTo(20, 20);
引きたい線の終点を指定します。: ctx.lineTo(120, 20);
引きたい線の終点を指定します。: ctx.lineTo(120, 120);
引きたい線の終点を指定します。: ctx.lineTo(20, 120);
描いた線を閉じます。: ctx.closePath();
できた図形を線で描画します。: ctx.stroke();

ctx.fillStyle = "#FF0000";
ctx.fillRect(20, 20, 150, 100); 

getContextメソッドに渡せる引数は2dのみのようなので、getContext("2d")


図形描写色々

https://ics.media/tutorial-createjs/shape_draw/


関数コーナー

・getRandomNumber (// 0-255のランダムな数値を取得する)
・getRandomColor (// ランダムな色を返す)
・Math.random()は、0~1の数値をランダムに返す関数

・Math.round()で四捨五入

https://qiita.com/SPdotTS/items/8e427d9cbbbdc7560e8b
Math.Round 関数は単純な四捨五入ではない

一般的な四捨五入にしたい場合は、MidpointRounding.AwayFromZero を指定します。

続く

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