20201120のJavaScriptに関する記事は29件です。

ハンバーガー

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My Site</title>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link rel="stylesheet" href="css/styles.css">
  <style>
body {
  margin: 0;
  font-family: Verdana, sans-serif;
}

header {
  display: flex;
  padding: 0 16px;
}

header h1 {
  margin: 0;
  font-size: 22px;
  line-height: 64px;
}

.sp-menu {
  margin-left: auto;
}

.sp-menu #open {
  font-size: 32px;
  line-height: 64px;
  cursor: pointer;
}

.sp-menu #open.hide {
  display: none;
}

main {
  padding: 0 16px;
}

.overlay {
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  background: rgba(255, 255, 255, 0.95);
  text-align: center;
  padding: 64px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .6s;
}

.overlay.show {
  opacity: 1;
  pointer-events: auto;
}

.overlay #close {
  position: absolute;
  top: 16px;
  right: 16px;
  font-size: 32px;
  cursor: pointer;
}

.overlay ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

.overlay li {
  margin-top: 24px;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .3s, transform .3s;
}

.overlay.show li {
  opacity: 1;
  transform: none;
}

.overlay.show li:nth-child(1) {
  transition-delay: .1s;
}

.overlay.show li:nth-child(2) {
  transition-delay: .2s;
}

.overlay.show li:nth-child(3) {
  transition-delay: .3s;
}

.pc-menu {
  display: none;
}

@media (min-width: 600px) {
  .pc-menu {
    display: block;
    margin-left: auto;
  }

  .pc-menu ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    display: flex;
  }

  .pc-menu a {
    display: block;
    width: 80px;
    line-height: 64px;
    text-align: center;
  }

  .pc-menu a:hover {
    background: #f2f2f2;
  }

  .sp-menu {
    display: none;
  }
}
  </style>
</head>
<body>
  <header>
    <div class="logo">
      <h1>LOGO</h1>
    </div>

    <div class="pc-menu">
      <nav>
        <ul>
          <li><a href="#">Menu</a></li>
          <li><a href="#">Menu</a></li>
          <li><a href="#">Menu</a></li>
        </ul>
      </nav>
    </div>

    <div class="sp-menu">
      <span class="material-icons" id="open">menu</span>
    </div>
  </header>

  <div class="overlay">
    <span class="material-icons" id="close">close</span>
    <nav>
      <ul>
        <li><a href="#">Menu</a></li>
        <li><a href="#">Menu</a></li>
        <li><a href="#">Menu</a></li>
      </ul>
    </nav>
  </div>

  <main>
    <p>こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。</p>
    <p>こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。</p>
  </main>

  <script>
    {
  const open = document.getElementById('open');
  const overlay = document.querySelector('.overlay');
  const close = document.getElementById('close');

  open.addEventListener('click', () => {
    overlay.classList.add('show');
    open.classList.add('hide');
  });

  close.addEventListener('click', () => {
    overlay.classList.remove('show');
    open.classList.remove('hide');
  });
}
  </script>
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【firebase authentication】永続化された認証情報の参照にAuth.currentUserは使ってはいけない

firebase.auth().currentUserとは

現在のユーザー認証情報を所持するプロパティです。

firebaseでは認証情報をクライアントに永続化が可能となっているので、永続化された認証情報が存在すれば

現在のユーザー認証情報

の対象となります。公式ドキュメント

リロードしようがブラウザ閉じようが認証状態は継続しているということですね、firebaseさまさまです。

本題

意気揚々Reactでこんなコードを書きます。

firebase.initializeApp({....})

const App = () => {
    retutn <Router>
        <LoginPage />
        <HogePage />
        <HugaPage />
    <Router />
}

if (firebase.auth().currentUser !== null) {
    console.log("前の認証情報が残っているよ!")
} else {
    console.log("認証情報は残っていないよ!")
}

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

実行してみましょう、LoginPageで何かしら認証を行いリロード!
ですが表示されるのは

認証情報は残っていないよ!

無慈悲.....

調査するところ認証情報の永続化はデフォルトでsessionモードと言われるものとなっており、今回の意図通りの挙動をしてくれる様子です。公式ドキュメント

もう少し調べると、認証し→リロード→HogePageを動かしている際にはcurrentUserが参照できる

ここから推測するにcurrentUserにデータが入ってくるのはfirebaseSDKのモジュールが生成されるタイミングではないようで少しラグがあるようのでは。。。。。?

ですがcurrentUserに入ってくるタイミングなんてものはもちろん検知できません。
そこでAuth.onAuthStateChangedを発見、signin,signoutが発生した際にコールバックを処理してくれるようです。
使えそうなので書き換えてみましょう。

const getStoredFirebaseAuthenticatedUser = () => 
    new Promise<firebase.User | null>(
        (resolve, reject) => firebase.auth().onAuthStateChanged(resolve, reject)
    );

firebase.initializeApp({....})

const App = () => {
    return <Router>
        <LoginPage />
        <HogePage />
        <HugaPage />
    <Router />
}

getStoredFirebaseAuthenticatedUser().then(user => {
    if (firebase.auth().currentUser !== null) {
        console.log("前の認証情報が残っているよ!")
    } else {
        console.log("認証情報は残っていないよ!")
    }
})

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

同じく、LoginPageで何かしら認証を行いリロードしてみます。

少し待ち

前の認証情報が残っているよ!

勝利!

まとめ

  • 永続化された認証情報の参照でcurrentUserを使うとnullになるケースがある
  • たぶんfirebaseSDKの初期化が終わってからcurrentUserに入るまでに時間がかかる
  • 初期化終了の検知はできないからOnChangeAuthStateでフックしてあげようね

(ならcurrentUserの定義() => Promsieにしろよ........)

ということでした、ハマったので皆さんお気をつけください。

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

Python学習 基礎編 ~文字列を出力(表示)するには?~

こちらではPython学習の備忘録と、Ruby、JavaScriptとの比較も含め記載していきたいと思います。
プログラミング初心者や他の言語にも興味、関心をお持ちの方の参考になれば幸いです。

文字を出力(表示)するには?

共通部分
文字列は、必ず半角の「''」シングルクォーテーション、または「""」ダブルクォーテーションのどちらかで囲む必要がある。
記述がない場合はエラーになるので注意!

Python
script.py
print('Hello World')
Ruby
index.rb
puts 'Hello World'
JavaScript
script.js
console.log('Hello World');

文の最後は「;」セミコロンで終わりにする。


Pythonにてクォーテーションの記述がなかった場合
SyntaxError: invalid syntax

「構文エラー:無効な構文」というエラーが発生します。

そして、 今後、記述が多くなると上記エラーが発生する確率も高くなるのでその際は下記をチェックしてみましょう。

  • Pythonのコマンドや関数の名前を打ち間違えていないか?
  • カッコを閉じ忘れていないか?
  • 半角スペースのところが全角になっていないか?
  • for文, if文, 関数などの「:」を書き忘れていないか?
  • 「''」シングルクォーテーション、または「""」ダブルクォーテーションを忘れていないか?

おわりに

私自身「Python」の学習を始めたばかりなので、これからよろしくお願い致します!
また間違いがあれば遠慮なくご指摘願います!!


参考記事
SyntaxError: invalid syntaxとは何ですか?

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

フロントエンドの「Svelte」とvueを比べてみる

はじめに

Qiitaをなんとなく眺めていたら、「SvelteはVueよりつよつよ」というようなことが書いてあった記事を見つけ、とりあえず試してみました。

TL;DR

Svelteは速い
Svelteはコード量が少ない
Svalteは書きやすそう
Svelteはドキュメントが少ない

Svelteとは

スヴェルトと読むようです。ライブラリというよりもコンパイラで、.svelte拡張子に記述したコードをCSS、javaScriptとして出力してくれます。

Svelteとvueを比べてみた

もともとvueで作っていたシンプルなアプリケーションをSvelteで作ってみて、どんな差があるのかを見てみました。

アプリケーションはフランス語で数字の0~30がランダムに再生されるので、それを答えるという大変ニッチなものです。
ページも1枚、ルーティングもなければコンポーネント分けもしていないシンプルなものです。

スクリーンショット 2020-11-20 9.58.29.png

Svelteは速い

コードを見る前に、実際にコンパイル、ビルドの所要時間を見てみます。

vueでのビルド

⠸ Building for production...
DONE Compiled successfully in 6461ms

Svelteでのビルド

src/main.js → public/build/bundle.js...
created public/build/bundle.js in 840ms

6,461ms対840ms秒ということで、7~8倍の差があります。
また、ローカルでの開発サーバー起動に関しても、vueでは1,800ms程度かかるところが、Svelteだと340ms程度でした。
小さいプロジェクトでの実施ですので絶対的な処理時間自体対して長くないですが、プロジェクトが大きくなったり、Docker上で動かしたりすると顕著に差が出てくるでしょう。

Svelteはコード量が少ない

今回用いたアプリケーションの実際のコードを比べてみます。

vueのソースコード(実装部分)

<template>
  <div >
      <div>question Number {{questionNumber}}</div>
      <div v-if="isAnswerChecking">
          <span class="response"  v-bind:class="{ 'is-correct': isCorrectAnswer }">
              <span v-if="isCorrectAnswer">Correct!!!</span>
              <span v-else>Oops!!</span>
          </span>
      </div>
      <div v-else>
          <audio v-bind:src="questions[selected].audio" controls />
          <div>
          <label>input number</label>
          <input type="number" v-model="inputNumber"/>
          <button v-on:click="answer()" >Go</button>

          </div>
      </div>
  </div>
</template>

<script>

export default {
  name: 'NumberListening',
  data() {
      return {
          questionNumber: 1,
          selected: 0,
          questions: [
              {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/0.mp3",answer:0},

              {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/30.mp3",answer:30},                       
          ],
          inputNumber: "",
          isAnswerChecking: false,
          isCorrectAnswer: false
      }
  },
  created: function() {
      this.generateQuestion();
  },

  methods: {
      answer: function(){
          this.isAnswerChecking = true;
          this.answerCheck();
          setTimeout(this.refreshResponse,1500);
      },
      answerCheck: function() {
          this.isCorrectAnswer = (this.inputNumber == this.selected)
      },
      refreshResponse: function() {
          if (this.isCorrectAnswer) {
              this.generateQuestion();
              this.questionNumber++;
          }
          this.inputNumber = "";
          this.isAnswerChecking = false;
          this.isCorrectAnswer = false;
      },
      generateQuestion: function() {
          this.selected = Math.floor(Math.random() * this.questions.length);
      }
  }

}
</script>

Svelteのコード

<script>
    import { onMount } from 'svelte';
    let questionNumber = 1;
    let selected =  0;
    let questions = [
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/0.mp3",answer:0},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/1.mp3",answer:1},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/2.mp3",answer:2},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/3.mp3",answer:3},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/4.mp3",answer:4},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/5.mp3",answer:5},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/6.mp3",answer:6},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/7.mp3",answer:7},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/8.mp3",answer:8},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/9.mp3",answer:9},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/10.mp3",answer:10},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/11.mp3",answer:11},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/12.mp3",answer:12},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/13.mp3",answer:13},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/14.mp3",answer:14},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/15.mp3",answer:15},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/16.mp3",answer:16},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/17.mp3",answer:17},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/18.mp3",answer:18},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/19.mp3",answer:19},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/20.mp3",answer:20},     
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/21.mp3",answer:21},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/22.mp3",answer:22},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/23.mp3",answer:23},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/24.mp3",answer:24},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/25.mp3",answer:25},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/26.mp3",answer:26},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/27.mp3",answer:27},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/28.mp3",answer:28},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/29.mp3",answer:29},
        {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/30.mp3",answer:30},                       
    ];
    let inputNumber;
    let isAnswerChecking = false;
    let isCorrectAnswer = false;

    onMount(async () => {
        generateQuestion()
    })
    function answer(){
        isAnswerChecking = true;
        answerCheck();
        setTimeout(refreshResponse,1500);
    }
    function answerCheck() {
        isCorrectAnswer = (inputNumber == selected)
    }
    function refreshResponse() {
        if (isCorrectAnswer) {
            generateQuestion();
            questionNumber++;
        }
        inputNumber = "";
        isAnswerChecking = false;
        isCorrectAnswer = false;
    }
    function generateQuestion(){
        selected = Math.floor(Math.random() * questions.length);
    }     
</script>

