20210108のJavaScriptに関する記事は21件です。

Vue+Vuex+Vue router+AxiosによるAPIエラーハンドリングとページ遷移の実装パターン

この記事について

  • Vueコンポーネント内やVuexのAction内などあちこちでAxiosによるAPIリクエストをしていると、エラーハンドリングを個別に記述しなければならなくなり、DRYではなくなってしまう
  • APIリクエストでエラーが発生した場合に特定のページに遷移させたいのに、Vueインスタンスの外からは$router.push()が呼べない(例えばVuex内から)

この記事では、上記のよくある問題をなるべくDRYかつシンプルに解決した方法を述べます。

環境

  • Vue2系(Options API)
  • Vuex
  • Vue Router
  • JavaScript

APIリクエストを1箇所で管理する

参考:https://kntmr.hatenablog.com/entry/2018/02/28/200112

各所でAxiosをそのまま用いて非同期通信をすると、それぞれでエラーをcatchしなければなりません。それは辛いので、別にモジュールを用意してAxiosをラップした関数を定義し、その中でエラーハンドリングを行います。基本的に参考サイトのままですが、関数の部分だけ、get, postなどメソッドで分けずに、configで指定するようにしています。

/src/api/index.js
import axios from 'axios'

const debug = process.env.NODE_ENV !== 'production'

const onSuccess = (resp) => {
  if (debug) {
    console.log(' << ' + JSON.stringify(resp.data))
  }
  return Promise.resolve(resp.data)
}
const onError = () => {
  throw new Error('API error.')
}

// リクエストメソッド問わず同じ関数を用いる
const api = (config) => {
    if (debug) {
        console.log(`${config.method} ${config.url} >> ${config.params}`);
    }
    return axios(config)
        .then(onSuccess)
        .catch(onError);
};

export default api;

コンポーネントでaxios()を使っていた箇所をapi()に置き換えることで、エラーハンドリングの記述が1箇所・1回で済みます、イッツDRY。

エラー時にVue Routerでページ遷移

上記まででエラーハンドリングは1箇所で管理することが出来ました。

さてその際、APIアクセスが失敗した時に、Vue Routerの特定にページに遷移させたい時があります。なので、上記の/api/index.jsonError()で、$router.push()したい訳です。

ですがコンポーネントの外(=Vueインスタンスの外)から$router.push()は使うことが出来ません。なのでちょっと工夫します。

Vueインスタンスのexport

Vueプロジェクトのエントリーポイントである/src/main.jsは、Vue CLIでプロジェクトを生成した際は下記のようにVueインスタンスを生成・HTMLを描画しています。

/src/main.js
// 略

new Vue({
  render: h => h(App),
}).$mount('#app')

ここで、Vueインスタンスをエクスポートします。

/src/main.js
// 略

const vm = new Vue({
  render: h => h(App),
}).$mount('#app')

export default vm;

ここでexportしているvmは、Vueインスタンスのシングルトンです。どこでimportしても常に同一のインスタンスを参照します。

参考:https://qiita.com/NeGI1009/items/f8b17d856a4b15b1ecbc

なので/src/main.jsをimportすればどこからでもVueインスタンスにアクセス出来る訳です。

とはいえ、様々な場所で無秩序にVueインスタンスを操作するのは避けるべきと考えます。

apiモジュールでVueインスタンスにアクセス

/src/api/index.js
import axios from 'axios'

// Vueインスタンス
import vm from '../main';

//略

const onError = () => {
  //エラー時にルートへ遷移
  vm.$router.push('/')
  throw new Error('API error.')
}

//略

この時、Vuex APIにもRouterと同様、vm.$storeでアクセス出来ます。

以上でAPIリクエスト時のエラーハンドリングの集中管理およびエラー時のページ遷移が実現しました。あちこちに散逸しがちなエラーハンドリングに対する悪くない実装パターンではないでしょうか。

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

Vue+Vue router+AxiosによるAPIエラーハンドリングとページ遷移の実装パターン

この記事について

  • Vueコンポーネント内やVuexのAction内などあちこちでAxiosによるAPIリクエストをしていると、エラーハンドリングを個別に記述しなければならなくなり、DRYではなくなってしまう
  • APIリクエストでエラーが発生した場合に特定のページに遷移させたいのに、Vueインスタンスの外からは$router.push()が呼べない

この記事では、上記のよくある問題をなるべくDRYかつシンプルに解決した方法を述べます。

検証環境

  • Vue2系(Options API、でもcomposition APIでも動くと思う)
  • Vuex(今回の実装には関係ないけれど、Vuexを組み合わせる事も出来る)
  • Vue Router
  • JavaScript

APIリクエストを1箇所で管理する

参考:https://kntmr.hatenablog.com/entry/2018/02/28/200112

各所でAxiosをそのまま用いて非同期通信をすると、それぞれでエラーをcatchしなければなりません。それは辛いので、別にモジュールを用意してAxiosをラップした関数を定義し、その中でエラーハンドリングを行います。基本的に参考サイトのままですが、関数の部分だけ、get, postなどメソッドで分けずに、configで指定するようにしています。

/src/api/index.js
import axios from 'axios'

const debug = process.env.NODE_ENV !== 'production'

const onSuccess = (resp) => {
  if (debug) {
    console.log(' << ' + JSON.stringify(resp.data))
  }
  return Promise.resolve(resp.data)
}
const onError = () => {
  throw new Error('API error.')
}

// リクエストメソッド問わず同じ関数を用いる
const api = (config) => {
    if (debug) {
        console.log(`${config.method} ${config.url} >> ${config.params}`);
    }
    return axios(config)
        .then(onSuccess)
        .catch(onError);
};

export default api;

コンポーネントやVuexでaxios()を使っていた箇所をapi()に置き換えることで、エラーハンドリングの記述が1箇所・1回で済みます、イッツDRY。

エラー時にVue Routerでページ遷移

上記まででエラーハンドリングは1箇所で管理することが出来ました。

さてその際、APIアクセスが失敗した時に、Vue Routerの特定にページに遷移させたい時があります。なので、上記の/src/api/index.jsonError()で、$router.push()したい訳です。

ですがコンポーネントの外(=Vueインスタンスの外)から$router.push()は使うことが出来ません。なのでちょっと工夫します。

Vueインスタンスのexport

Vueプロジェクトのエントリーポイントである/src/main.jsは、Vue CLIでプロジェクトを生成した際は下記のようにVueインスタンスを生成・HTMLを描画しています。

/src/main.js
// 略

new Vue({
  render: h => h(App),
}).$mount('#app')

ここで、Vueインスタンスをエクスポートします。

/src/main.js
// 略

const vm = new Vue({
  render: h => h(App),
}).$mount('#app')

export default vm;

ここでexportしているvmは、Vueインスタンスのシングルトンです。どこでimportしても常に同一のインスタンスを参照します。

参考:https://qiita.com/NeGI1009/items/f8b17d856a4b15b1ecbc

なので/src/main.jsをimportすればどこからでもVueインスタンスにアクセス出来る訳です。

とはいえ、様々な場所で無秩序にVueインスタンスを操作するのは避けるべきと考えます。

apiモジュールでVueインスタンスにアクセス

/src/api/index.js
import axios from 'axios'

// Vueインスタンス
import vm from '../main';

//略

const onError = () => {
  //エラー時にルートへ遷移
  vm.$router.push('/')
  throw new Error('API error.')
}

//略

この時、Vuex APIにもRouterと同様、vm.$storeでアクセス出来ます。つまりVuexに保存した変数を取得出来るし(getter)、setterやactionを発火させる事も出来ます。

以上でAPIリクエスト時のエラーハンドリングの集中管理およびエラー時のページ遷移が実現しました。あちこちに散逸しがちなエラーハンドリングに対する悪くない実装パターンではないでしょうか。

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

