- 投稿日:2019-06-22T23:07:53+09:00
jsp javascript での bool値の判定について
jsp上でのbool値の判定について
- jsp上でifでbool値の判定を行うプログラムを作成しました。
Test.javapackage boolCheck; public class Test { private boolean bool; public boolean isBool() { return bool; } public void setBool(boolean bool) { this.bool = bool; } @Override public String toString() { return "Test [bool=" + bool + "]"; } }BoolCheckServlet.javapackage boolCheck; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class BoolCheckServlet */ @WebServlet("/BoolCheckServlet") public class BoolCheckServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public BoolCheckServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //response.getWriter().append("Served at: ").append(request.getContextPath()); Test bean = new Test(); bean.setBool(false); //falseをセット request.setAttribute("bean", bean); //JSPに遷移する RequestDispatcher disp = request.getRequestDispatcher("boolCheck.jsp"); disp.forward(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }boolCheck.jsp<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <jsp:useBean id="bean" class="boolCheck.Test" scope="request" /> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <%= bean.toString() %> <% System.out.println(bean);%> <% System.out.println(bean.isBool());%> <!-- false --> <% if(bean.isBool()){ %> <h1>this is true</h1> <%}else{ %> <h1>this is false </h1> <% } %> <!-- false --> <script> if(<%= bean.isBool()%>){ alert("this is true"); }else{ alert("this is false"); } </script> <!-- ''で囲むと文字列となり、結果trueとなる --> <script> if('<%= bean.isBool()%>'){ alert("this is true"); }else{ alert("this is false"); } </script> </body> </html>
- 投稿日:2019-06-22T23:01:21+09:00
jest で Redux をテストする
jestで非同期関数をテストする - Qiita
jest で React component をテストする - Qiita
jest で Redux をテストする - Qiita
以下のドキュメントに沿って、jestでReduxをテストする手順を追います。
Writing Tests - Redux1.環境設定
テストディレクトリを作る。
mkdir redux cd redux/reduxをテストするための必要最小限のパッケージをインストールする。
yarn add redux yarn add --dev jest babel-jest @babel/preset-envbabelの設定ファイルです。
babel.config.jsmodule.exports = { presets: ['@babel/preset-env'], };package.jsonに以下を追加します。
package.json{ "scripts": { "test": "jest" } }2.Action Creators
Action CreatorはAction オブジェクトを作成してくれるものです。テストでは、Action Creatorを正しく呼びだすことができること、retrunされたAction オブジェクトが期待するものであるかをテストします。
ActionTypes.jsexport const ADD_TODO = 'ADD_TODO'以下がテスト対象となるAction Creatorです。
TodoActions.jsimport * as types from './ActionTypes' export function addTodo(text) { return { type: 'ADD_TODO', text } }以下がAction creatorをテストするものです。
TodoActions.test.jsimport * as actions from './TodoActions' import * as types from './ActionTypes' describe('actions', () => { it('should create an action to add a todo', () => { const text = 'Finish docs' const expectedAction = { type: types.ADD_TODO, text } expect(actions.addTodo(text)).toEqual(expectedAction) }) })実行結果は以下のようになります。
$ yarn test yarn run v1.16.0 warning package.json: No license field $ jest PASS ./TodoActions.test.js actions ? should create an action to add a todo (14ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 3.255s Ran all test suites. Done in 4.50s.3. Async Action Creators
Async Action Creatorsのテストでは、Async Action関数の中で、期待通りのActionが実行されていることをテストします。
redux-thunk を使うことにより、Actionとして関数(Async Action Creator)を使うことができるようになります。
ここではdmitry-zaets/redux-mock-store を使って、Async Action Creatorをテストします。
yarn add cross-fetch redux-thunk yarn add --dev fetch-mock redux-mock-store以下がテストの対象となるAsync Action Creatorです。
ActionTypes.jsexport const FETCH_TODOS_REQUEST = 'FETCH_TODOS_REQUEST' export const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS' export const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE'TodoActions.jsimport 'cross-fetch/polyfill' import * as types from './ActionTypes' function fetchTodosRequest() { return { type: types.FETCH_TODOS_REQUEST } } function fetchTodosSuccess(body) { return { type: types.FETCH_TODOS_SUCCESS, body } } function fetchTodosFailure(ex) { return { type: types.FETCH_TODOS_FAILURE, ex } } export function fetchTodos() { return dispatch => { dispatch(fetchTodosRequest()) // Return the promise return fetch('http://example.com/todos') .then(res => res.json()) .then(body => dispatch(fetchTodosSuccess(body))) .catch(ex => dispatch(fetchTodosFailure(ex))) } }Async Action CreatorのfetchTodos()は関数を返しますが、その関数はpromiseを返していることに注意してください。
cross-fetch は Node や Browsers、 React NativeのためのFetch APIです。==> cross-fetch
以下のテストで、Async Action の中で正しくActionが実行されていることを確認します。
TodoActions.test.jsimport configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import * as actions from './TodoActions' import * as types from './ActionTypes' import fetchMock from 'fetch-mock' const middlewares = [thunk] const mockStore = configureMockStore(middlewares) describe('async actions', () => { afterEach(() => { fetchMock.restore() // fetchMockのリセット }) it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => { // fetchのmock化、一度だけ。 fetchMock.getOnce('http://example.com/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } }) // Async Action の中で実行されると期待されるAction配列 const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) // Return the promise return store.dispatch(actions.fetchTodos()).then(() => { console.log(JSON.stringify(store.getActions())) // 結果出力 expect(store.getActions()).toEqual(expectedActions) }) }) })fetch-mock によって、fetch を使ったhttp requestをmock化することができます。 ==> fetch-mock - doc
ここで使われているredux-mock-store APIの説明は以下の通りです。
- configureStore(middlewares?: Array) => mockStore: Function : middlewaresを指定してmock storeを作ります。
- mockStore(getState?: Object,Function) => store: Function : mock storeのインスタンスを返します。storeのリセットにも使われます。
- store.dispatch(action) => action : mock storeに対して action をdispatch します。 actionはインスタンス(store)に保存され、実行されます。
- store.getActions() => actions: Array : インスタンス(store)に保存されたactionの配列を返します。
実行結果として、インスタンスに保存されたaction配列を出力したものです。
[{"type":"FETCH_TODOS_REQUEST"},{"type":"FETCH_TODOS_SUCCESS","body":{"todos":["do something"]}}]テストコマンドの出力結果です。
$ yarn test yarn run v1.16.0 warning package.json: No license field $ jest PASS ./TodoActions.test.js async actions ? creates FETCH_TODOS_SUCCESS when fetching todos has been done (51ms) console.log TodoActions.test.js:32 [{"type":"FETCH_TODOS_REQUEST"},{"type":"FETCH_TODOS_SUCCESS","body":{"todos":["do something"]}}] Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 3.216s Ran all test suites. Done in 4.88s.4.Reducers
Reducersのテストでは、stateの初期状態や、actionを実行した結果のstateが期待したものであることをテストします。
ActionTypes.jsexport const ADD_TODO = 'ADD_TODO'以下がテスト対象となるReducerです。
todos.jsimport { ADD_TODO } from './ActionTypes' const initialState = [ { text: 'Use Redux', completed: false, id: 0 } ] export default function todos(state = initialState, action) { switch (action.type) { case ADD_TODO: return [ { id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, completed: false, text: action.text }, ...state ] default: return state } }ちょっと読みにくい部分があるとすれば、reduce関数でしょうか。idを設定するときに、現在stateにあるidの最大値を求めるのにreduceを使っています。意味的には、id=idの最大値+1、としています。 ==> Array.prototype.reduce()
ちなみに個人的には、Huttonの「プログラミング Haskell」における以下の定義が記憶しやすいです。vが初期値、(+)が二項演算(関数)で、リストの左から順番に累積値を求めるさまが直感的に把握できます。
foldl (+) v [x0,x1,...,xn] = (...( (v (+) x0) (+) xn )...) (+) xn以下がテスト関数です。まず初期状態をテストします。次にActionを加える前後の状態をテストします。
todos.test.jsimport reducer from './todos' import * as types from './ActionTypes' describe('todos reducer', () => { it('should return the initial state', () => { expect(reducer(undefined, {})).toEqual([ { text: 'Use Redux', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use Redux', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use Redux', completed: false, id: 0 } ]) }) })テストの実行結果です。passを確認します。
$ yarn test yarn run v1.16.0 warning package.json: No license field $ jest PASS ./todos.test.js todos reducer ? should return the initial state (10ms) ? should handle ADD_TODO (2ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 2.539s Ran all test suites. Done in 3.97s.※ちなみに、このドキュメントにあるEnzymeを使ったReact componentのテストについては、以下の記事にまとめてあります。
jest で React component をテストする - Qiita今回は以上です。
参考記事
- 投稿日:2019-06-22T23:00:13+09:00
2019年にRails + Ajaxを整理してみる(サンプルアプリ&コード付き)
Rails上でAjaxを動かす、という良くありそうな話。
ただ調べてみるとやり方が色々あって、
Rails歴半年ちょいの私には何が正しいのかさっぱり分からなかった。という訳で整理してみる。同じ境遇にある人の助けになれば嬉しい。
jQueryとかを用いた、古式ゆかしい(らしい)やり方ですので、
Vue/React等をお使いの方々はおかえりください(涙)間違ってる部分とかあったら、コメントいただければ幸いです。
(特に図の部分)この記事がよく刺さりそうな人
- Railsの基礎はわかる
- Ajaxの雰囲気はわかる
- JavaScript & jQueryも本気出せばちょっと書ける
(決してチョットデキルではない)- RailsでAjaxはあまりやった事がない
もしくは「良く分からんけどまぁ動いてるからヨシ!」で乗り切ったとりあえず結論
Rails + Ajax の実現方法は、
ざっくり以下の3パターン&その組み合わせっぽい。1. Rails推奨方式
2. フロントはJavaScriptだけでやる方式
3. AjaxのリクエストはRails & レスポンス以降はJavaScriptでやる方式方式別サンプルアプリ&コード
作ったのは、フォームに文字を入力&ボタンを押すと、Ajaxを使って文字が書き換わるアプリ。
(アプリを名乗るのはおこがましいかもしれない)これを上の3方式のそれぞれでやってみた。
Ajaxでやる意味ある?というツッコミは無しで...コードは本当に必要最低限なので、色々細かいツッコミはご容赦くださいm(_ _)m
逆に手元で再現する分にはやりやすいはず。。。あとjQueryを使ってます。入れ方は以下参照。
Rails 5.2 jQuery 動かし方 - Qiita共通処理
サーバ(Rails)側の処理は、3つの方式でほぼ共通。
routes.rbRails.application.routes.draw do get 'static/top' post 'static/ajax_update', to: 'static#ajax_update' post 'static/ajax_update2', to: 'static#ajax_update2' endstatic_controller.rbclass StaticController < ApplicationController def top end # 1. Rails推奨方式 で使用 def ajax_update @text = params[:data] render end # 2. フロント側はJavaScriptだけでやる方式 # 3. AjaxのリクエストはRails & レスポンス以降はJavaScriptでやる方式 # で使用 def ajax_update2 @text = params[:data] render plain: @text end end1. Rails推奨方式
Railsガイドに書かれたやり方
Rails で JavaScript を使用する - Rails ガイドajax_update.js.erbvar user = '<%= "#{ @text }" %>' $('#ajax-test1').text(user);top.html.slimh1 Static#top p Find me in app/views/static/top.html.slim #ajax-test1 Ajax: Rails依存 #ajax-request1 = form_with url: static_ajax_update_path do |f| = f.text_field :data = f.submit 'Post Ajax'図に表すと多分以下の感じ。
もうガッツリRailsに乗っかっている状態。肝は以下2点
- xxx.js.erbからJavaScriptをレンダリングしてフロントに返す
- フロント側で受け取ったJavaScriptを実行
個人的にはRails側で、JavaScriptをレンダリングしている辺り、
少し気持ち悪い。。。ただコード量は必要最低限で済むし、
Rails推奨であることからトラブルも起きにくそう。
基本はこれでいいのではないだろうか。なお細かい処理がしたい場合には不便になることもある様子で、
何だかんだ使わないと言う話もあるらしい。
参考:https://qiita.com/ka215/items/dfa602f1ccc652cf28882. フロント側はJavaScriptだけでやる方式
Railsにあえて叛逆していくやり方。
top.html.slim#ajax-test4 Ajax: ほぼJS(jQuery) = text_field_tag 'static[ajax_data2]' = button_tag 'Post Ajax', id: 'btn2'ajax_request_response.js$(document).ready( () => { $('#btn2').on('click', (e) => { e.preventDefault(); const param = $('#static_ajax_data2').val(); // CSRFトークンを取得&セット $.ajaxPrefilter( (options, originalOptions, jqXHR) => { if (!options.crossDomain) { const token = $('meta[name="csrf-token"]').attr('content'); if (token) { return jqXHR.setRequestHeader('X-CSRF-Token', token); } } }); $.ajax({ url: `/static/ajax_update2`, type: 'POST', data: { data: param } }) .done( (data, textStatus, jqXHR) => { var result = $('#ajax-test4'); result.text(data); }); }); });図に表すと多分以下の感じ。
Ajaxに関しては、Railsには頼らないという強い意思が見える。肝は、
RailsのCSRF対策のために、
CSRFトークンの取得&セットを行なっている所。具体的なやり方は以下の記事を完全リスペクトしましたm(_ _)m
https://qiita.com/a_ishidaaa/items/7c3fa339d3bea25a9ba8ざっくり言うと、RailsではCSRFという脆弱性への対策として、
Postのリクエスト時にトークン(身分証明みたいなもの)を使っている。
ここをカバーしてあげないと、JavaScriptからPostは出来ない。そう、Railsからの叛逆に成功したと思いきや、
実はその呪縛から逃れきれていなかったのだ。
なんかエモい。なお、RailsのCSRFについての詳細は以下の記事等をご参照ください。
外部からPOSTできない?RailsのCSRF対策をまとめてみた - Qiita3. AjaxのリクエストはRails & レスポンス以降はJavaScriptでやる方式
Railsへの依存を減らしつつ、CSRF対策はRailsによろしくできるやり方。
top.html.slim= form_with url: static_ajax_update2_path, id: 'ajax-request-3' do |f| = f.text_field :data = f.submit 'Post Ajax'ajax_response.js// Ajax: form送信はRails、受信以降はJS $( () => { $('#ajax-request-3').on('ajax:success', (e) => { const result = $('#ajax-test3'); result.text(e.detail[0]); }); });図に表すと多分以下の感じ。
折衷案な雰囲気。肝は、
RailsとJavaScriptの間で、
どのようにデータをやり取りされるかの理解が必要な所。適当にやってると変なハマり方をしそう。。。
ただそこさえクリアすれば、
Railsっぽさと自由度をある程度両立できる?気がする(よく分かってない)まとめ
- 基本的には大人しく「1. Rails推奨方式」を使った方がいい気がする。
(特に経験浅めの人)- ただ不便な場合もある(らしい)ので、
その際は「3. AjaxのリクエストはRails...」を採用、
もしくは「1. Rails推奨方式」と組み合わせて使えば良さそう。- Railsで開発するけどなるべく依存したくないというワガママな人は、
「2. フロント側はJavaScriptだけでやる方式」を使えばいい...のか?参考サイト
以下本記事作成に際しお世話になったサイト。見ると理解がすごく深まる。。。
Ruby on RailsのAjax処理のおさらい - Qiita
Rails 5.1+jQueryでajaxを試す (罠にハマる) - Qiita
Rails 5.2 jQuery 動かし方 - Qiita
RailsでのAjax - Qiita
jQuery.ajax()のまとめ: 小粋空間
Rails 5.2 jQuery 動かし方 - Qiita
- 投稿日:2019-06-22T22:30:29+09:00
【Nuxt】共通のJavaScriptをheadに記述する方法
こんにちは、ブログ「学生ブロックチェーンエンジニアのブログ」を運営しているアカネヤ(@ToshioAkaneya)です。
【Nuxt】共通のJavaScriptをheadに記述する方法
このように
nuxt.config.js
に記述することで、全てのpageで共通のJavaScriptを実行することができます。nuxt.config.js// ... head: { script: [ { innerHTML: `alert('Hello!');` } ], __dangerouslyDisableSanitizers: ['script'], // ...
__dangerouslyDisableSanitizers: ['script']
は、innerHTML内の文字がエスケープされるのを防ぐためのオプションです。これがないと文字列がうまく出力できません。はてなブックマーク・Pocketはこちらから
- 投稿日:2019-06-22T21:19:50+09:00
HTMLとJavaScriptで作ったサイトがHerokuにデプロイできねええって時の対処法
HTML/JavaScriptで用いて作ってる勉強用のページを
Herokuにデプロイする時失敗しましたが、簡単に解決できたのでその対処法を書きます。git push heroku masterでデプロイ失敗・・・
PS C:\Users\***\Desktop\***> git push heroku master Enumerating objects: 372, done. Counting objects: 100% (372/372), done. Delta compression using up to 8 threads Compressing objects: 100% (344/344), done. Writing objects: 100% (372/372), 230.52 KiB | 2.59 MiB/s, done. Total 372 (delta 111), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> App not compatible with buildpack: https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/nodejs.tgz remote: remote: ! ERROR: Application not supported by 'heroku/nodejs' buildpack remote: ! remote: ! The 'heroku/nodejs' buildpack is set on this application, but was remote: ! unable to detect a Node.js codebase. remote: ! remote: ! A Node.js app on Heroku requires a 'package.json' at the root of remote: ! the directory structure. remote: ! remote: ! If you are trying to deploy a Node.js application, ensure that this remote: ! file is present at the top level directory. This directory has the remote: ! following files: remote: ! remote: ! If you are trying to deploy an application written in another remote: ! language, you need to change the list of buildpacks set on your remote: ! Heroku app using the 'heroku buildpacks' command. remote: ! remote: ! For more information, refer to the following documentation: remote: ! https://devcenter.heroku.com/articles/buildpacks remote: ! https://devcenter.heroku.com/articles/nodejs-support#activation remote: remote: remote: More info: https://devcenter.heroku.com/articles/buildpacks#detection-failure remote: remote: ! Push failed remote: Verifying deploy... remote: remote: ! Push rejected to *** remote: To https://git.heroku.com/***.git ! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 'https://git.heroku.com/***/めっちゃ長いけど、heroku側でbuildpackをnodejsに設定してる場合、package.jsonファイルが必要なんだとか。
要はアプリとして認識されてないってことね。
で、実際に作って再度デプロイするもapplication Errorに。。。
これはハマりそう・・・。簡単に解決にできないかなーと思ったら、こんな投稿を発見。
単純なHTML/CSS/JSをHerokuにデプロイする時つまずいたこと
なるほど、ルートディレクトリにpackage.jsonとindex.phpを作って、index.phpでindex.htmlをrequire_onceすればいいのね。
(要はphpアプリとしてデプロイするってこと)解決方法
index.htmlと同じ階層に以下の二つのファイルを作成
index.php<?php include_once("index.html"); ?>package.json{}commitして、再度git push heroku master
PS /***> git push heroku master remote: https://***.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/***.gitデプロイ成功!
アプリじゃないけどデプロイしちゃう
herokuはサーバー周り設定要らずで簡単にWebページを無料で公開できるのでかなりお勧めです。
- 投稿日:2019-06-22T21:15:26+09:00
AWS Training の動画を早送りしたい
AWS Training の動画を早送りしたい
この記事に必要な範囲で自己紹介すると、
- 最近 AWS の勉強をはじめた
- とりあえず AWS Training を進めている
- リアルの会話はゆっくりのほうが嬉しいし、自分自身の喋りはとろい
- (何でもかんでも早送りにしたい人ではない、ということです)
という感じです。
AWS Training とは
AWS が公式に提供してくれている学習用リソースです。まだ始めたばかりでよく分かっていませんが、ブラウザでアクセスして、動画を見ながら勉強できます。様々なコンテンツが準備されていて、興味のあるものを選ぶことができます。
しかし、動画に早送り機能がありません。速く進めたいけど途中に重要な情報があるかもしれないから飛ばしたくはない、という場合に困ってしまいます。
ページの要素を調べる
AWS Training のページ構成はコンテンツによって異なりますが、いくつか見た感じでは、
<video>
要素として動画が配置されているという点は共通しているようです。<video>
要素の interface はHTMLVideoElement
で、これはHTMLMediaElement
を継承しています。そして、HTMLMediaElement
のplaybackRate
プロパティで再生速度を制御することができます。開発ツールを開き、コンソールで下記を実行すれば、再生速度が 1.25 倍になります。
const videos = document.body.getElementsByTagName("video"); videos[0].playbackRate = 1.25;注意点
- 自分が確認に使ったブラウザは Firefox です。
- コンテンツによっては、
<video>
要素が複数存在する場合があります。メインの動画が一番前にある可能性が高そうですが、確認した訳ではないので断言できません。videos
のインデックスを0
から変更する必要が生じるかもしれません。- コンテンツによっては、動画のスピードを変えても seek bar の速度が変わりません。その結果、実際の動画と seek bar にずれが生じてしまいます。このずれの解消にはソースのスクリプトを変更する必要がありそうなので、あきらめました。
おわりに
ブラウザの開発ツールは、アプリケーションをつくるときだけでなく、日々の生活をちょっと便利にするためにも使えるんだと改めて感じました。
- 投稿日:2019-06-22T21:15:11+09:00
HTML5でVideo Playerを作る
Videoタグを使ってMp4の再生プレイヤーを作る
下記のように入力すれば、一応再生プレイヤーが作ることは不可能でない。list.html<html> <head> <title>title name</title> <link rel="stylesheet" type="text/css" href="list.css"> <script type="text/javascript"> var namedata = [ "sample.mp4" ]; var name_num = 0; function videoch(num){ var tmp = num -0; if (tmp >= namedata.length){ tmp = namedata.length -1; } if (tmp < 0){ tmp = 0; } name_num = tmp document.getElementById("video").src = encodeURIComponent(namedata[name_num]); document.getElementById("title_v").innerHTML = namedata[name_num]; document.title = namedata[name_num].replace(".mp4","") } function coutdata(num){ var tmp = num -0; name_num = name_num + tmp if (name_num >= namedata.length){ name_num = 0 } if (name_num < 0){ name_num = namedata.length -1 } //encodeURI //var data = encodeURIComponent("となりの吸血鬼さん #12(終) 吸収産高画質追加「巡る季節と吸血鬼」 - ひまわり動画.mp4"); document.getElementById("video").src = encodeURIComponent(namedata[name_num]); document.getElementById("title_v").innerHTML = namedata[name_num]; document.title = namedata[name_num].replace(".mp4","") } function pauseVideo() { //動画を一時停止 var video =document.getElementById("video"); console.log(video.paused); video.pause(); } function playVideo() { //動画を一時停止 var video =document.getElementById("video"); console.log(video.paused); video.play(); } function playpauseVideo(){ var video = document.getElementById("video"); if (video.paused){ playVideo(); document.getElementById("playpause").innerHTML = "=" }else{ pauseVideo(); document.getElementById("playpause").innerHTML = ">" } } function listwrite(){ var numlist document.open(); document.write("list<br>"); for (var i = 0; i < namedata.length; i++){ numlist = "<li>"; numlist = numlist + "<a href='#' onclick=\"videoch("+(i)+");return false\"" //numlist += "<a href='#' onclick();return false\") >" numlist = numlist + "title =\""+namedata[i] +"\">"+(i+1)+"</a></li>"; console.log(numlist) document.write(numlist); } document.close(); } function onloadbody(){ document.getElementById("video").src = encodeURIComponent(namedata[name_num]); document.getElementById("title_v").innerHTML = namedata[name_num]; document.title = namedata[name_num].replace(".mp4","") } function onloadvideo(){ //video.play() onloadbody() var video =document.getElementById("video"); //console.log("aaa") video.addEventListener("ended", function(){ coutdata(1); //document.getElementById("kanryou").innerHTML = "動画の再生が完了しました。"; }, false); //console.log("bb") } </script> </head> <body onload="onloadvideo()"> <div id="top_body"> <a href="#" onclick="coutdata(-1);return false"><<</a> <a href="#" onclick="coutdata(1);return false">>></a> <!--<a href="#" onclick="playVideo();return false">></a> <a href="#" onclick="pauseVideo();return false">=</a>--> <a id="playpause" href="#" onclick="playpauseVideo();return false">=</a> </div> <div id="main_body_l"> <table> <tr> <td width="80%"> <video id="video" width="1280px" height="720px" src="" autoplay controls></video> </td> <td width="20%"> <script>listwrite()</script> </td> </tr> </table> </div> <!-- <div id="main_body_r"> llllllllllllllllllllllllll </div>--> <div id="buttom_body" > <div id="title_v"></div> </div> </body> </html>list.cssdiv#top_body{ height: 25px; width: 100%; background-color: #E91E63; } div#main_body_l{ /*background-color: #8BC34A; width: 80%;*/ } div#main_body_r{ /*background-color: rgb(70, 82, 72); width: 20%;*/ } div#buttom_body{ height: 25px; width: 100%; background-color: #00BCD4; } table td { word-break : break-all; }
- 投稿日:2019-06-22T20:39:33+09:00
Vueのテンプレート中でspreadしてpropsを渡すにはどうすればいいか
はじめに
よくたくさんの項目をpropsをとして渡さなければいけないときがあります。
今までReactを使っていたときは「...」で良かったですが、Vueだとテンプレート中でspread operatorを使えません。
そこでどうやるかのメモ。
spreadとは
以下Javascript/MDNからコード引用
オブジェクトを分割して変数に入れることができます。
var obj1 = { foo: 'bar', x: 42 }; var obj2 = { foo: 'baz', y: 13 }; var clonedObj = { ...obj1 }; // Object { foo: "bar", x: 42 } var mergedObj = { ...obj1, ...obj2 }; // Object { foo: "baz", x: 42, y: 13 }悪い例
以下は最初にやりがち。よくやる悪い例。自分もspreadを知らないときはこう書いていました。
<template> ... <Form :firstname="firstname" :lastname="lastname" :birthday="birthday" :phonenumber="phonenumber" /> ... </template> <script lang="ts"> import { Component, Vue } from "vue-property-decorator"; import IUser from "@/Models/User"; import Form from "@/components/Organisms/Form.vue" @Component({ name: "UserForm", component: { Form } }) export default class UserForm extends Vue { @Props() private user: IUser; } </script>良い例
これでオブジェクトをspreadで分けてpropsとして渡すことができる。
<template> <Form v-bind="user"/> </template> <script lang="ts"> import { Component, Vue } from "vue-property-decorator"; import IUser from "@/Models/User"; import Form from "@/components/Organisms/Form.vue" @Component({ name: "UserForm", component: { Form } }) export default class UserForm extends Vue { @Props() private user: IUser; } </script>spreadして渡すにはv-bindを使用します。
これでコードがスッキリしました。
ちゃんと分割してからPropsとして渡すことができます。
まとめ
spreadを活用してPropsへ分割してから渡すようにすることでコーディングの時間を減らしていきましょう
- 投稿日:2019-06-22T18:05:39+09:00
オブジェクト指向についての考え方[自分用]
そもそもなんでオブジェクト指向にするの?
全部手続き型でいいじゃん!組みやすいし!
グローバル変数でいいでしょ!!!えぇ、私も正直最初期はそう思ってました、というかそうでしょ楽に実装できるし
簡単に作成可能だし。
一回きりならそんなんでいいんじゃないの?みたいな実際問題最初のうちなら別にいいんですけども、
そのうちこれを長期間使うかも
みたいなことになった時に死にます。このコード何してるの…って。
簡単なオブジェクト指向の説明
今更ながらにこれは自分用なので他の記事見たほうがいいんじゃないでしょうかね?
まぁ、私のがトップに出ると思いませんが。閑話休題
そもそもオブジェクトってなにっていうところで私もわかりません、
細かい事柄はグーグルさんにでも、基本的には物体とかいう意味だったはずです。
物体事に考える思考と思えば。ゲームとかだと主人公はHPをもっててー武器を持っててーみたいな感じで分かりやすいですよね
Instance Hero{ HP:int; MP:int; Attack():void; }こんな感じで
でも実際作業時の場合ってなかなかこんな風に考えにくくないですかね、
というか私は無理でしたが。さてコードでの説明はこれ以降しないとして
オブジェクト指向、ものみたいな考え方です。
までを認識すればとりあえずはOKです。ものみたいな考えとは
例えばなんですけど
今ならばみさなん携帯持ってますよね?電源と音量キー
電源キーを押したらスリープから覚めるし
音量キー押せば音が大きくなったり小さくなったりとでもその中身は知らない、
音量キーを押してなんで音が大きくなった、小さくなった
電源長押ししたら電源切りますか?の文が出てくるがなぜ出てくるかは知らない。
でも使える。これがオブジェクト指向です!!
わかるわけねぇだろ
いや初心者にはほんとわかんないです、意味が
言葉で説明されても画像で見たって正直なところ理解なんてできません。
書いて身に着けるしかない…。あぁ、もっとチートみたく技術習得できないの?身近にあるもので例えましょう。
そう、例えばおうちの中、
基本的に読んでる人は家に住んでいると思っています。
いや別に会社とかでもいいんですけれども…電気を入れるためのスイッチがあって、コンセントがあって…
スイッチを押せば電気がついて押せば電気が消える、
+因子がこうなってて~とかになると物理なので私は知らないですが。
こうすればこう動く、ということは知っています。その状態に作っていくことをオブジェクト指向です。
何かをすれば結果が返ってくる
でも細かい中身は知らない。結局のところ作るっているのはその知らなくてもいい中身なのでアレなんですけども
でも状態がわかっりさえすればいいです。Aボタンを押した、だからジャンプした
この流れさえわかってればゲームが遊べる。
その形にしていくというのがオブジェクト指向である、とまとめ
オブジェクト指向は正直1日で身についたら凄いと思います。
私なんてゆるゆるやって2年ぐらいかかってる気がしますし→ボタンを押せば右に動く、
その中身でもまた色々とオブジェクト指向がー…となりますが
とりあえずのとっかかりとして、音量ボタンを押せば音量が上がる、下がる、
中の配線がどうのこうのを知らなくても
私たちは生活ができている、ものみたいな考え、オブジェクト指向はまさに現実になぞらえた考え方です。
まとめもまとめられてませんけど取り敢えず自分用として、ポストはしておきましょう。
- 投稿日:2019-06-22T17:52:31+09:00
【2019年6月版】jsでクロージャに渡す変数はとりあえずconstしておけッ!!
結論
クロージャの中で使いたい変数の宣言に注意しよう。
- 参照のみの変数は
const
宣言マスト。でもあとは気にしなくてOK。- 書き込みしたい変数は
let
宣言して腹をくくる。時々、忘れてしまうのでメモです。
クロージャ内から使用される変数に刮目せよ
js ではクロージャをバンバン使いますが、クロージャ内で使用する変数には適切な宣言をしておかないとバグを埋め込む原因となります。
例えば以下のようなコードで、// DB や APIなどから非同期で処理〜 function getPromise(c) { return new Promise((accept)=>accept(c)); } // 何かのマスターを元にループで処理したい const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }) msg = `[${a}] <- 終了!お疲れ様でした。` console.log(msg); }
getPromise(a).then(v => {...}
で変数a
の値が変数v
に渡されてきますが、このv => {...}
の中で参照されている変数msg
の宣言はどうなっているでしょうか?
msg = '[${a}] <- イマココ'
で宣言無しにいきなり変数定義してしまっています。熟練の方は一瞬でをいをいおと気づいていただけるでしょうが、例えば僕は数千行に渡るデータコンバート用の単発のバッチを涙ながらにjsでガリガリ書いているわけです。DB
A
の テーブルB
からデータを読み込み、 DBC
の テーブルD
のマスターを参照しつつデータを変換して DBE
の テーブルF
へデータを書き込んでいくわけです。いちいち変数宣言してられるかーーー!!・・・とりあえず区切りのいいところで試しに動かして見るか、とテスト環境を対象に上記のバッチを流してみると・・・
$ npm start [1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。
処理中:
のメッセージでイマココ
を確認したかったのに、終了
メッセージが表示されています。ウホー!
最初の5行の出力はちょっと目障りなので出力をやめてみましょう。function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); msg = `[${a}] <- 終了!お疲れ様でした。`; }これを実行してみます。
$ npm start > node index.js 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。終了メッセージに書き換わっている・・・。
いやいや、
getPromise(a)
の後でmsg = `[${a}] <- 終了!お疲れ様でした。`;
とmsgの代入処理をしているのでこの問題点はすぐに気づけるはずです。俺としたことが・・・ちょっと疲れたのかな。。。それではこれではどうでしょう?
function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); }
getPromise(a)
の後でmsg
には何も入れていません。というかmsg
は宣言してからクロージャの中でしか使用していません。よし、万全。指差し確認、OK!では、バッチを動かしてみましょう。
$ npm start > node index.js 値: 1 処理中: [5] <- イマココ 値: 2 処理中: [5] <- イマココ 値: 3 処理中: [5] <- イマココ 値: 4 処理中: [5] <- イマココ 値: 5 処理中: [5] <- イマココ
イマココ
メッセージが全部5
になっているーーー!!!
これはメッセージに出力しているだけなので、msg
が変わってしまっていても別にフーンで済むと思うかもしれません。
では、このmsg
変数がDBの書き込み先だったり、APIのエンドポイントだったと想定したらどうでしょう?
すべてのデータが5番の接続先へ流れていきます。APIやRDBMSなら引数エラーや最悪でもスキーマの不一致などでフェイタルエラーで止まってくれるかもしれませんが、MongoDBやKVSなどのスキーマレスのNoSQLやただのフォルダ、アップロードするだけなどの場合は普通に処理が完了してしまうでしょう。
もう、白目、ですね・・・。想像しただけで胃が縮む思いです。どうしてこうなったのか・・・? 理解するのには最初の冗長なメッセージを出していたバージョンのプログラムをもう一度、見るのがわかりやすいのです。
function getPromise(c) { return new Promise((accept)=>accept(c)); } for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); msg = `[${a}] <- 終了!お疲れ様でした。`; console.log(msg); }そして実行結果は、
$ npm start [1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。となります。
forループの最後で宣言した
console.log(msg);
の結果である、[1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。がすべて先に表示されています。
つまり、先にfor ループ が完了している ということです。
Promise
で処理を後回しにしたので、すべての for ループが完了してから、Promise
の(accept)=>accept(c)
が呼び出され、v => {...}
が呼び出されます。・・・
for
ループが完了しているのに、for ループ内で宣言した変数msg
はどうなっているのか・・・?これがクロージャがクロージャたる所以なのですが、
v => {...}
が宣言された時点でのすべての変数がレキシカル環境として記憶されています。要はどこかで変数msg
の存在を覚えててくれるわけですが、この変数が書き込み有りか無しかで持ち方が変わってしまう、というのがキモなワケです。今回は手抜きをして変数宣言をすっ飛ばした結果、デフォルトの宣言である書き込み可能な
var
として定義されてしまいました。
よってクロージャの中では書き込み用のmsg
というみんなで読み書きできる1つの場所
として変数がmsg
が使用されています。その結果、forの最後のループで書き込まれた5
がすべてのクロージャで参照された、ということになります。これはクロージャの中から値を取り出したい場合のような
明確な書き込み用途
の変数の場合は当然の結果です。
というわけで、書き込み用途ではない場合には明確に読み取り専用変数であるconst
の宣言がマストなのです。ここでは
msg
は変わって欲しくない変数のため、以下のようにconst
宣言をちゃんとします。function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { const msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); }そして、実行してみると・・・
$ npm start > node index.js 値: 1 処理中: [1] <- イマココ 値: 2 処理中: [2] <- イマココ 値: 3 処理中: [3] <- イマココ 値: 4 処理中: [4] <- イマココ 値: 5 処理中: [5] <- イマココ
const
宣言をして、宣言した時点の値をちゃんとキャプチャさせた結果、ちゃんと 1〜5 のイマココ
メッセージが表示されました。やれやれだぜ。。。
var
はやばい。さらに今回は変数の宣言をせずにデフォルトの
var
が使われてしまいましたが、var
は関数スコープでfunction
オブジェクトのプロパティ
のように振る舞うので、副作用が大きいです。function majisuka() { var x = 'マジで?' let y = '嘘でしょ?' { var x = 'マジすか?' let y = '本当に?' } console.log(`x is [${x}]`); console.log(`y is [${y}]`); } majisuka();こんな適当なプログラムでも、ちゃんと結果を予測できますか?結果は以下のようになります。
x is [マジすか?] y is [嘘でしょ?]
var
で宣言したx
はブロック内で宣言したx
で見事に上書きされています。マジすか?!
そんなわけでクロージャ内で書き込みたい変数にはちゃんとlet
宣言をしましょう!!ちなみに java では
String msg = "こんにちわ"; // final 宣言していない new Thread(() -> { msg = "こんばんわ"; // -> これは"コンパイルエラー" System.out.println(msg); });などの、クロージャ外で宣言された変数へのアクセスはコンパイルエラーとなります。
final
宣言がMust
で読み取り専用の変数しか渡せません。例えば上記のように本当にマルチスレッドで動かすためのクロージャでは、クロージャの外部へ値を書き出すことはそのまま
スレッド間通信
となるので、変数への書き込みのようなお手軽な読み書きじゃダメなのですね。
このように場合に応じてロックのストラテジを各自でちゃんと考えて用意してね、というわけなのです。js はシングルスレッドで動くかなり特殊な言語で、クロージャが同時に実行されることはないので、一つの変数の読み書き、というレベルで済んでしまいます。お手軽なのはいいですが、副作用は大きいので気をつけてね!というお話でした〜。
参考ページ
- 投稿日:2019-06-22T17:52:31+09:00
【2019年6月版】jsのクロージャとconst
結論
クロージャの中で使いたい変数の宣言に注意しよう。
- 参照のみの変数は
const
宣言マスト。でもあとは気にしなくてOK。- 書き込みしたい変数は
let
宣言して腹をくくる。時々、忘れてしまうのでメモです。
クロージャ内から使用される変数に刮目せよ
js ではクロージャをバンバン使いますが、クロージャ内で使用する変数の宣言を適当にしてしまうとバグを埋め込む原因となります。
例えば以下のようなコードで、// DB や APIなどから非同期で処理〜 function getPromise(c) { return new Promise((accept)=>accept(c)); } // 何かのマスターを元にループで処理したい const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }) msg = `[${a}] <- 終了!お疲れ様でした。` console.log(msg); }
getPromise(a).then(v => {...}
で変数a
の値が変数v
に渡されてきますが、このv => {...}
の中で参照されている変数msg
の宣言はどうなっているでしょうか?
msg = '[${a}] <- イマココ'
で宣言無しにいきなり変数定義してしまっています。熟練の方は一瞬でをいをいおと気づいていただけるでしょうが、例えば僕は数千行に渡るデータコンバート用の単発のバッチを涙ながらにjsでガリガリ書いているわけです。DB
A
の テーブルB
からデータを読み込み、 DBC
の テーブルD
のマスターを参照しつつデータを変換して DBE
の テーブルF
へデータを書き込んでいくわけです。いちいち変数宣言してられるかーーー!!・・・とりあえず区切りのいいところで試しに動かして見るか、とテスト環境を対象に上記のバッチを流してみると・・・
$ npm start [1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。
処理中:
のメッセージでイマココ
を確認したかったのに、終了
メッセージが表示されています。ウホー!
最初の5行の出力はちょっと目障りなので出力をやめてみましょう。function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); msg = `[${a}] <- 終了!お疲れ様でした。`; }これを実行してみます。
$ npm start > node index.js 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。終了メッセージに書き換わっている・・・。
いやいや、
getPromise(a)
の後でmsg = `[${a}] <- 終了!お疲れ様でした。`;
とmsgの代入処理をしているのでこの問題点はすぐに気づけるはずです。俺としたことが・・・ちょっと疲れたのかな。。。それではこれではどうでしょう?
function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); }
getPromise(a)
の後でmsg
には何も入れていません。というかmsg
は宣言してからクロージャの中でしか使用していません。よし、万全。指差し確認、OK!では、バッチを動かしてみましょう。
$ npm start > node index.js 値: 1 処理中: [5] <- イマココ 値: 2 処理中: [5] <- イマココ 値: 3 処理中: [5] <- イマココ 値: 4 処理中: [5] <- イマココ 値: 5 処理中: [5] <- イマココ
イマココ
メッセージが全部5
になっているーーー!!!
これはメッセージに出力しているだけなので、msg
が変わってしまっていても別にフーンで済むと思うかもしれません。
では、このmsg
変数がDBの書き込み先だったり、APIのエンドポイントだったと想定したらどうでしょう?
すべてのデータが5番の接続先へ流れていきます。APIやRDBMSなら引数エラーや最悪でもスキーマの不一致などでフェイタルエラーで止まってくれるかもしれませんが、MongoDBやKVSなどのスキーマレスのNoSQLやただのフォルダ、アップロードするだけなどの場合は普通に処理が完了してしまうでしょう。
もう、白目、ですね・・・。想像しただけで胃が縮む思いです。どうしてこうなったのか・・・? 理解するのには最初の冗長なメッセージを出していたバージョンのプログラムをもう一度、見るのがわかりやすいのです。
function getPromise(c) { return new Promise((accept)=>accept(c)); } for(a of array) { msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); msg = `[${a}] <- 終了!お疲れ様でした。`; console.log(msg); }そして実行結果は、
$ npm start [1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。 値: 1 処理中: [5] <- 終了!お疲れ様でした。 値: 2 処理中: [5] <- 終了!お疲れ様でした。 値: 3 処理中: [5] <- 終了!お疲れ様でした。 値: 4 処理中: [5] <- 終了!お疲れ様でした。 値: 5 処理中: [5] <- 終了!お疲れ様でした。となります。
forループの最後で宣言した
console.log(msg);
の結果である、[1] <- 終了!お疲れ様でした。 [2] <- 終了!お疲れ様でした。 [3] <- 終了!お疲れ様でした。 [4] <- 終了!お疲れ様でした。 [5] <- 終了!お疲れ様でした。がすべて先に表示されています。
つまり、先にfor ループ が完了している ということです。
Promise
で処理を後回しにしたので、すべての for ループが完了してから、Promise
の(accept)=>accept(c)
が呼び出され、v => {...}
が呼び出されます。・・・
for
ループが完了しているのに、for ループ内で宣言した変数msg
はどうなっているのか・・・?これがクロージャがクロージャたる所以なのですが、
v => {...}
が宣言された時点でのすべての変数がレキシカル環境として記憶されています。要はどこかで変数msg
の存在を覚えててくれるわけですが、この変数が書き込み有りか無しかで持ち方が変わってしまう、というのがキモなワケです。今回は手抜きをして変数宣言をすっ飛ばした結果、デフォルトの宣言である書き込み可能な
var
として定義されてしまいました。
よってクロージャの中では書き込み用のmsg
というみんなで読み書きできる1つの場所
として変数がmsg
が使用されています。その結果、forの最後のループで書き込まれた5
がすべてのクロージャで参照された、ということになります。これはクロージャの中から値を取り出したい場合のような
明確な書き込み用途
の変数の場合は当然の結果です。
というわけで、書き込み用途ではない場合には明確に読み取り専用変数であるconst
の宣言がマストなのです。ここでは
msg
は変わって欲しくない変数のため、以下のようにconst
宣言をちゃんとします。function getPromise(c) { return new Promise((accept)=>accept(c)); } const array = [1,2,3,4,5]; for(a of array) { const msg = `[${a}] <- イマココ`; getPromise(a).then(v => { console.log(`値: ${v}`); console.log(`処理中: ${msg}`); }); }そして、実行してみると・・・
$ npm start > node index.js 値: 1 処理中: [1] <- イマココ 値: 2 処理中: [2] <- イマココ 値: 3 処理中: [3] <- イマココ 値: 4 処理中: [4] <- イマココ 値: 5 処理中: [5] <- イマココ
const
宣言をして、宣言した時点の値をちゃんとキャプチャさせた結果、ちゃんと 1〜5 のイマココ
メッセージが表示されました。やれやれだぜ。。。さらに今回は変数の宣言をせずにデフォルトの
var
が使われてしまいましたが、var
は関数スコープでfunction
オブジェクトのプロパティ
のように振る舞うので、副作用が大きいです。function majisuka() { var x = 'マジで?' let y = '嘘でしょ?' { var x = 'マジすか?' let y = '本当に?' } console.log(`x is [${x}]`); console.log(`y is [${y}]`); } majisuka();こんな適当なプログラムでも、ちゃんと結果を予測できますか?結果は以下のようになります。
x is [マジすか?] y is [嘘でしょ?]
var
で宣言したx
はブロック内で宣言したx
で見事に上書きされています。マジすか?!
そんなわけでクロージャ内で書き込みたい変数にはちゃんとlet
宣言をしましょう!!ちなみに java では
String msg = "こんにちわ"; // final 宣言していない new Thread(() -> { msg = "こんばんわ"; // -> これは"コンパイルエラー" System.out.println(msg); });などの、クロージャ外で宣言された変数へのアクセスはコンパイルエラーとなります。
final
宣言がMust
で読み取り専用の変数しか渡せません。例えば上記のように本当にマルチスレッドで動かすためのクロージャでは、クロージャの外部へ値を書き出すことはそのまま
スレッド間通信
となるので、変数への書き込みのようなお手軽な読み書きじゃダメなのですね。
このように場合に応じてロックのストラテジを各自でちゃんと考えて用意してね、というわけなのです。js はシングルスレッドで動くかなり特殊な言語で、クロージャが同時に実行されることはないので、一つの変数の読み書き、というレベルで済んでしまいます。お手軽なのはいいですが、副作用は大きいので気をつけてね!というお話でした〜。
参考ページ
- 投稿日:2019-06-22T16:34:20+09:00
条件分岐 if を一切使わずに Jacascript でカレンダーを作る
みなさん、カレンダーつくっていますか?
今どきは Google Calendar 埋め込んだりして済ませることも多いんでしょうけど、稀に自分で作らなければいけなくなることがありますよね?
無い方はさようなら。
で、カレンダーの作り方をいろいろ探してみたんですが、ちょっとまどろっこしいというか、条件分岐めっちゃつかっててスマートじゃないなって感じたんですよ。
ということで、ifに頼ることなくカレンダーを作ろうという試みです。
まずはコードを御覧ください
最初に結論というか、コードを載せてしまいます。
const date = new Date(); // 今月の最終日を取得する date.setMonth(date.getMonth() + 1); date.setDate(0); const lastDate = date.getDate(); // 一ヶ月の日付を配列にする const dateList = [...Array(lastDate).keys()].map(item => item + 1); // 今月1日の曜日を取得する date.setDate(1); const firstDay = date.getDay(); // 月初より前の部分を作るための配列 const spacer = [...Array(firstDay)]; // カレンダーの最初から最後まであるリストを作る calendarItems = [...spacer, ...dateList]; // カレンダーに何週分必要か計算 const weekCount = Math.ceil(calendarItems.length / 7); // 空のカレンダーを作る const $month = $('<div>') const $week = $('<tr>'); for (let i = 0; i < 7; i++) { $week.append('<td>'); } for (let i = 0; i < weekCount; i++) { $month.append($week.prop('outerHTML'), '\n'); } // カレンダーに日付を記入 calendarItems.forEach((item, index) => { $month.find('td').eq(index).html(item); }) // カレンダーTABLEに反映させる $('#calendar-body').html($month.html());HTMLは下記の通り。
<div id="calendar"> <table> <thead> <tr> <td>日</td><td>月</td><td>火</td><td>水</td><td>木</td><td>金</td><td>土</td> </tr> </thead> <tbody id="calendar-body"> </tbody> </table> </div>サンプルはCodePenに。
See the Pen Simple jQuery Calendar by Shingo Matsui (@shingorow) on CodePen.
考え方
ここからはこのカレンダーに関する考え方を説明していきます。
なるべくわかりやすくするため、説明がコードの順序と異なります。ご了承ください。
カレンダーの枠が決まってるんだから
カレンダーって、枠が決まっているじゃないですか。
必ず1行あたりに7日間あって、それが並んでいるという。
ということは、日付を記載するHTML要素をリスト化して、1から最終日まで日付を入力していけばいいだけなんですよね。
そうすれば条件分岐無しで全然行けてしまいます。
ただ、問題が起こります。
はい、必ず初日が日曜日になってしまいます。
そこでどうするか。
日付リストの初日のインデックスが適切であればいいよね
ということで、初日のインデックスを適切にオフセットしてあげれば単純なループで済ませることができるんです。
でもどうやってオフセットしましょう。
そう、初日の曜日番号をとって、その数の要素を日付配列の最初に追加してあげるといいですね。
それが
const firstDay = date.getDay(); const spacer = [...Array(firstDay)];ということです。
ここまでできたらあとはループをブンブン振り回してあげればカレンダーの完成です。
月曜日スタートにしたかったらどうすりゃいいの?
日付リストを
calendarItems.shift()
してあげればいいんじゃないでしょうか。というわけで
無事、条件分岐を使わずにカレンダーを作ることができました。
これさえつくってしまえば、月の切り替えとかはさほど苦労なくできるでしょう。
もし時間があれば月の切り替えとか、曜日ごとの色付け、祝日の色付けあたりについても投稿してみようかな。
では、これで。
- 投稿日:2019-06-22T15:31:52+09:00
【GAS】4行で「英単語を翻訳して保存スプレッドシートに保存する」スプレッドシートスクリプトを作る【GoogleAppsScript】
社内で勉強会をすることになり、トップバッターが私になりました。
何処にでもある話だと思いますが、社内でTECH勉強会を開催することになりました。会社のキャラクターとしては、テック寄りのITコンサルというところです。
私の所属する部署(最大150人程度?)宛に招待メールを送ることになり、仮に30%が参加するとすると、45人が参加することになります。
勉強会というと、もっとこじんまりとしたものを想像していたのでこの数字にちょっとビビり始めます。
さて。これは難しい問題です。
何が難しいって、「全員に分かるようなレベル感の題材を選ぶ必要があり、全員にとって役に立ったと思わせる単元」を採用しないといけないからです。
これはマジで難しい...
そこで目を付けたのが、恐らく誰しも使っているであろう「Googleスプレッドシート」。ただそれだけだと「勉強会」の題材としてちょっと弱いよなあ...そうだ!GASにしよう!ということでGoogleAppsScriptを題材にすることにしました。
題材は「Googleスプレッドシートで業務効率化!」になったが、オーダーで「ハンズオンして欲しい」と言われる...
GoogleAppsScript x 業務効率化 であれば、エンジニアもマネージャー層(来ないと思うけど)にも興味をもたせられるだろう、ということでこれに決めました。
しかし、そこにさらに追加で、「どうせなら実際に手を動かしてもらった方がいいよね。体にも刻み付けた方が記憶に残るだろうし!」と偉い人から言われました。
マジかよ...
あんまりにショボすぎるハンズオンだと勉強会の質が疑われるハメになるし、レベルが高すぎると「ああやっぱエンジニアってこうだよね。まわりのこと考えてない」みたいな評価を受けることになってしまいます...
丁度いいレベル感で、やった気になれるハンズオンの題材は無いかな...あった!そうだ!翻訳アプリ作ろう!!
誰でもやった気になれる、「4行で英単語を日本語に翻訳して保存するアプリ」をハンズオンの題材にした
というわけで、ハンズオンの題材が決まりました。英単語を入力してもらうとそれを翻訳して保存するスプレッドシートメモ帳みたいな感じのアプリにします!
これなら、作りやすいですし、多少の意義もありますし、アプリの本質である「入力→ロジック→出力」を学べるということで最高の題材です!うおお!よくこんな丁度いい案が湧いてきたな!
というわけで、早速作っていきます。本当に4行でできるのか!?
ソースコードはこちら
できました。最小動作のみを行うもので、下記のように4行になっています。(function定義部は除いて4行ね)
function myFunction() { var enText = Browser.inputBox("英単語を入力してください!") var jaText = LanguageApp.translate(enText, "en", "ja") var sheet = SpreadsheetApp.getActiveSheet() sheet.getRange(sheet.getLastRow() + 1, 1, 1, 2).setValues([[enText, jaText]]) }です。
ちなみにこれ、最初に出てくるプロンプトでキャンセル押すと、cancelの文字列を日本語翻訳して「キャンセル」がスプレッドシートに追加されてしまいます。ので、実際はエラーチェックする必要があるんでプラス数行になります。
(微妙に気になるのは、ユーザがマジで「cancel」を入力した場合はどうなるのかというところですが、まあ今回は対象外ですね)動かすまでの手順がこちら
まずは、スプレッドシートからスクリプトビューに移動します。
次に、表示されるスクリプトビューに先ほどのソースを書いていきます。
問題なければ保存して、コードを名前を付けて保存します。(ここでエラーが起きたら、メッセージ内容を参考にしてなおします。なおさないと保存できない...はず...)
保存ができたら、関数を実行してみます。下記の通り。
実行→関数を実行→myFunction(別名をつけた人はそれを選択)
実際の動作の様子
果たして動くのか!?
実行したら、スクリプトビューからもとのスプレッドシートに移動して、ダイアログが表示されていることを確認します。
ここに翻訳したい単語を入力します。でOK押下。結果は...
うおおおおおお!!翻訳されて追加されているぞおおおおおい!!
こんな感じになります。
まあ正直、だからどうした的なところはありますが、4行でできて雰囲気を掴むための題材としては極めて有用なのではないかと思いました。
欲を言うなら、「スプレッドシートから値を取得して」という部分も入れ込みたかったんですが、そうするとたぶんループすることになり、コード行数が微妙に増えるかなと。ともすると、エラーにハマる人の割合が数パーセント上昇するし...というところでやめました。
興味のある人はさらに深堀しておけばいいよね。うんうん。
- 投稿日:2019-06-22T15:02:44+09:00
[JavaScript] 特定のテキストを含む要素を取得する (without jQuery)
例題
<ul> <li>田中 あすか</li> <li>中川 夏紀</li> <li>黄前 久美子</li> <li>久石 奏</li> </ul>この中から「久」を含む li 要素を取得する。jQuery を使えば
$('li:contains(久)');と書ける。では jQuery を使わずに同様のことを実現するのはどうしたらいいだろうか
解答
Document.evaluate() と XPath の contain 関数を使う。
let xpath = "//li[contains(text(), '久')]"; let resultType = XPathResult.ORDERED_NODE_ITERATOR_TYPE; document.evaluate(xpath, document, null, resultType, null);この式は独自のイテレータを返す。値は例えば次のように取り出す。
let xpath = "//li[contains(text(), '久')]"; let resultType = XPathResult.ORDERED_NODE_ITERATOR_TYPE; let results = document.evaluate(xpath, document, null, resultType, null); while (true) { let li = results.iterateNext(); if (!li) break; console.log(li) }console<li>黄前 久美子</li> <li>久石 奏</li>ちなみに、残念ながらこのイテレータは iterator プロトコル を満たしていないため、Array.from() や for...of は使えなかった。
要素を最初の 1 つだけ取得する場合は
resultType
を変えて次のように書く。let xpath = "//li[contains(text(), '久')]"; let resultType = XPathResult.FIRST_ORDERED_NODE_TYPE; let li = document.evaluate(xpath, document, null, resultType, null).singleNodeValue; console.log(li);console<li>黄前 久美子</li>テキストを完全一致させる場合は XPath を次のように変える。
let xpath = "//li[text()='久石 奏']";参考
- MDN web docs
- Document.evaluate()
- XSLT/XPath リファレンス
- Stack Overflow
- 投稿日:2019-06-22T14:35:34+09:00
JavaScriptの可変長引数の個数
可変長引数の個数には気をつけろ
JavaScriptの可変長引数の個数には限界があり、うっかりハマる可能性があるということです。
例
例として、ある配列に別の配列を足したい時、
push
を使うとしましょう。
新しい配列を生成すればよいのではないかとお思いでしょうが、例ですので、まあ聞いてください。const array = ["foo", "bar"]; array.push(...createSomeArray()); console.log(array.length);スプレッド構文を使えば、ループも書かなくてよく、便利です。
が、createSomeArray
が50万の長さを持つ配列を返した場合、どうなるでしょうか。# Node.jsで実行した結果 array.push(...createSomeArray()); ^ RangeError: Maximum call stack size exceededエラーになります。
Cなどは関数を呼び出すとき、引数をスタックに積んでから関数呼び出しを行います。
JavaScriptでも内部で同様のことをしているのだと推察できます。詳しい方、実際のところを教えてください。
ちなみに
なお、Node.js v10.15.2で試したところ、
createSomeArray()
は125328個の配列まで返すことができました。
また、--stack-size
オプションでスタックサイズを増やすことができますので、よければ試してみるとよいでしょう。以上です。よろしくお願いします。
- 投稿日:2019-06-22T13:46:53+09:00
jQueryのサンプルを見かけたら標準APIでも書いてみる。(selector 絞り込み
jQueryのサンプルを見かけたら標準APIでも書いてみます。
仮想DOM等他のライブラリ使わない、thisもなるべく使わない縛りです。よく見かける、親セレクタを変更時に子セレクタを絞り込む実装です。
https://qiita.com/BRSF/items/1aa9d154bde497b0baa0
https://try-m.co.jp/blog/web-create/936/方針
- 最初の子セレクタの順番をキャッシュしておく(ref
- DocumentFragment を backgound と foreground として使い リアルDOMへのappendChildはなるべく一回で済ます。
- selected なるべく維持する
こうしました。
https://qiita.com/BRSF/items/1aa9d154bde497b0baa0
See the Pen select filter by kei nakoshi (@nakoshi-k) on CodePen.
https://try-m.co.jp/blog/web-create/936/
See the Pen VJpEbE by kei nakoshi (@nakoshi-k) on CodePen.
- 投稿日:2019-06-22T13:31:32+09:00
Reactの基本まとめ(Hooks含む)
最終的にReactでTodoリストを構築します。
Reduxなどの状態管理ライブラリは含みません。Reactとは
Reactは、クライアントサイドでWebページのレンダリングを行うサイトSPA(Single Page Application)を構築する際に便利なライブラリーです。
SPAでは、Webページ表示後も引き続きクライアント側でコンテンツの表示非表示等の状態管理を、継続的に行う必要があります。
Reactでは、予めJSによるロジックとHTMLがセットになった部品(コンポーネント)を用意しておき、それらを組み合わせることでWebページを構成する、コンポーネントベースと呼ばれる考え方を採用しています。
Reactは内部でJSのロジックと対応するDOM要素をマッピングしており、JS側の処理に応じて自動でDOM要素を更新してくれます。
このあたりの仕組みは仮想DOMと呼ばれています。静的ページやサーバーサイドレンダリングのページでは、引き続きjQueryも便利かなと思っています。
Reactの本家サイトは、最近日本語にも対応しておりとても充実しています。
ここのチュートリアルを一通り実践することもお勧めいたします。※Node.jsとnpmの利用環境が既に整っていることを前提としています。
Node.js / npmをインストールする(for Windows)
過去に投稿したこちらの記事でも触れています。
Node.jsとExpressでローカルサーバーを構築する(1) ―Node.jsとnpmの導入―環境構築
React公式では、Create React Appというスターターキットを用意しています。
今回は、簡単にはじめられるParcelを利用して環境を構築したいと思います。インストール
React本体と、ReactとDOM要素をつなげるためのReactDOMをインストールします。
$ npm install --save react react-dom引き続き、ParcelとBabelのReact用プリセットをインストールします。
$ npm install --save-dev parcel-bundler @babel/preset-reactトランスパイラ
Reactは、通常のJavaScript構文だけで構築することも可能ですが、基本的にJSXと呼ばれる構文を利用するのが一般的です。
ブラウザに導入されているわけではないので、トランスパイラと呼ばれるツールを用いてJavaScripの構文に変換(トランスパイル)する必要があります。
このトランスパイルには一般的にBabelというツールが用いられます。公式提供スターターキットのCreateReactAppでも内部でBabelが利用されているようです。
Babel自体は、自分で色々設定が出来る自由度の高いツールであると同時に、設定が面倒なツールでもあります。
ParcelはこのBabelを内包した上で、細かな設定をせずともすぐに使えるように作られています。
プリセットは、Babelのオプション用パッケージのことを指します。
ReactのJSXのトランスパイルはオプション機能として提供されている為、別途インストールが必要です。
Parcelの場合、インストールするだけで設定は特に不要です。バンドラ
es2015にて、JavaScripにモジュール機能(ESModules)が導入されました。
用途ごとに分割された外部JSファイルを、import・export文を通して利用する仕組みです。
HTMLファイル上での読み込み順序を気にすることなく、JS側でファイルの依存関係の管理が完結します。
また、各モジュール毎にスコープが閉じているので、ファイル間での名前の衝突の心配がなくなります。既にブラウザに機能導入が進んでいるようです。
対象のJSファイルを読み込む際に<script type="module">
とすると、ESModulesの対象となります。
ただ、読み込むファイル数が増えればリクエストの回数も増えてしまうので、今のところモジュールバンドラーというツールを用いて、ある程度まとめてしまう(バンドルする)のが一般的です。モジュールバンドラーは色々ありますが、特に人気なのはwebpackでしょうか。
webpackもBabelと同様、自由度が高い分、設定の手間が多いツールです。
実は、Parcelはバンドルもしてくれます。
こちらも細かな設定は不要なので、インストールした時点でほぼほぼ環境構築が完了しています。JavaScript modules | MDN
webpackとBabelについては、過去に投稿した記事もございます。
webpackとBabelの基本を理解する(1) ―webpack編―Reactコンポーネントの基本
JSXをHTMLで出力する
ひとまず、Reactを使って何かしらのHTMLを出力してみましょう。
下記のフォルダ構成でファイルを用意します。
ファイルの拡張子はjs
以外にjsx
も使えます。root └ src ├ index.html └ index.jsx<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>React Todo</title> </head> <body> <div id="root"></div> <script src="index.jsx"></script> </body> </html>// Reactパッケージの読み込み import React from 'react'; import ReactDOM from 'react-dom'; // Reactコンポーネント class App extends React.Component { render() { return ( <h1>Hello React!</h1> ); } } // HTMLタグにReactコンポーネントを紐付ける ReactDOM.render( <App />, document.getElementById('root') );rootフォルダで下記コマンドを実行してみます。
npx parcel src/index.htmlParcelによるバンドル
初回は
./dist
フォルダが作成され、html,js,mapファイルが出力されています。
コマンドで指定したhtmlをスタート地点として、<script>
で読み込んでいるJSファイルからimport
文を頼りに芋づる式に辿って、一つのファイルにまとめていきます。
出力されたJSファイルには暫定的な名前がつけられ、HTMLファイルの方もこれを参照するように書き換わっています。
mapファイルは、元ファイルと出力後ファイルのコードの対応を示す情報です。
ブラウザのDevToolなどでデバッグする際に、元ファイルの方を参照することが出来ます。実は、Parcelはテストサーバも立ち上げてくれます。
コンソールに出力されたローカルサーバのURLにアクセスすると、出力を確認できます。
また、特に指定をしなければwatchモードで起動する為、関連ファイルを編集すると勝手にコンパイル(トランスパイルとバンドル)を実行してくれます。カスタムコンポーネント
React.Component
クラスを継承したサブクラスApp
が、ユーザ定義のReactコンポーネント(カスタムコンポーネント)です。
名前は大文字で始める必要があります。
App
クラスのrender
というメソッドにて、戻り値を定義している部分に書かれているHTMLのようなものがJSXです。
正確にはXMLに近く、空要素には必ずスラッシュ/
を入れる必要があります。ReactDOM
ReactコンポーネントとDOM要素をつなぐ役割を果たします。
上記の例では、静的メソッドrender
を利用して、<div id="root">
配下にAPPで定義したHTMLが展開されるようにしています。対話による状態変更
ボタンのクリックに応じて、文字が切り替わるようにしてみます。
class App extends React.Component { constructor(props) { super(props); this.state = { isMorning: true }; } render() { return ( <div> <button onClick={e => { this.setState( { isMorning: !this.state.isMorning } ); }} > Click </button> <h1> {this.state.isMorning ? 'Good Morning' : 'Hello'} React! </h1> </div> ); } }
button
タグをクリックする度に文字が切り替わるようになりました。
このコンポーネントは、内部で状態isMorning
を保持・管理しています。
クリックするとisMorning
の状態が切り替わり、それに応じてHTMLも再レンダリングされます。
render
メソッド内のJSXの部分が、通常のHTMLとは様子が異なってきました。
onClick
はHTMLタグのonclick
属性とは異なる、JSX側の構文です。
用途はonclick
属性と同様、HTML要素にイベントハンドラを設置する為のものです。
h1
のテキストコンテンツ部分には、JSの三項演算子が追加されました。
isMorning
の値に応じて異なる文字列を返すようにしています。つまりHTMLにJSのコードを埋め込んだものがJSXです。
埋め込み部分は波括弧{ }
で囲みます。
PHPなどでテンプレートページを作る時と要領は似ており、JSの実行結果がHTML上に描画されます。
コンテンツのデータ、そのデータの処理ロジック、描画が一通りセットになったオブジェクトがReactコンポーネントです。
このコンポーネントを適宜組み合わせることで、状況に応じたWebページを表現します。renderメソッド
サブクラスで必ず定義しなくてはならないのが、
render
メソッドです。
render
メソッドでは、コンテンツをどう描画するのかをJSXなどで定義した情報を返す必要があります。
コンテンツに変更があれば、Reactはrender
メソッドを呼び出し、定義に基づいてコンテンツに紐づくHTML要素を更新します。
この際、全体を丸ごと更新するのではなく、差分を確認して必要な部分のみ更新してくれます。※差分の確認およびDOM要素の更新は、ReactDOMが担っています。
stateとsetState
前項で「コンテンツに変更があれば」と表しましたが、
render
メソッドにの実行に関わる情報は、厳密にはコンポーネントの「状態」を表す情報です。
このコンポーネントの「状態」を表す情報は、クラスのstate
プロパティとして、コンストラクタ内で定義します。
そして、このstate
プロパティを更新する専用メソッドsetState
を利用して、コンポーネントの状態を更新します。
するとrender
メソッドが呼び出され、state
に紐づくHTML要素が再レンダリングされます。
プロパティをstate
以外の名前で定義したり、直接更新するなどのsetState
メソッド以外の手段でstate
を更新した場合、render
は呼ばれません。上記サンプルコードにてボタンがクリックされた時に行っていることは、状態の変更だけです。
this.state.isMorning
の値を変更しているだけです。
isMorning
の値に紐づいているh1
タグは、Reactが勝手に更新してくれます。
HTMLのDOM要素を直接管理する手間から開放され、JavaScript上でのデータ管理を気にかけるだけで済みます。setStateを利用したstateの更新例
setStateで渡されたstateの断片は、最終的に
this.state
にマージされます。
配列を利用する場合は一旦複製する必要があります。// stateを複数定義する this.state = { stateA: 'hoge', stateB: [ 'cat', 'dog' ] }; // 特定のstateを更新する this.setState({ stateA: 'moimoi' }); // 配列の場合は、一旦複製 const stateB = this.state.stateB.slice(); stateB.push('rabbit'); this.setState({ stateB }); // 下記では再描画が発生しない this.state.stateB.push('rabbit'); this.setState({ stateB: this.state.stateB });Reactは、更新内容を最小限にとどめる為に差分チェックをします。
setState
を実行しても、前回と値に変わりが無ければ再レンダリングされません。
その際はObject.isのアルゴリズムに基づいて比較を行います。
配列は、保存されたメモリの位置でざっくりと比較されるので、新規配列を渡す必要があります。const stateA = ['cat', 'dog']; const stateB = stateA; //stateAもstateBも同じ配列を参照している const stateC = stateA.slice(); stateB.push('rabbit'); stateC.push('rabbit'); console.log(Object.is(stateA, stateB)); // true console.log(Object.is(stateA, stateC)); // falsesetState() | React
state とライフサイクル | Reactイベント処理
イベントハンドラに無名関数を渡すのではなく、クラスのメソッドを設定する場合は以下のようになります。
Reactに限った話ではありませんが、イベントリスナーを登録する際にelement.addEventListener('click', this.someMethod)
の要領でメソッドをそのまま渡しても、コールバックで実行された関数内でのthis
の参照はそのメソッドが属するオブジェクトにはなりません。
素のJSでは、イベントを登録したDOM要素がthisになります。Reactの場合はundefined
でした。
その為、コンストラクタ内にてbindメソッドで所定のオブジェクトをthis
として参照する関数を生成して、上書きしています。class App extends React.Component { constructor(props) { super(props); this.state = { isMorning: true }; // コールバック関数内で、thisの参照がこのクラスを指すための設定 this.handleClick = this.handleClick.bind(this); } // クリック時のハンドラ handleClick(e) { this.setState( { isMorning: !this.state.isMorning } ); } render() { return ( <div> <button onClick={this.handleClick} > Click </button> <h1> {this.state.isMorning ? 'Good Morning' : 'Hello'} React! </h1> </div> ); } }無名関数を直接渡す場合は、アロー関数を利用します。
propsによるデータの受け渡し
複数のコンポーネントを組み合わせてこそのReactなので、ボタンを別のコンポーネントとして切り出してみます。
// 子コンポーネント // ボタンコンポーネント class MyButton extends React.Component { // state等の宣言をしない場合は省略可 constructor(props) { super(props); } render() { return ( <button onClick={this.props.onClick} > {this.props.text} </button> ); } } // 親コンポーネント class App extends React.Component { constructor(props) { super(props); this.state = { isMorning: true }; this.handleClick = this.handleClick.bind(this); } handleClick(e) { this.setState( { isMorning: !this.state.isMorning } ); } // <button>の代わりに<MyButton /> render() { return ( <div> {/* 文字列とイベントハンドラを渡す */} <MyButton text="Click" onClick={this.handleClick} /> <h1> {this.state.isMorning ? 'Good Morning' : 'Hello'} React! </h1> </div> ); } }JSX上で、一つのタグとしてコンポーネント名を記述します。
この際、子コンポーネントとなるコンポーネントに値を渡すことが出来ます。
受け取った側のコンポーネントは、this.props
からこの値にアクセスできます。
コンストラクタ内で受け取っているprops
がそうです。
this.props
は読み取り専用です。this.state
の様に更新することは出来ません。
this.props
もまた、更新されるとrender
メソッドが呼び出され、紐づくDOM要素が再レンダリングされます。外部から渡される読み取り専用データ
props
と、内部で管理する制御データstate
、この二つがコンポーネントのレンダリング実行に関係します。尚、Reactコンポーネントが大文字で始める必要があるのは、通常のHTMLタグと区別する為です。
<div>
はhtmlのdivタグとして認識されますが、<Div>
はReactコンポーネントと見なされます。
スコープ内に該当コンポーネントが無い場合はエラーになります。関数コンポーネント
上記ボタンコンポーネントは、内部で状態
state
を持ちません。
受け取ったprops
をJSXで展開するのみです。
この場合、クラス構文ではなく関数の構文で定義する方が好ましいです。// 一つの連想配列でpropsを受け取る function MyButton(props) { return ( <button onClick={props.onClick} > {props.text} </button> ); } // 分割代入が便利 function MyButton({ onClick, text }) { return ( <button onClick={onClick} > {text} </button> ); }
render
メソッドと同じく、JSXを返します。
Reactがインポートされているモジュール内であれば、そのままReactコンポーネントとして認識されます。Hooks
ReactのVer.16.8から、新機能Hooksが導入されました。
Hooksは、関数コンポーネントに追加できる様々な機能群です。
クラスコンポーネントでしか出来なかったことが、関数コンポーネントでも出来るようになります。
公式によると、クラスコンポーネントはトランスパイル後のソースにて、関数コンポーネントほど最適化されていないようです。
今のところクラスコンポーネントを将来的に廃止するとかそういう話にはなっていませんが、これからはシンプルな関数コンポーネントの組み合わせによる構成が推奨されているようです。ステートフック
ステートフックは関数コンポーネントにも内部状態を持たせる仕組みです。
これまで、stateを保持する場合はクラスコンポーネント、保持しない場合は関数コンポーネントと使い分けていたのが、Hooksを利用すると関数コンポーネントでもstateを持つことが出来ます。上記のクラスコンポーネント
App
をHookを利用した関数コンポーネントに書き換えてみます。import React, { useState } from 'react'; function App() { // useStateの引数はstateの初期値 const [isMorning, toggleFlag] = useState(false); return ( <div> <MyButton onClick={e => toggleFlag(!isMorning)} text="Click" /> <h1> {isMorning ? 'Good Morning' : 'Hello'} React! </h1> </div> ); }
useState
関数をインポートして利用します。
必ず、関数コンポーネント直下のスコープで実行する必要があります。
引数はstateの初期値です。
戻り値は配列です。0番目が現在のstate、1番目がstateを更新する為の関数です。分割代入で受け取っています。
それぞれ、クラスコンポーネントのthis.state
とthis.setState
と同じ役割を果たします。
初回実行時はuseState
の引数で渡した初期値をstateとして受け取ります。
2回目以降は現在のstateの値(更新用関数で更新した場合は更新後の値)を受け取ります。
必要に応じて複数のstateを用意することも出来ますし、クラスコンポーネントの様に一つのオブジェクトで管理する事もできます。function Example() { // 別々にstateを用意する const [count, setCount] = useState(0); const [animal, setAnimal] = useState(['cat']); // もしくは一つのオブジェクトで定義 // 但し、this.setStateとは異なり、古いものにマージされるのではなく置換されるので注意 const [state, setState] = useState({ count: 0, animal: ['cat'] });自己定義関数
useState関数はレンダリングの度に実行されますが、引数が有効なのは初回レンダリング時のみです。
実際に中のソースがどうなっているのかは分かりませんが、挙動としては自己定義関数が近いでしょうか。let animal = (first) => { // 初回しか実行されない処理 console.log(first); // 自らを上書き animal = () => { // 2回目以降に実行される処理 console.log('cat!'); }; }; animal('dog!'); // dog! animal('dog!'); // cat! animal('dog!'); // cat!Todoアプリ
シンプルなTodoアプリを作成します。
Todoリストを表示する
ひとまず、予め用意したTodoリストを表示するコンポーネントを作成します。
リストは配列で管理します。import React, { useState } from 'react'; import ReactDOM from 'react-dom'; // Todo項目 function Todo({ text }) { const [completed, setState] = useState(false); return ( <li onClick={e => setState(!completed)} style={{ textDecoration: completed ? 'line-through' : 'none' }} > {text} </li> ); } // Todoリスト function TodoList() { const [todos, setTodo] = useState( [ '牛乳 2本', '卵 10ヶ入 1パック', '食パン 5枚切り 1袋' ] ); // todosを基に<Todo />の配列を作成 return ( <ul> {todos.map((todo, index) => ( <Todo key={index} text={todo} /> ))} </ul> ); } ReactDOM.render( <TodoList />, document.getElementById('root') );コンポーネント
Todo
は、一つのtodo項目を表します。
完了の状態を保持し、クリックすると完了を示す打ち消し線が表示され、再度クリックすると未完了の状態になります。
JSXにてstyle属性を適用したい場合は、オブジェクト{style名:値}
を渡します。
text-decoration
の様なハイフン-
を含む名前は、ローワーキャメルケースで指定します。コンポーネント
TodoList
は、todoリストを保持します。
配列をJSX内で展開する場合はmapメソッドを利用します。
その際、key
値も指定する必要があります。
keyはReactが内部でリストの各要素を識別する為に用いる為、リスト内で一意の値でなくてはなりません。
固有のID等の値が無い場合、配列のインデックス値を適用することも可能ですが、インデックスは随時振りなおされるものなので、あまりお勧めは出来ません。Todoを追加する
Todoを自分で入力するためのコンポーネントを追加します。
function Todo({ text }) { /* 変更なし */ } // Todoを追加する function AddTodo({ addTodo }) { const [inputText, setInputText] = useState(''); return ( <form onSubmit={e => { e.preventDefault(); // 素のJSのEvent.preventDefault()と同じ if (!inputText.trim()) { return; } addTodo(inputText); setInputText(''); }} > <input type="text" value={inputText} onChange={e => setInputText(e.target.value)} /> <button type="submit">Add Todo</button> </form> ); } // Todoリスト function TodoList() { const [todos, setTodo] = useState([]); return ( <div> <AddTodo addTodo={newTodo => setTodo(todos.concat(newTodo))} /> <ul> {todos.map((todo, index) => ( <Todo key={index} text={todo} /> ))} </ul> </div> ); }ReactにてInputフォームを利用するにあたり、Input要素の状態(value属性の値)を管理する為のstateを用意しています。
入力値を受け取りtodos
を更新する関数を、propsを通してTodoList
からAddTodo
に渡しています。Ref
DOM要素に直接アクセスする方法として、Refがあります。
React.createRef()
メソッドを利用してRef
を作成します。
そのRef
を対象のReact要素のref
属性に設定すると、current
プロパティを通してDOM要素に直接アクセスすることが可能になります。function AddTodo({ addTodo }) { let textInput = React.createRef(); return ( <form onSubmit={e => { e.preventDefault(); const elInput = textInput.current; if (!elInput.value.trim()) { return; } addTodo(elInput.value); elInput.value = ''; }} > <input type="text" ref={textInput} /> <button type="submit">Add Todo</button> </form> ); }HTML要素ではなく、カスタムクラスコンポーネントの
ref
属性にRef
を適用した場合、curret
プロパティでアクセスできるのは、そのクラスのインスタンスです。
関数コンポーネントはインスタンスが存在しない為、ref
属性は利用できません。
あまり多用するとReactを利用するメリットが薄くなるので、基本的にはstateとpropsを通して管理するのが望ましいです。Todoに表示フィルタを追加する
Todoリストを「全て・完了・未完了」で切り替えられるよう、フィルター機能を追加します。
// 表示切替用フィルタ const VisibilityFilters = { SHOW_ALL: 'All', SHOW_COMPLETED: 'Completed', SHOW_ACTIVE: 'Active' }; function Todo({ text, filter }) { const [completed, setState] = useState(false); if ( (filter === VisibilityFilters.SHOW_ACTIVE && completed) || (filter === VisibilityFilters.SHOW_COMPLETED && !completed) ) { // 表示対象外の場合はnullを返す(非表示) return null; } return ( <li onClick={e => setState(!completed)} style={{ textDecoration: completed ? 'line-through' : 'none' }} > {text} </li> ); } function AddTodo({ addTodo }) { /* 変更なし */ } // フィルターボタン function Link({ active, onClick, children }) { // 予めオブジェクトで定義して展開するのもあり const params = { onClick: onClick, disabled: active, style: { marginLeft: '4px' } }; return ( <button {...params}> {children} </button> ); } // 表示の切替 function Footer({ filter, setFilter }) { const linkList = Object.values(VisibilityFilters); return ( <div> {linkList.map(myFilter => ( <Link key={myFilter} active={myFilter === filter} onClick={e => setFilter(myFilter)} > {myFilter} </Link> ))} </div> ); } // Todoリスト function TodoList() { const [todos, setTodo] = useState([]); const [filter, setFilter] = useState(VisibilityFilters.SHOW_ALL); return ( <div> <AddTodo addTodo={newTodo => setTodo(todos.concat(newTodo))} /> <ul> {todos.map((todo, index) => ( <Todo key={index} text={todo} filter={filter} /> ))} </ul> <Footer filter={filter} setFilter={setFilter} /> </div> ); }
TodoList
に、現在のフィルターを表す新たなstatefilter
を追加しました。
Todo
コンポーネント内では、フィルターと項目の完了状態が一致しない場合にnull
を返すようにしています。
null
を返すと、そのコンポーネントはレンダリングの対象から外れます。props.children
Reactコンポーネントは、空要素以外にコンテンツを閉じタグで囲って記述することも可能です。
この場合、子要素はprops.children
で取得できます。
他のReactコンポーネントを渡すことも可能で、コンポーネントのネストが深くなることを防ぐことが出来ます。function Child(props) { return <p>{props.children}</p> } function OtherChild(props) { return <span>{props.text}</span> } function Parent() { return ( <Child> <OtherChild text="HOGE" /> </Child> ); } //<p> // <span>HOGE</span> //</p>Flux
上記例では、Todoのテキストのリストは
TodoList
コンポーネントが、項目ごとの完了未完了の状態はTodo
コンポーネントが保持しています。
大きなアプリケーションでは、このstateが各コンポーネントに散らばっていることで、管理が煩雑になってしまうかもしれません。
Reactを提供するFaceBookでは、アプリケーションの状態を一元的に管理する設計手法として、Fluxという考え方を提示しています。Fluxに関しては、以前に投稿したこちらの記事がございます。
- FluxによるReactアプリの状態管理 Flux・FluxUtils編
- FluxによるReactアプリの状態管理 Redux・React-Redux編
- FluxによるReactアプリの状態管理 ReactHooks編
デベロッパーツール
公式からデバッグ用ツールが提供されています。
- 投稿日:2019-06-22T13:22:54+09:00
【Javascript】addEventlistenerについて
前回こちらの記事で、【Javascript】即時関数とFunctionコンストラクタ、関数宣言、関数式についてJavascriptの関数について学習しました。
基本的には、関数式の形態を使用することや、他のコードを汚染しないために、即時関数をいう手法もあることを知りました。
しかし、まだまだ分からないことばかりです。
今回はソースコードの中にあった、addEventListener
について学習したいと思います。ソースコードは前回と変わらず以下のとおりです。
今回はCSSのコードは省略しています。index.html<!DOCKTYPE html> <html lang = 'ja'> <head> <meta charset = 'utf-8'> <title>Comment Form</title> <link rel='stylesheet' href='css/styles.css'> </head> <body> <div class='container'> <textarea id='comment' placeholder = 'your comment here'></textarea> <div class='btn'>Submit</div> <p><span id='label'>30</span>characters left</p> </div> <script src= "js/main.js"></script> </body> </html>main.js(function(){ //即時関数 'use strict'; var comment = document.getElementById('comment'); var label = document.getElementById('label'); var LIMIT = 30; var WARNING = 10; label.innerHTML = LIMIT; comment.addEventListener('keyup',function(){ //addEventListenerとはなんなのか var remaining = LIMIT - this.value.length; label.innerHTML = remaining; if(remaining<WARNING){ label.className = 'warning'; } else{ label.className = ''; } }) })();基本:イベントとは
まず、イベントとはなんなのかという話です。
Qiita:イベントを参考にしてみると、イベントとは、簡単に言うと、何かをしたときに関数が実行されることを指します。
例えば、ボタンをおしたときに色が赤くなるとか、カーソルを上に置いたときにアラートが出てくるとか様々です。
よく使われるイベントたちは
- onload(読み込みが終わったとき)
- onclick(マウスをクリックしたとき)
- onkeyup(キーボードのキーを離したとき)
こういうのがあります。
調べたらいろいろ書いてあるサイトを見つけることができます。JavaScriptのイベントハンドラ一覧そして、このコードをいかにして使うかというと、JavaScript初級者から中級者になろう三章第一回 イベントプロパティを参考にします。
簡単にですが、引用します。HTML要素にイベントに対応した属性をつける
以下のようにHTML要素にonclickイベントを直接つける方法。こうすると、クリックしたときに特定のイベントを起こすことができます。
<!doctype html> <html> <head> <title>test</title> </head> <body> <p onclick="console.log('click');">test</p> </body> </html>もしくは別の場所に関数を作ってそれを呼び出す。
<!doctype html> <html> <head> <title>test</title> <script type="text/javascript"> function aaa(){ console.log('aaa!'); } </script> </head> <body> <p onclick="aaa();">test</p> </body> </html>どちらも、HTMLの要素に直接書いている形になります。
イベントプロパティ:JavaScriptから上のイベントを操作する
Javascriptに関数を記述して、その関数を呼び出すことでイベントを起こすこともできます。
<!doctype html> <html> <head> <title>test</title> </head> <body> <p id="abcd">test</p> <script type="text/javascript"> function aaa(){ console.log('aaa!'); } var p = document.getElementById('abcd'); //要素pを取得 p.onclick = aaa; //onclickに関数aaaを代入することで、onclickを実行すると関数aaaが実行される //onclickをイベントプロパティという </script> </body> </html>しかし、これには欠点があります。
同時に2つ以上のイベントを起こすことができないということです。
まるまる引用ですが、、<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja" dir="ltr"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>test</title> </head> <body> <div id='box'>This is box</div> <button id='btn'>box</button> <script type="text/javascript"> window.onload = function() { //要素を取得 var btn = document.getElementById('btn'); var box = document.getElementById('box'); // 背景色を赤くするイベント btn.onclick = function() { box.style.backgroundColor = 'red'; }; // クリックしたらアラートを出す btn.onclick = function() { alert('clicked'); }; }; </script> </html>この結果は一つ目の背景色を赤くするイベントは上書きされてイベントとして出てきません。
それを解決し、同時に2つ以上のイベントを発生させることができるのがAddEventLisrenerということです。
addEventListenerの書き方
対象の要素.addEventListener(何か起きたら, 何かする, false); ↓つまり オブジェクト.addEventListener('イベント名',function(){処理内容},false) // falseは省略可能であると書いてあるサイトもあります //なぜここでfalseが必要なのかという話は「イベントキャプチャリング」という、また別の話になるため、省略します。AddEventListenerを用いた例
例えば以下のようなコード例では、P要素をクリックすると、aaa!というログとbbb!というログが表示されます。
つまり、関数が2回表示されたということがわかります。
これを使わなかければ、上書きされてaaa!は表示されないため、便利なメソッドです。<!doctype html> <html> <head> <title>test</title> </head> <body> <p id="abcd">test</p> <script type="text/javascript"> function aaa(){ console.log('aaa!'); } function bbb(){ console.log('bbb!'); } var p = document.getElementById('abcd'); p.addEventListener('click', aaa, false); p.addEventListener('click', bbb, false); </script> </body> </html>以上で、メソッドAddEventListenerについての復習を終わります。
参考にしたサイト
・JavaScript初級者から中級者になろう 三章第二回 イベントリスナ
・Qiitaイベント処理
- 投稿日:2019-06-22T12:43:04+09:00
[Vue]watchフックで配列を監視する場合、ディープウォッチャーにしておく必要がある件
先に結論のコード
See the Pen watchフックについて(3) by riotam (@riotam4) on CodePen.
JS側11行目(ディープウォッチャーの指定部分)watch: { users: { handler: function(){ alert('変更を検出しました'); }, deep: true } },今回したいこと
Vue.jsにはwatchフックという仕組みがあり、指定したデータに変更があった際、仕込んでおいたメソッドを起動してくれます。
便利な仕組みで重宝されますが、watchフックは監視する対象が配列(またはオブジェクト)の場合、配列自体が変更されると検知してくれるのですが、配列の中(要素など)の変更については、検知してくれません。
以下に具体例を出しながら、今回はこれについて一歩深入りしてみたいと思います。監視対象が単なる文字列の場合
See the Pen watchフックについて by riotam (@riotam4) on CodePen.
入力欄を変更すると、その度に変更を検知して、
JS側6行目watch: { user: function(){ alert('変更を検出しました'); } },↑が起動して、ダイアログを出します。
ちょっと、うっとしい感じになっちゃってますが…笑
ちゃんと、監視してくれてて、ほぼリアルタイムで検知してくれていることを確認してみてください。監視対象が配列で、変更対象がその中の1要素の場合
See the Pen watchフックについて(2) by riotam (@riotam4) on CodePen.
次にこちらを試してみてください。
ちゃんと変更の反映はされますが、変更を検知していない様子。
これが、はじめの方にも書かせてもらった、監視できていないパターンです。
これの対策方法が、今回の本題です。対策は「ディープウォッチャー」にすること
See the Pen watchフックについて(3) by riotam (@riotam4) on CodePen.
これが、ディープウォッチャーにしているパターンです。
変更を毎回検知してくれているのが、確認できるかと思います。
具体的に変更を加えるのは、JS側11行目watch: { users: { handler: function(){ alert('変更を検出しました'); }, deep: true } },ここです。
処理内容は、hundler部分に転記して、加えてdeep:true
とすることで、ディープウォッチャーモードにしています。結論
watchフックは、Vue.jsでも非常に便利な機能ですが、配列などに使う際にはディープウォッチャーモードにできているか、注意が必要。
以上です。
最後ありがとうございました。
- 投稿日:2019-06-22T12:43:04+09:00
[Vue]watchフックで連想配列を監視する場合、ディープウォッチャーにしておく必要がある件
先に結論のコード
See the Pen watchフックについて(3) by riotam (@riotam4) on CodePen.
JS側11行目(ディープウォッチャーの指定部分)watch: { users: { handler: function(){ alert('変更を検出しました'); }, deep: true } },今回したいこと
Vue.jsにはwatchフックという仕組みがあり、指定したデータに変更があった際、仕込んでおいたメソッドを起動してくれます。
便利な仕組みで重宝されますが、watchフックは監視する対象が連想配列の場合、連想配列自体が変更されると検知してくれるのですが、連想配列の中(要素など)の変更については、検知してくれません。
以下に具体例を出しながら、今回はこれについて一歩深入りしてみたいと思います。監視対象が単なる文字列の場合
See the Pen watchフックについて by riotam (@riotam4) on CodePen.
入力欄を変更すると、その度に変更を検知して、
JS側6行目watch: { user: function(){ alert('変更を検出しました'); } },↑が起動して、ダイアログを出します。
ちょっと、うっとしい感じになっちゃってますが…笑
ちゃんと、監視してくれてて、ほぼリアルタイムで検知してくれていることを確認してみてください。監視対象が連想配列で、変更対象がその中の1要素の場合
See the Pen watchフックについて(2) by riotam (@riotam4) on CodePen.
次にこちらを試してみてください。
ちゃんと変更の反映はされますが、変更を検知していない様子。
これが、はじめの方にも書かせてもらった、監視できていないパターンです。
これの対策方法が、今回の本題です。対策は「ディープウォッチャー」にすること
See the Pen watchフックについて(3) by riotam (@riotam4) on CodePen.
これが、ディープウォッチャーにしているパターンです。
変更を毎回検知してくれているのが、確認できるかと思います。
具体的に変更を加えるのは、JS側11行目watch: { users: { handler: function(){ alert('変更を検出しました'); }, deep: true } },ここです。
処理内容は、hundler部分に転記して、加えてdeep:true
とすることで、ディープウォッチャーモードにしています。結論
watchフックは、Vue.jsでも非常に便利な機能ですが、連想配列などに使う際にはディープウォッチャーモードにできているか、注意が必要。
以上です。
最後ありがとうございました。
- 投稿日:2019-06-22T12:26:31+09:00
Javascriptでブラウザの「戻る」ボタンを無効にする
はじめに
先日とある案件で業務アプリを実装していたところ、「ブラウザで戻れないようにしてほしい」という要件を頂いて実装したので記録に残しておきます。
この問題をどの様に対処すればいいのかいくつか調べたところ2パターンほど記事が見つかりました。
1つには戻るボタンを押して履歴をさかのぼった際にすぐ一つ進んで元のページに戻ってくる方法です。
しかしこれではだめです。ユーザーの要件はそもそもブラウザで戻れないようにすること。
これを実現するためには2つ目の方法が現実的になります。
それがページを遷移した際に現在の履歴を削除するという方法です。
今回はこの方法について紹介していきます。
ブラウザの「戻る」ボタンを無効にする
結論からいきましょう
history.pushState(null, null, null); $(window).on("popstate", function (event) { if (!event.originalEvent.state) { history.pushState(null, null, null); return; } });こちらのコードを全ページ共通で使っているJSに貼り付けるのみです。
それでは何をやっているのか説明していきます。
まず以下のwindowオブジェクト中のhistoryオブジェクトはブラウザの履歴を操作するためのメソッドを提供しています。
window.history今回はこのメソッドを利用してページ遷移した直後に前の履歴を消去します
そしてhistoryオブジェクトにはpushStateメソッドを持っており、これを使えば履歴を削除することができます。
pushStateメソッドの第1引数にstate オブジェクト、第2引数にタイトル、第3引数にURLを渡します。
今回は遷移したタイミングで今の履歴を残したくないので引数には全てnullを渡し、空の履歴に変更しましょう。
history.pushState(null, null, null);上記では初回アクセス時の処理しか対応できていません。
戻るボタンが押されたときにはブラウザが履歴を残そうとするのですぐにそれを消去しなければなりません。
これに対応するためonメソッドを使ってpopstateイベントを定義していきましょう。
popstateイベントとはブラウザの履歴を操作しようとした際に発火するイベントのことです。ブラウザの戻るボタンや進むボタンを押した際に発火します。
渡されたeventイベントオブジェクトの中にoriginalEventというプロパティが含まれています。
$(window).on("popstate", function (event) { if (!event.originalEvent.state) { history.pushState(null, null, null); return; } });これで戻るボタンを押した際に履歴を残す処理も防ぐことが出来ました。
まとめ
いかがでしたでしょうか?
これで簡単にブラウザバックを無効にして戻るを防ぐことができます。
フォームの送信でデータを頻繁に変更したりするサイトなんかでは特に有効ではないでしょうか?
- 投稿日:2019-06-22T10:35:06+09:00
アルゴリアの費用改善方法について
現在進行形で、algoliaの費用削減の施策を行っています
今回は、アルゴリアへのオペレーション数(検索回数)を改善することで、費用の削減を行いました。
アルゴリアはプランのオペレーション数を超えると、超えた分が課金されます。
経緯
カーナベルECサイトの検索のオペレーション数やカード枚数からアルゴリアの費用を見積もりして、社長からの承認を経て、
今年の3月頃に、ECサイトにアルゴリアを導入しました
その後、今年の5月に社長から費用削減(目的)の指令がありました
当初、見積もりして報告した金額より、請求金額が高いことが原因です
環境とシステム構成
前回の記事を参考
調査
金額が高い理由を調べたところ、当初想定した検索回数より多いことが原因でした
検索回数が多い原因を、アルゴリアのアナリティクスで確認しました。
確認したのは下記の項目です。
- 「良く使われる検索キーワード(Most popular searches)」
- 「検索結果なし(No Result)」
原因
その結果、下記の2つの原因があることがわかりました。
ローマ字の子音入力中の母音の検索の割合が多い
「検索結果なし率(No Result)」を確認し(38%以上)のうち、ほとんどが母音の検索でした。
ブルーアイズをローマ字入力する時の「ぶr」など
ローマ字入力中の日本語+母音は、検索結果にマッチせず、検索結果が「0件」になってしまいます。
アルゴリアのinstant-searchクライアントの仕様であり、
ローマ字入力中の日本語+子音の検索(IME入力)を考慮されていないためです
キーワード無しによる全検索の割合が多い
「良く使われる検索キーワード(Most popular searches)」を確認し(25%以上)をしめました。
検索画面の初期表示や検索ワードの再入力する際など、
検索ワードが無い時に全検索が行われ、キーワード無しによる全検索の回数が多くなっていました
解決方法
2つの原因に対して、下記の改善により解決を行ないました
ローマ字入力中の子音入力中の母音問題
ローマ字入力中に末尾に英字(母音)があるときは検索しないように改善しました
「検索結果なし率(No Result)」の割合が減り、検索回数は少なくなりました
キーワード無しによる全検索の割合が多い問題
単純に検索キーワードが空のときは検索しないように改善しました
これにより、全検索(
<empty search>
)は無くなりましたまとめ
この手法をとることで、費用が元々より4割ほど削減されました
- 投稿日:2019-06-22T09:02:50+09:00
[JS][Vue]confirm()とalert()の根本的な違いについて
今日のコード
See the Pen confirmについて(1) by riotam (@riotam4) on CodePen.
今回、横着したいという気持ちからVue.jsで書いてますが、説明したいの部分は生JSの部分です。
confirm()について、alert()と比較して説明したいと思います。コードの解説
HTMLの3行目<button @click="culc">計算する</button>ここの部分で、ボタンをクリックすると「culcメソッド」が発火します。
これはVueの書き方なので、今回は説明を割愛。JSの4行目culc() { if(confirm('計算しますか?')){ alert( 1 + 1); } }それで、発火されるメソッドはこちら。
confirmで確認されてOKなら、1+1の結果をalertで出力している…という、感じです。confirm()とalert()の違い
今回はconfirmについて調べていきましょう。
書き方はalertとよく似ていて、実行結果もよく似ています。
しかし、この2つは致命的に違う部分があります。それは、戻り値です。
alert()の戻り値
See the Pen confirmについて(3) by riotam (@riotam4) on CodePen.
簡単な確認用のアプリを用意しました。
「調べる」を押すとダイアログが出現し、「OK」を押すと、consoleにその戻り値を出力します。alert()はundefinedを返す
結果は、確認できたでしょうか。
undefined(未定義)が返されています。
つまり、本当にダイアログを出力することのみに、機能が限定されています。
余計なものを戻して、戻した先で何かの影響を与えることもなさそうです。confirm()の戻り値
See the Pen confirmについて(2) by riotam (@riotam4) on CodePen.
こちらも、同じ要領で「調べる」を押すとダイアログが出現し、「OK」を押すと、consoleにその戻り値を出力します。
ただし、中身はalertではなく、confirmに変えています。
ここではぜひ、「キャンセル」の方も押してみてください。confirm()はブーリアン型(true/false)を返す
「OK」で「true」、「キャンセル」で「false」を返すようになっているのが、確認できるかと思います。今回のコードは、こういったconfirm()の特性を、if文に活かした形で作られています。
alert()と、confirm()…似ているようで戻り値の違う関数なんですね。
ちょっとした応用
!confirm()
についてSee the Pen confirmについて(4) by riotam (@riotam4) on CodePen.
はい、お分かりになられますでしょうか。
true/falseが逆に返されています。
これは、たとえば下のような質問の確認に使えます。
See the Pen
confirmについて(5) by riotam (@riotam4)
on CodePen.
これなら、「はい」を選択して結果を出力せず、「キャンセル」を選択して結果を出力してくれます。confirm()の「はい」「キャンセル」はカスタムできない
さっきの例でも、「キャンセル」という選択肢だとなんか分かりにくいですよね。
できれば、「はい/キャンセル」を「自分で計算する/計算して」に変えたいところです。
しかし、残念ながら生JSではカスタムできません。HTML&CSSで、ダイアログみたいなものをつくって、それぞれのボタンに機能を割り当てる…という方法なら、できなくはないですが…面倒。
そんな方は、jQueryのプラグインやライブラリなら、カスタムに対応しているものもいくつかあるようですので、必要であれば調べてみてください。というわけで、今回はconfirm()とalert()の違いについてでした!
ありがとうございました。
- 投稿日:2019-06-22T08:20:09+09:00
jsのクリックイベントの謎??ご教示くださいませ。m(__)m
javaScriptとjqueryのクリックイベントの挙動が違うのは
わかったのですが・・・。
これは結構奥が深い気がします。
ご教示くださいませ。m(__)m
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title></title> <meta name="description" content=""> <meta name="keywords" content=""> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="naze.js"></script> <style> body{ background-color:#333; } a{ color:#fff; } </style> </head> <body> <a class="mm" href="#naze">clickイベント1</a> <a class="mm" href="javascript:void(0);">clickイベント2</a> <a class="mm" href="#naze" onclick="return false;">clickイベント3</a> <a class="mm" href="#naze"onclick="stopAction();">clickイベント4</a> <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> <nav id="naze">naze</nav> </body> </html>naze.js$(function(){ $(".mm").on("click",function(){ event.preventDefault(); alert("JQuery:" + $(this).text()); }); }); function stopAction() { event.preventDefault(); } //var q = document.querySelector(".mm"); //q.addEventListener("click",Action, false); var q = document.querySelectorAll(".mm"); for(var i=0;i<q.length;i++){ q[i].addEventListener("click",Action, false); } function Action(){ alert("javascript:" + this.text); }