<main>
  <div>
      <div>question Number {questionNumber}</div>
    {#if isAnswerChecking}    
        <span class="response">
            {#if isCorrectAnswer}
                <span class={ isCorrectAnswer ? 'is-correct': '' }>Correct!!!</span>
            {:else}
                <span>Oops!!</span>
            {/if}
        </span>
    {:else}
          <!-- svelte-ignore a11y-media-has-caption -->
          <audio src={questions[selected].audio} controls />
          <div>
          <!-- svelte-ignore a11y-label-has-associated-control -->
          <label>input number</label>
          <input type="number" bind:value={inputNumber}/>
          <button  on:click={answer} >Go</button>
          </div>
    {/if }
  </div>
</main>

文字数にして、3,763 vs 3,735です(インデント、スペースを排除して測定)。
言うほど変わらなかったので、適当な切り口として、<script>内で、固定値で長ったらしく宣言している配列の部分を除いた部分で文字数の比較をしてみたところ、741 vs 636ということで、それなりに良さそうな差が出ました。
この差が出た一番大きな理由はおそらく thisの有無でしょう。(その程度の差しかでないアプリケーションでした)。
とはいえ、読みにくくなるわけでもなく文字数が減ることに対して異論のある人はあまりいないでしょう。

Svalteは書きやすそう

先ほどのコードを比べてみた時に、目立った差としては以下のあたりでしょうか
- thisの有無(Svelteでは不要)
- 変数の宣言方法(vueは data(){return} とか独特の記法あり)
- 条件付きレンダリング(if-else)の書き方(vueはタグ内に、Svelteはタグとは別の{}くくり)

個人的にはいずれも、Svelteの書き方の方が読みやすく、書きやすい印象があります。jsそのままに近くて、フレームワーク特有記法ではないと感じます(条件付きレンダリングについてはその限りではないですが...)。

vueの代わりにSvelteを使うか?

現時点でまだそれはできないかなと思います。まだドキュメントも少なく、(おそらく)こなれていないため今後大きく変更があってもおかしくないことを思うと、この程度の小さいコードが置き換えられたくらいではさすがに踏み込めないですね。
とはいえ、今メジャーな3大フロントエンドフレームワークvue,React,Angularよりも取っつきやすいのは間違いないので、シンプルなフロントエンド開発をちゃちゃっとやりたい人たちを取り込んでいくことが期待できると思います。

今後のSvelteに期待しましょう。

参考

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

three.jsのRawShaderMaterialを使い回す

three.jsのRawShaderMaterial(またはShaderMaterial)を使うとシェーダーを自分で書くことができます。このときにシェーダーのコードは同じだけどuniform変数の値だけを変えて複数のメッシュを描画したいときがあります。

単純な例ですが、以下のように色だけを変えて複数の四角形をRawShaderMaterialで描画したいとします。
ReuseRawShaderMaterial_Small.png

何も考えずに書くと、次のコードのようにシェーダー自体は同じなのにも関わらず一つのメッシュごとにマテリアルを作成してしまいます。しかし、これではCPU的にもメモリ的にも無駄な作業をしているように見えます(正確なパフォーマンスの検証はしていませんが...)。

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const plane = new THREE.PlaneBufferGeometry(0.75, 0.75);

const vertexShader = 
`uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;

attribute vec3 position;

void main() {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`;
const fragmentShader =
`precision highp float;

uniform vec3 color;

void main() {
  gl_FragColor = vec4(color, 1.0);
}
`;

const planeNum = new THREE.Vector2(10, 10);
for (let x = 0; x < planeNum.x; x++) {
  for (let y = 0; y < planeNum.y; y++) {
    const material = new THREE.RawShaderMaterial({
      vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      uniforms: {
        color: {
          value: new THREE.Color(x / planeNum.x, y / planeNum.y, 1.0)
        },
      }
    });
    const mesh = new THREE.Mesh(plane, material);
    mesh.position.set(x - 0.5 * planeNum.x, y - 0.5 * planeNum.y, 0);
    scene.add(mesh);
  }
}

camera.position.z = 15.0;
renderer.render(scene, camera);

Object3D.onBeforeRenderを使う方法

ShaderMaterialの解説に書いてあるようにObject3D.onBeforeRender内でuniform値を更新することで、1つのマテリアルを使いまわして複数のメッシュを描画することができます。更新するためにはマテリアルのuniformsNeedUpdatetrueにすることも必要です。

You're recommended to update custom Uniform values depending on object and camera in Object3D.onBeforeRender because Material can be shared among meshes, matrixWorld of Scene and Camera are updated in WebGLRenderer.render, and some effects render a scene with their own private cameras.
(筆者訳) マテリアルがメッシュ間で共有されたり、シーンやカメラのmatrixWorldがWebGLRenderer.renderで更新されたり、自分自身のプライベートなカメラでシーンを描画するエフェクトがあるため、オブジェクトまたはカメラのObject3D.onBeforeRendern内でカスタムのuniform値を更新することをおすすめします。

ということで、Object3D.onBeforeRenderを使用して上述したコードを書き直すと以下のようになります。RawShaderMaterialは最初に1回だけ作成して使い回されていることがわかると思います。

...
const material = new THREE.RawShaderMaterial({
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  uniforms: {
    color: {
      value: null,
    },
  },
});

const planeNum = new THREE.Vector2(10, 10);
for (let x = 0; x < planeNum.x; x++) {
  for (let y = 0; y < planeNum.y; y++) {
    const mesh = new THREE.Mesh(plane, material);
    mesh.position.set(x - 0.5 * planeNum.x, y - 0.5 * planeNum.y, 0);
    mesh.onBeforeRender = () => {
      mesh.material.uniforms.color.value = new THREE.Color(x / planeNum.x, y / planeNum.y, 1.0);
      mesh.material.uniformsNeedUpdate = true;
    };
    scene.add(mesh);
  }
}
...

ShaderMaterial.clone を使う方法

別の方法として、ShaderMaterial.cloneを使う方法があります。これは正確に言うとメッシュごとにマテリアルを作成することになるのですが、解説に書いてあるようにコンパイル済みのシェーダーが共有されるので効率化に繋がります。

Generates a shallow copy of this material. Note that the vertexShader and fragmentShader are copied by reference, as are the definitions of the attributes; this means that clones of the material will share the same compiled WebGLProgram. However, the uniforms are copied by value, which allows you to have different sets of uniforms for different copies of the material.
(筆者訳) このマテリアルの浅いコピーを生成します。頂点シェーダーとフラグメントシェーダーは参照としてコピーされます。つまりマテリアルのクローンは同じコンパイル済みのWebGLProgramを共有します。しかしuniformsは値としてコピーされるので、異なるマテリアルのコピーには異なるuniformの組み合わせを持たせることができます。

ShaderMaterial.cloneを使用すると次のようなコードになります。

...
const planeNum = new THREE.Vector2(10, 10);
for (let x = 0; x < planeNum.x; x++) {
  for (let y = 0; y < planeNum.y; y++) {
    const clonedMaterial = material.clone();
    clonedMaterial.uniforms.color.value = new THREE.Color(x / planeNum.x, y / planeNum.y, 1.0);
    const mesh = new THREE.Mesh(plane, clonedMaterial);
    mesh.position.set(x - 0.5 * planeNum.x, y - 0.5 * planeNum.y, 0);
    scene.add(mesh);
  }
}
...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Nuxt.js】ルーティングとページ遷移についてまとめ

静的ルーティング

Nuxt.jsではpagesディレクトリにファイルを作成することで自動的にルートの設定をしてくれます。

デフォルトではpages/index.vueが存在しており、
http://localhost:3000/のURLにアクセスすることでそのページを確認できます。

pages/index.vue
<template>
  <div>
    <h1>/index</h1>
  </div>
</template>

スクリーンショット 2020-11-20 11.18.52.png


ではpages/test.vueを作ってみます。

この場合URLはhttp://localhost:3000/testとなります。

pages/test.vue
<template>
  <div>
    <h1>/test</h1>
  </div>
</template>

スクリーンショット 2020-11-20 11.25.02.png


次にpages/users/index.vueを作って見てみます。
URLはhttp://localhost:3000/usersとなります。

このようなディレクトリの階層構造になっていても自動的にルートが設定されています。

pages/users/index.vue
<template>
  <div>
    <h1>/test</h1>
  </div>
</template>

スクリーンショット 2020-11-20 11.46.13.png

動的ルーティング

_id.vueという名前のファイルを作成することで動的ルーティングが設定できます。

pages/users/_id.vueを作成します。

URL:http://localhost:3000/users/********(任意のID)

********には何を入力しても大丈夫です。
適当に入れましょう。

pages/users/_id.vue
<template>
  <div>
    <h1>/users/_id.vue</h1>
  </div>
</template>

スクリーンショット 2020-11-20 12.42.26.png


では********の部分を取得したいと思います。

$route.params.id********の部分を取得できます。

pages/users/_id.vue
<template>
  <div>
    <h1>{{$route.params.id}}</h1>
  </div>
</template>

スクリーンショット 2020-11-20 12.47.29.png


続いてリンクを貼ってページ間を遷移させていきます。

NuxtLink

全てのページにリンクを設けたいのでlayouts/default.vueにナビゲーションメニューを作ります。

ページ間の遷移を行うにはNuxt.jsで用意されているNuxtLinkコンポーネントを使います。

<NuxtLink></NuxtLink>

layouts/default.vue
<template>
  <div>
    <hr>
    <NuxtLink to='/'>/index</NuxtLink>
    <NuxtLink to='/users'>/users/index</NuxtLink>
    <NuxtLink to='/users/適当'>users/:id</NuxtLink>
    <hr>
    <Nuxt/>
  </div>
</template>

demo


router.push

ボタンを押したときなどにページを遷移させたい場合はrouter.pushが使えます。

layouts/default.vue
<template>
  <div>
    <hr>
    <NuxtLink to='/'>/index</NuxtLink>
    <NuxtLink to='/users'>/users/index</NuxtLink>
    <NuxtLink to='/users/適当'>users/:id</NuxtLink>
    <br>
    <button @click="$router.push('/')">/index</button>
    <button @click="$router.push('/users')">/users/index</button>
    <button @click="$router.push('/users/適当')">users/:id</button>
    <hr>
    <Nuxt/>
  </div>
</template>

demo


おまけ ページ上でIDを指定して遷移させるアプリ

demo

layouts/default.vue
<template>
  <div>
    <hr>
    <NuxtLink to='/'>/index</NuxtLink>
    <NuxtLink to='/users'>/users/index</NuxtLink>
    <NuxtLink to='/users/適当'>users/:id</NuxtLink>
    <br>
    <button @click="$router.push('/')">/index</button>
    <button @click="$router.push('/users')">/users/index</button>
    <button @click="$router.push('/users/適当')">users/:id</button>
    <br>
    /users/<input type="text" v-model="id"><button @click="goTo">遷移</button>
    <hr>
    <Nuxt/>
  </div>
</template>

<script>
export default {
  data() {
    return {
      id: ''
    }
  },
  methods: {
    goTo() {
      this.$router.push(`/users/${this.id}`);
      this.id = '';
    }
  }
}
</script>

以上です。
ここまで見て頂きありがとうございました!

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

javascript : .innerHTML と Math.floor() について

例えば
addTaxDom.innerHTML = Math.floor(inputValue * 0.1);
この場合、
addTaxDomは、ブラウザ上の金額を表示する「場所」を示しているにすぎず、
あくまでそこに計算結果(inputValue * 0.1)が「表示」されているだけ。
addTaxDomに数値が「定義」されているわけではない!←:fast_parrot:

これをprofitDom.innerHTML = Math.floor(●●)で、●●を引き算の形にしたいのであれば、
const tax = Math.floor(inputValue * 0.1);で、計算式を一旦適当なconst○○に入れて、
消費税:addTaxDom.innerHTML = tax
儲け :profitDom.innerHTML = Math.floor(inputValue - tax);
とする。
Rubyの代入感覚で考えていると、ミスしてしまう落とし穴だー:thinking:

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

スクロールをトリガーにイベントを発火させる方法(ページ自体はスクロールさせない)

サンプル

See the Pen scroll1 by pd_kosaka (@pd_kosaka) on CodePen.

解説

サンプルはスクロールをトリガーにイベントを発火させ、実際にページはスクロールさせない方法です。
①トリガーとなるスクロール用の要素を見えない状態で用意
②その要素のスクロールでイベントを発火

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

jQueryを使ってクリックするとリンク先までスクロールするアニメーションの実装する

はじめに

jQueryを使って、クリックするとリンク先までスクロールするアニメーションを実装していきます。

完成イメージ

後日添付

環境

MacOS 10.15.7
ruby 2.6.5
Ruby on Rails 6.0.0

前提条件

  • jQueryが導入済みであること

作業していきます。

①html.erbの作成

application.html.erb
...上記一部省略...
<body>
  <header class="header">
    <div class="menus">
      <a href="#0", class="menuLink">Top</a>
      <a href="#1", class="menuLink">Category</a>
      <a href="#2", class="menuLink">Prefecture</a>
      <a href="#3", class="menuLink">New Posts</a>
    </div>
  </header>
</body>

hrefにそれぞれ「#0」「#1」「#2」「#3」と設定します。

○○○.html.erb
<div class='mainWrapper'>
  <div class='menu' id="0">
    <p class='headMessage'>
      menu1
    </p>
  </div>
  <div class='menu' id="1">
    <p class='headMessage'>
      menu2
    </p>
  </div>
  <div class='menu' id="2">
    <p class='headMessage'>
      menu3
    </p>
  </div>
  <div class='menu' id="3">
    <p class='headMessage'>
      menu4
    </p>
  </div>
</div>

menuごとにそれぞれidを付与します。

②jsファイルの作成

○○○.js
$(function(){
  $('a[href^="#"]').click(function(){
    let speed = 500; /* 数値を変更することでスクロールスピードが変化 */
    let href= $(this).attr("href");
    let target = $(href == "#" || href == "" ? 'html' : href);
    let position = target.offset().top;
    $("html, body").animate({scrollTop:position}, speed, "swing");
    return false;
  });
});

$('a[href^="#"]').click(function()でhrefに「#」が付与されているもの全てをクリックするとスクロールイベントが発火するようにして、クリックされたmenuの場所にスクロールアニメーションが発動します。

これで完成です。
(CSSは適宜設定をしてください)

終わりに

ご覧いただきありがとうございました。

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

ct.jsでキー入力の判定を時間軸で解説。

ボタンが押された瞬間なのか、押され続けているのか、離された瞬間なのかを判定したい。

どんな時に必要か?

毎フレーム、キャラクターの座標を加減算するとゲームによっては違和感のある移動になることがある。
例えばマス目を利用した移動を行いたいとき。
隣のマスに移動したい場合、「1マス移動(座標の加減算)する」という処理が毎フレーム実行されると、
1フレームだけキーを押さなくてはならない。30fpsであれば、1フレームは0.03秒である
そんな神業をユーザーに求めるわけにはいかないのでそういう場合に押された瞬間の判定が欲しい。

ct.jsならばメソッドが用意されている。

press,down,releaseが用意されている。
それぞれがtrueを返す場合を下記にまとめてみた。

無題の図形描画.jpg

これを認識してif分岐することで自分の望む移動を実装しよう。

あとがき

ct.jsってタグがないことに今気づいた。
今後紹介記事も簡単に書ければと考えている。

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

【JavaScript】数値範囲による分岐2

【JavaScript】数値範囲による分岐
過去に書いた記事のコードを少しスッキリ(?)させただけです。

コード

//Object.entriesだけでも動作はする
const generations = new Map(Object.entries({
    12: 'under 12',
    20: 'teenager',
    30: '20s',
    40: '30s',
    50: '40s',
    60: '50s',
    70: '60s',
    80: '70s',
    90: '80s',
    100: '90s',
}));

const getGeneration = age => {
    if (!Number.isFinite(age) || age < 0) return; 

    for (const [key, value] of generations) {
        if (age < key) return value;
    }

    return 'over 100';
};

console.log( getGeneration(-1) );       // undefined
console.log( getGeneration('14') );     // undefined
console.log( getGeneration(14) );       // 'teenager'
console.log( getGeneration(28) );       // '20s'
console.log( getGeneration(200) );      // 'over 100'

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

【javascript】配列をN個で分割(前方開始・後方開始)

【JavaScript】自分なりのワンライナー(配列操作)
過去に書いた上記記事で、配列の分割をワンライナーで載せましたが、そうじゃない書き方をしたかったので書きました。

それだけじゃつまらないので後方から分割するものも書いてみました。

(追記)ご指摘がありました、第2引数検査を追加しました。

コード(前方から)

const splitArray = (arr, n = 1) => {
    if (!Number.isFinite(n) || n < 1) return arr;       //追記

    const result = new Array();
    for (let i = 0, len = arr.length; i < len; i += n) {
        result.push( arr.slice(i, i + n) );
    }
    //n個未満の配列を除外したい時に追加
    //if (result.slice(-1)[0].length < n) result.pop();
    return result;
};

const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log ( splitArray(array, 3) );      //[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
console.log ( splitArray(array, 4) );      //[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9]]

コード(後方から)

const splitArrayBack = (arr, n = 1) => {
    if (!Number.isFinite(n) || n < 1) return arr;       //追記

    const result = new Array();
    for (let i = arr.length, len = i + n; i > 0; i -= n) {
        result.unshift( arr.slice(i - len, i) );
    }
    //n個未満の配列を除外したい時に追加
    //if (result[0].length < n) result.shift();
    return result;
};

const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log ( splitArrayBack(array, 3) );      //[[0], [1, 2, 3], [4, 5, 6], [7, 8, 9]]
console.log ( splitArrayBack(array, 4) );      //[[0, 1], [2, 3, 4, 5], [6, 7, 8, 9]]

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

Kinx 実現技術 - JIT: sljit の使い方

sljit の使い方

はじめに

こんにちは。皆さん、お元気でしょうか。

今日は JIT Compiler の話です。Just In Time Compiler 技術です。技術の紹介をして皆の役に立とうという気持ちもありますが、半分は 自分のための備忘録 の意味もあります。なにせ、情報が少なくて元のソースコードを解析しながら使っているので。

Kinx では native 関数を定義できる機能があり、いわゆる JIT 実行させています。ここで Kinx って何?な方は下記をご参照ください。「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」で頑張っているアレです。

さて JIT です。JIT したいですよね。

そう、JIT はみんなやりたがるのですが、ここで 1 つ大きな問題があります。それは アセンブラは CPU ごとに違う ということです。

それを解決するのが sljit です。ここではその sljit が便利だったのでご紹介します。

...という趣旨で下書きしていたのですが、実は現在 MIR というものに夢中です。これに関してはまた別途記事にしていこうと思います。今回ひとまず、備忘録として sljit に関する本記事を残しておきます。

sljit とは

公式? 説明文書

私が知る限り、参考になる文書は以下程度しか見つかりませんでした。

参考にはなります。

GitHub のリポジトリは以下です。

サポート状況

上記によれば、以下のプラットフォームをサポートしているようです。

  • Intel-x86 32
  • AMD-x86 64
  • ARM 32 (ARM-v5, ARM-v7 and Thumb2 instruction sets)
  • ARM 64
  • PowerPC 32
  • PowerPC 64
  • MIPS 32 (III, R1)
  • MIPS 64 (III, R1)
  • SPARC 32

アセンブラを抽象化

sljit とは一言でいえば 抽象化アセンブラ であり、sljit 形式で書くことによってバックエンドで各 CPU 命令にトランスレートしてくれるのです。いやぁありがたい。

つまり、x86 だけ、とか x64 だけなら頑張ってできることも、ARM でも使いたいとか SPARC でも使いたいとか、うちは PowerPC メインなんで、とか言われたりすると、全部違う実装しなくてはなりません。ただでさえ面倒な JIT の実装なのに、やってられませんよね。それを吸収してくれるのが sljit です。

ということで、Kinx では sljit を使ってアーキテクチャの違いを吸収しようと試みているのでした。

ただし、x64 でしか試していません。

sljit の使い方(基本編)

ソースコード

ソースコードはここから入手できます。

$ git clone https://github.com/zherczeg/sljit.git

必要なのは、このリポジトリの sljit_src です。このフォルダ内のファイルだけマルっとコピーすれば OK です。

ビルド方法

ビルドも上記のファイルの中の sljitLir.h をインクルードして、sljitLir.c を一緒にコンパイルすれば OK です。その時、SLJIT_CONFIG_AUTO マクロを 1 に指定してください。アーキテクチャを自動的に選択してくれます。

つまり、sljit フォルダに先ほどのファイル一式を展開(コピー)した場合、以下のようになります。

$ cl /DSLJIT_CONFIG_AUTO=1 /I sljit ソースファイル.c sljit\sljitLir.c

gcc の場合は以下の通り。

$ gcc -DSLJIT_CONFIG_AUTO=1 -I sljit ソースファイル.c sljit/sljitLir.c

レジスタの制約

sljit レジスタの種類

レジスタには汎用レジスタと浮動小数演算レジスタの 2 種類があり、それぞれに Scratch レジスタSaved レジスタ というものがあります。

下記のソースコードの説明に従うと、Scratch レジスタとは一時的に使うレジスタで関数呼び出し後に保存されていない可能性があるもの、Saved レジスタとは関数呼び出し後であっても値が保持されているもの、となります。

/*
   Scratch (R) registers: registers whose may not preserve their values
   across function calls.

   Saved (S) registers: registers whose preserve their values across
   function calls.
*/

それぞれ、scratchsavedfscratchfsaved と区別されて名前付けされています。表にすると以下の通り。

- Scratch Saved
汎用レジスタ scratch
SLJIT_R0, SLJIT_R1, ...
saved
SLJIT_S0, SLJIT_S1, ...
浮動小数演算レジスタ fscratch
SLJIT_FR0, SLJIT_FR1, ...
fsaved
SLJIT_FS0, SLJIT_FS1, ...

ちなみに引数は SLJIT_S0SLJIT_S1SLJIT_S2 で渡されてくる決まりですが、後述する様々なアーキテクチャに対応するため、そしてそれぞれの呼び出し規約に対応するため、引数は 3 つまでしか対応していない とのことです。引数は 3 つまで。

Kinx では、ローカル変数領域に引数領域を取っておき、そこへのポインタを引数をとして渡すようにしています。これで複数の引数を疑似的に渡せます(ただし現状最大 32 個まで)。

様々なアーキへの対応

様々なアーキに対応させるには、最大公約数的な使い方をしなくてはなりません。つまり、使うレジスタの数は全てのプラットフォームで許されてる数までに制限して使います。

幸い、ソースコード上に以下のコメントがあるのでどこまで使えるかが分かります。

/*
   ...

   Note: On all supported architectures SLJIT_NUMBER_OF_REGISTERS >= 12
         and SLJIT_NUMBER_OF_SAVED_REGISTERS >= 6.
   ...
*/
/*
   ...
   Note: the following conditions must met:
         0 <= scratches <= SLJIT_NUMBER_OF_REGISTERS
         0 <= saveds <= SLJIT_NUMBER_OF_REGISTERS
         scratches + saveds <= SLJIT_NUMBER_OF_REGISTERS
         0 <= fscratches <= SLJIT_NUMBER_OF_FLOAT_REGISTERS
         0 <= fsaveds <= SLJIT_NUMBER_OF_FLOAT_REGISTERS
         fscratches + fsaveds <= SLJIT_NUMBER_OF_FLOAT_REGISTERS
   ...
*/

SLJIT_NUMBER_OF_SCRATCH_REGISTERS == SLJIT_NUMBER_OF_REGISTERS - SLJIT_NUMBER_OF_SAVED_REGISTERS なので、Scratch レジスタ x 6 個、Saved レジスタ x 6 個の 12 個までなら全てのプラットフォームで使える、ということです。

浮動小数演算レジスタのほう(float/double)のレジスタは、色々調べたところ、SLJIT_NUMBER_OF_FLOAT_REGISTERS が 6 で Saved が 0 or 1 だった(というか x64)ので、fscratch x 5、fsaved x 0 で考えておくのが良さそうです。

sljit を使うソースコードの構造

sljit でのコードジェネレーションでは、基本的に関数ポインタとして復帰してきます。つまり、関数のエントリポイントとリターンのコードを最初と最後につけます。テンプレートとしては、以下の通り。

sljit_create_compiler でコンパイラ・オブジェクトを作成し、sljit_emit_enter で関数の入り口を作成します。ここでは引数が 1 つ(SW は Signed Word)、ローカル変数領域は 0 バイトとしています(使わない例)。sljit_generate_code で実際のコードを作成して、先頭アドレスへのポインタを返します。

#include "sljitLir.h"

int main(void)
{
    /* コンパイラ・オブジェクトの作成 */
    struct sljit_compiler *C = sljit_create_compiler(NULL);

    /* 関数のエントリポイント */
    sljit_emit_enter(C, 0,
        SLJIT_ARG1(SW),         /* argument type */
        6,  /* scratch  : temporary R0-R5   */
        6,  /* saved    : safety    S0-S5   */
        5,  /* fscratch : temporary FR0-FR4 */
        0,  /* fsaved   : safety    -       */
        0   /* local    :                   */
    );

    /* コード作成部分 */
    /*
        ここに sljit でのアセンブラコードを書いていく
        尚、関数の引数は、SLJIT_S0, SLJIT_S1, ... と saved レジスタが使われる。
    */

    /* 復帰コード */
    sljit_emit_return(C, SLJIT_MOV, SLJIT_IMM, 0);

    /* コード生成 */
    void *code = sljit_generate_code(C);
    int (*func)(sljit_sw) = (int (*)(sljit_sw))code;

    /* 実際の関数呼び出し */
    int r = func(100);

    /* 後始末 */
    sljit_free_compiler(C);
    sljit_free_code(code);
    return 0;
}

簡単なサンプル

ここまでの前提知識と、いくつかの sljit のインターフェースを使って簡単なサンプルを作ってみましょう。さっきのテンプレートにコード作成部分を追加します。

サンプル(足し算プログラム)

ソースコード

3 つの引数を受け取って、全部足し合わせて結果を返します。やっていることは以下の通り。

  1. SLJIT_R0 レジスタに SLJIT_S0 レジスタと SLJIT_S1 レジスタを加えた値を代入します。
  2. SLJIT_R0 レジスタに、さらに SLJIT_S2 レジスタを加えた値を代入します。
  3. sljit_emit_returnSLJIT_R0 レジスタの値を返します。

また、以下の変更をしています。

  • sljit_emit_enter の指定で、関数内で使うレジスタ数を最小に(Scratch x 1、Saved x 3)。
  • sljit_emit_enter の指定で、引数を 3 つ受け取る(SLJIT_ARG1(SW) | SLJIT_ARG2(SW) | SLJIT_ARG3(SW))ように変更。
  • 関数プロトタイプも引数を 3 つ受け取る(int (*)(sljit_sw,sljit_sw,sljit_sw))ように変更。
#include "sljitLir.h"

int main(int ac, char **av)
{
    if (ac != 4) {
        return 1;
    }

    /* 計算に使う数値の入力 */
    int a = atoi(av[1]), b = atoi(av[2]), c = atoi(av[3]);

    /* コンパイラ・オブジェクトの作成 */
    struct sljit_compiler *C = sljit_create_compiler(NULL);

    /* 関数のエントリポイント */
    sljit_emit_enter(C, 0,
        SLJIT_ARG1(SW) | SLJIT_ARG2(SW) | SLJIT_ARG3(SW), /* argument type */
        1,  /* scratch  : temporary R0      */
        3,  /* saved    : safety    S0-S2   */
        0,  /* fscratch : temporary -       */
        0,  /* fsaved   : safety    -       */
        0   /* local    :                   */
    );

    /* コード作成部分 */
    sljit_emit_op2(C, SLJIT_ADD, SLJIT_R0, 0, SLJIT_S0, 0, SLJIT_S1, 0);
    sljit_emit_op2(C, SLJIT_ADD, SLJIT_R0, 0, SLJIT_R0, 0, SLJIT_S2, 0);

    /* 復帰コード */
    sljit_emit_return(C, SLJIT_MOV, SLJIT_R0, 0);

    /* コード生成 */
    void *code = sljit_generate_code(C);
    int (*func)(sljit_sw,sljit_sw,sljit_sw) = (int (*)(sljit_sw,sljit_sw,sljit_sw))code;

    /* 実際の関数呼び出し */
    int r = func(a, b, c);
    printf("r = %d\n", r);

    /* 後始末 */
    sljit_free_compiler(C);
    sljit_free_code(code);
    return 0;
}

sljit_add3.c というファイル名で保存し、コンパイルして実行してみましょう。Visual Studio (cl.exe) でのコンパイルの仕方は以下の通り。

$ cl /DSLJIT_CONFIG_AUTO=1 /I sljit sljit_add3.c sljit\sljitLir.c

$ sljit_add3.exe 100 101 102
r = 303

出力コード

ディスアセンブラが無いので、出力コードが良くわかりませんね。一部細工して生成されたコードを逆アセンブルできるようにした上で出力してみると以下のようになりました。

       0:   53                                          push rbx
       1:   56                                          push rsi
       2:   57                                          push rdi
       3:   48 8b d9                                    mov rbx, rcx
       6:   48 8b f2                                    mov rsi, rdx
       9:   49 8b f8                                    mov rdi, r8
       c:   4c 8b 4c 24 f0                              mov r9, [rsp-0x10]
      11:   48 83 ec 10                                 sub rsp, 0x10
      15:   48 8d 04 33                                 lea rax, [rbx+rsi]
      19:   48 03 c7                                    add rax, rdi
      1c:   48 83 c4 10                                 add rsp, 0x10
      20:   5f                                          pop rdi
      21:   5e                                          pop rsi
      22:   5b                                          pop rbx
      23:   c3                                          ret

関数のプロローグとエピローグでいくつかの push/pop がありますが、先の Saved レジスタを実現するためでしょう。レジスタ数を最大まで使うようにすると、もっと増えます。

もう少し JIT っぽいアプローチ

ソースコード

JIT コンパイルを使う意義というのは色々ありますが、即値 というのも重要な要素でしょう。コンパイルコードに直接値を埋め込んでコンパイルしてしまいます。

尚、ここでは sljit_emit_enter による関数定義とは別の方法を、紹介がてら使ってみます。

sljit には引数が無い場合、もっと簡単に関数定義するための sljit_emit_fast_enterSLJIT_FAST_RETURN を使う手段も提供されています。これは、先ほどのプロローグやエピローグを一切出力しません。

sljit_emit_fast_enter で指定したレジスタにリターン・アドレスを保存しておき、それを使ってリターンするようにするだけです。ただし、sljit_emit_enter の代わりに sljit_set_context する必要があります。使用するレジスタを明示するだけのようです。Saved レジスタの対処やローカル変数領域などは指定しても領域確保はしてくれませんが、sljit_set_context で指定した数以上のレジスタを使おうとすると assertion が入ります。

主に、JIT <=> JIT 間での簡易関数コールなどに使えます。ここには呼出規約も何もないので、好きなレジスタに値を設定して、コールすれば良いだけです。最小限のコードを出力してくれます。

ただ、ここでやってる使い方は結構危険かもしれないですね。R1/R0 レジスタが呼び出し元レジスタとして割り付けられてた場合、最悪クラッシュします。今回のケースでは多分大丈夫(ちゃんと見てないけど直観)。通常は JIT <=> JIT 間で使うのが良いですね。

こんな風に書けます。

int main(int ac, char **av)
{
    if (ac != 4) {
        return 1;
    }
    int a = atoi(av[1]), b = atoi(av[2]), c = atoi(av[3]);

    /* コンパイラ・オブジェクトの作成 */
    struct sljit_compiler *C = sljit_create_compiler(NULL);

    /* 関数のエントリポイント */
    sljit_set_context(C, 0, 0, 2, 0, 0, 0, 0); // SLJIT_R0 と SLJIT_R1 しか使わない.
    sljit_emit_fast_enter(C, SLJIT_R1, 0);     // SLJIT_R1 にリターン・アドレスを保存.

    /* コード作成部分 */
    sljit_emit_op2(C, SLJIT_ADD, SLJIT_R0, 0, SLJIT_IMM, a, SLJIT_IMM, b);
    sljit_emit_op2(C, SLJIT_ADD, SLJIT_R0, 0, SLJIT_R0,  0, SLJIT_IMM, c);
    /* 戻り値は SLJIT_R0 */

    /* 復帰コード */
    sljit_emit_op_src(C, SLJIT_FAST_RETURN, SLJIT_R1, 0); // SLJIT_R1 のアドレスにリターン

    /* コード生成 */
    void *code = sljit_generate_code(C);
    int (*func)() = (int (*)())code;

    /* 実際の関数呼び出し */
    int r = func();
    printf("r = %d\n", r);

    /* 後始末 */
    sljit_free_compiler(C);
    sljit_free_code(code);
    return 0;
}

動作は同じです。

$ cl /DSLJIT_CONFIG_AUTO=1 /I sljit sljit_add3.c sljit\sljitLir.c

$ sljit_add3.exe 100 101 102
r = 303
$ sljit_add3.exe 1000 1001 1002
r = 3003

なぜ 2 回やったかというと、動的コンパイルの良さがここに表れるからですね。次の逆アセンブルリストを見てみましょう。

出力コード

最初のコマンドラインではこうです。

       0:   5a                                          pop rdx
       1:   48 c7 c0 64 00 00 00                        mov rax, 0x64
       8:   48 83 c0 65                                 add rax, 0x65
       c:   48 83 c0 66                                 add rax, 0x66
      10:   52                                          push rdx
      11:   c3                                          ret

関数のプロローグやエピローグが無いのはもちろんですが、0x64, 0x65, 0x66 という値が直接埋め込まれています。コール直後はスタックにリターンアドレスがあるので、rdx レジスタに保存しておいて、最後にスタックにリターンアドレスをプッシュして ret します。ret 命令はスタックトップの値をアドレスと見立ててそこにジャンプする命令です。

では 2 回目の実行ではどうなるでしょう。

       0:   5a                                          pop rdx
       1:   48 c7 c0 e8 03 00 00                        mov rax, 0x3e8
       8:   48 81 c0 e9 03 00 00                        add rax, 0x3e9
       f:   48 05 ea 03 00 00                           add rax, 0x3ea
      15:   52                                          push rdx
      16:   c3                                          ret

コード自体が変わりました。こういうやり方ができるので、JIT は事前コンパイル(AOT)より速い場合があるのです。

おわりに

今回は sljit の関数の作り方でした。一部命令(ADD)も使いましたが、他にも命令は色々あります。また、プログラムを作るうえでは条件分岐は欠かせません。そういうのも含めて、使い方の記録を残しておこうと思います。

では、また続き(あるのかなー)をお楽しみに。

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

デフォルト引数が素敵【2020/11/20】

javascriptでは、デフォルト引数が設定できる

ある条件がtrueの時だけ、モデルを呼び出して処理をしたい

  • メソッド側
model.method(data, true) 
model.method(data)
  • モデル側(model.js)
method(data, isHTML = false) {
    if(isHTML) {
      console.log(data)
    }
  }

とすれば、メソッド側でtrue/falseの分岐を一々書かなくても、必要なものにtrueを追加で引数に入れるだけで良い。
コードがシンプルになる。

参照: MDN-Docs

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

JavaScriptでEventDispatcher作ってみた

はじめに

TypescriptでEventDispatcher作ってみた(javascript)
これがすごくに気にっているので、ES6用にJavaScript移植。

EventDispathcer.js
export class EventDispatcher {
    constructor() {
        this.listeners = {};
    }
    /**
     * イベントをイベントフローに送出します。
     * @param {Event}event ベントフローに送出されるイベントオブジェクトです。
     */
    dispatchEvent(event) {
        let e;
        let type;
        if (event instanceof Event) {
            type = event.type;
            e = event;
        } else {
            type = event;
            e = new Event(type);
        }

        if(this.listeners[type] != null){
            e.currentTarget = this;
            for (let i = 0; i < this.listeners[type].length; i++){
                let listener = this.listeners[type][i];
                try {
                    listener.handler(e);
                } catch (error) {
                    if (window.console) {
                        console.error(error.stack);
                    }
                }
            }
        }
    }

    /**
     * イベントリスナーオブジェクトを EventDispatcher オブジェクトに登録し、リスナーがイベントの通知を受け取るようにします。
     * @param {string}type イベントのタイプです。
     * @param {function}callback イベントを処理するリスナー関数です。この関数は、次の例のように、Event オブジェクトを唯一のパラメーターとして受け取り、何も返さないものである必要があります。
     * @param {number}priority 優先度
     */
    addEventListener(type, callback, priority = 0) {
        if(this.listeners[type] == null){
            this.listeners[type] = [];
        }
        this.listeners[type].push(new EventListener(type, callback, priority));
        this.listeners[type].sort(function (listener1, listener2) {
            return listener2.priority - listener1.priority;
        });
    }

    /**
     * オブジェクトからリスナーを削除します。
     * @param {string}type イベントのタイプです。
     * @param {function}callback 削除するリスナーオブジェクトです。
     */
    removeEventListener(type, callback) {
        if(this.hasEventListener(type, callback)) {
            for(let i=0; i < this.listeners[type].length; i++){
                let listener = this.listeners[type][i];
                if(listener.equalCurrentListener(type, callback)) {
                    listener.handler = null;
                    this.listeners[type].splice(i,1);
                    return;
                }
            }
        }
    }

    /**
     * リスナーの初期化
     */
    clearEventListener() {
        this.listeners = {};
    }

    /**
     * EventDispatcher オブジェクトに、特定のイベントタイプに対して登録されたリスナーがあるかどうかを確認します。
     * @param {string}type イベントのタイプです。
     * @returns {boolean} 指定したタイプのリスナーが登録されている場合は true、それ以外の場合は false です。
     */
    containEventListener(type) {
        if (this.listeners[type] == null) return false;
        return this.listeners[type].length > 0;
    }

    /**
     * EventDispatcher オブジェクトに、特定のイベントタイプとリスナー関数が登録されたリスナーがあるかどうかを確認します。
     * @param {string}type イベントのタイプです。
     * @param {function}callback
     * @returns {boolean} 指定したタイプとリスナー関数をもつリスナーオブジェクトが登録されている場合は true、それ以外の場合は false です。
     */
    hasEventListener(type, callback) {
        if(this.listeners[type] == null) return false;
        for(let i=0; i < this.listeners[type].length; i++){
            let listener = this.listeners[type][i];
            if(listener.equalCurrentListener(type, callback)) {
                return true;
            }
        }
        return false;
    }
}

/**
 * リスナーの登録オブジェクト
 */
export class EventListener {
    constructor( type = null,  handler = null,  priority = 0) {
        this.type = type;
        this.handler = handler;
    }
    equalCurrentListener(type, handler) {
        if (this.type == type && this.handler == handler) {
            return true;
        }
        return false;
    }
}

/**
 * イベントリスナーにパラメーターとして渡す Event オブジェクトを作成します。
 */
export class Event {
    constructor( type = null,  value = null) {
        this.type = type;
        this.value = value;
        Event.COMPLETE = "complete";
        Event.CHANGE_PROPERTY ="changeProperty";
    }
}

継承したEventは以下の通り。
定数の書き方はclass内はいけないっぽい。

CommonEvent.js
import {Event} from "./EventDispatcher";
export class CommonEvent extends Event{
    constructor(type = null, value = null) {
        super(type, value);
    }
}
//Constant
CommonEvent.MODEL_COMPLETE_EVENT = "model_complete_event";

currentTargetも取れるし、変数も遅れるし、ザクザク開発できそう。
ES6かTypeScrptか悩ましい。

git
https://github.com/kawamurashin/JavaScriptEventDispacher

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

過去と未来、全てのデグレを生まれる前にこの世から消し去りたい

因果律そのものに対する反逆だ!

「――!」
「その祈りは――そんな祈りが叶うとすれば、それは時間干渉なんてレベルじゃない!君は、本当に神になるつもりかい?」

「神様でも何でもいい」
「今日までデグレと戦ってきたみんなを、希望を信じた開発チームを、私は泣かせたくない。最後まで笑顔でいてほしい」

(なお筆者はまどマギ未見のためこれ以降は普通のテンションでお送りします)

やりたいこと

デグレを防ぐと言えば回帰(リグレッション)テストですが、「過去と未来」とまで言ってしまったからには、実装前の段階からE2Eテストを書き始め、継続してメンテナンスできることを目標にします 1
流れ的にはだいたい下記のようなイメージになります。

  • アイディアだけがある段階から自動テストのスクリプトを書く
  • 動くアプリケーションを実装する
  • テストが(そこそこ)ちゃんと動く
  • 機能追加などの際にもテストが壊れにくい

使うツール

  • NodeJS, npm (事前にインストールしておいてください)
  • CodeceptJS

準備

事前にNodeJSをインストールしておいてください。

$ mkdir degure-dame-zettai
$ cd degure-dame-zettai
$ npm init -y
$ npm install --save-dev codeceptjs playwright
$ npx codeceptjs init 

最後の npx codeceptjs init ではCodeceptJSの設定を求められますが、すべてデフォルトのままで大丈夫です2
面倒な人は yes '' | npx codeceptjs init とすれば全部自動で終わります。すごいね。

今回作るアプリ

こんなクソ記事であまり凝ったものを作ってもしょうがないので 今回はごくかんたんなフォーム入力のサンプルを作ります。「お名前」と書かれたフォームに入力すると、 お前の名前は${xxx}です と表示するだけのアプリです。

jOgCedZZtq.gif

仮実装

テストの仮実装

想定されるユースケースを元にテストを先に書いてしまいます。ユースケースと言っても、出来ることは入力と送信だけですので、テストケースは一つだけで良いでしょう。

  • http://localhost にアクセスする
  • お名前tsuemura と入力する
  • 送信 をクリックする
  • ページに おまえの名前はtsuemuraです と表示されていることを確認する

これをテストコードに直すと以下のようになります。

form_test.js
Feature('入力フォーム')

Scenario('入力から送信完了まで', ({ I }) => {
  I.amOnPage('http://localhost')
  I.fillField('お名前', 'tsuemura')
  I.click('送信')
  I.see('おまえの名前はtsuemuraです')
})

このコードを sample_test.js として保存し、ターミナルから以下のコマンドを実行しましょう。

$ npx codeceptjs run sample_test.js

image.png

……当たり前ですが、Webアプリをまだ作ってないので、テストは失敗しますね!

アプリの仮実装

というわけで、最低限動くものを作ってみます。

index.php
<?= $_POST['name'] ? "おまえの名前は${_POST['name']}です" : '' ?>
<form method="POST">
    <label for="name" >お名前</label>
    <input type="text" id="name" name="name">
    <button type="submit">送信</button>
</form>

突然生のPHPコードが出てきて度肝を抜かれますが、仮なので一旦これでいいことにします。上記のコードを index.php として保存しておいてください。

このコードを実際にWebアプリケーションとして立ち上げるにはPHPとWebサーバが必要になりますが、人類の大半はPHPの実行環境を手元に用意していないと思うので、下記のコマンドでPHPとApacheを立ち上げておくと良いでしょう。

$ docker run -d -p 8080:80 -v "$PWD":/var/www/html php:7.2-apache

実行すると、 http://localhost:8080/index.php で先程作ったPHPファイルにアクセスできるようになります。

自動テストを動かす

Webアプリ側が(雑ですが)動くようになったので、先程書いたテストを動かしてみましょう。URLのみ、先程立ち上げた http://localhost:8080/index.php に直しておきます。

form_test.js
Feature('入力フォーム')

Scenario('入力から送信完了まで', ({ I }) => {
  I.amOnPage('http://localhost:8080/index.php')  // ここだけ直す
  I.fillField('お名前', 'tsuemura')
  I.click('送信')
  I.see('おまえの名前はtsuemuraです')
})

テストコードを修正したら、テストを実行します。

$ npx codeceptjs run sample_test.js

image.png

ブラウザが起動し、テストがpassしたら成功です。

ちょっと解説:CodeceptJSのロケータについて

このテストコード、「なんで動くの?」と思う方もいらっしゃるのではないでしょうかと思うので、少し説明しておきます。

通常、Webアプリのテストコードと言えば、 idclass などのCSSセレクタを使って要素を特定しますね。

// idがhogeの要素を取得する
document.querySelector('#hoge')

ですが、今回のテストコードにはCSSセレクタは一つも出てきませんでした。

// 「送信」is 何
I.click('送信')

実は、CodeceptJSはCSSセレクタでもXPathでも無い文字列がロケータ(要素探索のキー)として指定されると、表示されている文字列を用いて要素を探索します。例えば、クリックであれば <a> <button> などのクリックできそうなタグの中から、指定された文字列を持つ要素を探し、最初に見つかったものを返します。

また、アサーションは要素を指定せず、ページ全体をクロールして目当ての文字列を探しています。

Scenario('入力から送信完了まで', ({ I }) => {
  I.amOnPage('http://localhost:8080/index.php')

  /* "お名前" という文字列を持つlabelに紐づくinputまたはtextareaを探す */
  I.fillField('お名前', 'tsuemura')  

  /* "送信" という文字列を持つaまたはbuttonを探す */
  I.click('送信') 

  /* ページ全体からこの文字列を含む要素の有無を調べる */
  I.see('おまえの名前はtsuemuraです')
})

これらの仕組みにより、Webアプリケーション側の実装が未完了だったとしても、「そこそこ動く」自動テストが作れます。

大規模なリファクタリングを行う

急にPHPに嫌気がさしたのでReactで全面的に作り直します

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>フォームのサンプル</title>
  <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/light.min.css">
</head>
<body>
  <div id="app"></div>
  <script crossorigin src="./index.js" type="text/babel"></script>
</body>
</html>
index.js
"use strict";

const e = React.createElement;

class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = { };
  }

  handleSubmit = e  => {
    e.preventDefault()
    this.setState({
      name: e.target.name.value
    })
  }

  render() {
    const text = this.state.name ? `お前の名前は${this.state.name}です` : ''
    return (
      <div>
        <p>{text}</p>
        <form onSubmit={this.handleSubmit}>
          <label htmlFor="name">お名前</label>
          <input id="name" name="name" />
          <button type="submit">送信</button>
        </form>
      </div>
    )
  }
}

const domContainer = document.querySelector("#app");
ReactDOM.render(e(Form), domContainer);

上記のコードを、それぞれ index.html index.js として保存しておいてください。先ほどPHPのアプリケーションを動かすために起動したDockerコンテナで、これらのファイルも配信することが出来ます。 http://localhost:8080/index.html にアクセスすると、先ほどのPHPとほぼ変わらないものが立ち上がります。

image.png

さて、この状態でテストを実行するとどうなるでしょうか。テストコードのURLを index.html に変更してテストを回してみましょう。

form_test.js
Feature('入力フォーム')

Scenario('入力から送信完了まで', ({ I }) => {
  I.amOnPage('http://localhost:8080/index.html')  // ここだけ直す
  I.fillField('お名前', 'tsuemura')
  I.click('送信')
  I.see('おまえの名前はtsuemuraです')
})
# 実行する
$ npx codeceptjs run sample_test.js

image.png

「お名前」というラベルを持つテキストフィールドが見つからず、テストが失敗してしまいました。
これはReactを使っているとかは全く関係なく、作り変える過程で <label><input> の紐付けを消してしまったからです。

<!-- labelに指定した "for" 属性によってinputとの紐付けが出来ている -->
<label for="name">お名前</label>
<input id="name" name="name" />

<!-- labelとinputとの紐付けが出来ていない -->
<label>お名前</label>
<input name="name" />

Webアプリ側を直すことも可能ですが、ここは一つテストコード側で工夫してみましょう。input要素の探索戦略を変えてみます。ここでは、「特定の文字列を持つlabel要素の直後にあるinput要素」という条件で探索してみましょう。

const locateInput = text => (
  locate('input').after(
      locate('label').withText(text)
))

これをテストコード側で利用してみます。

form_test.js
Feature('入力フォーム')

/* input要素の探索方法を定義 */
const locateInput = text => (
  locate('input').after(
      locate('label').withText(text)
))

Scenario('入力から送信完了まで', ({ I }) => {
  I.amOnPage('http://localhost:8080/index.html')
  I.fillField(locateInput('お名前'), 'tsuemura')  // 定義したlocateInputを使う
  I.click('送信')
  I.see('おまえの名前はtsuemuraです')
})
# 実行する
$ npx codeceptjs run sample_test.js

image.png

無事にテストが成功するようになりました。

要素内の文字列とページの構造を使ってテストを記述することで、テストコードから実装依存の部分を減らすことができます。
今回の場合、仕様自体が単純で、やったこともほぼ元のPHPコードからReactコードへの載せ替えだったのであまりメリットを感じられないかもしれませんが、 システム移行プロジェクトなどでは実装が大きく変わったときにも動作する テストコードのメリットを感じやすいと思います。

機能を追加する

さて、このアプリに機能を追加し、最大3人まで入力が出来るようにしましょう。
なぜかは分かりません。分からなくてもやらなければいけないときがあるのです。人生とはそういうものです3

image.png

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>フォームのサンプル</title>
  <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/light.min.css">
</head>
<body>
  <!-- タグを3つに増やした -->
  <div id="form1"></div>
  <div id="form2"></div>
  <div id="form3"></div>
  <script crossorigin src="./index.js" type="text/babel"></script>
</body>
</html>
index.js
"use strict";

const e = React.createElement;

class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = { };
  }

  handleSubmit = e  => {
    e.preventDefault()
    this.setState({
      name: e.target.name.value
    })
  }

  render() {
    const header = `${this.props.id}人目`  /* 何人目かわからないと不便だからね */
    const text = this.state.name ? `おまえの名前は${this.state.name}です` : ''
    return (
      <div>
        <h3>{header}</h3>
        <p>{text}</p>
        <form onSubmit={this.handleSubmit}>
          <label>お名前</label>
          <input name="name"/>
          <button type="submit">送信</button>
        </form>
      </div>
    )
  }
}