【初心者でもわかる】nth-of-typeはクラス名基準でx番目指定できない・・・からjsでなんとかする方法

どうも7noteです。初心者が陥りやすい、nth-of-typeの罠。

「x番目の要素にだけCSSを適応」

このようにある特定の要素にだけCSSを適応したい時によく使われるのが

・nth-child()
・nth-of-type()

ですが、今回はこのnth-of-type()についての罠の話です。

「クラス名(.hogehoge)のx番目」という指定はできない!

うまくいかない例

index.html
<div class="asa">おはよう</div>
<div class="asa">おはよう</div>
<div class="yoru">おやすみ</div>
<div class="yoru">おやすみ</div>
<div class="yoru">おやすみ</div>
style.css
.yoru:nth-of-type(2) {
  color: red;
}

イメージでは、「クラス[yoru]がついているの2つ目が赤色になる!」と思いますが、実際にはどれも色は変わりません。

うまくいかなかった結果

おはよう
おはよう
おやすみ
おやすみ
おやすみ

思った通りにいかない理由

そもそも、、、

「クラス名を基準にしてx番目の指定」はできない!!!

訂正→「同一要素(div)のx番目が特定のクラス(.yoru)の場合」にしか効かない

意外と気付きにくいものです。
私もずっと上手くいかなくて調べてもnth-childとnth-of-typeの比較記事しかでなくて原因の調査に時間がかかりました。。。

追記:コメントでnth-of-type()について更に詳しく教えていただきましたのでそちらもご覧ください。↓↓↓

つまりは、

「nth-of-type()は、クラス名ではなく要素そのものを基準に何番目かを計算して動いている」

ということになります。

nth-of-typeの使い方例

index.html
<div>おはよう</div>
<div>おはよう</div>
<p>おやすみ</p>
<p>おやすみ</p>
<p>おやすみ</p>
style.css
p:nth-of-type(2) {
  color: red;
}

結果

おはよう
おはよう
おやすみ
おやすみ
おやすみ

じゃあクラス名基準でx番目の要素にCSSを当てるには?

javascriptのeqを使ってx番目の要素を取得することができます。

クラス名基準でx番目の要素に任意のクラス(.hoge)を付与して、そのクラス専用のCSSを予めCSSで用意する方法が良いかなと思います。

※eqは0からカウントが始まるので、「2番目の要素」にしたいときは「eq(1)」と書かなければなりません!

style.css
/* .hogeのクラスがついているものにだけ追加のCSSを用意 */
.hoge {
  color: red;
}
script.js
$('.yoru:eq(1)').addClass('hoge');
//もしくは
$('.yoru').eq(1).addClass('hoge');
//もしくは
$($('.yoru').get(1)).addClass('hoge');

まとめ

実はsafariにのみ、ofフィルターと呼ばれるようなものがあり特定の条件を満たす要素のみカウントさせることができるようです。
ただsafariだけなので、実用的ではないので現状クラス名基準でx番目を指定するのであればjavascriptを使うのが一番最適な方法です。

クラス名を沢山つけすぎてもややこしくなる場合があり開発が困難になるのであれば、div要素を増やすなどでしっかりと住み分けできるものは住み分けさせる方が一番理想的な形かもしれませんね。

おそまつ!

~ Qiitaで毎日投稿中!! ~
【初心者向け】WEB制作のちょいテク詰め合わせ

参考
https://pisuke-code.com/css-nth-child-class-not-working/
https://teratail.com/questions/9634

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

【JavaScript】Babelとは何か

プログラミング勉強日記

2021年1月8日
TwitterでBabelの存在を知ったので、Babelとはいったい何なのか気になり調べてみた。

Babelとは

 Babel(読み方:「バベル」)は、次の世代のJavaScriptの標準機能をブラウザのサポートを待たずに使えるようにするNode.js製のツールである。次の世代の標準機能を使って書かれたコードを、それらの機能をサポートしないブラウザでも動くコードに変換する。
 簡単に言うと、JavaScriptのコードを新しい書き方から古い書き方に変換するツールである。具体的には、JavaScriptの言語仕様であるES2015以上の仕様のJavaScriptで記述すると、Internet Explorer11といった古いブラウザでは動作しない。そこで、Babelを使ってES2015・ES2016といった仕様で記述したJavaScriptファイルを互換性のあるEXMAScript5に変換する。

環境構築

 Babelの公式サイトで変換の確認ができる。

1. インストール

 package.jsonがある場所で以下のコマンドを実行する。babel-cliはbabelをcliで実行するための本体モジュールで、babel-preset-envはES6の内容をES5に変換する情報のようなもの。

npm install —save–dev babel–cli

npm install –save-dev babel-preset-env

2. .babelrcファイルの作成

 babel-preset-envを使用するための設定として、.babelrcファイルを作成して下記の内容を記述する。

.babelrc
{
"presets": ["env"]
}

3. 実行

 下記コマンドで実行する。 

./node_modules/.bin/babel sample.js

参考文献

最新版で学ぶwebpack 5入門 Babel 7でES2020環境の構築
Babelとは
BabelでモダンなJavaScript開発環境を作る方法を現役エンジニアが解説【初心者向け】

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

JavaScriptのテストフレームワークJasmine

Jasmineとは?

  • JavaScriptのテストフレームワーク
  • RailsでいうRSpecのようなもの
  • 簡単にテストを記述することができます

そもそも何?

  • JavaScriptのフレームワークテスト

何のために?

  • JavaScriptのテストコードを書くために

メリットは?

  • 簡単にJavaScriptのテストを書ける

Jasmineの基本的な使い方

  • Jasmineを利用したテストスイートは、 describe という関数から始まります。 describe には引数にテストのタイトル(文字列)と実際に実行するコードブロック(関数)を渡すことができます。

導入方法

参考にした記事(いつもありがとうございます。)

JavaScriptのテストフレームワークJasmineでできる基本的なこと

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

setTimeout(func(), delay) とsetTimeout(() => func(), delay) の違い

つまづいたのでメモ

これの違い

setTimeout(func(), delay)
setTimeout(() => func(), delay)

説明

const log = () => console.log('aaa')
setTimeout(log(), 5000) // => 5sまたずに、 aaa 出力

ブラウザが左から解釈していくとします。
第一引数のlog()つまりconnsole.logを実行して、5秒後に tsでいう void、つまり何も返さずに実行されて終わります。

const log = () => console.log('aaa')
setTimeout(() => log(), 5000); // => 5s待って、 aaa 出力

ブラウザが左から解釈していくとします。
第一引数の() => log()つまりconnsole.logを実行することを定義された無名関数がセットされ、その関数が5秒後に実行されます。

参考

WindowOrWorkerGlobalScope.setTimeout() - Web API | MDN

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

isTypedArrayの実装をいくつか

JavaScriptでTypedArrayかどうかを判定する方法をまとめました。
意外とどうしたらいいのかわからなくて困りましたからね。

1. 正規表現

正規表現を使う方法です。
babelのランタイム関数 _unsupportedIterableToArray でも似たような正規表現が使われていたりします。1
この方法は今後この正規表現でカバーできないTypedArrayが追加された場合、更新する必要があります。
流石にもうない気がしますが。

また、Object.prototype.toStringと正規表現に依存しているので、@@toStringTagに正規表現にマッチする文字列が入っていたら誤判定します。

const toString = Object.prototype.toString;
const regexp = /^(Big)?(Uin|In|Floa)t(8|16|32|64)(Clamped)?Array$/;
const isTypedArray = (value) => (
    regexp.test(toString.call(value))
);

// 誤判定するケース
isTypedArray({
    [Symbol.toStringTag]: "BigFloat8ClampedArray"
}); // > true

2. %TypedArray%

