- 投稿日:2020-09-27T23:02:28+09:00
【JavaScript】setTimeoutを使って処理のスケジューリング
JavaScript
で指定時間後に処理を実行するにはsetTimeoutを使用します。■書き方
第二引数に与えられた実行タイミング(ミリ秒)で、第一引数に定義された処理内容を1度実行します
setTimeout(処理内容,実行タイミング(ミリ秒))サンプルコード
以下は実行開始から5秒後にアラート表示される処理です。
example.jsvar alertMessage = function(){ alert("5秒経過しました!"); } setTimeout(alertMessage, 5000);関数に引数を渡す場合
example.jsfunction hello(name) { alert('Hello,' + name); } setTimeout(hello, 5000, 'Taro');コールバック関数を使用する場合
example.jsfunction hello(name) { alert('Hello,' + name); } setTimeout(function() { hello('Taro'); }, 5000);参照
- 投稿日:2020-09-27T22:57:37+09:00
JavaScriptを使ってTwitterのbotを作ってみた その1
JavaScriptを使ってTwitterのbotを作ってみた その1
プログラミングを勉強し始めて約5ヵ月くらい。そろそろGitHubやQiitaを活用してみようかなと思い、アカウントを作成してみました。
今回は初投稿ということで、何の記事を書こうか迷いましたが、自分がJavaScriptを使ってTwitterのbot作りに挑戦していたときに、割とJavaScriptを使ったTwitterのbot作りに関するサイトが少ないなと感じたので、この記事を書こうと思いました。プログラミングでTwitterのアカウントをいじることで、今自分の使っているアカウントにフォロー機能やいいね機能などを自動化させることも出来ますし、この記事を見てくれている方がやろうとしているbot作りだって出来ます。簡単な機能だけしか使わない場合は、Twitterのbotが手軽に作れるサイトで出来ますが、プログラミングで作ることで、さらに凝った機能を搭載したbotを作ることが出来るので、すごくおすすめです!
なお、TwitterのAPIキーの取得方法や必要なツールのインストール(node.jsやtwitterなど)方法は、他のサイトでも紹介されているので、この記事では、そういった事前作業は完了している前提で、もうコードを書くことが出来る状態から始めていきます。もし、まだAPIキーを取得していなかったり、ツールのインストールが完了していない場合は、こちらのサイトで分かりやすい説明をしてくれているので、そのサイトを参考にして取得してみましょう。
まずはtwitterのライブラリ取得と、APIキーを書くところから
bot.js'use strict'; let twitter = require('twitter'); // twiiterモジュールを使えるようにする let bot = new twitter({ // ここにtwitterのbotのAPIキーを入力する consumer_key : "取得したAPI Key", consumer_secret : "取得したAPI seacret Key", access_token_key : "取得したAccess token", access_token_secret : "取得したAccess token secret" });まずはbotをいじるために、初期設定をしていきます。 上記のコードを書くことで、開発をすることが出来ます。
試しにコマンドを書いてみる
さて、botを開発する際は、twitterモジュールが用意しているgetメソッド、postメソッドなどを使っていきます。(後1つありますが、そちらはほとんど使わないので、省きます。)
そして、そのメソッドを使って、リツイートをしたり、いいねをしたりするためのコマンドを入力していきます。bot.jsbot.post('statuses/update', {status: "これはツイートをするためのコードです!"}, function(err, tweet, res) { if(err) { console.log(err); }else{ console.log(tweet); }; });言葉で説明しても分かりにくいと思うので、実際に書いてみましょう! 上記のコードは、ツイートをするためのコードです。
まずはpostメソッドを使って、その第1引数にツイートをするためのコマンドを書いています。 コマンドについては、こちらのサイトが日本語で書かれていて、すごく分かりやすいのでおすすめです!
サイトに移動すると、この画面が表示されると思います。コマンドを確認したいときは、左にある【リファレンス】という項目のところにある【friends】や【favorites】などをクリックすると、見ることが出来ます。第2引数には、Twitterの方で決められているパラメータというものを書いていきます。今回は、ツイートをする機能を書いていくので、必須のパラメータであるstatusというキーを書きます。値には、ツイートしたい内容を書きます。
第3引数には、関数を書いていきます。その関数の引数も3つ指定していきます。1つ目にエラーが起こった際の引数、2つ目が成功した際の引数、3つ目は詳細を表示するための引数です。(3つ目の引数は滅多に使わないと思います。)
その次にif文を書いています。これは、もしエラーが起こってしまったらそのエラーのメッセージを表示するようにしています。よく起こるエラーでは、ツイートする内容の重複が挙げられます。
試しに、自分のアカウントで、何でもいいのでツイートをしてみてください。そしたら、その後すぐに全く同じ内容のツイートをしてみてください。「ツイートが重複しています」と出ませんか?そのエラーが、botを開発する際にも適用されています。node bot.js
では、ツイートが出来たか確認してみましょう。 先ほどのコードを実行するには、コマンドライン(ターミナルとかPowerShellなど)上で上記のコマンドを入力します。(bot.jsの部分はTwitterのbotを開発するファイル名)
エラーが起こらかった場合は、上記のようにたくさんのキーや値が表示されます。また、ツイートの方もちゃんとされているのが分かります。
私は百合がすごく大好きなので、百合botを作成しました笑ちなみに、成功したときは、
console.log(tweet);
と入力して、上記の結果が表示されましたが、その結果の中にあるキーを上手く利用することで、機能を書く際に制限を付けることが出来たりするので、ものすごく重宝します。例えば、結果の中にあるtextというキーを使って、
console.log(tweet.text);
という風に書き換えると、結果にはツイートした内容が表示されます。(今回は「ツイート検証中」)postメソッドとgetメソッドの違い
先ほど紹介したpostメソッドとgetメソッドの違いについて。postメソッドを使うときは、実際に機能を行いたいときに使用します。(リツイートしたり、いいねしたり、フォローしたりなど)
一方、getメソッドは、機能は行わないで、情報だけを取得するときに使用します。(botのフォロワーを確認したり、指定したアカウントのツイート内容を取得したりなど)まとめ
まずは1回目として、基本的なTwitterの機能を実装するための方法について紹介していきました。まずは先ほど紹介したサイトでいろいろなコマンドを見てみて、それを実装してみるということから始めてみましょう! 実装するだけでしたら、結構簡単に出来ちゃいます!
次回は、それらの機能を使った応用編を紹介していきたいと思います。具体的には、1回ツイートするだけでなく、指定した時間ごとに自動ツイートしてくれるようにしたり、ある内容が含まれたツイートだけいいねリツイートするようにしたり、フォロワーを確認して、まだbotがフォローしていないフォロワーのことだけフォローする方法などを紹介していきます。初めてのQiitaでの記事投稿で、うまく書けているか分かりませんが、少しでもお役に立てたらすごく嬉しいです♪ ありがとうございました!
- 投稿日:2020-09-27T19:31:59+09:00
ライブラリレスで0からJavaScriptで手書き数字認識やってみた(MNISTデータ使用)
概要
前回は学習用データとテストデータの読み込みを実装しました。
機械学習メモ - ブラウザ上でMNISTデータファイルをDrag&Dropで受け取って手書き数字をとりあえず表示する - Qiita今回はバックプロパゲーションを実装し、実際に学習~認識までを試してみました。(中間層は1個だけ。フィルタ層とかは無し。)
ネットワークの学習の様子を可視化したかったですが、エッジ数が数万個になるので断念しました。。データ読み込み後の画面キャプチャ
CodePen上で動作するもの
対象のデータをDrag&Dropすると、学習用データ60000個中の20000個分を学習します。※十数秒以上はかかると思います。
今回の実装だと精度は実用には耐えれないレベル。実用レベルにもっていくには結構専門知識が要りそう。See the Pen Neural Network learning step demo using MNIST by kob58im (@kob58im) on CodePen.
参考サイト
YouTube
音量注意!!
- 【深層学習】誤差逆伝播法|バックプロパゲーション
- Deep Learning入門:ニューラルネットワーク学習の仕組み
- さらに専門的な内容 ⇒ Neural Network Console - YouTube
その他
- AIプログラマになれる本 - 日経ソフトウェア / サンプルコード1
- 誤差逆伝播法をはじめからていねいに - Qiita
- 機械学習メモ - ブラウザ上でMNISTデータファイルをDrag&Dropで受け取って手書き数字をとりあえず表示する - Qiita
直リンクを貼るのはよろしくない気がしたので、リンク先から入手してください。今回参考にしたのは
ai_mook_download.zip\1\Part1\DetectApp
の中にあるC#のソースコードです。(移植するのはライセンス的にまずそうだったので、答え合わせ的な使い方をしました。) ↩
- 投稿日:2020-09-27T19:09:56+09:00
非同期処理async,awaitについて
非同期処理について
JavaScriptでは非同期処理を行う選択肢は主に3種類あります。
「Promise」「async,await」「Observable」です。
ここではasync,awaitについて記載します。async,awaitの特徴
async,awaitはPromiseの派生です。
Promiseを簡略化して記載することができます。
また、非同期処理を直列で実行したい場合に入れ子にならない利点があります。
Promiseとasync,awaitは混同して利用することができます。構文
Promiseで記載した非同期処理をasync,awaitに書き換えます。
Promise
method() { asyncFunction(parameter).then(closureOfSucceedCase).catch(closureOfFailureCase); }async, await
async method() { try { const response = await asyncFunction(parameter); } catch (error) { // 異常系処理 } }PromiseをreturnするasyncFunctionの前にawaitをつけて呼び出すことで、
同期呼び出しのように呼び出すことができます。
responseにはPromiseでthen()で渡されるパラメータがセットされます。
catchケースは、Promiseでcatch()が呼ばれる時に動きます。
また、awaitを利用する処理を書いている関数には必ずasyncを書かなければなりません。
asyncが付いた関数を呼ぶ関数にもasyncをつけなければなりません。動作する順序
下記の順で処理が動きます。
awaitは非同期処理が完了するまで次の行に移りません。async method() { // ① try { // ② const response = await asyncFunction(parameter); // ③ (非同期成功時) } catch (error) { // 異常系処理 // ③ (非同期失敗時) } // ④ }メリット
下記のように非同期処理を連続して実行したい場合に、Promiseのような入れ子にはならず
見易くなる点がメリットです。const response1 = await asyncFunction1(parameter); const response2 = await asyncFunction2(parameter); const response3 = await asyncFunction3(parameter);
- 投稿日:2020-09-27T18:55:11+09:00
JSで関数の処理から本質的でない処理を分離するProxyパターン
どのような言語であり汎用的な関数の設計は
SOLID原則の単一責任の原則に基づいたほうがいいと思っています。ただ、ビジネスロジックが入り込むと特定の処理に対し、ログやメール送信するなど
いわゆる付随する組み合わせ(Compound)な処理になっていきます。
すると、本質的な処理から付随した処理を分離しづらくなっていきます。このようなオブジェクト指向ではうまく分離できない特徴(クラス間を横断 (cross-cutting) するような機能)をアスペクトと呼びます。
JavaScriptでは関数に対してProxyを使うことで体系的に本質でない処理を関数から分離することが出来ます。
// 単体テストするのはこっち(本質的な処理) function sum(a, b) { return a + b } const handler = { // ロギング、メール送信などの処理を分離する apply: function(target, thisArg, argumentsList) { // 実行前にログを残す console.log(`args: ${argumentsList}`) // 引数をそのまま渡す const result = target(...argumentsList) // 結果のログや付随するメール送信などの処理など console.log(`result: ${result}`) return result } } const wrapSum = new Proxy(sum, handler) wrapSum(1, 2)次のように書いても同じやんと言われたらそうなんですけど、
Proxyのほうが体系的に書けるのがいいのかなと思います。// 単体テストするのはこっち(本質的な処理) function sum(a, b) { return a + b } function wrapSum(target, ...argumentsList) { // 実行前にログを残す console.log(`args: ${argumentsList}`) // 引数をそのまま渡す const result = target(...argumentsList) // 結果のログや付随するメール送信などの処理など console.log(`result: ${result}`) return result } wrapSum(sum, 1, 2)
- 投稿日:2020-09-27T18:24:59+09:00
非同期処理Promiseについて
非同期処理について
JavaScriptでは非同期処理を行う選択肢は主に3種類あります。
「Promise」「async〜await」「Observable」です。
ここではPromiseについて記載します。Promiseの特徴
非同期処理の完了を待ち合わせ、成功した場合、失敗した場合の処理を登録することができます。
また、複数の非同期処理を纏めて実行し、全ての完了を待ち合わせることもできます。構文
利用側
一つの非同期処理を実行する
asyncFunction(parameter).then(closureOfSucceedCase).catch(closureOfFailureCase);closureOfSucceedCaseには非同期処理が成功した時の処理を登録し、
closureOfFailureCaseには失敗したときの処理を登録することができます。(非同期実行結果から受け取るパラメータ) => { 非同期が完了したときの処理 }例) REST APIを呼ぶときのサンプルコード
// ① const url = 'http://127.0.0.1:4300/newsfeeds'; // ② this.httpClient.get(url).toPromise().then(response => { // ⑤ (成功時のみ) const newsfeeds = new Newsfeeds(); newsfeeds.decode(response); resolve(new Response(newsfeeds)); // ⑥ (成功時のみ) }).catch(error => { // ⑤ (失敗時のみ) console.error(error); reject(error); // ⑥ (失敗時のみ) }); // ③ console.log('非同期が終わる前に動く'); // ④コード内に数字を埋め込みましたが、処理は数字の順に動きます。
⑤⑥は非同期処理が完了したときに呼ばれますので、③や④より後に動作します。
⑤⑥はサーバからレスポンスが返ってきたときに動作します。複数の非同期処理の完了を待ち合わせる
下記のように記載します。
const promises = []; promises.push(new Promise((resolve, reject) => { // 非同期処理を実行する // 非同期処理が成功したらresolve()を呼ぶ // 非同期処理が失敗したらreject()を呼ぶ }); // ↑promisesに必要な分非同期処理を追加する Promise.all(promises).then(closureOfSucceedCase).catch(closureOfFailureCase);closureOfSucceedCaseはpromisesに登録した非同期処理が全て成功した場合に呼び出され、
closureOfFailureCaseはpromisesに登録した非同期処理が一つでも失敗した場合に呼び出されます。提供側
Promiseを提供する処理は下記のように記載します。
asyncFunction(parameter): Promise<型> { return new Promise((resolve, reject) => { // 何か処理をする if (xx) { // 成功した場合はresolve()を呼ぶ // すると呼び出し元でthen()に設定したクロージャが動き出す // 引数にはthen()のクロージャに渡すパラメータを入れる resolve(data); } else { // 失敗した場合はreject()を呼ぶ // すると呼び出し元でcatch()に設定したクロージャが動き出す // 引数にはcatch()でクロージャに渡すパラメータを入れる reject(error); } }); }
- 投稿日:2020-09-27T18:09:07+09:00
Redux ExampleのTodoListをReact Native(expo)に置き換えて解説-ToggleTodo編
Redux ExampleのTodoListをReact Native(expo)に置き換えて解説のToggleTodo編です。
AddTodo編は以下です。
Redux ExampleのTodoListをReact Native(expo)に置き換えて解説AddTodo編注:僕は掛け出しエンジニアであり、自分の勉強としての投稿という面もあるので、もしミスや勘違い、ベストプラクティスではない、等がありましたら、コメントしていただけると幸いです。
TODOに属性を追加
まずは、todoにcomplete属性を追加してtodoが完了済みなのかactive状態なのかを判定できるようにします。初期値はfalseにしておきます。
reducers/todos.jsconst todoReducers = (state = [], action) => { switch(action.type){ case 'ADD_TODO': return [ ...state, { id: action.id, text: action.text, completed: false //追加 } ] default: return state } } export default todoReducersTodoのcompleted属性がtrueかfalseによって見た目を変えたいので、TodoListコンポーネントを修正します。
components/TodoList.jsclass TodoList extends Component { render() { return ( <View> <FlatList data={this.props.todos} renderItem={({ item }) => ( <View style={styles.todoList}> <Text style={{ textDecorationLine: item.completed ? "line-through" : "none", }} //追加↑ > {item.text} </Text> </View> )} keyExtractor={(item) => item.id.toString()} /> </View> ); } }ここで、動作確認のため、reducers/todos.jsのcompleted属性をtrueにしてみましょう。todoに斜線が引かれていると思います。
Actionからcompleted属性を操作する
次に、上記でやったtrue,falseの操作をActionを経由して操作できるようにしましょう。
まずはActionCreatorを作成します。どのtodoに横線を引くかを判別するためにidを取得します。
src/actions/index.jslet nextTodoId = 0 export const addTodo = text => ({ type: 'ADD_TODO', id: nextTodoId++, text }) //追加 export const toggleTodo = (id) => { return { type: "TOGGLE_TODO", id, }; };reducersも変更します。
reducers/todos.jsconst todoReducers = (state = [], action) => { switch (action.type) { case "ADD_TODO": return [ ...state, { id: action.id, text: action.text, completed: true, }, ]; case "TOGGLE_TODO": //追加 return state.map((todo) => todo.id === action.id ? { ...todo, completed: !todo.completed } : todo ); default: return state; } }; export default todoReducers;やっていることとしては、stateに保存されている全てのtodoについて、それぞれのidとActionCreatorに渡された、横線を引きたいtodoのidを比べて、一致したらそのtodoのcompleted属性を逆転させるというものです。
ここで、例の如くApp.jsから動作確認をしてみましょう
App.jsに以下を追加してみましょう。App.jsimport { addTodo, toggleTodo } from './actions' store.dispatch(addTodo('Hello React!')) store.dispatch(toggleTodo(0))画面をみてみると斜線が引かれたtodoが追加されているはずです。
Todoをクリックしてcompleted属性を操作できるようにする
それでは、画面上からtodoの完了、未完了の操作ができるようにしましょう。mapDispatchToPropsを作成します。
これはcomponentで(今回で言えばTodoListで)dispatchをpropsとして渡せるものです。こうすることで、component側ではthis.props.onTodoClick(id)の形で先ほどApp.jsでやったdispatch(toggleTodo(0))みたいなことができるようになります。
containers/VisibleTodoList.jsimport { connect } from "react-redux"; import TodoList from "../components/TodoList"; import { toggleTodo } from "../actions"; //追加 const mapStateToProps = (state) => { return { todos: state.todoReducers }; }; //追加 const mapDispatchToProps = (dispatch) => { return { onTodoClick: (id) => { dispatch(toggleTodo(id)); }, }; }; //追加 const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList); export default VisibleTodoList;これでTodoListの方でonTodoClickが使えるようになったので、追加してみましょう。
components/TodoList.jsreturn ( <View> <FlatList data={this.props.todos} renderItem={({ item }) => ( <View style={styles.todoList}> <Text onPress={() => this.props.onTodoClick(item.id)} style={{ textDecorationLine: item.completed ? "line-through" : "none", }} > {item.text} </Text> </View> )} keyExtractor={(item) => item.id.toString()} /> </View> );これでtodoをクリックすると該当のtodoに斜線が引かれます。
ToggleTodo編は完成です!
お手元のシミュレーターでお試しください。
ここまでのソースコードはGitHubに上げていますのでご参考ください。次回は表示するTodoをcompleted属性によって切り替える「FilterTodo」機能を実装していきます。
参考
- 投稿日:2020-09-27T17:49:18+09:00
JavaでTODOアプリを制作しよう9 TODOの表示を作成日時が新しい順にソートする + 期日のデフォルトを当日の日付にする
こんにちは。
今回の記事では今まで作ってきたTODOの微調整を行います。TODOアプリ作成リンク集
1: [超基礎の理解] MVCの簡単な説明
2: [雛形を用意する] Spring Initializrで雛形を作ってHello worldしたい
3: [MySQLとの接続・設定・データの表示] MySQLに仮のデータを保存 -> 全取得 -> topに表示する
4: [POST機能] 投稿機能の実装
5: [PATCH機能] TODOの表示を切り替える
6: [JpaRepositoryの簡単な使い方] 検索機能の実装
7: [Thymeleaf テンプレートフラグメントで共通化] Headerの作成
8: [PUT機能] 編集機能の実装
9: TODOの表示を作成日時が新しい順にソートする + 期日のデフォルトを今日の日付にする(今ここ)TODOの表示を作成日時が新しい順にする
現状の仕様ではTODOリストは作成日時が古い順に表示されるようになっています。
つまり新しいTODOは表示の一番下にくるわけです。
細かいですがこれを登録した日付順(つまり作成日時が新しい順)にソートしましょう!
ソート作業はデータを取り扱う上でついて回る作業なので結構大事です!
TodoRepositoryを編集する
java/com/example/todo/dao/TodoRepository.java@Repository public interface TodoRepository extends JpaRepository<TodoEntity, Long> { List<TodoEntity> findByTitleContaining(String searchWord); //↓追加する List<TodoEntity> findAllByOrderByCreateTimeDesc(); }Repositoryには部分一致検索を実装するメソッドが追加されていましたが、新しく
findAllByOrderByCreateTimeDesc()
を追加してやります。こうすることでJpaRepositoryがDB上にある全データを作成日時がdescending(降順)にソートされた状態で渡してくれます。
超便利ですね!
TodoServiceを編集する
次にサービスクラスを編集します。
java/com/example/todo/TodoService.javapublic List<TodoEntity> findAllTodo() { //return todoRepository.findAll(); return todoRepository.findAllByOrderByCreateTimeDesc(); }コメントアウトしている部分が今までの記述でしたが、先ほど作った関数を呼んでやります。
こうすることで/topで表示されているTODOのリストが作成日時の降順になります!
TODO登録フォームの日付を今日の日付にする
JSで今日の日付を取得してformに埋め込む
今回はJSを直接top.htmlに書くことにします(直接書くのはあまり良くないですが、今回はシンプルなアプリなのでよしとしましょうw)
resources/templates/top.html<script> var today = new Date(); today.setDate(today.getDate()); var yyyy = today.getFullYear(); var mm = ("0" + (today.getMonth() + 1)).slice(-2); var dd = ("0" + today.getDate()).slice(-2); $("#date").val(`${yyyy}-${mm}-${dd}`); </script>こんなスクリプトをbodyの閉じタグの直前に書いてやればOKです。
slice(-2)
とする事でStringの最後の2桁だけを表示する様にしています。こうする事で一桁の数字は01, 05のように0がついた状態で表示され、二桁の数字は0がついてない状態で表示されます(012の下2桁だけ表示するという事です。)
このTODOアプリ作成手順を参考にされている方は既に
resources/templates/top.html<input type="date" id="date" name="deadline" class="col-9 my-0">となっているかと思いますが、実はここでid="date"としているのでJQueryで今日の日付を渡しています。
以上で今回の微調整となります。
次回以降は例外処理について触れていきたいと思います!
- 投稿日:2020-09-27T16:26:47+09:00
Javascriptの構文
このチュートリアルでは、Javascriptにおける大文字小文字の区別、識別子、コメント、文、式について学べるように記載しました。
Javascriptにおける大文字小文字の区別
変数、関数、名前、クラス名など含むすべてのJavascriptは、大文字小文字を区別します。
例えば、関数にinstanceofという名前をつけることはできません。
それは、キーワードとして既に使用されているからです。
ただし、instanceOfであれば有効な関数名として使用できます。識別子
識別子は、変数名、関数名、パラメータ名、クラス名です。識別子は1つあるいは複数の文字から成っており、以下の規則があります。
- 最初の文字は大文字小文字の全て、アンダースコア、$のみ使用可能
- 2文字目以降は、上記に加えて数字が使用可能
使用できる文字は、アスキー文字のみに限定されませんが拡張アスキー文字やUnicodeは推奨されていません。
識別子にはキャメルケースを使用するのが最良の方法です。
キャメルケースとは、最初の単語を小文字で開始し、次の意味を成す単語を大文字で開始することです。
以下のような例です。counter inArray beginWith redirectPageコメント
Javascriptにおいて、コメントを記載する方法は以下の2パターンがあります。
* コメントが1行のみの場合、文頭に//を入れる
* コメントが複数行の場合、/*で初めて*/で閉じる// this is a single-line comment /* * This is a block comment that can * span multiple lines */構文
Javascriptは必ずしも文末にセミコロンを必要としないが、使用することが推奨されています。
理由としては、コードが読みやすい、問題が起きたときの対処がしやすいことがあります。var a = 10; var b = 20;複数の構文をまとめる場合、文の最初に中括弧{を記載し、最後に}で閉じる必要があります。
if( a > b) { console.log('a is greater than b'); return 1; }計算式
変数aに変数bを足す構文は以下となります。
a + b;キーワード及び予約語
Javascriptには、いくつかの予約後とキーワードが定義されています。
我々は、これらを識別名として使用はできません。
- 投稿日:2020-09-27T16:25:45+09:00
next.jsでdotenvで環境変数を設定するのは間違い!たった3分で環境変数を設定する方法
概要
・next.jsでは「.env.local」ファイルで環境変数を設定できる。
・クライアント側でも環境変数を設定したいときは接頭語に「NEXT_PUBLIC_」をつける
・開発環境は「.env.development」、本番環境は「.env.production」でそれぞれの環境変数を設定できる環境変数を設定する(サーバー編)
next.jsでは簡単に環境変数を変更する仕組みがあります。
ルートディレクトリに「.env.local」ファイルを置き、その中で定義した環境変数はアプリの中で使うことができます。たとえば、開発環境と本番環境で異なるデータベースを使いますよね。そんな時に「.env.local」ファイルに設定すれば環境変数をアプリ内で利用できます。
env.localDB_HOST=localhost DB_USER=root DB_PASSWORD=root環境変数の使用.jsexport async function getServerSideProps(context) { console.log(process.env.DB_HOST) //コンソールに「localhost」を出力(サーバー側) return {props: {}} }環境変数に他の環境変数を設定したい場合
また、「.env.local」で設定した環境変数を別の環境変数で使うには、「$」を使います。
たとえば、ホスト名にポート番号を追加したいときには以下のように定義します。env.localPORT=3000 HOST=http://localhost:$PORTindex.jsexport async function getServerSideProps(context) { console.log(process.env.HOST) //コンソールに「http:localhost:3000」を出力(サーバー側) return {props: {}} }環境変数を設定する(クライアント編)
先ほどの、「.env.local」で設定した環境変数はnode.jsのみで適用されます。そのため、ブラウザ側で動くプログラムでは使うことができません、
クライアント側でも環境変数を使いたいときにはどうすればいいのでしょうか?この問題を解決するのが環境変数の接頭語の「NEXT_PUBLIC_」です。環境変数の接頭語に「NEXT_PUBLIC_」をつければ、クライアント側でも使うことができます。
これにより、ブラウザ側、サーバー側どちらでも環境変数を利用できるのです。env.localNEXT_PUBLIC_TEST=client-serverindex.jsexport async function getServerSideProps(context) { console.log(process.env.NEXT_PUBLIC_TEST) //コンソールに「client-server」を出力(サーバー側) return {props: {}} } const index = () => { return <p>{process.env.NEXT_PUBLIC_TEST}</p> //画面に「client-server」を出力(クライアント側) } export default index環境によって読み込むファイルを変更する方法
環境ごとに読み込む環境変数を変更したい場合があります。
たとえば、開発環境では開発用のDBを使うけど、本番環境では本番用のDBを使いたいなど環境ごとにDBを切り替えたい場面などです。そんな時は環境ごとのenvファイルを用意します。以下の4種類でそれぞれの環境変数を設定できます。
・.env:全環境共通の環境変数
・.env.development:開発環境(next dev)の環境変数
・.env.production:本番環境(next start)の環境変数
・.env.local:デフォルトの環境変数(すべての環境変数を上書き)これの環境変数の優先順位は以下の通りです。
①.env.local
②.env.development/.env.production
③.env基本的に、「.env」にデフォルトで全ての環境変数を設定して、「.env.development」、「.env.production」で上書きするのがいいのではないでしょうか?
参考
- 投稿日:2020-09-27T15:49:32+09:00
jQueryのプラグインを複数追加したら動かない
はじめに
自分の振り返りを兼ねて、ここに記していきたいと思います。
jQueryのプラグイン「drawer.js」を新たに実装したとき、ディベロッパーツールでエラーが起きていることを発見しました。
原因と解決方法を記していきたいと思います。原因
Uncaught TypeError: $(...).drawer is not a function at HTMLDocument. (index.html:747)
一体なんだ?と詳しくみてみると
エラー箇所はHTML内の下記に示されていました。
ただし、コード自体に誤りはなさそうでした。<script> $(document).ready(function() { $('.drawer').drawer(); }); </script>上記の場合だけでは、反映することができず、ドロワーメニューは開くことができません。
いろいろ調べた結果、、、
今回の場合は、他のプラグインを入れていることで動かなくなっている
つまり、コンフリクト(プラグイン同士の競合) が原因でした。解決方法
先ほどのコードに下記のコードを追加しました。
<script> jQuery.noConflict(); (function($) { $(document).ready(function() { $('.drawer').drawer(); }); })(jQuery); </script>
jQuery.noConflict();
を追加することで解決することができました。
- 投稿日:2020-09-27T15:30:33+09:00
JavascriptにおけるHello Worldの例
概要
このチュートリアルは、HTMLページにJavascriptコードを埋め込む方法について示します。
HTMLページにJavascriptを記述するには<script>要素を使用します。
HTMLページにJavascriptを記述する方法は、以下の2つがあります。
- HTMLページに直接Javascriptコードを埋め込む
- 外部にJavascriptコードを記載したファイルを作成し、それを参照する
HTMLページへのJavascriptコードの埋め込み
HTMLページに直接Javascriptコードを埋め込むことは、推奨されていません。
この方法で記載する場合は、検証目的にみにするべきであります。
<script>要素に記載されたJavascriptコードは、上から下まで解釈されます。<!DOCTYPE html> <html lang="jp"> <head> <meta charset="UTF-8"> <title>JavaScript Hello World Example</title> <script> alert('Hello, World!'); </script> </head> <body> </body> </html>上の例では、<script>要素の中でalert()関数を使用することによりHello, World!メッセージを画面に表示します。
外部Javascriptファイルの読み込み
以下に、外部Javascriptファイルを使用する方法を記載します。
1.拡張子.jsのファイルを作成する(例:main.js)
2.jsファイルを保存するフォルダーを作成し、1で作成したフィアルをその中に保存する
3.HTMLファイルの<script>タグ内にsrc属性で2で作成したファイルを指定するHTMLファイルの記載例
default.html<!DOCTYPE html> <html lang="jp"> <head> <meta charset="UTF-8"> <title>JavaScript Hello World Example</title> <script src="js/mail.js"></script> </head> <body> </body> </html>main.jsファイルに以下の内容を記載し、default.htmlを開くと"Hello, World!"とメッセージが表示される。
main.jsalert('Hello, World!');1つのページに複数のJavascriptファイルを読み込ませる場合、表示されている順に実行される。
<script src="js/main1.js"></script> <script src="js/main2.js"></script>この例では先にmain1.jsが実行されその後、main2.jsが実行される。
Javascriptファイルを読み込ませる記述は、</body>タグの直前に記載するほうがよい。
それは、ページのレンダリングフェーズにおいてブランクページが表示されるからです。async及びdefer属性
Javascriptコードのロードと実行する方法を変えるために、<script>要素内にasync及びdefar属性を使用することができます。
これらの属性はJavascriptファイルを外部に置く場合のみ作用します。
async属性は、Webブラウザに非同期にJavascriptを実行するように指示するものです。
この属性は、Javascriptファイルを順番に実行することを保証しないものです。
以下の例で言えば、必ず最初にmain.jsが実行されるわけではないということになります。<script async src="main1.js"></script> <script async src="main2.js"></script>defar属性を使用すると、ドキュメントが全て解析された後にJavascriptファイルが実行されます。
default.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JavaScript defer demonstration</title> <script defer src="main.js"></script> </head> <body> </body> </html>このページでは、HTMLファイルの中でJavascriptを使用する方法について記載しました。
- 投稿日:2020-09-27T15:17:25+09:00
InstagramのDMを保存する方法
背景
最近は、LINEよりもインスタグラムのDMを使用する方が増えてきたようです。
しかし、インスタグラムのDMはメッセージを検索したり、遡りすることができません。
そこで、インスタグラムの画面から、JavaScriptを実行して、テキストとして保存する方法をご紹介します。(利用規約的にもセーフだと思います。アウトだったら消します)手順
手順を示します。途中で失敗したら、ブラウザの更新ボタンを押して、やり直してください。
- PCブラウザで保存したいDM画面を開きます。https://www.instagram.com/direct/t/[番号]
- ブラウザの検証機能を開き、コンソール画面を開きます。
- 以下のコードを実行して、保存したいところまで遡ります。(量が多いと時間かかります)
timer = setInterval(() => { document.getElementsByClassName('frMpI -sxBV')[0].scrollBy(0, -window.innerHeight); }, 100)4.保存したいところまで来たら、以下のコードで止めます。
clearInterval(timer);5.テキストファイルとして保存します。
out = document.getElementsByClassName('VUU41')[0].innerText let blob = new Blob([out],{type:"text/plan"}); let link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'out.txt'; link.click();
- 投稿日:2020-09-27T12:16:49+09:00
初心者が今はやりの技術を調べてみた
目的
今回、Node.jsを使ってQiitaAPIを使っていろいろやってみようということで、今、初心者向けには何が流行っているのかを調べてみました。
調査方法
QiitaAPIを使って、「初心者」という単語を含んでいる記事を検索しました。
プログラム実行時点(2020年9月27日11時30分時点)で投稿時間が新しいものから上位100件を対象としています。コード
// axiosモジュールを読み込む const axios = require('axios'); // main()関数を定義する async function main() { // QiitaAPIで「初心者」という単語で記事を検索する let response = await axios.get('https://qiita.com/api/v2/items?per_page=100&query=' + encodeURIComponent('初心者')); // 結果を出力する for (let i=0; i<response.data.length; i++) { console.log(response.data[i].tags[0].name); } main();結果
2020年9月27日11時30分時点の結果をExcelで集計しています。
考察
やはり、初心者向けはPythonやJavaScriptの記事が多いことが分かりました。
次に多いのがPHPやRubyということで、いまだに根強い人気があるのかなという印象です。
※私が勉強したことあるJavaについては少なくなってきているという印象です。今後
今回はExcelを使って分析しましたが、Python使って分析できるようになってみようと思います。
- 投稿日:2020-09-27T11:55:40+09:00
JavaScriptの基礎
JavaScriptの基礎を復習しました。
まだまだHTMLとCSSを組み合わせて、カレンダーを作ることを目指しています。
学習したコード
var firstName = 'myname'; var age = 32; console.log(firstName + '' + ' is' + age); var job, is front engineer; job = 'driver'; is front engineer = false; console.log(firstNa me + 'is a ' + age + ' year old ' + job +'.Is I front engineer? ' + is front engineer); // Variable mutation age = 'thirty two'; job ='office worker'; alert(firstName + 'is a ' + age + ' year old ' + job +'.Is I front engineer? ' + is front engineer); var lastName = prompt('What is my last name?'); console.log(firstName + ' ' +lastName);alertのメッセージをクリックして、 promptにlastName(苗字)を入力できる。
C#をJavaScriptに変換する。
var seireki=0; var heisei=0; for( seireki=1989; seireki<=2017; seireki++) { Console.log("西暦 " + seireki + "年は、"); heisei = seireki - 1988; Console.log("平成 " + heisei + "年");上のコードは、エラーが出るかもしれません。For, loopのコードを調べてみます。
今日の振り返り
JavaScriptの基礎固めして、カレンダー作りに必要なコードを覚える。
- 投稿日:2020-09-27T10:20:10+09:00
Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #4 サインアップページの作成
←Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #3 個別記事ページの作成
サインアップ(登録)ページの作成
サインアップページ及びそのロジックを作っていきますが、難易度が一気に上がります。
一気にすべて理解しようとせず、少しずつコードを読み解いていってください。まずはサインアップページを作ります。
pages/sign_up.vue<template> <v-app id="inspire"> <v-main> <v-container class="fill-height" fluid > <v-row align="center" justify="center" > <v-col cols="12" sm="8" md="4" > <form @submit.prevent="signUp"> <v-card class="elevation-12"> <v-toolbar color="primary" dark flat > <v-toolbar-title>SignUp form</v-toolbar-title> </v-toolbar> <v-card-text> <ul v-if="errors"> <li v-for="(message, i) in errors.full_messages" :key="i"> {{ message }} </li> </ul> <v-text-field id="name" v-model="name" label="name" name="name" prepend-icon="mdi-account" type="text" /> <v-text-field id="email" v-model="email" label="email" name="email" prepend-icon="mdi-email" type="email" /> <v-text-field id="password" v-model="password" label="Password" name="password" prepend-icon="mdi-lock" type="password" /> </v-card-text> <v-card-actions> <v-spacer /> <v-btn color="primary" type="submit"> 登録 </v-btn> </v-card-actions> </v-card> </form> </v-col> </v-row> </v-container> </v-main> </v-app> </template> <script> export default { data () { return { name: '', email: '', password: '', errors: [] } }, methods: { async signUp () { try { const data = await this.$store.dispatch('signUp', { name: this.name, email: this.email, password: this.password }) this.$store.commit('setUser', data.data) this.$router.push('/') } catch (e) { this.errors = e.data.errors } } } } </script>フォームを送信すると
signUp()
メソッドが発火し、storeのsignUpアクション(この後作ります)でRails APIにPOSTし、正常終了したらsetUser
でユーザーデータをstoreに保存(この後作ります)。
エラーが起きたらcatchしてエラーを出力している、という形です。参考:Vue Material Component Framework — Vuetify.js
store側の実装
ログインに関わる処理はサイト全体で共通して使いそうなので、store/index.jsに書いていきます。
store/index.jsexport const state = () => ({ user: null, auth: {}, logged_in: false }) export const getters = { user (state) { return state.user }, logged_in (state) { return state.logged_in }, auth (state) { return state.auth } } export const mutations = { setUser (state, value) { state.logged_in = !!value state.user = value }, setAuth (state, value) { state.auth = value } } export const actions = { async signUp (_c, user) { return await this.$axios.$post('/v1/auth', user) } }ログインしているかどうかの判定に使うlogged_inに注目。
setUser(state, value) {
state.logged_in = !!value
state.user = value
},
ユーザー情報セットの際、二重否定を使うことでuserの中身があればtrue, なければfalseが入るようにしています。axiosの拡張
続いてaxiosのデフォルト挙動を変えます。
axiosでサーバサイドと通信する度にgetter['auth']
してヘッダに付与するのは冗長かつしんどいので共通化します。
また、レスポンスが返ってくるたびにアクセストークンをstoreに保存するのも毎度書いていたら辛いので一緒に共通化します。plugins/axios.jsexport default ({ $axios, store }) => { $axios.onRequest((config) => { config.headers = store.getters.auth }) $axios.onResponse((response) => { if (response.headers['access-token']) { const authHeaders = { 'access-token': response.headers['access-token'], client: response.headers.client, expiry: response.headers.expiry, uid: response.headers.uid } store.commit('setAuth', authHeaders) } }) $axios.onError((error) => { return Promise.reject(error.response) }) }nuxt.config.jsplugins: [ + '~/plugins/axios' ],
これで、以後axiosを使う度、リクエストにはアクセストークンが自動付与されて、レスポンス後はトークンがstoreに自動保存されます。
セッション永続化の準備
今のままだと認証情報はstoreに入れているだけなので、画面遷移する分には問題ないのですが、F5押下のようにリロードするとログアウト状態になってしまいます。
これを永続化するためにはトークン情報をcookieかWebStorageに保存する必要があります。
今回は簡易的な機能のためlocalStorageを使いますが、トークンをlocalStorageに保存することはセキュリティ的には弱いため、ちゃんとしたプロダクトでは避けた方が良いかもしれません。
参考:HTML5のLocal Storageを使ってはいけない(翻訳)
- cookie:サーバサイド・クライアントサイドどちらでも読み込み書き込みできるため、SSRでも使える。ただし値セットが面倒
- WebStorage(LocalStorage):クライアントサイドのみのため、SSRでは使えない。そのため一瞬未ログイン画面が出てしまうデメリットがあるが、pluginを入れれば超簡単にstoreを永続化できる
今回は手軽さを取ってLocalStorageを使います。
$ yarn add vuex-persistedstatenuxt.config.jsplugins: [ - '~/plugins/axios' + '~/plugins/axios', + { src: '~/plugins/localStorage.js', ssr: false } ],plugins/localStorage.jsimport createPersistedState from 'vuex-persistedstate' export default ({ store }) => { createPersistedState({ key: 'bbs_session', paths: [ 'auth' ] })(store) }これだけの設定で、
state.auth
がbbs_session
というlocalStorageのkeyで保存されます。
特にlocalStorageを意識することなくstate.auth
に値をセットするとlocalStorageにもセットされるというすぐれものです。とはいえこれだけでは保存しているだけで、画面リロード時に読み込む処理が入っていません。
そのロジックは共通レイアウトに入れてしまいます。共通レイアウトの修正
ここまでで機能の大部分はできました。
ですが共通レイアウトが暗く見づらい上、不要なサイドバー等があるので作り直す勢いで修正します。また、前述の通り画面リロード時のセッション復元も作ります。
layouts/default.vue<template> <v-app> <v-app-bar fixed app > <n-link to="/"> <v-toolbar-title v-text="title" /> </n-link> <v-spacer /> <span v-if="logged_in"> {{ current_user.name }}さん </span> <v-btn v-if="logged_in" icon > <v-icon>mdi-logout</v-icon> </v-btn> <v-btn v-else icon to="/sign_up" nuxt > <v-icon>mdi-account-plus</v-icon> </v-btn> </v-app-bar> <v-main> <v-container> <nuxt /> </v-container> </v-main> <v-footer app > <span>© {{ new Date().getFullYear() }}</span> </v-footer> </v-app> </template> <script> export default { data () { return { title: 'Sample bbs' } }, computed: { current_user () { return this.$store.getters.user }, logged_in () { return this.$store.getters.logged_in } }, async beforeMount () { const session = JSON.parse(window.localStorage.getItem('bbs_session')) if (session && Object.keys(session.auth).length) { await this.$axios.$get('/v1/auth/validate_token') .then(data => this.$store.commit('setUser', data.data)) .catch(() => { this.$store.commit('setUser', null) this.$store.commit('setAuth', {}) }) } } } </script>
beforeMount()
はCSR(クライアントサイドレンダリング)でのみ最初に実行されます。
localStorageはサーバサイドで使えないので、クライアントサイド描画されるタイミングでセッションの値を取得し、auth
が保存されている場合(画面更新直前までログイン状態だった場合)にvalidate_tokenを叩きにいき、user情報とauthの更新をしてログイン状態を復帰します。
無効なauthパラメータだった場合はuserとauthをクリアします(自動的にlocalStorageもクリアになります)。ここまで組み込めば、ログイン後に画面更新してもセッション復帰する状態になるはずです。
ページを開いた直後は未ログイン状態ですが、ちょっと待てばログインになります。
このタイムラグが気になる場合はcookieで実装が必要になります。nuxt.config.jsvuetify: { customVariables: ['~/assets/variables.scss'], theme: { - dark: true, + dark: false, themes: { - dark: { + light: { primary: colors.blue.darken2, accent: colors.grey.darken3, secondary: colors.amber.darken3,そして色変え。これでだいぶスッキリするはずです。
参考
Rails devise token authとNuxt.jsを連携(Twitter認証)
Nuxt.jsのStoreによるデータ保存 [vuex-persistedstate][js-cookie]続き
→Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #5 ログイン・ログアウトの実装
【連載目次へ】
- 投稿日:2020-09-27T09:53:56+09:00
【JavaScript】人生いろいろ 配列操作もいろいろ ~初級編~
こんにちは、どいこです。
タイトル、島倉千代子を意識しましたが語呂が悪い。
(♪人生いろいろ 男もいろいろ~)今回はJavaScriptの配列操作のメソッドと
それを使った関数の例を取り上げていきます。※「処理の流れ」はイメージしやすさを考慮して書いたため
厳密にいうと実際の処理順とは異なる場合があります。findIndex()
findIndex() メソッドは、配列内の指定されたテスト関数を満たす最初の要素の位置を返します。
テスト関数を満たす要素がない場合を含め、それ以外の場合は -1 を返します。
Array.prototype.findIndex() -MDN web docs関数例
配列
array
の中から指定した文字char
を部分文字列として持つ
最初の要素のインデックスを返す
ただし、配列の中に該当する要素が見つからなかった場合は-1を返すfunction findIndexOfIncludeChar(array, char) { const result = array.findIndex(item => { return item.includes(char) }) return result }処理の流れ
①
findIndex()
で配列の要素を頭から順番にチェック
②includes()
で要素をチェック ⇒char
(該当の文字)が含まれる場合はtrueが返される
③ trueの場合は該当する要素のインデックスを返し処理を終了
含まれる要素が見つからなかった場合は-1が返されるfilter()
filter() メソッドは、与えられた関数によって実装されたテストに合格したすべての配列からなる
新しい配列を生成します。
Array.prototype.filter() -MDN web docs関数例
与えられた配列
array
の要素から
偶数のみを取り出して新しい配列を返す
偶数値がなかった場合は空の配列を返すfunction filterEvenNumber(array) { const evenValue = 2 const result = array.filter(value => { return value % evenValue === 0 }) return result }処理の流れ
①
filter()
で配列の要素を順番にチェック
② 偶数(2で割り切れる値)だった場合はtrueとなり新しい配列に追加される
奇数だった場合はfalseとなりスキップされる
③ チェックが終わったら新しい配列を返すmap()
map() メソッドは、与えられた関数を配列のすべての要素に対して呼び出し
その結果からなる新しい配列を生成します。
Array.prototype.map() -MDN web docs関数例
配列
array
の各要素の値を2倍した新しい配列を返すfunction multiplyDouble(array) { const doubleValue = 2 const result = array.map(value => { return value * doubleValue }) return result }処理の流れ
①
map()
で配列の要素に対して順番に処理を行う(値に2をかける)
② 処理を行うごとに処理後の値が新しい配列に追加される
③ 処理が終わったら新しい配列を返すあとがき
よく使うであろう配列操作のメソッドを取り上げてみました。
次回は中級編と題して、少し難易度が上がるものを紹介する予定です。記事を読んでいただいて、「こう書いた方がわかりやすいのでは?」などアドバイスありましたら
お気軽に頂けると幸いです。
(言い方は優しくしてくれると喜びまする )お読みいただき、ありがとうございました◎
- 投稿日:2020-09-27T09:46:03+09:00
Webブラウザでシリアル通信を行う
他の投稿はこちら
- Webアプリの限界を超える方法
- Webアプリの限界を超える方法(セキュリティ編)
- ブラウザで(WebUSBもActiveXも使わずに)FeliCaリーダーを読み込む
- ブラウザでブラウザを操作してスクレイピングを行う
シリアル通信を行うアプリを開発する場合、通常はネイティブアプリを選択すると思います。
今回は、Webブラウザでシリアル通信を行い、LEDを点灯させてみたいと思います。
(いわゆるLチカです)概要
構成は以下の通りです。
- シリアル通信を行うネイティブアプリを作成する
- LEDとプログラムを組み込んだArduinoを用意する
- ネイティブアプリ上でWebSocketサーバーを立ち上げる
- ブラウザからネイティブアプリのWebSocketサーバーにローカルホスト接続する
- ネイティブアプリからArduinoにシリアル通信接続する
- ArduinoからLEDを点灯させる
デモ
上記デモはFirefoxで動作しています。
通常、Webブラウザでシリアル通信というと、NodeJSや、ActiveXを使用する事が多いと思います。
また、Chromeではシリアル通信APIが実装されているようですね。今回ご紹介した実装方法なら、特定のブラウザに依存せず、シリアル通信を実装できます。
まとめ
さて、今回はブラウザでLチカを行いましたが
WebアプリでLチカが出来るということは、Webアプリで機械制御が出来るということです。機械制御を伴うアプリは、機械への命令の送信といった、低レイヤーのアクセスを必要とする性格上、通常はネイティブアプリとして実装します。
しかし、今回のような実装方法ですと、低レイヤーの部分だけネイティブアプリで実装し
データ分析やフロー制御など高レイヤーの部分をWebアプリに切り出すことが可能です。機械制御の最低限の命令だけをネイティブアプリに実装し、
命令の組み合わせなど他の部分をWebアプリで実装すると
開発保守をWebアプリに寄せることが出来るので、メンテナンスコストの低下が期待できます。
- 投稿日:2020-09-27T09:30:01+09:00
nullや空文字列を厳格に判定すること
空文字列を渡したいという場合がある。(あった)
その場合、const testFunc = (value?: string) => { if(value){ //何か処理 } } const result = testFunc("")こうすると文字列はnullともundefinedとも区別されずFalseとみなされてしまう。
=こうするconst testFunc = (value?: string) => { if(value === ""){ //何か処理 } } const result = testFunc("")当たり前っちゃ当たり前の話。
nullだろうが""だろうがfalseになるんだから比較省略した方が短くかけて素敵やんと思ってたが
コードの意図もわからないしバグにも繋がりかねないので厳格に判定した方が良いことを再認識。
- 投稿日:2020-09-27T06:16:41+09:00
constで定数管理するのはヤバい!?値の変更を禁止するたった1つの方法!
概要
・constは再代入できないが参照型は変更が可能
・参照型はObject.freezeメソッドで不変な値にできるconstは変更できる
まず初めに、constで定数を定義すると再代入・再宣言ができません。
そのため、次のコードでは「TypeError: Assignment to constant variable.」というエラーが発生します。定数が変更できない例.jsconst teisu = "定数です。" teisu = "変更しました" //エラー発生 console.log(teisu)しかし、constは再代入することができないだけでプロパティの一部を変更することができます。
定数が変更できる例.jsconst teisu = ["定数です。"] teisu[0] = "変更しました" console.log(teisu[0]) //「変更しました」が出力なぜconstを変更できたのか?
なぜStringは変更できなかったのに、配列の要素は変更できたのでしょうか?
これには2種類のデータ型が関連しています。
・基本型(プリミティブ型)
・参照型この2つの型は値の保存方法が異なります。
基本型は値を直接格納するのに対し、参照型は定数値の格納先のアドレスを格納します。
・基本型 ⇒ 値を格納
・参照型 ⇒ アドレスを格納定数は値を変更することができません。そのため、基本型は変更すればエラーが発生しました。
しかし、参照型はアドレスを変更せず、アドレスが指している値を変更しているため変更が可能なのです。参照型の変更を禁止するには?
アプリを作ると定数ファイルを用意して、共通の値を管理したいことが出てきます。その場合、参照型の値も保持したい場面が出てくるでしょう。
しかし、参照型はconstで定義しても変更することができるのでした。それでは本来の目的である不変な値を維持することができません。
どうすればいいのでしょうか?この問題を解決するのが、Object.freezeメソッドです。
このメソッドを使って定義すれば、プロパティの変更・追加・削除をしても内容が反映されません。次のソースでは配列で定義した値をソース上は変更していますが、値を出力すると変更されていないことがわかります。
freezeメソッドで変更を禁止.jsconst teisu = Object.freeze(["定数です。"]) teisu[0] = "変更しました" console.log(teisu[0]) //「定数です。」が出力また、Strictモードにすると TypeError エラーが発生させることができます。
これにより、定数を書き換える処理をテストの段階で取り除けるため、無駄な記述を減らせて可読性が上がります。strictモードのエラー.js"use strict" const teisu = Object.freeze(["定数です。"]) teisu[0] = "変更しました" //エラー発生 console.log(teisu[0])ただし、Object.freezeメソッドを使うには注意点が必要です。このメソッドはobjectの直下のプロパティーのみに適応されます。
そのため、入れ子になっているプロパティーの値は変更することができます。入れ子のプロパティは変更可能.jsconst teisu = Object.freeze(["定数です。", ["定数の中の値"]]) teisu[1][0] = "変更しました" console.log(teisu[1][0]) //"変更しました"この問題を解決するために公式ではdeepFreezeメソッドを紹介しています。
このメソッドを使えば、入れ子になっている値も変更することができません。入れ子のプロパティも変更できない.jsfunction deepFreeze(object) { // オブジェクトで定義されたプロパティ名を取得 var propNames = Object.getOwnPropertyNames(object); // 自分自身を凍結する前にプロパティを凍結 for (let name of propNames) { let value = object[name]; //「値がある かつ オブジェクト型」の場合、freezeを適用 object[name] = value && typeof value === "object" ? deepFreeze(value) : value; } return Object.freeze(object); } var obj2 = { internal: { a: null } }; deepFreeze(obj2); obj2.internal.a = 'anotherValue'; // 非 strict モードでは暗黙に失敗 obj2.internal.a; // null参考
- 投稿日:2020-09-27T04:23:09+09:00
JavaScript でも Python みたく`range()`したい!
はじめに
for i in range(10): print(i) # かっこいい理屈はいいから早く見せろ!
/** * @param {...number} args */ const range = (...args) => { const rangeGen = function* (from = 0, to = Infinity, step = 1) { for (let v = from; v < to; v += step) { yield v; } }; return args.length === 0 ? rangeGen(undefined, undefined, undefined) : args.length === 1 ? rangeGen(undefined, args[0], undefined) : args.length === 2 ? rangeGen(args[0], args[1], undefined) : rangeGen(...args); }; for (const v of range(2, 10)) { console.log(v); // 2 ~ 9までが順番に出力される }なにやってんだよ?
range
関数はジェネレータ関数を実行した結果を返します。引数をジェネレータ関数に振り分けるだけのラッパーです。
rangeGen
がジェネレータ関数です。
- ジェネレータってなんだよ
- ES2015 で追加された JavaScript の比較的新しい機能です
- 何をジェネレートすんだよ
- 反復可能オブジェクトをジェネレートします
- 反復ってなんだよ
for-of
- スプレッド構文(
...iterator
)- 分割代入(
const [hoge, huga] = iterator;
)- とかです。多分
- 配列じゃだめなの?
- 無限を扱えます
詳しいジェネレータ関数の使い方は、各位ググるなりMDN読むなりしてください。
とりあえずここでは、
from
からto
までの数字を列挙することのできるやつを作っています。おわりに
これで JavaScript でも
for (const i of range(10)) {
できます。おまけ
個人的には Python のソレは引数の順番に気を使わないといけなくて面倒だったりします。
というわけで以下(TypeScript)。const range = function* ({ start = 0, stop = Infinity, step = 1, }: { start?: number; stop: number; step?: number; }) { while (start < stop) { yield start; start += step; } }; for (const v of range({ stop: 10 })) { console.log(v); }以上です。
- 投稿日:2020-09-27T04:16:08+09:00
JavaScript で Intersection Observer API を使って要素が表示されているか調べる
はじめに
JavaScript で Intersection Observer API を使って要素がビューポート内に表示されているか調べました。
概略
(async () => { /** * @param {Element} elem */ const isDisplaying = (elem) => { return new Promise((r) => { const iObserver = new IntersectionObserver(([{ isIntersecting }]) => { r(isIntersecting); iObserver.unobserve(elem); }); iObserver.observe(elem); }); }; console.log( await Promise.all( [...document.body.querySelectorAll('*')].map(async (elem) => ({ elem, isDisplaying: await isDisplaying(elem), })) ) ); })();詳細
Intersection Observer API とは
スクロールによって要素(Element)がビューポート(ブラウザの表示領域)の端と「交差」するのを監視する API。
画像などの遅延読み込みや無限スクロールなどが簡単に実装できます。↓ 簡単な使い方
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Intersection Observer API Demo</title> <style> #targetTop, #targetBottom { border: 1px solid black; } </style> </head> <body> <div id="targetTop"> <p>#targetTop</p> </div> <div> <p>適当なスペース</p> <p>適当なスペース</p> <p>適当なスペース</p> <p>適当なスペース</p> <p>適当なスペース</p> <p>適当なスペース</p> <p>適当なスペース</p> <p>適当なスペース</p> <p>適当なスペース</p> <p>適当なスペース</p> </div> <div id="targetBottom"> <p>#targetBottom</p> </div> <script> document.addEventListener('DOMContentLoaded', () => { const $targetTop = document.getElementById('targetTop'); const $targetBottom = document.getElementById('targetBottom'); const iObserver = new IntersectionObserver((changes) => { for (const change of changes) { console.log(change); } }); iObserver.observe($targetTop); iObserver.observe($targetBottom); }); </script> </body> </html>ウィンドウサイズを小さくした上でスクロールしてみてください。
#targetTop
や#targetBottom
が画面に映ったり映らなくなったりするたびに callback が実行されIntersectionObserverEntry
がconsole.log
されます。Promise で同期的に扱えるようにする
概略のところでは、
Promise
のresolve
を callback 内で呼び出すことで、await
を使って同期的にIntersectionObserverEntry
を得ることができるようにしました。
Promise.all
を使って Web ページのbody
より下にあるすべての要素について一気に調べています。おわりに
この API は実験的であるため動作が変更されるかもしれません。
- 投稿日:2020-09-27T02:07:21+09:00
通知がしたいだけならPWAでいいじゃない
本当にあるかもしれない怖い話
作成したウェブサイトを活性化させたい、サイトの更新を知らせる仕組みがあれば活性化するはず。プッシュ通知と言えばアプリ。WebViewアプリを作りましょう。ブラウザと通知機能だけだから簡単にできるでしょ?
はい分かりましたとアプリ開発経験がなかった開発会社は安い見積を提出してしまいました。
…あなたが開発会社の窓口だとしたらどうしますか?
アプリプロジェクトの難しさ
顧客が本当にやりたいことは、今までできたことが普通にできることに加えて 『通知をしたい』 だけなので安い見積しか通らない。それにも関わらずプロジェクトの難易度が高い点を以下にあげてみます。
難しさ 1. Android / iOS 両対応
コード量は大変少ないものの、Android Studio で Java(Kotlin)、Xcode で Swift も書ける要員を確保しないといけない。こんな事ができる要員はなかなかいない。
難しさ 2. WebView
WebView は標準のブラウザに比べて不自由なことだらけ。タブは複数開けないし、戻るや進むボタンもなければ、ピンチズームなんかも制限されてたり。
工数をかけて元のサイトより品質の悪い物を作ることになりがち。難しさ 3. 開発者登録 / 開発環境
開発者登録費用(Android: 2500円くらい/初回 / iPhone: 1万円くらい/毎年)も必要、バイナリを作成して申請するのにMacも必要。
難しさ 4. OSバージョンアップ
OSのバージョンが上がった時に色々と不具合が出る可能性がある。
いつ上がるか分からないOSのバージョンアップのために開発要員を抱え続けられない。Webプッシュ
顧客が本当にやりたいことが 『通知をしたい』 だけであれば、『アプリを作る』ことは妥当でしょうか?顧客のビジネスに全く関係のないところでリスクを増やしたり、開発物に対しては妥当だが要件に対して無駄に高額な費用を請求することになっていないか?
通知をしたいだけでよければ、Webプッシュができればよいので、既存のWebサイトをPWA化することも視野に入れて良いのではないでしょうか?
※色々書きましたが、2020/9時点では、iOS は Push通知非対応の模様。顧客とよく話し合ってください。
既存サイトの PWA (Progressive Web Apps)化
いささか大げさでしたが、PWAを知らない人に向けて『なぜ』PWAを使うのかを書いてみました。わたしも顧客との距離を縮めて妥当な提案をするべく勉強していますが、誤り等あったらご指摘頂けますと助かります。
既存のWebサイトが https のサイトであれば、いくつかのファイルを追加するだけで簡単に PWA 化できます。
- manifest ファイル追加
- icon ファイル追加
- service worker ファイル追加
- service worker 登録
https のサイトを作るのは面倒に思えますが、github のリポジトリを github pages で公開する設定にすれば、費用も手間も不要で簡単に作成できます。
実際に非PWAサイトをPWA化したソースコードをgithubに公開しています。よろしければ、あわせてご参照ください。
PWA 構成
PWA化に際して追加したファイルは以下で、前述の通り非常に少ないです。
PWAフォルダの中に全部配置してポータビリティを上げたかったのですが、service worker 本体と manifest ファイルは、公開サイトのルートに配置する必要があるようでした。manifest.json # PWAアプリの設定。entry point など記述 sw.js # offline でも動作可能とする service worker。 # いくつかのイベントハンドラを記述する必要がある pwa/ main.js # service worker 登録処理。定形処理。 icons/ icon-192x192.png # Home Screen に配置するアイコン icon-512x512.png # 起動スプラッシュアイコンmanifest ファイル追加
このファイルは特に難しくはないです。
start_url には、FQDN部分以降の起動ページの path を記述します。
https://kaku3.github.io/the-sheep-in-the-desert/
なので、
/the-sheep-in-the-desert/index.html?utm_source=homescreen
と記述します。manifest.json{ "name":"砂漠のひつじ", "short_name":"砂漠のひつじ", "icons": [{ "src": "pwa/icons/icon-192x192.png", "sizes":"192x192", "type": "image/png" }, { "src": "pwa/icons/icon-512x512.png", "sizes":"512x512", "type": "image/png" }], "start_url": "/the-sheep-in-the-desert/index.html?utm_source=homescreen", "display": "standalone", "background_color": "#FFFFFF", "theme_color": "#FFFFFF" }また、
index.html
を修正して、manifest.json を読み込む様にします。index.html<head> ... <link rel="manifest" href="manifest.json"> ... </head>icon ファイル追加
このファイルも特に難しくないです。
Home Screen と、起動スプラッシュ2種類用意してください。
ひょっとしたら起動スプラッシュは画像がなくても大丈夫かもしれません。service worker ファイル追加
簡単と書きましたが、このファイルは難しいです。
ただ書くだけなら難しくないのですが、『キャッシュするファイル』を正しく管理するには、webpack などで、キャッシュ対象ファイルのバージョンを管理できる仕組みが必要と思われます。Service Worker の処理の記述には、Workbox を利用するのがよさそうです。
sw.js 自体は、webpack を利用していない場合は
urls
以外の部分はそのまま利用できるかと思います。sw.js// CDN から Workbox を読み込み importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js'); workbox.loadModule('workbox-strategies'); const precacheController = new workbox.precaching.PrecacheController(); // キャッシュするファイルを定義する。 const baseUrl = location.href.replace(/\/sw.js$/, ''); const urls = [ ... { url:'/css/fonts.css', revision: '0.01' }, ... ].map(o => { o.url = baseUrl + o.url; return o; }) precacheController.addToCacheList(urls); self.addEventListener('install', (event) => { event.waitUntil(precacheController.install()); }); self.addEventListener('activate', (event) => { event.waitUntil(precacheController.activate()); }); self.addEventListener('fetch', (event) => { event.respondWith( caches .match(event.request) .then(function(response) { return response ? response : fetch(event.request); }) ); });キャッシュ容量はブラウザにより異なり、ブラウザによっては50MB程度であるためサイト内のどのファイルをキャッシュするかは設計が必要になると思います。
キャッシュは以下のような形で保存されます。
上記より分かる通り、
?__WB_REVISION__={revision}
パラメータつきでキャッシュするため、読み込みタグも同様に?__WB_REVISION__={revision}
をつける必要があります。index.html<link rel="stylesheet" href="css/fonts.css?__WB_REVISION__=0.01">main.css.game { ... background-image: url("../image/bg.png?__WB_REVISION__=0.01"); ... }読み込むファイル数にもよりますが、この部分を手動で更新するのは難しいと思いますので、実務レベルでは「パフォーマンスを確認した上で全てキャッシュしない」か「webpack」を利用するという判断になるのかなあと思います。
service worker 登録
別ファイルにする必要もなかったのですが、再利用しやすい様に別ファイルとしました。
index.html<script src="pwa/main.js"></script>pwa/main.jsif('serviceWorker' in navigator) { // service worker のファイル名を変更するのであればこの行を修正。 const serviceWorkerJs = `${location.href}sw.js` console.info('register service worker : ', serviceWorkerJs) navigator.serviceWorker.register(serviceWorkerJs) .then((r) => { console.info('Service worker registered.', r) }) }動作させるまでには試行錯誤が必要になると思います。
https 環境ではないと動作しないとありますが、localhost については http でも大丈夫でした。
また、初回のみキャッシュされる動作をしますが、毎回キャッシュを消して確認するのが大変だったので、適当に php でサーバを立てて開発しました。php -S localhost:8000 php -S localhost:8001 php -S localhost:8002 php -S localhost:8003 ...ローカルで動作確認が済んだら、github に push して確認してみてください。
A2HS(Add to Home Screen) ダイアログが確認できれば実装完了です。
おつかれさまでした。おわりに
通知についての記事のはずでしたが、PWA実装確認までで時間切れでした。
次回は通知について書きたいと思います。参考
大変分かりやすかったです。ありがとうございました。
https://qiita.com/umamichi/items/0e2b4b1c578e7335ba20
- 投稿日:2020-09-27T00:36:33+09:00
【Salesforce】VisualforceではJavaScriptだけでレコードを取得できる
SObjectModelを使う
スクリプトタグ内で以下のようにインスタンス化させる。
var contact = new SObjectModel.Contact();あとはcontact.retrieveでレコードを取得できる。
サンプルコード
<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0"> <html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="x-ua-compatible" content="ie=edge" /> <title>Salesforce Lightning Design System Trailhead Module</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- Import the Design System style sheet --> <apex:slds /> </head> <apex:remoteObjects> <apex:remoteObjectModel name="Contact" fields="Id,Name,Title,LastModifiedDate,PhotoUrl" /> </apex:remoteObjects> <body> <!-- REQUIRED SLDS WRAPPER --> <div class="slds-scope"> <!-- MASTHEAD --> <p class="slds-text-heading_label slds-m-bottom_small"> Salesforce Lightning Design System Visualforce Workshop </p> <!-- / MASTHEAD --> <!-- PAGE HEADER --> <div class="slds-page-header"> <!-- LAYOUT GRID --> <div class="slds-grid"> <!-- GRID COL --> <div class="slds-col slds-has-flexi-truncate"> <!-- HEADING AREA --> <!-- MEDIA OBJECT = FIGURE + BODY --> <div class="slds-media slds-no-space slds-grow"> <div class="slds-media__figure"> <svg aria-hidden="true" class="slds-icon slds-icon-standard-contact"> <use xlink:href="{!URLFOR($Asset.SLDS, 'assets/icons/standard-sprite/svg/symbols.svg#contact')}"></use> </svg> </div> <div class="slds-media__body"> <p class="slds-text-title_caps slds-line-height_reset">Contacts</p> <h1 class="slds-page-header__title slds-m-right_small slds-align-middle slds-truncate" title="My Contacts">My Contacts</h1> </div> </div> <!-- / MEDIA OBJECT --> <!-- /HEADING AREA --> </div> <!-- ACTION BUTTONS --> <div class="slds-col slds-no-flex slds-grid slds-align-top"> <div class="slds-button-group" role="group"> <button class="slds-button slds-button_neutral"> Add Contact </button> <button class="slds-button slds-button_neutral"> More </button> </div> </div> <!-- / ACTION BUTTONS --> </div> <!-- / LAYOUT GRID --> <!-- PAGE HEADER DETAIL ROW --> <ul class="slds-grid slds-page-header__detail-row"> <li class="slds-page-header__detail-block"> <p class="slds-text-title slds-truncate slds-m-bottom_xx-small" title="Field 1">Field 1</p> <p class="slds-text-body_regular slds-truncate" title="Description that demonstrates truncation with a long text field">Description that demonstrates truncation with a long text field.</p> </li> <li class="slds-page-header__detail-block"> <p class="slds-text-title slds-truncate slds-m-bottom_xx-small" title="Field2 (3)">Field 2 (3) <button class="slds-button slds-button_icon" aria-haspopup="true" title="More actions"> <svg aria-hidden="true" class="slds-button__icon slds-button__icon_small"> <use xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#down"></use> </svg> <span class="slds-assistive-text">More Actions</span> </button> </p> <p class="slds-text-body_regular">Multiple Values</p> </li> <li class="slds-page-header__detail-block"> <p class="slds-text-title slds-truncate slds-m-bottom_xx-small" title="Field 3">Field 3</p> <a href="javascript:void(0);">Hyperlink</a> </li> <li class="slds-page-header__detail-block"> <p class="slds-text-title slds-truncate slds-m-bottom_xx-small" title="Field 4">Field 4</p> <p> <span title="Description (2-line truncation—must use JS to truncate).">Description (2-line truncat...</span> </p> </li> </ul> <!-- / PAGE HEADER DETAIL ROW --> </div> <!-- / PAGE HEADER --> <!-- PRIMARY CONTENT WRAPPER --> <div class="myapp slds-p-horizontal_medium"> <ul id="contact-list" class="slds-has-dividers_bottom-space"></ul> </div> <!-- / PRIMARY CONTENT WRAPPER --> <!-- FOOTER --> <footer role="contentinfo" class="slds-p-around_large"> <!-- LAYOUT GRID --> <div class="slds-grid slds-grid_align-spread"> <p class="slds-col">Salesforce Lightning Design System Example</p> <p class="slds-col">© Your Name Here</p> </div> <!-- / LAYOUT GRID --> </footer> <!-- / FOOTER --> </div> <!-- / REQUIRED SLDS WRAPPER --> <!-- JAVASCRIPT --> <script> (function () { var contact = new SObjectModel.Contact(); var contactList = document.getElementById('contact-list'); /* FUNCTION createTile */ function createTile(record) { return [ '<li class="slds-item">', '<div class="slds-tile slds-media">', '<div class="slds-media__figure">', '<img class="slds-avatar slds-avatar_medium" src="', record.get('PhotoUrl'), '" alt="" />', '</div>', '<div class="slds-media__body">', '<h3 class="slds-truncate" title="', record.get('Name'), '"><a href="javascript:void(0);">', record.get('Name'), '</a></h3>', '<div class="slds-tile__detail slds-text-body_small">', '<p class="slds-truncate">', record.get('Title'), '</p>', '</div>', '</div>', '</div>', '</li>' ].join(''); } /* FUNCTION createTile */ contact.retrieve({ orderby: [{ LastModifiedDate: 'DESC' }], limit: 810 }, function (error, records) { if (error) { alert(error.message); } else { contactList.innerHTML = records.map(createTile).join(''); } }); })(); </script> <!-- JAVASCRIPT --> </body> </html> </apex:page>
- 投稿日:2020-09-27T00:01:34+09:00
JavaScriptでnew Date()で日付と時刻を取得する
プログラミング勉強日記
2020年9月27日
JSでnew Date()
を使って現在の日付・時刻を取得する方法をまとめる。基本的な使い方
以下のように
new Date()
で現在の日付・時刻する。var nowDate = new Date(); console.log(nowDate);コンソール結果Sun Sep 27 2020 09:22:20 GMT+0900 (日本標準時)〇〇年〇月〇日を表示する
2020年9月27日を表示するためには、
getFullYear()
,getMonth()
,getDate()
を使う。getMonth()
は1月が0、2月が1となるので、getMonth()+1
とする。var nowDate = new Date(); var year = nowDate.getFullYear(); var month = nowDate.getMonth()+1; var date = nowDate.getDate(); var today = year + '年' + month + '月' + date + '日'; console.log(today);コンソール結果2020年9月27日曜日を取得する
曜日の取得には
getDay()
を使う。getDay()
は日曜が0、月曜が1と返ってくる。var nowDate = new Date(); var year = nowDate.getFullYear(); var month = nowDate.getMonth()+1; var date = nowDate.getDate(); /* 曜日追加 */ var day_array = ['日','月','火','水','木','金','土'] var day = day_array[now.getDay()]; var today = year + '年' + month + '月' + date + '日' + '(' + day + ')'; console.log(today);コンソール結果2020年9月27日(日)参考文献