/* 増やしたタグそれぞれにアタッチしていく */
ReactDOM.render(e(Form, { id: 1 }), document.querySelector("#form1"));
ReactDOM.render(e(Form, { id: 2 }), document.querySelector("#form2"));
ReactDOM.render(e(Form, { id: 3 }), document.querySelector("#form3"));

コピペにより効率よく3倍に増やすことができました。
テストコードも同様に3倍に増やしていきます。

form_test.js
Feature('入力フォーム')

const locateInput = text => (
  locate('input').after(
      locate('label').withText(text)
))

Scenario('入力から送信完了まで', ({ I }) => {
  I.amOnPage("http://localhost:8080/index.html");

  // 1人目
  I.fillField(locateInput("お名前"), "hoge");
  I.click("送信");
  I.see("おまえの名前はhogeです");

  // 2人目
  I.fillField(locateInput("お名前"), "fuga");
  I.click("送信");
  I.see("おまえの名前はfugaです");

  // 3人目
  I.fillField(locateInput("お名前"), "poge");
  I.click("送信");
  I.see("おまえの名前はpogeです");
})

勘のいい人はお気づきでしょうが、このテストはうまく動きません
どのフォームに入力するかを指定していない為、全て1人目のところに入力してしまいます。

xLHnJKIYsS.gif