全てのTypedArrayは%TypedArray%を継承しているので、%TypedArray%のインスタンスかを調べる方法です。
有効でない%TypedArray%のインスタンスもtrueなんですが、%TypedArray%はそもそもエラーで呼べないのでObject.create(%TypedArray%.prototype)でないと作れません。このくらい許しましょう。

const $TypedArray$ = Object.getPrototypeOf(Int8Array);
const isTypedArray = (value) => (
    value instanceof $TypedArray$
);

// 誤判定するケース
isTypedArray(Object.create($TypedArray$.prototype));

3. ArrayBuffer.isView

ArrayBuffer.isViewを使っています。
ネイティブで[[ViewedArrayBuffer]]を持っているか確認しているので、一番堅牢な方法だと思います。
TypedArrayとDataView以外にArrayBuffer.isViewがtrueを返すオブジェクトが追加されたら更新しないといけませんが、まあないでしょう。
仕様書:https://tc39.es/ecma262/#sec-arraybuffer.isview

const isTypedArray = (value) => (
    ArrayBuffer.isView(value) && !(value instanceof DataView)
);
// 誤判定するケースは思いつきませんでした。多分ないと思います。

4. constructor

TypedArrayの配列を作ってconstructorと比較する方法です。
これだとconstructorが上書きされていたりTypedArrayを継承したクラスを判定できません。2
それぞれにinstanceofするように変えてもそれ2番目のでよくね?となります。
また、BigInt64Arrayなどの最近の仕様にしか存在しないものは、対応していないブラウザなどでエラーが出ます。
他のを使いましょう。

const typedArrays = [
    Int8Array,
    Uint8Array,
    Uint8ClampedArray,
    Int16Array,
    Uint16Array,
    Int32Array,
    Uint32Array,
    Float32Array,
    Float64Array,
    BigInt64Array,
    BigUint64Array,
];
const isTypedArray = (value) => (
    typedArrays.includes(value.constructor)
);

// 誤判定するケース
isTypedArray({
    constructor: Int8Array
}); // > true

class MyTypedArray extends Int8Array {}
isTypedArray(new MyTypedArray); // > false

// 代替案
const isTypedArray = (value) => (
    typedArrays.some(ctor => value instanceof ctor)
);

typescript

最後におまけとしてTSの型を置いておきます。

type TypedArray = (
    | Int8Array
    | Uint8Array
    | Uint8ClampedArray
    | Int16Array
    | Uint16Array
    | Int32Array
    | Uint32Array
    | Float32Array
    | Float64Array
    | BigInt64Array
    | BigUint64Array
);

const isTypedArray = (value: unknown): value is TypedArray => (
    // 実装
);

参考

https://tc39.es/ecma262/
https://stackoverflow.com/questions/58280379/how-to-find-the-type-of-a-typedarray


  1. https://babeljs.io/repl[...""]とか書くと確認できると思います。 

  2. TypedArrayを継承して使うことなんてあるんでしょうかね? 

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

【JS】プロパティの値を破壊→非破壊処理にする方法。

個人メモです。

テーブルでボタンクリックに合わせtdタグ、thタグを切り替える処理でバグが発生。

プロパティの値の変更の破壊処理を非破壊に変更したらバグが直ったので忘備録として。

破壊処理をデバッグで確認したが、処理は問題なく最終的なアウトプットがおかしい状況。(vue.jsの処理のせい??)

対処方法

変数をコピーする必要がある。