特定のフォームの中でのみ操作を行うために、 within を使って操作のスコープを限定します。
まずは、フォームを指し示すロケータを作ります。

const locateForm = text => (
  locate('form').withText(text)
)

次に、シナリオを次のように修正します。
within の引数として locateForm('1人目') locateForm('2人目') のように操作対象のフォームを指定しているので、指定した文字列を持つ <form> 要素の中から お名前 送信 などの文字列を含む要素を探索するようになっているはずです。

form_test.js
...

Scenario('入力から送信完了まで', ({ I }) => {
  I.amOnPage("http://localhost:8080/index.html");

  within(locateForm('1人目'), () => {
    I.fillField(locateInput("お名前"), "hoge");
    I.click("送信");
    I.see("おまえの名前はhogeです");
  })

  within(locateForm('2人目'), () => {
    I.fillField(locateInput("お名前"), "fuga");
    I.click("送信");
    I.see("おまえの名前はfugaです");
  })

  within(locateForm('3人目'), () => {
    I.fillField(locateInput("お名前"), "poge");
    I.click("送信");
    I.see("おまえの名前はpogeです");
  })
})
# 実行する
$ npx codeceptjs run sample_test.js

WIEsUTH2rj.gif

上手く行きましたね!
先ほどとは異なり、1人目、2人目、3人目にそれぞれ入力することができました。
元のコードに書かれていた // 1人目 // 2人目 のような余計なコメント行も消すことが出来て読みやすくなりました。

おわりに

いかがでしたか?これで希望を信じた開発チームも最後まで笑顔でいられそうですね!

まどマギは定年までには見ようと思います!それでは、良いテストライフを!


  1. BDD(Behavior Driven Development: 振る舞い駆動開発) や ATDD(Acceptance Test Driven Development: 受け入れテスト駆動開発) と呼ばれるものです。 

  2. デフォルトの場合、自動操作のドライバには Playwright が使われます。 

  3. 諸説あり 

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

【jQuery】datepickerを使って、カレンダーを実装!

問い合わせフォームの作成時に、カレンダーにカスタムが必要で使用したdatepickerの概要を説明します。

【目次】

1.datepickerでできること
2.datepickerでカレンダーを作ってみる
3.付けられるオプション

1.datepickerでできること

前提として、カレンダーから日付をワンクリックで選択できる機能を使うことができる他に、ドロップダウンリストから選択できる機能など様々なカスタムができるものである。
それ以外にもオプションで、
・英語/日本語表記にする
・指定の日付フォーマットを使用できる
・曜日や時間も表示させられる
・カレンダーを表示させる時のアニメーションを設定できる
・過去の日付は表示させない ...etc

2.datepickerでカレンダーを作ってみる

①まず、必要なソースを打ち込む。※そのままコピペでOK!
1.https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
2.
3.https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.min.css
1.<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.min.css">
2. 
3.<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>

これで、jQueryUIが使えるようになりまいした!

②inputタブで簡単なカレンダー表記をさせてみましょう!
1.<input type="text" id="datepicker">
1.$('#datepicker').datepicker();

これでカレンダー設置完了です!
こんな感じ↓
記事用スクショ.png

【!!これで完成!!】

あとは、好きなようにカスタムしてみてください★

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

addEventListenerメソッドについて

はじめに:プログラミングを学習する上で、メモとして投稿しておりますので、間違いなどがあればご指摘いただけると幸いです。

addEventListenerメソッドとは

 1つのイベントや1つの要素に対して、複数のイベントハンドラを設定できるメソッドです。

 HTMLの中に、onclick等を記述してイベントハンドラを設定することもできるが、1つの要素やイベントに対して、1つのイベントハンドラしか設定できないので、イベントハンドラを複数設定したいときに利用する。
 また、本来レイアウトを定義すべきHTMLにJavaScriptのコードを記述することはモダンな書き方ではないのでaddEventListenerメソッドの利用を推奨しているらしいです、、、。