オブジェクトの外側のデータをコピー
//修正前
this.innerValue = this.innerValue.map((tr, rowKey) => {


//修正後
this.innerValue = this.innerValue.map((tr, rowKey) => {
        const _tr = {...tr}

オブジェクトをスプレッド構文で展開してコピーする。

オブジェクトの内側のデータをコピー
//修正前
cell['item_type'] = 'TYPE_TH'
return cell


//修正後
return {
    ...cell,
    item_type: 'TYPE_TD'
}

変数の値を直接変更していたものを、スプレッド構文に展開(別データとしてコピー)して作成。


コード

修正後
    changeRowTh(){
      this.innerValue = this.innerValue.map((tr, rowKey) => {
        const _tr = {...tr}
        if(rowKey === 0){
          _tr.article_items = _tr.article_items.map((cell, cellKey) => {
            return {
              ...cell,
              item_type: 'TYPE_TH',
            }
          })
        }else{
          _tr.article_items = _tr.article_items.map((cell, cellKey) => {
            return {
              ...cell,
              item_type: 'TYPE_TD'
            }
          })
        }
        return _tr
      })
    },
修正前
    changeRowTh(){
      this.innerValue = this.innerValue.map((tr, rowKey) => {
        if(rowKey === 0){
          tr.article_items = tr.article_items.map((cell, cellKey) => {
            cell['item_type'] = 'TYPE_TH'
            return cell
          })
        }else{
          tr.article_items = tr.article_items.map((cell, cellKey) => {
            cell['item_type'] = 'TYPE_TH'
            return cell
          })
        }
        return tr
      })
    },





プロジェクトのデザインパターンが破壊処理を使わない方針の場合、非破壊処理にするよう注意が必要。

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

Reactでxmlファイルをアップロードして使う

use-file-uploadを使ってXMLファイルをアップロードしてパースする手順のメモです

use-file-upload

今回はuse-file-uploadを使用しました
https://www.npmjs.com/package/use-file-upload

use-file-uploadはReactフックとして動作するライブラリで、useFileUpload()でstateを作っておいて、buttonなどからonclickで呼ぶ事でアップロードしたファイルをstateとして扱えるようにしてくれます。

以下の例のsource, name, size, fileはそれぞれblob url, ファイル名、ファイルサイズ、ファイルオブジェクトになります。画像ファイルをアップロードして表示する場合は、blob urlであるsourceをimgタグのsrcに与える事でサーバー上のファイルと同様に表示できます。

src/app.js
import React from 'react'
import { useFileUpload } from 'use-file-upload'

const App = () => {
  const [file, selectFile] = useFileUpload()

  return (
    <div>
      <button
        onClick={() => {
          // Single File Upload
          selectFile({}, ({ source, name, size, file }) => {
            // file - is the raw File Object
            console.log({ source, name, size, file })
            // Todo: Upload to cloud.
          })
        }}
      >
        Click to Upload
      </button>

      {file ? (
        <div>
          <img src={file.source} alt='preview' />
          <span> Name: {file.name} </span>
          <span> Size: {file.size} </span>
        </div>
      ) : (
        <span>No file selected</span>
      )}
    </div>
  )
}

export default App;

上記の例ではbuttonタグにCSSを設定せずに表示するので以下のようなシンプルなボタンが表示されます
image.png

アップロードしたXMLファイルをオブジェクトに変換

fileオブジェクトを直接触るやり方が分からなかったので、blob urlをXMLHttpRequestで読んでくるやり方でやりました。XMLHttpRequestでblobを読みに行ってresponseXMLでXMLとして取得します。XMLとして読めなかった場合はresponseXMLにnullが入りますので、nullでなかった場合は表示するというようにしました。

import React from 'react'
import { useFileUpload } from 'use-file-upload'

function applyGPX(xml){
  console.log(xml);
};

function getXML(source) {
  console.log('source:');
  console.log(source);

  var xhr = new XMLHttpRequest();
  xhr.onload = function(e){
    const xml = xhr.responseXML;
    if (xml != null){
      applyGPX(xml);
    } else {
      console.log('not xml file');
    }
  };
  xhr.open("GET", source);
  xhr.send();
};

const App = () => {
  const [file, selectFile] = useFileUpload()

  return (
    <div>
      <button
        onClick={() => {
          selectFile({}, ({ source, name, size, file }) => {
            getXML(source);
          })
        }}
      >
        Click to Upload
      </button>

      {file ? (
        <div>
          <span> Name: {file.name} </span>
          <span> Size: {file.size} </span>
        </div>
      ) : (
        <span>No file selected</span>
      )}
    </div>
  )
};

export default App;

xmlオブジェクトからほしい情報を拾う

XMLオブジェクトが作れたのでほしい情報を拾っていこうと思います。XMLHttpRequest.responseXMLで取得したオブジェクトはgetElementsByTagName(), getElementById(), getElementsByClassName()などで取得できます。

tag名で探す
const elements = hoge.getElementsByTagName('fuga')
ID名で探す
const elements = hoge.getElementById('fuga')
class名で探す
const elements = hoge.getElementsByClassName('fuga')

また、取得したelementのattributeは以下のように取得できます

attributeの取得
const attribute = elements[0].getAttribute('foo')

今回はgpxファイルを読み込むので以下のようにして緯度、経度、高度を取得しました。

function getTracks(xml){
  console.log('getTracks');
  const trackTags = xml.getElementsByTagName('trkpt');
  console.log('track length: ' + trackTags.length);

  var tracks = [];
  var elevations = [];
  for(let k = 0; k < trackTags.length; k++) {
    let element = trackTags[k];
    let latitude = parseFloat(element.getAttribute('lat'));
    let longitude = parseFloat(element.getAttribute('lon'));
    tracks.push([latitude, longitude]);

    let element2 = element.getElementsByTagName('ele')[0]
    let elevation = parseFloat(element2.innerHTML)
    elevations.push(elevation);
  };
  console.log([tracks, elevations]);
  return [tracks, elevations]

余談

use-file-upload以外にもいくつかReact用のアップロードライブラリを試したんですが、blobぐらい分かるよね?的なノリで書いてあって地味に苦戦しました。ウェブ関係のライブラリ制作者が常識だから省いても良いと思っている部分を勉強できる本があったら読みたいので、ご存知の方がいらっしゃいましたら教えて下さい。

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

【typescript】classのconstructorを省略していけない!!

あるエンジニアがjavascriptを使用して開発を行っていたが、開発速度をあげるためにtypescriptを導入した。
型を定義することで変数の中身が明確になったりと、開発速度があがった。
しかし、型の定義を行っているはずが、型のバグに悩まされていた。

結論からいうと、tsconfigは厳しめに設定すべきだった

型を定義しても、それにそっていない内容のものがあり、想定外のところでバグを起こしてしまっていた。
以下のような例があげられる。


と、その前にクラスについて

クラスのインスタンス

インスタンス化、つまり、newしたものがインスタンス
newしていないものはインスタンスでない。インスタンスでないと、クラスメソッドなどを使用できない。

以下はただの object を型アサーションで User にしたもの。
インスタンスではないので、クラスメソッドの getFullName を使用できない。(TypeErrorになる。)

class User {
  public id: string;
  public firstName: string;
  public lastName: string;

  constructor (input?: Partial<User>) {
    this.id = input?.id ?? "id"
    this.firstName = input?.firstName ?? "firstName"
    this.lastName = input?.lastName ?? "lastName"
  }

  getFullName(): string {
    return `${this.firstName} ${this.lastName}`
  }
}

const _user = {
  id: "id",
  firstName: "firstName",
  lastName: "lastName"
}

{
  const user = _user as User;

  console.log(_user instanceof User) // false
  console.log(user instanceof User) // false

  console.log(user.getFullName()) // TypeError: user.getFullName is not a function
}

{
  const user = new User(_user);

  console.log(user instanceof User) // true

  console.log(user.getFullName()) // firstName lastName
}

constructorを省略

constructorを省略することで以下のような型と違う状況が起きる可能性がある。

class User {
  id: string;
  name: string;
}

const _user = new User();
_user.id = "id";
console.log(_user.name) // undefined

const user = new User();
user.id = "id";
user.name = "name";
console.log(user.name) // name

ただの型なのか、classを用意すべきかは考えたほうがいい。
もし型だけなら以下のようにする。

type User = {
  id: string;
  name: string;
}

const _user: User = {
  id: "id"
};
// Property 'name' is missing in type '{ id: string; }' but required in type 'User'.
// 'name' is declared here.

const user: User = {
  id: "id",
  name: "name"
};

console.log(user) // { id: 'string', name: 'string' }

クラスを用意するならconstructorを定義する

class User {
  id: string;
  name: string;

  constructor (input?: Partial<User>) {
    this.id = input?.id ?? "id"
    this.name = input?.name ?? "name"
  }
}

const user = new User()
console.log(user.name) // name

delete(オブジェクトからプロパティを削除)

deleteにも要注意。型アサーションと同様のことが起きる。

class User {
  id: string;
  name: string;

  constructor (input?: Partial<User>) {
    this.id = input?.id ?? "id"
    this.name = input?.name ?? "name"
  }
}

const user = new User()
delete user.name
console.log(user.name) // undefined

他にもany型など開発者を泣かせるtypescriptの仕様がある。

以下のような記事にもあるとおり、 asany はtypescriptの効果をなくしてしまう。

敗北者のTypeScript


Docs

typescript: https://www.typescriptlang.org/


やるからには厳しくせなあかんな
(やるからには厳しくしないといけないな)

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

プログラムを少しかじった私がバックエンドエンジニアとして働くためにECサイトを作り始めてみた その④

対象者

・バックエンドエンジニアを志す人
・web開発初学者
・ECサイトを作成しようと思っている人

はじめに

こんにちは!2021年がはじまりました。
この投稿はその③の続きです。

その③では一通りのざっくりとしたhtmlとcssを書いたところまででした。
今回はDB設計とバックエンド機能の設計を進めていきます。

目次

1.今回の作業報告
2.反省点
3.次回までの目標

1.今回の作業報告

年末から年始12月26日から1月8日までざっくりまとめます。
意気揚々とhtml、cssを書きましたが・・・・

年末にDBを取り組んで撃沈。
そこから別件でも追われ気持ち的に着手できませんでした(反省)

テーブルを作成しました。
データ型についてこだわるべきなのかもしれませんが動けば良しでいきます。

スクリーンショット 2021-01-08 17.31.09.png

会員登録するところでエラー。ググルもなかなか解決することができず(,が抜けてただけだった・・)ここでかなり時間を溶かしてしまいました。
スクリーンショット 2021-01-08 17.29.46.png

商品で検索の機能を実装することができました。(いつかいい感じの画像にしますw)
スクリーンショット 2021-01-08 17.40.47.png

2.反省点

1.エラーを放置してしまった。
2.有識者がいるなかで聞けずにいたところ

とりあえず大きく2点ですね。
ググりはしたものの3日ぐらい溶かしてそのまま放置してしまいました。
自分で解決できないのであれば有識者にヒントもらうなりする必要があったと思いました。
まあ、自分で解決できればそれに越したことないと思いますが・・・

3.次のステップ:機能の優先順位洗い出し。引き続き実装!

幸いにも前工程で予定していたスケジュールより早かったために大きくスケジュール変更をしなくてよさそうです。別でプロダクトの開発選手権があるのでそちらのスケジュールと両立をやりきります。

ものすごく簡単ですが今回はこのへんで!(プロダクト開発に時間をさきます)

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

【 JavaScript 】Async/awaitとpromiseの非同期処理で詰まった話

初投稿です。忘備録としてQiitaに投稿します。

VueとLIFFでLINEからuser_idを取得し、user_idをHTTPパラメータとしてaxiosでHTTPS通信を行おうとしたのですが、詰まったので記録として残しておきます。

起きた問題

user_idをパラメータとして入力しているはずなのに、パラメータ部がundefinedとなっていて通信できない。

promiseでの非同期処理コード

created(){
  liff.init({ liffId: this.myLiffId }).then(() => {
    ・・・
   }

  liff.getProfile().then((response) => {
    ・・・
  }
}

(汚いコードですいません)
クソ雑魚プログラマの自分にはなぜうまく行かないか分からなかったのですが、先輩に聞いたところthenでPromiseチェーンを作った場合、
同期的処理になるのはPromiseチェーンの中だけで、外部は非同期的に処理されるらしいです。
(ここもあまり理解できていないので間違っていた場合はご指摘ください)

そのため、vueのtemplete内の処理は非同期的に処理されるため、user_idがまだ取得されていない状態でtemplete処理が起こるようです。

解決法

Promiseチェーンではなく、async/awaitではuser_idが取得されてからtemplete処理が行われるようになるため、async/awaitで非同期処理を行います。

async/awaitでの非同期処理コード

async created() {
  await liff.init({ liffId: this.myLiffId });
    ・・・
  await liff.getProfile().then((response) => {
    ・・・
    });

user_idが取得されてからHTTPSリクエストが行われるようになりました。
Vueは専門ではないのに解決策を提示していただいた先輩には頭が上がりません。

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

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

React.js + AWSでプログラミング無しで自動ブラウザテストが出来るサーバレスWebアプリを作ったお話

敵「リリースした後に既存のコンテンツがおかしくなってないか"目視"でチェックしてね」

ぼく「はい (やりたくねぇ〜〜〜〜〜大体どんなパターン網羅して、どこまでチェックしたらいいんだ? 大体人間が目視でチェックしてもそのうち精度が落ちて結局見落としggg) 」

というのが発端で、以下のようにリリース前後のWebページのスクリーンショットを取得・比較して変更点を視覚的に検出するブラウザテストWebアプリを作りました。

appvisual.png

実装したアプリ「Diffender」と利用した技術要素などについて紹介していきます。

作ったアプリ「Diffender」の紹介

何が出来るか?

Webページのスクリーンショットの取得、取得したスクリーンショット間の変更点の検出ができます。

appvisual2.png
こんな感じで2つスクリーンショットを取得した後に差分を取得することで、リリース前後で表示内容に想定外の変更がないことのチェックが行えます。

一度テスト設定を作成した後は、ワンクリックで繰り返し同じ内容のテストを行えます。

ここ見てもらうのが一番はやいかもです
https://diffender.hassoubeat.com/about

コンセプト

「エンジニア以外の人も簡単に自動ブラウザテスト」

従来の自動ブラウザテストはSeleniumとかPuppeteerなどを利用してブラウザ操作(クリック、テキスト入力)をプログラミングする必要があるため、エンジニア以外の人にはハードルが高いです。

そのハードルを下げるべく、プログラミングをしなくてもブラウザ操作が行えて、テスト結果がスクリーンショットとして出力されるため誰でも結果の正否が判断が行えるブラウザテストアプリを実装しました。

目視でデグレチェックという辛い作業からサヨナラ。

なんで作ったの?

① 非エンジニアのディレクターさんやステークホルダーの方でも見て分かるデグレ検証結果が欲しかった

品質の担保としてはユニットテストとかでもいいんですけど、そのユニットテストの実施内容が分からない非エンジニアの方でも実際に表示される画面のスクリーンショットの差分という形でリリース前後のテスト結果が見れると安心できるようにしたいなーと思ったのが一つ目です。

※ 勿論ユニットテストの代わりになるものではないです

② ブラウザテストの度プログラムを書きたくない、すぐ実行できるようにしたい

すでに触れていますが、従来のブラウザテストはSelenium、Puppeteerなどを利用してプログラミングを行う必要がありました。ハードルが高すぎる。。。

しまいにはプログラムを書いてもテスト大量のページが沢山あると、全ページのテスト完了までにすごい時間がかかります。
そこらへんを並列でササッと実行してくれるやつが欲しかったのが2つ目です。

③ サーバレスSPAを実装する経験値が欲しかった

サーバレスSPAをゼロから構築する経験がなかったので、実際に設計・実装を通して経験値を積みたい!というのが3つ目。(理由の8割くらい)

最終的には形になるものが実装できましたが、効率の良い開発環境はもう少し改善の余地がありそうでした。
※ 主にAWSのローカルモック周りの最適解がよくわからない...

アーキテクチャ

Diffenderのアーキテクチャはこんな感じです。

arch.png

フロントエンドはReact.js、バックエンドはAWS関連サービスで実装しています。
言語はフロント、バックエンド両方JavaScript(Node.js)です。

フロントエンド

JavaScript

js.png
今回はプレーンなJavaScriptで実装しました。
TypeScriptはまた今度...。

React.js

react.png
UI構築用のJavaScriptフレームワーク。
Vue.jsと悩んだんですけど、JavaScriptのレベルアップにはReact.jsがいいみたいな記事をどっかで読んで選んでみました。
確かにJavaScriptの理解が深まった気がします。
(でも終始create react appの優しさに抱かれていた気が...自前でビルド設定のカスタマイズとかメンテナンスするのしんどそう)

次はVue.jsでチャレンジしたいです。

Redux

redux.png
アプリケーション全体の状態管理ライブラリ。
一番設計が難しかった気がする。
どこにビジネスロジックを集約するのがいいのか未だに最適解が分からない...。

AWS Amplify

amplify.png

バックエンドリソースへのアクセス用ライブラリとして利用しました。
再考するとaxiosとかでも十分だったかもしれない。。。

バックエンド

AWS Lambda

lambda.png

イベントトリガーで好きなコードを実行できるAWSサービス。
ランタイムはフロントと揃えてJavaScript(Node.js)

サーバサイドの処理は全部Lambdaで実装しています。サーバレスバンザイ。
Provisioned Concurrencyはケチってコールドスタートなので、初回アクセス時はちょっと時間かかります。。。

後述するSQSと組み合わせて、スクリーンショットの取得や差分の取得といった時間のかかる処理は並列処理にしています。

AWS API Gateway

apigateway.png

簡単にREST APIの構築ができるようにするフルマネージドなAWSサービス。
フロントエンドからコールしたいLambdaのコードと連携してREST APIにしています。

AWS SQS

sqs.png

AWSのフルマネージドなメッセージキューイングサービス。
Lambdaで時間のかかる処理を非同期で並列処理するために採用しました。
この構成お手軽なのに強力すぎる...。

AWS DynamoDB

dynamodb.png

フルマネージドなNoSQLデータベースを提供するAWSサービス。
LambdaのRDS Proxyを使って使い慣れてるRDSを使うという方法も検討したんですが、RDS Proxyが高かったんでDynamoDBにしました(貧困)
RDBMSと違って独特の制約が多くて設計に苦労しました。GSIサイッキョ!

でも後から取得したいクエリの種類が増えると、GSIの貼り直しが必要になったりしてつらみ。
早い段階での要件の洗い出しが大事だ。。。

AWS Cognito

cognito.png

モバイル・Webアプリケーションでのユーザ作成・認証・管理機能をフルマネージドで提供するAWSサービス。
面倒なユーザ管理周りをスクラッチしたくなくて使いました。
分かったら便利だけど、やっぱり使い方が分かるまでのハードルが高いよ...
でもAPI Gatewayと認証の連携が簡単にできるのはAWSサービスで統一する良さを実感できました。

AWS S3

s3.png

容量無制限のデータ保存ができるオブジェクト型ストレージを提供するサービス。
撮影したスクリーンショットを格納先として利用してます。

他にもWebページそのもののデプロイ先としてもAWS CloudFrontとセットで活用してます。

その他

AWS SAM

awssam.png

テンプレートファイルに記載された通りにAWSリソースをデプロイするInfrastructure as Codeを実現してくれるサービス。
AWSマネジメントコンソールでポチポチしてデプロイするよりも遥かにデプロイとリソース管理が楽になったけれど、
最終的には800行ぐらいの巨大ファイルになってビルド・デプロイに時間がかかるのがストレスフルでした。

後発のAWS CDKを使ったらストレスフリーになったりするのかな。

作成期間

学習期間込みで大体週35〜40時間くらいで3ヶ月ちょっとくらい...。

課題

・(UI・UXが)哀れ
必要なクリック数、入力項目が多い。。。
実装する前にちゃんとしたワイヤーフレームを引いて、メインストリームの機能を利用するまでのクリック数などを検討すべきでした。

・AWSサービスを絡めた開発のススメ方
開発当初は毎回コードをAWSにデプロイしてた。。。(時間がかかりすぎる)
単体テストコードを書いてローカルで実行するのが最速?
localstackみたいなサービス公式から出して...
AWS SAMのテンプレートの管理がヤバい。800行ぐらい行ってる。AWS CDK使うべき。。。

他にも色々ありますが、キリがないので割愛。。。

最後に

やっぱり一つのサービスを全て一人で作るのは大変だけど、得るものも多い。。。
始める前はサーバレスSPAの実装ってまったくイメージがついてなかったんですが、今となっては動くものが作れてどこが良くなかったかという観点の振り返りまで出来るようになりました。
ただ有識者に聞いたら一発なところも手探りで進めていたのは非効率だなぁと思わずには居られない場面も多々ありました。
(React.js, AWSサービスの仕様・制約、効率的な開発環境などなど)

気軽に聞けるエンジニア仲間みたいな人がいたら、もっと詰まらずにいいものが出来てたんじゃないかと強く思ったので
今年の抱負として、"えんじにあともだちをふやす" を掲げたいと思います。まる。

その前に新しいお仕事が見つかるといいなぁ...(切実)

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

[ JavaScript入門 ] falsyな値とtruthyな値とはなんなのか?またどのような時に使用するのか。

falsyな値とは:point_up:

 falsyな値というのはBooleanで真偽値に変換した場合にfalseになる値のこと。

どゆこと、、、?

まずは、Booleanってなに?
Booleanは真偽値に変換するもの:raised_hand:

例えば、

let a = 0;
console.log(Boolean(a));


結果としてコンソールにはfalseと表示される

どういう事かというと、今回の場合、0というのは、Booleanによって真偽値に変換した時falseと等価になることがわかる。

falsyな値

falsyな値
false
null
0
undefined
0n
NaN

< 空文字列 "" も falsy ですね。

JavaScript の仕様には含まれませんが、ブラウザ環境だと歴史的な理由により document.all も falsy だったりします。
(オブジェクトであるにもかかわらず何かに変換しようとすると undefined としてふるまう) >

@nagtkk さんにご指摘頂きました。
有り難うございます!

これらがfalsyな値としてあげられる:raised_hand:

truthyな値

truthyな値は、falsyな値以外と覚えておく

どのような時活用するのか

プログラムを書いている際に、変数に値が入っているかどうか確認したい時がある:frowning2: (値がnull、undefined以外か確認する)
そんな時活用!!

let a = parseInt("");
console.log(Boolean(a));

if(a) {
    console.log('hello');
} 

このようにif文の中にいれることによってaの値がfalsyな値かどうか確認することができる。この段階ではaという値はfalsyな値であることから、if文の中の記述は実行されない。

let a = parseInt("");
console.log(Boolean(a));

if(!a) {
    console.log('hello');
} 

このようにすることで、逆にfalsyな値であった時実行する。そのようなことも可能。

しかし、今回、値がnull、undefined以外か確認するということをしたい為、他のfalsyな値である0のときだけを確認したい場合、違った記述が必要となる。:frowning2:

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

【JS】真偽値を確認する方法。ビックリマーク2つ(!!)の意味。

個人メモです。

変数や処理の冒頭にビックリマークを2つ!!つけると、真偽値の状態を確認できる。

x = 100
console.log(!!x)
//true

arr = [1,2,3]
console.log(!!arr)
//true

変数に!(反転の意)一つをつけると、値が存在すればfalse、存在しなければtrueを返す。

さらにこの戻り値にビックリマークをつけて!!とすれば、実際の状態にあった真偽値が返る。

▼注意点
0や空の配列はfalseで返る。

x = 0
console.log(!!x)
//false

arr = []
console.log(!!arr)
//false



if文の条件式で変数を指定した場合に、tureかfalseどっちになっているかを確認するのに便利。

Boolean()

!!値Boolean(値)と同じ。
可読性の観点ではBooleanの方がわかりやすい。

x = 100
console.log( Boolean(x) )
//true

arr = [1,2,3]
console.log( Boolean(arr) )
//true
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue3のテンプレート内でES2020構文が使えない原因

Vue3のテンプレートのコンパイラは、デフォルトでES2020の構文をサポートしています。

にも関わらず、テンプレート内でOptional chaining?.)やnullish coalescing??)を使うと、
vue-loaderは以下のメッセージを出力し、コンパイルに失敗します。

You may need an additional loader to handle the result of these loaders.

これはvue-cliなどで環境を作成した場合は発生しないので、大抵の人は起きない問題です。

原因:

さんざんvue-loaderbabelなどを調べましたが、結果としてはこちらの記事を読んでWebpackの問題だと分かりました。

Webpack では JavaScript の新構文を変換なしでバンドルできない

対応:

Webpack5系にアップデートすることで解決しました。

しかし、vue-cliで作成した環境では、Webpack4系だったので、恐らくWebpackがバンドルをする前に変換して対処しているのかなと想像します。

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

すべての人にマークルパトリシアツリーの力を

今回はSymbol form NEMのREST APIで取得できるようになったマークルパトリシアツリーについて検証したいと思います。

マークルパトリシアツリーについては目指せ北海道さんのこちらの記事を参考にしてください。
【NEM技術勉強会】4.3 マークルパトリシアツリー【Symbol白書】- nemlog
なお、今回の記事はplanethoukiさんの助言と記事を数多く参考させていただき、完成することができました。この場を借りて御礼申し上げます。

では、マークルパトリシアツリーを使用してブラウザ上に表示されたアカウント情報の検証を行ってみましょう。

まず固定値の指定とライブラリのインポートします。

const NODE = 'http://api-01.ap-southeast-1.0.10.0.x.symboldev.network:3000';
const GENERATION_HASH = '6C1B92391CCB41C96478471C2634C111D9E989DECD66130C0430B5B8D20117CD';

const nem = require("/node_modules/symbol-sdk");
const op = require("/node_modules/rxjs/operators");
const rxjs = require("/node_modules/rxjs");
const sha3_256 = require('/node_modules/js-sha3').sha3_256;

symbol-sdkはbrowserify化したsymbol-sdk-0.22.2.jsを使用します。
https://github.com/xembook/nem2-browserify/blob/master/symbol-sdk-0.22.2.js

インスタンス生成
const repo = new nem.RepositoryFactoryHttp(NODE, nem.NetworkType.TEST_NET,GENERATION_HASH);
const accountRepo = repo.createAccountRepository()
const blockRepo = repo.createBlockRepository()
const stateProofService = new nem.StateProofService(repo);

今回主役となるのがStateProofServiceです。

アカウント生成
const alice = nem.Account.createFromPrivateKey(
  "F153F89498331537CB9436965DBE2F41660FBEFA911BD61635ADF3DE15ECD367",
  nem.NetworkType.TEST_NET
);

生成したaliceアカウントに対して、アカウントのアドレス(RawAddress)とアカウント情報(AccountInfo)を取得してそのハッシュ値を求めます。

アカウントアドレスのハッシュ値取得
hasher = sha3_256.create();
aliceHash = hasher.update(
  nem.RawAddress.stringToAddress(alice.address.plain())
).hex().toUpperCase();
console.log("Raw Address Hash:" + aliceHash);

スクリーンショット 2021-01-06 23.18.42.png

使用するのはプレーンアドレスではなく、Rawアドレスを16進数化したものを使用します。

アカウント情報のハッシュ値取得
hasher = sha3_256.create();
accountRepo.getAccountInfo(alice.address)
.subscribe(aliceInfo =>{
    aliceInfoHash = hasher.update(aliceInfo.serialize()).hex().toUpperCase();
    console.log(aliceInfo)
    console.log("AccountInfo Hash:" + aliceInfoHash);
})

アカウント情報を出力するとこのような情報が取得できます。
スクリーンショット 2021-01-06 23.26.03.png

このアカウント情報をシリアライズしたものをハッシュ化して使用します。

スクリーンショット 2021-01-06 23.27.34.png

これらの2つの情報が埋め込まれたマークルパトリシアツリーを取得します。取得したツリーにはブロックヘッダーの情報が含まれるので、現在の最新ブロックヘッダーも同時に取得します。

ブロックヘッダーからステートハッシュ値を取得
blockRepo.search({order: nem.Order.Desc})
.subscribe(x=>{
  console.log("Block State Hash:" + x.data[0].stateHashSubCacheMerkleRoots[0])
})

最新のブロックヘッダーとして以下のような情報を取得できました。
スクリーンショット 2021-01-06 23.38.06.png

このうちアカウント情報を集約したステートハッシュ値は
stateHashSubCacheMerkleRootsの0番目に集約されることになります。

スクリーンショット 2021-01-06 23.42.18.png

マークルツリー取得
stateProofService.accountById(alice.address)
.subscribe(proof=>{
    console.log(proof);

    bit = "";
    link = proof.merkleTree.leaf.leafHash;
    merkle = proof.merkleTree
    merkleBranches = merkle.branches.reverse();
    for(let i = 0; i < merkleBranches.length; i++){
        merkleTreeBranchLink = merkleBranches[i].links.find(x=>x.link === link)
        link = merkleBranches[i].branchHash;
        bit =  merkleTreeBranchLink.bit + bit;
    }
    addressHash = "bits:" + bit + "+path:"+ merkle.leaf.path;
    console.log(addressHash);
})

aliceアドレスでアカウント情報のマークルパトリシアツリーを取得すると以下のような情報が表示されます。

スクリーンショット 2021-01-06 23.45.52.png

図で表現するとこんな感じです。

image.png

ツリーは複数階層からなるブランチと末端のリーフで構成されます。
それぞれのハッシュ値を計算すると、一階層上の構成要素になっていることが分かります。
(リーフは末端ブランチ、最上位ブランチはルートハッシュ)

ブランチのハッシュ値計算
function getBranchHash(encodedPath, links){
    const branchLinks = Array(16).fill(nem.Convert.uint8ToHex(new Uint8Array(32)));
    links.forEach((link) => {
        branchLinks[parseInt(`0x${link.bit}`, 16)] = link.link;
    });
    hasher = sha3_256.create();
    return hasher.update(nem.Convert.hexToUint8(encodedPath + branchLinks.join(''))).hex().toUpperCase();
}
リーフのハッシュ値計算
function getLeafHash(encodedPath, leafValue){
    hasher = sha3_256.create();
    return hasher.update(nem.Convert.hexToUint8(encodedPath + leafValue)).hex().toUpperCase();
}

最後に、rootHash、stateHash、merkleTree.branchesのbit値とleaf.valueに注目します。
stateHashがaccountInfoのハッシュ値と一致、
rootHashがブロックヘッダーのstateHashSubCacheMerkleRoots[0]と一致、
RawAddressの文字が以下の計算で出力される値と一致

link = proof.merkleTree.leaf.leafHash;
merkle = proof.merkleTree
merkleBranches = merkle.branches.reverse();
for(let i = 0; i < merkleBranches.length; i++){
  merkleTreeBranchLink = merkleBranches[i].links.find(x=>x.link === link)
  link = merkleBranches[i].branchHash;
  bit =  merkleTreeBranchLink.bit + bit;
}
bit + merkle.leaf.path

これらの3つが一致することで、取得したマークルパトリシアツリー内にアドレスをkeyとしたアカウント情報のハッシュ値valueが存在し、ツリーのルートハッシュがブロックヘッダーに含まれていることが確認できました。

これは何を意味するの?

ブロックチェーンにマークルパトリシアツリーのルートが保存されていることで、ブロックチェーンがツリーの存在を認知していることがわかります。そしてそのツリーは、アカウントアドレスをキー値、アカウント情報をバリュー値とした探索可能なツリーであることが証明されます。
ブロックチェーンは本来、トランザクションの署名検証は簡単に行えるのですが、その結果としてのアカウント情報を検証できるような仕組みではありません。そこでマークルパトリシアツリーのルートハッシュをブロックヘッダーに含めることで、”トランザクションによる更新の蓄積結果としてのデータベース”の状態を検証可能にしています。このデータベースはブロックチェーンの外部に保存されていても検証可能です。

これが何に使えるの?

まず、本来の目的としてノード間でそれぞれがチェーン外部に持つデータが改ざんされずに保存されていることを確認するために使います。イーサリアムもこのような目的でフル活用されていると思います。

ここから一歩進めて活用方法を考えてみましょう。

NEMでは不特定多数がアクセスできるREST APIでマークルパトリシアツリーが提供されています。つまり、ノード間検証の対話のために設計されたものが、NEMではブロックチェーンとユーザの対話と置き換えてアプリケーション設計に利用することが可能です。

アカウント情報などのチェーン外部に保存されたデータも含めてノードであることを確認できるということは、ファイナライズされたブロックヘッダーを別経路で取得できれば、相手がだれであっても、途中でどんなエージェントやキャリアが経由しても、マークルパトリシアツリーが記録する範囲でノードと対話していると考えても問題ないということを意味します。

たとえばnemlogを考えてみましょう。

nemlog - 暗号通貨 nemを使用した寄付機能付きブログコミュニケーションブログコミュニケーションプラットフォーム

nemlogでやり取りされるXEMが、改ざんできないことはご存じのとおりです。これはトランザクションが送信者の署名によって検証できるからです。それではnemlogで表示されているXEM残高についてはどうでしょうか?これはnemlogを信用するしかありません。nemlogが信用できるとしてもブラウザの拡張機能が悪さする場合もあるでしょう。表示される情報というのは人間の目に届くまでは改ざんの余地はあるのです(目に届いた情報を検証することは可能です)。

今のところ、たとえnemlogが嘘の残高を表示していたとしても、送金できる量、取引所で買い取ってくれる金額に変わりはないので大きな問題にはならないかもしれません。しかし、ブロックチェーンの社会実装が進み、取引所が扱わない価値をアカウントが持ち出したらどうなるでしょうか?それはトークンだけとは限りません。信頼できる組織からの証明メッセージ、価値を生み出す仲間からのマルチシグ、そしてそれらを根拠として経済が動き出した時、金融庁などははたしてこれらのすべての価値の管理や表示についてまで監督を行うでしょうか?

ブロックチェーンを利用したシステムが巷にあふれたとしても、私たちがその情報にアクセスするのはブラウザやアプリケーションを経由して見やすく整えられ可視化された情報です。つまり良くも悪くも誰かに操作されているのです。その場合でも、ノードが他のノードを信頼するように、Symbol from NEMではユーザがアプリケーションを信頼するための手がかりをREST APIを通じて提供してくれます。マークルパトリシアツリーがブロックチェーンと表示されている情報との架け橋となることができるのです。

マークルパトリシアツリーはそんな時代がきた時に、私たちに情報の信頼の仕方を教えてくれる重要な鍵となるでしょう。

すべての人にマークルパトリシアツリーの力を

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

【JavaScript】日にちを比較したり、時間や日数の差分を計算する

はじめに

この記事では、JavaScriptのDateオブジェクトを使って日にちを比較したり時間や日数の差分を表示する方法を説明しています。

日にちを比較する

Dateオブジェクトは比較演算子「<」「>」などを使って日にちを比較することができます。

    <script>
      var day1 = new Date(2021, 0, 10, 6);
      var day2 = new Date(2021, 0, 17, 6);
      if (day1.getTime() < day2.getTime()) {
        console.log(day2.toLocaleString() + "の方が先の日にちです");
      } else {
        console.log(day1.toLocaleString() + "の方が先の日にちです");
      }
    </script>
    <!-- 出力結果:2021/1/17 6:00:00の方が先の日にちです -->

時間の差分を計算する

次は、経過ミリ秒を使って日にちの差分を計算してみます。

経過ミリ秒はgetTime()メソッドを利用することで取得できます。また、経過ミリ秒は減算演算子「-」を使って計算することができるので2つの日にちの差を出すことができます。

注意すべきことは、それぞれの日にちの経過ミリ秒を単純に引き算をしているので、ミリ秒を時間になおす必要があるという点です。コードは下のようになります。

<script>
  /* 日にちオブジェクト①を指定する */
  var date1 = new Date(2021, 0, 10, 6);
  /* 日にちオブジェクト②を指定する */
  var date2 = new Date(2021, 0, 17, 6);
  /* 日にち①と日にち②の差(ミリ秒)を計算する */
  var date_difference = date2.getTime() - date1.getTime();
  /* getTime()メソッドは、1月1日を起点とした経過ミリ秒を取得 */
  var difference_hh = date_difference / (60 * 60 * 1000);
  console.log(date1.toLocaleString() + "" + date2.toLocaleString() + "の差分は" +
    difference_hh + "時間です");
</script>
<!-- 出力結果:2021/1/10 6:00:00と2021/1/17 6:00:00の差分は168時間です -->

日数の差分を計算する

こちらも先ほどと同様に、経過ミリ秒の差分を計算しミリ秒を日数になおします。コードは下のようになります。

<script>
  //日にちオブジェクト①を指定する
  var date1 = new Date(2021, 0, 10, 6);
  //日にちオブジェクト②を指定する
  var date2 = new Date(2021, 0, 17, 6);
  //日にち①と日にち②の差分(ミリ秒)を計算する
  var date_difference = date2.getTime() - date1.getTime();
  var difference_dd = date_difference / (24 * 60 * 60 * 1000);
  console.log(date1.toLocaleString() + "" + date2.toLocaleString() + "の差分は" +
    difference_dd + "日です");
</script>
<!-- 出力結果:2021/1/10 6:00:00と2021/1/17 6:00:00の差分は7日です -->
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】puppeteerでurl取得

ご無沙汰してます。おおのんです。

URL取得する方法メモ。
現在ページのURLが期待値と比較したいけど、なぜか取得できない。

url取得できない例
let puppeteer = require("puppeteer");
let browser;
let page;

beforeAll(async () => {
  browser = await puppeteer.launch({
    args: ["--disable-web-security"],
    headless: false,
    slowMo: 30
  });
  page = await browser.newPage();
  jest.setTimeout(20000);
});

afterAll(() => {
  browser.close();
});

describe("TEST", () => {
  test("toMypage", async () => {
    // 画面移動
    await page.goto("http://localhost:8000/mypage");
    // マイページへ遷移成功
    await page.waitForTimeout(5000);
    // location.hrefで完全なURL取得できるから、比較する
    await expect(location.href).toEqual("http://localhost:8000/mypage");
    await page.close();
  });
});
結果
● LOGIN TEST › Login
    expect(received).toEqual(expected) // deep equality
    Expected: "http://localhost:8000/mypage"
    Received: "http://localhost/"

・・・アカン。
Received: "http:/localhost/"になる。

こうすると取得できる。

取得できる例
// ~略~
  // location.href => url.path()にする
    await expect(url.path()).toEqual("http://localhost:8000/mypage");
// ~略~
結果
● LOGIN TEST › Login
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total

通った~。

【puppeteer/puppeteer】 how to get current url ? #2215
で既出でした。

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

webpackを使って手動でコンパイルしたjsをrails6で読み込むことに成功

方針

railsでデフォルトで入っているgem webpackerを使わずにwebpackを使って手動でjsをコンパイルする。
entryファイル内でvue.jsを読み込み、componentを使ってアプリの見た目を作っていく。
webpackを使ってbuildされたjsをrailsアプリで読み込み、componentをアプリに読み込む。

現状の実装内容

画像の通りindex.html.erbにcomponentで定義したh1要素を表示させることに成功
ChatVueApp.png

本日の実装の詳細

※最初にgitignoreが反映されていなかったので以下の手順にて修正

1.gitignoreを編集

2.以下のコマンドでcasheを削除

git rm -r --cached . //ファイル全体キャッシュ削除

3.commit & push

いろいろろ設定をいじったらちゃんとwebpackでコンパイルしたjsを読み込めた

結論以下のことを行った

application.rbでassetsのコンパイル対象を変更

config.assets.paths << Rails.root.join("public/javascripts")

assets.rbでjsとcssのコンパイル対象を増やす(application.rb書いても良さそう)

Rails.application.config.assets.precompile += %w(*.js *.css)
Rails.application.config.assets.precompile << /(^[^_\/]|\/[^_])[^\/]*(\.js|\.css)$/

manifest.jsでpublick/javascripts以下のファイルを読み込むようにする

//= link_directory ../../../public/javascripts .js

application.rbに記述しただけではpublic以下のファイルは読み込んでくれなさそう?な感じなので無理やりmanifest.jsで読み込むようにした。他にもやり方はありそうで、例えばwebpackを使ってコンパイルしたファイルをassets/javascritps内にbuildする方法とかもあるようだ。

で、これでrails sをすると…

無事にブラウザ表示できた。

しかし

Failed to mount component: template or render function not definedと出た

ファッ!?

見た感じVue.jsで作ったcomponentが読み込めていなさそう。ちょっと調べてみるか…

こんな記事にヒット

http://howdy.hatenablog.com/entry/2016/11/08/230439

どうやらresolveの設定が必要らしい。

ということでwebpack.config.jsに以下の記述を追加

resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
    }
  },

これで無事解決!!!!componentで設定したHello!を読み込んでくれました!

Sidebar.vue

<template>
  <h1>Hello!</h1>
</template>

<script>
</script>

App.vue

<template>
  <div>
    <sidebar></sidebar>
    <chat-container></chat-container>
  </div>
</template>

<script>
import Sidebar from './components/Sidebar.vue'
import ChatContainer from './components/ChatContainer.vue'

  export default {
    components:{
      Sidebar,
      ChatContainer
    }
  }
</script>

main.js

import Vue from 'vue';
import App from './App.vue';

// App.vueをエントリとしてレンダリング
new Vue({
  el: '#app',
  render: h => h(App)
})

index.html.erb

<div id="app"></div>

ChatVueApp.png

いまいちなぜ解決したのか自分でも理解できていないので整理

When using vue-loader or vueify, templates inside *.vue files are pre-compiled into JavaScript at build time. You don’t really need the compiler in the final bundle, and can therefore use the runtime-only build.

Since the runtime-only builds are roughly 30% lighter-weight than their full-build counterparts, you should use it whenever you can. If you still wish to use the full build instead, you need to configure an alias in your bundler:

(参考:https://vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only)

この文章を見る限りvue-loaderを使っているときはruntime状態のファイルを使うことができるので完全にcompileしたファイルを使う必要がない。しかしそれでも完全にコンパイルされたファイルを使いたいのであればresolveの設定をする必要があります

と言っているように思う。そして今回私はvue-loaderを使っている。つまりvueファイルの読み込みの仕方が良くないのかもしれない?いまいちよくわからないが読み込めたのでOK。パフォーマンスの良し悪しとかはまた調べてみよう。

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

Javascriptの練習帳

自分用の備忘録。

http://jsfiddle.net/kgtrxve2/

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