構文

要素オブジェクト.addEventListener(イベントの種類、function(){
イベントハンドラ
}, false);

addEventListener.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>addEventListener</title>
  </head>
  <body>
    <ul id="lists">
    </ul>
    <input id="result" type="text">
    <input id="button" type="button" value="" >

    <script src="js/addEventListener.js"></script>
  </body>
</html>

addEventListener.js
    var e =document.getElementById('button');
    e.addEventListener('click', function(){
      //イベントハンドラ指定
    }, false);

終わりに

初投稿なりにコードを挿入してみたりと、少し工夫してみました。まだまだ学習することがたくさんある為、日々の積み重ねを大切にし、アウトプット重視で頑張りたいと思います、、。

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

react-hook-formの基本

久しぶりの投稿になります。今後は週一記事以上は書けるように頑張ります!今回はreact-hook-formについての記事です。公式ドキュメントの内容を、自分なりにまとめてみました。より詳しく知りたい方は公式ドキュメントも見てみて下さい!

参考:公式ドキュメント

react-hook-formとは

inputとかのformに関係するデータを使う時に、Stateを使わなくて良くて、レンダリング回数を減らせる!

インストール

## yarnの場合
yarn add react-hook-form

## npmの場合
npm install react-hook-form

以上!TypeScriptの型定義も含まれてます。

↓↓↓簡単な例

import React from 'react';
import { useForm } from 'react-hook-form';
import { ErrorMessage } from '@hookform/error-message';

type FormInputs = {
  name: string;
  email: string;
};

export const Demo = () => {
  const { register, errors, handleSubmit, reset } = useForm<FormInputs>();

  const on_submit = (data: FormInputs) => {
    console.log(data);
    reset();
  };

  return (
    <form onSubmit={handleSubmit(on_submit)}>
      <input name="name" ref={register} />
      <input
        name="email"
        ref={register({
          required: true,
          maxLength: 60,
          pattern: {
            value: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
            message: 'メールアドレスの形式が不正です',
          },
        })}
      />
      <button type="submit">Submit</button>
      <ErrorMessage errors={errors} name="email" message={errors.email?.message} />
    </form>
  );
};

↓この例だと入力した値はこんな風に出力される。それぞれの機能を解説して行きます。

{name: 入力した値,email: 入力した値}

useForm

react-hook-formからインポートするものはいくつかあるが、ほとんどuseFormから取得することが多い。

const { register, reset, handleSubmit } = useForm({
  mode: onSubmit,
  defaultValues: {name: "aaa", email: test@test.com}
})

引数にオブジェクトで色んな設定みたいなのができる

mode 初回のバリデーションの実行タイミング
(初期値:onSubmit)
onChange or onBlur or onSubmit or onTouched or all
reValidateMode 初回のバリデーション実行後、次の実行タイミング
(初期値: onChange)
onChange or onBlur or onSubmit
defaultValues 初期値をオブジェクトで入力します
(resetでdefaultValuesの値に戻る)
{name属性の値: 初期値,...}
resolver 他のvalidationライブラリを使う時
shouldUnregister 入力要素がアンマウントされたら、値の参照を解除、falseなら入力された値を保持
(初期値: true)
true or false

register

inputなどに入力された値を参照するために使う。name属性を設定する必要があり、registerはrefに入れる。UIライブラリを使う場合はcontrol参照。一番使う。

↓nameの付け方によって、dataの形が変わる.

name="firstName" →{ firstName: 'value'}
name="firstName[0]" →{ firstName: [ 'value' ] }
name="name.firstName" →{ name: { firstName: 'value' } }
name="name.firstName[0]" →{ name: { firstName: [ 'value' ] } }

引数にオブジェクトで色んなバリデーションが設定できる.

required 必須項目にするか true or false or エラーメッセージ
maxLength 最大文字数 {value: number, message: エラーメッセージ}
minLenght 最小文字数 {value: number, message: エラーメッセージ}
max 最大データ量 {value: number, message: エラーメッセージ}
min 最小データ量 {value: number, message: エラーメッセージ}
pattern 文字の形式 {value: 正規表現, message: エラーメッセージ}
validate カスタム {任意の名前: value => booleanを返す式 or 関数 || エラーメッセージ}
<input
   name="email"
   ref={register({
   required: true,
   maxLength: 60,
   pattern: {
       value: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
       message: 'メールアドレスの形式が不正です',
      },
   })}
   validate: {
       lessThanTen: value => parseInt(value, 60) < 60 || '60文字以内で入力して下さい'
   }
/>

unregister

引数に入れたnameはregisterで参照しなくなる。配列を入れて、複数も可能。unregister(['name','email'])

<button onClick={() => unregister("email")}>email_unregister</button>

errors

registerなどで設定したバリデーションを通らなかった時にエラーのデータが入る。エラーメッセージを設定している場合は、それも取得できる。

<input name="name" ref={register({required: '入力して下さい'}) />
{errors.name && <p>errors.name.message</p>}

watch

第一引数入れたnameの値を監視。第二引数は初期値を設定可能。unregisterと同様に配列に入れて複数も可能。何も入れないと、全てのnameの値を取得。レンダリング回数は増える。

const watch_name = watch("name", false);
const watch_all_fields = watch();
const watch_fields = watch(["name", "email"]);

handleSubmit

ラップした関数にformのdataをオブジェクトの形で渡してくれる。on_submitの引数にdataがあるのはそのため。
event.preventDefalut()も必要なし。

const on_submit = (data: FormInputs) => {
    console.log(data);
};

return (
    <form onSubmit={handleSubmit(on_submit)}>
...

reset

formの中の状態を初期化する関数。defaultValueが設定されていたら、その値になる

const on_click =()=>{
  reset();
}

setError

第一引数に指定したnameにエラーをセットする関数。第二引数にtype(requiredやminLengthなど)とエラーメッセージをオブジェクトとしていれる。
typeを変えるとどう変わるかが、よく分からないので分かる方いたら教えて下さい!

<button onClick={() => setError("email", { type: "required", message: "" })}>

clearErrors

引数に入れたnameのerrorを消す関数。こちらも配列で複数指定できる。

<button onClick={() => clearErrors("name")}>

getValues

引数に入れたnameの現在の値を取得。こちらも配列に入れて複数、空なら全てのnameの値を取得。watchとの違いは常に監視せず、再レンダリングもされない。ボタンを押して時に取得するとかならこっちの方がパフォーマンス的に良さそう。

const values = getValues();
const single_value = getValues("name");
const multiple_values = getValues(["name", "email"]);

setValue

第一引数に入れたnameに第二引数に入れた値をセットする関数。その際にshouldValidateでバリデーションを実行するかどうか、shouldDirtyで変更後に変更の有無の判定を行うかどうか

<button onClick={()=> setValue('name', 'value', { shouldValidate: true, shouldDirty: true })}>

trigger

引数に入れたnameのバリデーションを実行する関数。こちらも配列で複数、空なら全体。

<button type="button" onClick={() => { trigger("lastName") }}>Trigger</button>

control

Controllerのnameの値を参照するのに使う。バリデーションはできない。useWatchuseFieldArrayにも使う。

<Controller
  as={<TextInput />}
  control={control}
  name="name"
/>

formState

form内の入力の有無や送信の状態などを取得できる。formState.isSubmittingはbuttonにローディングをつける時によく使う。

isDirty 全体で何かしら変更があったらtrueになる true or false
dirtyFields それぞれの入力要素で何かしらの変更があったらtrue {name属性の値: true or false,...}
touched それぞれの入力要素で何かしらの操作をしたらtrueになる
(フォーカスするだけでも、onBlurのタイミングで反映される)
{name属性の値: true or false,...}
isSubmitted formが送信されたらtrue、resetでfalseに戻る true or false
isSubmitSuccessful formの送信が成功したらtrue true or false
isSubmitting formの送信中にtrue true or false
submitCount formの送信された回数 number
isValid 何かしらエラーがあったらtrue
(modeがonChange or onBlurの時のみ)
true or false
errors バリデーションのエラーのオブジェクトが入る {name属性の値: {
type: "required",
message: "",
ref: <input name="name属性の値" type="text">,
},...}
import React from "react";
import { useForm } from "react-hook-form";

export const Demo = () => {
  const { register, handleSubmit, errors, formState } = useForm();
  const on_submit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="name" ref={register} />
      {isDirty && <p>Form is dirty.</p>}
      {isSubmitting && <span>Submitting...</span>}
      <button type="submit" />
    </form>
  );
}

Controller

UIライブラリを使う時に使用。registerだと参照できないことが多い。(MaterialUIのTextFieldとかはinputRefregisterを入れて参照できる)。ruleregisterの中に書く感じでバリデーションを書ける。defaultValueも設定可能
asの中に全部入れちゃうのが簡単

<Controller
  as={<TextInput />}
  control={control}
  name="name"
  rules={{required: true}}
  defaultValue="aaa"
/>

<Controller
  control={control}
  name="email"
  render={({ onChange, onBlur, value, name, ref }) => (
    <TextField
      onBlur={onBlur}
      onChange={onChange}
      checked={value}
      inputRef={ref}
    />
  )}
/>

ErrorMessage

指定したnameのエラーメッセージを表示できる。messageはstringならOK

<ErrorMessage errors={errors} name="email" message={errors.email?.message} />

useFormContext,FormProvider

FormProvideruseFormregistererrorsなどをまとめて渡して、ラップされたコンポーネントでuseFormContextを使うと、その渡された値を使うことができる。

import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";

export const Demo = () => {
  const methods = useForm();
  const on_submit = data => console.log(data);

  return (
    <FormProvider {...methods} >
      <form onSubmit={methods.handleSubmit(onSubmit)}>
        <Input />
        <button type="submit" />
      </form>
    </FormProvider>
  );
}

const Input = () => {
  const { register } = useFormContext();
  return <input name="name" ref={register} />;
}

useWatch

controlを渡すことでwatchを別のコンポーネントでも使えるようにした感じ。

import React from "react";
import { useForm, useWatch } from "react-hook-form";

const Watch = ({ control }) => {
  const name = useWatch({ control, name: 'name', defaultValue: 'aaa' });

  return <div>{name}</div>;
}

type FieldInputs = {
    name: string;
}

export const Demo = () => {
  const { register, control, handleSubmit } = useForm<FieldInputs>();

  return (
    <form onSubmit={handleSubmit(data => console.log("data", data))}>
      <input ref={register} name="name" />
      <Watch control={control} />
      <button type="submit" />
    </form>
  );
}

useFieldArray

下の写真みたいな感じで、同じinputなどを増減させたい時に使えます。公式ドキュメントから、YoutubeのuseFieldArryの紹介動画に飛べるので見るとわかりやすいです。

image.png

↓こんな感じでuseFormのように色んなメソッドを使えます。nameとcontrolが必要です。defaultValueはuseFormで設定できます。

const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({
    control,
    name: "demo"
});

↓3列目は例です。

fields mapを使ったりして、入力要素をレンダリングするのに使用。idやdefaultValueが入ったオブジェクト
append fieldsの最後にinputを追加 append({name: "aaa"})
prepend fieldsの先頭にinputを追加 prepend({name: "aaa"})
insert fieldsの特定の位置にinputを追加 insert(3,{name: "aaa"})
swap inputの位置を入れ替える swap(1,2)
move inputの位置を動かす move(1,2)
remove 特定のinputを削除、引数無しで全削除 remove(1) remove()
import React from 'react';
import { useForm, useFieldArray } from 'react-hook-form';

export const Demo = () => {
  const { register, control, handleSubmit } = useForm({
    defaultValues: {
      demo: [{ name: 'name', email: 'email@email.com' }],
    },
  });
  const { fields, append, prepend, remove } = useFieldArray({
    control,
    name: 'demo',
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      {fields.map((item, index) => (
        <div key={item.id}>
          <input
            name={`demo[${index}].name`}
            ref={register}
            defaultValue={item.name}
          />
          <input
            name={`demo[${index}].email`}
            ref={register}
            defaultValue={item.email}
          />
          <button type="button" onClick={() => remove(index)}>
            Delete
          </button>
        </div>
      ))}
      <button
        type="button"
        onClick={() =>
          append({ name: 'appendName', email: 'append@email.com' })
        }
      >
        append
      </button>
      <button
        type="button"
        onClick={() =>
          prepend({
            name: 'prependName',
            email: 'prepend@email.com',
          })
        }
      >
        prepend
      </button>
      <input type="submit" />
    </form>
  );
};


終わりに

ここまで読んでいただきありがとうございます!
少しでもreact-hook-formの理解が深まれば幸いです。自分も書きながら、react-hook-formの便利さが機能が理解できました。
質問、感想、改善点などをコメントでいただけるとモチベーションにつながりますのでよければお願いします!

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

react-hook-formの使い方を解説!

久しぶりの投稿になります。今後は週一記事以上は書けるように頑張ります!今回はreact-hook-formについての記事です。公式ドキュメントの内容を、自分なりにまとめてみました。より詳しく知りたい方は公式ドキュメントも見てみて下さい!

参考:公式ドキュメント

react-hook-formとは

inputとかのformに関係するデータを使う時に、Stateを使わなくて良くて、レンダリング回数を減らせる!

インストール

## yarnの場合
yarn add react-hook-form

## npmの場合
npm install react-hook-form

以上!TypeScriptの型定義も含まれてます。

↓↓↓簡単な例

import React from 'react';
import { useForm } from 'react-hook-form';
import { ErrorMessage } from '@hookform/error-message';

type FormInputs = {
  name: string;
  email: string;
};

export const Demo = () => {
  const { register, errors, handleSubmit, reset } = useForm<FormInputs>();

  const on_submit = (data: FormInputs) => {
    console.log(data);
    reset();
  };

  return (
    <form onSubmit={handleSubmit(on_submit)}>
      <input name="name" ref={register} />
      <input
        name="email"
        ref={register({
          required: true,
          maxLength: 60,
          pattern: {
            value: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
            message: 'メールアドレスの形式が不正です',
          },
        })}
      />
      <button type="submit">Submit</button>
      <ErrorMessage errors={errors} name="email" message={errors.email?.message} />
    </form>
  );
};

↓この例だと入力した値はこんな風に出力される。それぞれの機能を解説して行きます。

{name: 入力した値,email: 入力した値}

useForm

react-hook-formからインポートするものはいくつかあるが、ほとんどuseFormから取得することが多い。

const { register, reset, handleSubmit } = useForm({
  mode: onSubmit,
  defaultValues: {name: "aaa", email: test@test.com}
})

引数にオブジェクトで色んな設定みたいなのができる

mode 初回のバリデーションの実行タイミング
(初期値:onSubmit)
onChange or onBlur or onSubmit or onTouched or all
reValidateMode 初回のバリデーション実行後、次の実行タイミング
(初期値: onChange)
onChange or onBlur or onSubmit
defaultValues 初期値をオブジェクトで入力します
(resetでdefaultValuesの値に戻る)
{name属性の値: 初期値,...}
resolver 他のvalidationライブラリを使う時
shouldUnregister 入力要素がアンマウントされたら、値の参照を解除、falseなら入力された値を保持
(初期値: true)
true or false

register

inputなどに入力された値を参照するために使う。name属性を設定する必要があり、registerはrefに入れる。UIライブラリを使う場合はcontrol参照。一番使う。

↓nameの付け方によって、dataの形が変わる.

name="firstName" →{ firstName: 'value'}
name="firstName[0]" →{ firstName: [ 'value' ] }
name="name.firstName" →{ name: { firstName: 'value' } }
name="name.firstName[0]" →{ name: { firstName: [ 'value' ] } }

引数にオブジェクトで色んなバリデーションが設定できる.

required 必須項目にするか true or false or エラーメッセージ
maxLength 最大文字数 {value: number, message: エラーメッセージ}
minLenght 最小文字数 {value: number, message: エラーメッセージ}
max 最大データ量 {value: number, message: エラーメッセージ}
min 最小データ量 {value: number, message: エラーメッセージ}
pattern 文字の形式 {value: 正規表現, message: エラーメッセージ}
validate カスタム {任意の名前: value => booleanを返す式 or 関数 || エラーメッセージ}
<input
   name="email"
   ref={register({
   required: true,
   maxLength: 60,
   pattern: {
       value: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
       message: 'メールアドレスの形式が不正です',
      },
   })}
   validate: {
       lessThanTen: value => parseInt(value, 60) < 60 || '60文字以内で入力して下さい'
   }
/>

unregister

引数に入れたnameはregisterで参照しなくなる。配列を入れて、複数も可能。unregister(['name','email'])

<button onClick={() => unregister("email")}>email_unregister</button>

errors

registerなどで設定したバリデーションを通らなかった時にエラーのデータが入る。エラーメッセージを設定している場合は、それも取得できる。

<input name="name" ref={register({required: '入力して下さい'}) />
{errors.name && <p>errors.name.message</p>}

watch

第一引数入れたnameの値を監視。第二引数は初期値を設定可能。unregisterと同様に配列に入れて複数も可能。何も入れないと、全てのnameの値を取得。レンダリング回数は増える。

const watch_name = watch("name", false);
const watch_all_fields = watch();
const watch_fields = watch(["name", "email"]);

handleSubmit

ラップした関数にformのdataをオブジェクトの形で渡してくれる。on_submitの引数にdataがあるのはそのため。
event.preventDefalut()も必要なし。

const on_submit = (data: FormInputs) => {
    console.log(data);
};

return (
    <form onSubmit={handleSubmit(on_submit)}>
...

reset

formの中の状態を初期化する関数。defaultValueが設定されていたら、その値になる

const on_click =()=>{
  reset();
}

setError

第一引数に指定したnameにエラーをセットする関数。第二引数にtype(requiredやminLengthなど)とエラーメッセージをオブジェクトとしていれる。
typeを変えるとどう変わるかが、よく分からないので分かる方いたら教えて下さい!

<button onClick={() => setError("email", { type: "required", message: "" })}>

clearErrors

引数に入れたnameのerrorを消す関数。こちらも配列で複数指定できる。

<button onClick={() => clearErrors("name")}>

getValues

引数に入れたnameの現在の値を取得。こちらも配列に入れて複数、空なら全てのnameの値を取得。watchとの違いは常に監視せず、再レンダリングもされない。ボタンを押して時に取得するとかならこっちの方がパフォーマンス的に良さそう。

const values = getValues();
const single_value = getValues("name");
const multiple_values = getValues(["name", "email"]);

setValue

第一引数に入れたnameに第二引数に入れた値をセットする関数。その際にshouldValidateでバリデーションを実行するかどうか、shouldDirtyで変更後に変更の有無の判定を行うかどうか

<button onClick={()=> setValue('name', 'value', { shouldValidate: true, shouldDirty: true })}>

trigger

引数に入れたnameのバリデーションを実行する関数。こちらも配列で複数、空なら全体。

<button type="button" onClick={() => { trigger("lastName") }}>Trigger</button>

control

Controllerのnameの値を参照するのに使う。バリデーションはできない。useWatchuseFieldArrayにも使う。

<Controller
  as={<TextInput />}
  control={control}
  name="name"
/>

formState

form内の入力の有無や送信の状態などを取得できる。formState.isSubmittingはbuttonにローディングをつける時によく使う。

isDirty 全体で何かしら変更があったらtrueになる true or false
dirtyFields それぞれの入力要素で何かしらの変更があったらtrue {name属性の値: true or false,...}
touched それぞれの入力要素で何かしらの操作をしたらtrueになる
(フォーカスするだけでも、onBlurのタイミングで反映される)
{name属性の値: true or false,...}
isSubmitted formが送信されたらtrue、resetでfalseに戻る true or false
isSubmitSuccessful formの送信が成功したらtrue true or false
isSubmitting formの送信中にtrue true or false
submitCount formの送信された回数 number
isValid 何かしらエラーがあったらtrue
(modeがonChange or onBlurの時のみ)
true or false
errors バリデーションのエラーのオブジェクトが入る {name属性の値: {
type: "required",
message: "",
ref: <input name="name属性の値" type="text">,
},...}
import React from "react";
import { useForm } from "react-hook-form";

export const Demo = () => {
  const { register, handleSubmit, errors, formState } = useForm();
  const on_submit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="name" ref={register} />
      {isDirty && <p>Form is dirty.</p>}
      {isSubmitting && <span>Submitting...</span>}
      <button type="submit" />
    </form>
  );
}

Controller

UIライブラリを使う時に使用。registerだと参照できないことが多い。(MaterialUIのTextFieldとかはinputRefregisterを入れて参照できる)。ruleregisterの中に書く感じでバリデーションを書ける。defaultValueも設定可能
asの中に全部入れちゃうのが簡単

<Controller
  as={<TextInput />}
  control={control}
  name="name"
  rules={{required: true}}
  defaultValue="aaa"
/>

<Controller
  control={control}
  name="email"
  render={({ onChange, onBlur, value, name, ref }) => (
    <TextField
      onBlur={onBlur}
      onChange={onChange}
      checked={value}
      inputRef={ref}
    />
  )}
/>

ErrorMessage

指定したnameのエラーメッセージを表示できる。messageはstringならOK

<ErrorMessage errors={errors} name="email" message={errors.email?.message} />

useFormContext,FormProvider

FormProvideruseFormregistererrorsなどをまとめて渡して、ラップされたコンポーネントでuseFormContextを使うと、その渡された値を使うことができる。

import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";

export const Demo = () => {
  const methods = useForm();
  const on_submit = data => console.log(data);

  return (
    <FormProvider {...methods} >
      <form onSubmit={methods.handleSubmit(onSubmit)}>
        <Input />
        <button type="submit" />
      </form>
    </FormProvider>
  );
}

const Input = () => {
  const { register } = useFormContext();
  return <input name="name" ref={register} />;
}

useWatch

controlを渡すことでwatchを別のコンポーネントでも使えるようにした感じ。

import React from "react";
import { useForm, useWatch } from "react-hook-form";

const Watch = ({ control }) => {
  const name = useWatch({ control, name: 'name', defaultValue: 'aaa' });

  return <div>{name}</div>;
}

type FieldInputs = {
    name: string;
}

export const Demo = () => {
  const { register, control, handleSubmit } = useForm<FieldInputs>();

  return (
    <form onSubmit={handleSubmit(data => console.log("data", data))}>
      <input ref={register} name="name" />
      <Watch control={control} />
      <button type="submit" />
    </form>
  );
}

useFieldArray

下の写真みたいな感じで、同じinputなどを増減させたい時に使えます。公式ドキュメントから、YoutubeのuseFieldArryの紹介動画に飛べるので見るとわかりやすいです。

image.png

↓こんな感じでuseFormのように色んなメソッドを使えます。nameとcontrolが必要です。defaultValueはuseFormで設定できます。

const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({
    control,
    name: "demo"
});

↓3列目は例です。

fields mapを使ったりして、入力要素をレンダリングするのに使用。idやdefaultValueが入ったオブジェクト
append fieldsの最後にinputを追加 append({name: "aaa"})
prepend fieldsの先頭にinputを追加 prepend({name: "aaa"})
insert fieldsの特定の位置にinputを追加 insert(3,{name: "aaa"})
swap inputの位置を入れ替える swap(1,2)
move inputの位置を動かす move(1,2)
remove 特定のinputを削除、引数無しで全削除 remove(1) remove()
import React from 'react';
import { useForm, useFieldArray } from 'react-hook-form';

export const Demo = () => {
  const { register, control, handleSubmit } = useForm({
    defaultValues: {
      demo: [{ name: 'name', email: 'email@email.com' }],
    },
  });
  const { fields, append, prepend, remove } = useFieldArray({
    control,
    name: 'demo',
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      {fields.map((item, index) => (
        <div key={item.id}>
          <input
            name={`demo[${index}].name`}
            ref={register}
            defaultValue={item.name}
          />
          <input
            name={`demo[${index}].email`}
            ref={register}
            defaultValue={item.email}
          />
          <button type="button" onClick={() => remove(index)}>
            Delete
          </button>
        </div>
      ))}
      <button
        type="button"
        onClick={() =>
          append({ name: 'appendName', email: 'append@email.com' })
        }
      >
        append
      </button>
      <button
        type="button"
        onClick={() =>
          prepend({
            name: 'prependName',
            email: 'prepend@email.com',
          })
        }
      >
        prepend
      </button>
      <input type="submit" />
    </form>
  );
};


終わりに

ここまで読んでいただきありがとうございます!
少しでもreact-hook-formの理解が深まれば幸いです。自分も書きながら、react-hook-formの便利さが機能が理解できました。
質問、感想、改善点などをコメントでいただけるとモチベーションにつながりますのでよければお願いします!

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

【React】React Tableで行をクリックした際のonclickイベントハンドラを設定する方法

メモとして残します。

みんな大好き、React Tableですが、行をクリックした際のイベントハンドラの設定方法メモとして残します。

■やり方

下記の通りgetTdPropsプロパティに設定をします。

<ReactTable
    getTdProps={(state, rowInfo, column, instance) => {
        return {
            onClick: e => {
                //if(column.id !== 'col-check'){
                    // 基本的な処理
                //  return
                //}
            }
        }
    }}
/>

ちなみに、getTrPropsというプロパティもありますが、このプロパティだと、例外的にイベントを発火させたくない列があったときに対応できないため、getTrPropsはあえて、採用しませんでした。
他thといった要素にも設定できるため、公式で確認しましょう。
image.png

以上。

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

if (hoge) {~ 比較演算子を使わないIF文がどこまでを「true」とするのかを徹底的に検証してみた【テキスト版】

前提

if (text) {
  return true
} return false

これは、私が実務で初めてこの書き方を目にした時、
「えっ、これ比較演算子書き忘れてるやん」って思ったんだけど(バカ、俺のバカ!)

こういうちゃんとした書き方があることを知り、
ちょっとした衝撃を受けた時のこと。

その後、
私も弱輩ながらこの書き方をマネし初めたが、
色々と上手く行かない…
 

そんなことから、一体この書き方が

どの時が「true」で

どの時に「false」を返すのか

を検証してみたところから始まる。

 
 
 
*********************
前回の記事で記述したが、
今回は動画にも起こしてみようと思ってたけど、
書いてみたら動画があると見辛そうなレイアウトになったので
次回【動画版】として掲載しようと思う。

→前回の記事 【公開】Qiitaに投稿したYoutube動画を自動再生させる!
 
 
 
 

検証環境

・言語:React.js

※少なくともJavaScript系の言語は同じ結果だと思う。
 
 
・コード

// この変数にいろんな物を入れてみることで検証
let text = 'hoge'

const result = () => {
  // ↓ここの判定具合を検証する
  if (text) {
    console.log('text:', text, true) // 条件一致
    return text + ' ←textの中身'
  }
  console.log('text:', text, false) // 条件不一致
  return 'textになんも入ってないで'
}

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>{result()}</p> {/* ←ここで画面に表示 */}
      </header>
      <div></div>
    </div>
  );
}

export default App;

  
 
・表示
見づらいかも知れんが、
画面とコンソールにこのように表示される。

では検証開始!!!!!!!!

 
 
 

1.文字列

その1 中身あるやつ

let text = 'あなたはだんだん「いいね」したくなる'

・画面

・コンソールログ

☆ 結果 = true


その2 空欄

let text = ''

・画面

・コンソールログ

☆ 結果 = false
 
この辺は当然そうでしょう。
 
 

2.数値

その1 プラスの数値

let text = 1

・画面

・コンソールログ

☆ 結果 = true


その2 ゼロ

let text = 0

・画面

・コンソールログ

☆ 結果 = false

まあこの辺も想定内


その3 マイナスの数値

・画面

・コンソールログ

☆ 結果 = true

この辺りから私の心が叫び始める。
「あれぇーゼロより少ないのにtrueなの? なんで?」
どうやらゼロは「無」だけどマイナスは「有」であると判定するようだ。

続いて行ってみる。
 
 
 

3.カッコのやつ

エンジニアが「カッコのやつ」とか言ってると怒られるかも知れんが、
ここではあえてこの呼び方で行こうと思う。

その1 空配列

let text = []

・画面

・コンソールログ

☆ 結果 = true

これはtrue?
とりあえず目には見えない「何か」が存在する
ということで納得するとする。


その2 空オブジェクト

let text = {}

・画面

・コンソールログ

☆ 結果 = true

今度は何か見えた!?どゆこと?
あるの?ないの?
2つ目の「O(オー)」は大文字だし…わけわからんが、
つぎ行ってみよう。
 
 
 

4.真偽値

もうこうなったらまんま入れてみようと思う。

その1 真の値

let text = true

・画面

・コンソールログ

☆ 結果 = true

おっ!trueが画面に表示されてるってことは
文字列になってるってことか!?


その2 偽の値

let text = false

・画面

・コンソールログ

☆ 結果 = false

まーそれはそうですよね。
 
 
 

5.「無いよ」みたいなやつ

その1 ぬる

let text = null

・画面

・コンソールログ

☆ 結果 = false


その2 未定義

let text;

・画面

・コンソールログ

☆ 結果 = false

んーーーーーーundefined…
より分からんくなってきたねーー
 
 
 

6.ちょっとなんて表現していいか分からないやつ

その1

let text = 0 / 0

・画面

・コンソールログ

☆ 結果 = false

falseは何となく分かるけどゼロじゃないの!?
NaNって何?


その2

let text = 1 / 0

・画面

・コンソールログ

☆ 結果 = true

うわぁーまた何か出てきた!
何?Infinityって?
私の世代はDo as Infinityがバッと出てきちゃうのよ。
電卓も「数値じゃありません」って言ってるよ?

怖いわー


その3

let text = 1 / 0

・画面

・コンソールログ

☆ 結果 = false

これはゼロなんかい!
もう寝る!!!
 
 
 

まとめ

そんなこんなで検証した日はいつもよりも深い眠りについた訳だが、
お陰で覚えることができた。

そういうことなので、
「if (hoge) {~ 比較演算子を使わないIF文がどこまでを「true」とするのか」
結論…

「とにかく覚える」

これに限る。
こういう判定になる理由を言語化できるならいいが、
そうでないならベタで覚えるしかないのかな。

無理してこの書き方にする必要はないんだろうが、
もし使うときは、
特に配列とオブジェクトは気をつけたいところ。

そんな気難しいこの書き方、
私は使い続けていこうと思う。

なぜなら…

「なんかカッコええやん」
 

 
ということで次回こそ動画版で記事を書こうと思う。

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

【Moment.js】日本語の時刻表示から午前・午後を判定する方法

前提

Moment.jsで時刻表示を扱う場合は、まず時刻のフォーマットを指定して、文字列をmomentオブジェクトに変換しなければなりません。

以下のように第2引数で指定してあげましょう。

// 第2引数が大事!
moment('13:00', 'HH:mm');

これで準備はOKです。

コード

「午前」や「午後」という形にフォーマットする際はAまたはaを使います。
日本語の場合は「午前」も「午後」も漢字なので、どちらを指定しても大丈夫です。

moment.local('ja');

const noon = moment('12:00', 'HH:mm').format('a');
console.log(noon) // => 午後

const midnight = moment('24:00', 'HH:mm').format('a');
console.log(midnight) // => 午前

ただし、英語の場合はAaで出力結果が変わりますので、ご注意ください。

moment.local('en');

// 小文字'a'の場合
const noon = moment('12:00', 'HH:mm').format('a');
console.log(noon) // => pm

// 大文字'A'の場合
const noon = moment('12:00', 'HH:mm').format('A');
console.log(noon) // => PM

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

【個人開発】日常用のメモ帳アプリをリリースしてみた

はじめに

  • 日常のメモにマークダウンは重量級過ぎた。
  • テキストエリアを少し便利にしたくらいの感じが丁度いい。
  • 作りました。

https://time-note.app/

見栄え感

技術スタック

  • TypeScript
  • vue + compositionAPI (仮想DOM + 状態管理)
  • google drive api (データ保存)
  • IndexedDB + CacheAPI (キャッシュ、ログアウトで全削除されます)
  • service worker (PWA)
  • firebase remote config (アップデートのお知らせ)
  • firebase hosting (配信)
  • webpack + eslint + prettier

UIとUXに全力投球

使い勝手に能力値を全振りしました。

  • GoogleのWebアプリのように、右クリックで操作できます。UIがシンプルになった!

  • OSのように削除でゴミ箱へ移動します。削除確認が不要になった!

ありがとう!

使って頂けたら嬉しいです。
LGTMもやる気出ます。
ありがとう!

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

【個人開発】日常用のメモ帳アプリ(PWAアプリ)をリリースしてみた

はじめに

  • 日常のメモにマークダウンは重量級過ぎた。
  • テキストエリアをチョッピリ便利にしたくらいの感じが丁度いい。
  • 作りました。

https://time-note.app/

見栄え感

技術スタック

  • TypeScript
  • vue + compositionAPI (仮想DOM + 状態管理)
  • google drive api (データ保存)
  • IndexedDB + CacheAPI (キャッシュ、ログアウトで全削除されます)
  • service worker (PWA)
  • firebase remote config (アップデートのお知らせ)
  • firebase hosting (配信)
  • webpack + eslint + prettier

UIとUXに全力投球

使い勝手に能力値を全振りしました。

  • GoogleのWebアプリのように、右クリックで操作できます。UIがシンプルになった!

  • OSのように削除でゴミ箱へ移動します。削除確認が不要になった!

ありがとう!

使って頂けたら嬉しいです。
LGTMボタンもやる気出ます。
ありがとう!

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

ほぼ未経験Webエンジニアが入社半年でしたことまとめ(2020/11)

言語

・PHP
・HTML
・JavaScript
(・MySQL)

やったこと

・Web開発用の開発環境構築(XAMPP設定など)(なんだかんだ3,4回した気がする)
・変数や配列など基礎的な内容を理解
・Ajax勉強
・オブジェクト指向の勉強
・セキュリティ少し学んだ
・DBについて少し学んだ
・より良いコードが書けるようにリーダブルコードを読んだ
・リーダブルコードを英語で読みたいと思い、原書を購入(まだ読んでない…)
・mapboxのチュートリアル的なページを確認
・HTMLとJavaScriptにmapboxAPIを組み合わせて、マップ情報を活用したHTMLファイル作成(指定場所の面積計算など)
・エンジニア数十人が書いたエッセイ集みたいな本を買って読んだ
・メルカリ創業の本を読んだ(サクセスストーリー感があって非常に面白かった)
・kotlinとswiftをすこし内容みてみた
・UdemyでswiftとJavaScriptのコース(英語)を受講(現在進行中)

感想

・幅広く興味を持っていい経験ができた気がする
・mapboxに触れられたのはよかった
・Udemyの英語コースは、英語の勉強にもなってとてもよい
・ちょっと時間の使い方があまりうまくなかった
・もう少しやりたいことに時間を使えばよかった
・面白いものをもっと探求したかった、開発できていればよかった
・もっと本を読みたい

今後やりたいこと

・kotlinまたswiftに本格的に手を出す
・何か1つスマホアプリをリリースする
・フレームワークに手を出す
・JavaScriptでアニメーションっぽいものを駆使して何か面白いものをつくる
・mapboxを駆使して一風変わったワクワクするものをつくりたい
・mapboxでARのAPIが活用出来たら面白そうなので、ARで何かできないか模索する

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

馬鹿な。。。貴様は配列じゃない、、、オブジェクト、、、だと!?

あ…ありのまま 起こった事を話すぜ!

おれは今 配列のデータを ほんのちょっぴりだが 参照した

だが 配列のデータを参照できなかった

な… 何を言っているのか わからねーと思うが 

おれも 何が起きたか わからなかった…

頭がどうにかなりそうだった… 配列にデータがねぇだとか

そんなチャチなもんじゃあ 断じてねえ

もっと恐ろしいものの片鱗を 味わったぜ…

配列とオブジェクトってなんなんだ・・・・

なんか見た目似てるし、データ複数管理できるし、それぞれの違いってなんなんだよ。。。。。

配列

  • 複数の値をまとめて管理する
  • データの要素を[ ]を使って囲む
  • 要素と呼ばれる0から始まる数字でデータを管理
const name = ["佐藤","鈴木","高橋"];
console.log(name);
// 出力結果: Array ["佐藤", "鈴木", "高橋"]


//要素を指定
console.log(name[0]);
// 出力結果: "佐藤"

console.log(name[1]);
// 出力結果: "鈴木"

console.log(name[2]);
// 出力結果: "高橋"

オブジェクト(連想配列)

  • 連想配列とも呼ばれる
  • 複数のデータをまとめて管理
  • 要素を { } で囲む
  • オブジェクトはそれぞれの値にプロパティ(key)と呼ばれる文字列をつけてデータ(value)を管理
//アルファベット プロパティ(key)
//苗字     データ(value)

const  name = {"A":"佐藤","B":"鈴木","C":"高橋"};
console.log(name);
// 出力結果: Object { A: "佐藤", B: "鈴木", C: "高橋" }

//プロパティ(key)を指定
console.log(name.A);
// 出力結果: "佐藤"

console.log(name.B);
// 出力結果: "鈴木"

console.log(name.C);
// 出力結果: "高橋"

配列とオブジェクトを合わせて使う

簡単な例所持金で注文可能なもの判定
const  foods= [
{"name":"カレー","price":650},
{"name":"ラーメン","price":800},
{"name":"カツ丼","price":500}
];

const haveMoney = 600;

for(i=0; i<foods.length;i++){
  if(haveMoney > foods[i].price){
    console.log(`${foods[i].name}は注文可能です。`);
  }
  else{
    console.log(`所持金が${foods[i].price-haveMoney}円不足しているため、${foods[i].name}を注文できません`);
  }
}
実行結果
所持金が50円不足しているため、カレーを注文できません
所持金が200円不足しているため、ラーメンを注文できません
カツ丼は注文可能です。

まとめ

配列とオブジェクトそれぞれの使い方についてご説明させていただきました。
正直この記事を見ている方にとっては何を当たり前なことを説明してるんだよって思ったはずです。

この記事を書いた理由は、僕が大昔に配列だと思って参照した先が実はオブジェクトだったという非常にマヌケなことをしてしまったのを思い出して今ならちゃんと配列とオブジェクトの違い説明できるかなーと思って書きました。

いわば自分のための記事だよね。

ちゃんと説明できたかな・・・・不安だ。

おわり。

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

[Javascript]オブジェクトを定数に代入する

オブジェクトを定数に代入する

定数itemにオブジェクト内の値を代入する
1512365586993.png
console.logで出力するとオブジェクト内の値をまるっと表示させる事ができる
1512365596496.png

オブジェクトの値を取り出す

1行目は先ほどと同じようにitem定数に値を代入する
2行目でitem.nameでオブジェクト内のnameの値のみを出力する
151236560518.png
name内の文字列のみ出力させる事ができる
151236561010.png

const

constは定数なので上書きする事ができない。メリットとしてはコード量が多くなるほどconstで定義すれば可視化しやすい

let

letは変数なのでいくらでも上書きする事ができる。メリットとしては変数内の値を変えたい時に即座に変える事ができるが、本人しかわからないコードにならぬようコメントアウトしておいた方が吉

以上!

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

三項演算子の使い方(独り言)

突然なのですが。
プログラミングを書いていて、こんな時はありませんか?

・処理の条件をifが使いづらい状況でも使いたい
・スマートに書きたい

こんな時は、三項演算子がおすすめです。

では実際にどのような時に使うのか?
どんな記述の仕方なのか?

例を見ていきたいと思います。co

const test = "hoge"
return(
   <div>{ test ? "trueの処理" : "falseの処理"}</div>
)

このような書き方が「三項演算子と呼ばれるものです。」

今回の例でいうと、testという変数にhogeというString型の文字が入っています。
ifの処理でいうと、この処理はtrueと判定されるのでUIに出てくる文字は【trueの処理】とでてきます。

上の例を参考にして、もう少し分岐を多くすると

const num = 100
<div>{num < 99 ? "numは99より小さいです" : num > 1000 ? "numは1000より大きいです" : num === 100 ? "numは100です" : "hoge"}</div>

複雑にはなりますが、こんな書き方もできます。ちなみにこの例だと「numは100ですと出力されます」
三項演算子は使うことも多いですがあまり分岐が複雑だと、後で大変になるので
できるだけ、簡単な分岐をスマートに実装させたいときにお勧めです!

ではまた。

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