- 投稿日:2020-07-31T23:50:03+09:00
自分オリジナルのGitHubプロフィールを作る
はじめに
最近GithubのプロフィールにREADMEを置けるようになりました。GitHub Readme Stats を利用してGitHubプロフィールをカッコよくするのように素晴らしい物も登場しているのですが、私は人と被ったりするのがあまり好きではないので自分で作ってみようと思います。
完成品
構成
. ├── README.md ├── index.js ├── package.json ├── vercel.json ├── views │ └── scroll-face.html └── yarn.lockまずviewsディレクトリの中に配信したいsvgを入れます。
基本的にはsvgにアニメーションなどを付けて配信していく形になります。scroll-face.html<svg> <style> ... </style> ... </svg>今回は特にbodyやheadなどはいらないと思います。
次にこれを配信するためのserverのコードを書いていきます。index.jsconst express = require('express'); const app = express(); app.use('/', express.static(__dirname + '/views')); express.static.mime.define({ 'image/svg+xml': ['html'] }); app.get('/', (req, res) => { res.end('scroll-face'); }); app.listen(8080);やっていることは単純でviewsディレクトリ以下のファイルを配信しています。
ここで肝になってくるところはここです。express.static.mime.define({ 'image/svg+xml': ['html'] });こうすることで実際にmarkdown上でsvgが表示されるようになります。
ここではとても簡単なことしかやっていませんがもっとカスタマイズしたい方はgithub-readme-statsを読むと色々と参考になると思います。
デプロイ
今回はvercelを使ってデプロイしていきます。
まずはプロジェクトのルートに以下のようなvercel.jsonを配置します。vercel.json{ "builds": [ { "src": "index.js", "use": "@now/node-server" } ], "routes": [ { "src": "/.*", "dest": "/index.js" } ] }こうすることでindex.jsが実行されます。
次にvercelにアクセスしてgithubなどからimportします。
今回は特に設定をいじらずにDeployを押せば完了します。
最後に
何か間違っているところなどがありましたらコメントお願いします。
特にvercelを初めて使ったので設定周りで不備があれば言っていただけると嬉しいです
- 投稿日:2020-07-31T21:30:20+09:00
DynamoDBのデータをnode.jsのReadableStreamに流す
DynamoDBテーブルのデータをざーっと
ReadableStream
に流し込みたかったが、どうやってバックプレッシャーに対応すればいいのか分からなかったので対応させてみた。やりたいこと
- DynamoDBからデータを読み込む(基本は内部バッファサイズ分)
ReadableStream
にpush- ひたすら読み込んではpush
- 詰まったら一時停止
- 次回開始位置をpushしきれなかったアイテムに設定
- またpushできるようになったら続きから読み込み再開
- 以下繰り返し
- 全て読み込んでpushし終わったら処理終了
DynamoDBのQuery/ScanとLimit
DynamoDBのQuery/Scan操作では
Limit
を指定することで取得件数を制限することができる。
ただLimit
は検索結果の件数制限でなので、QueryFilter
やScanFilter
を掛けると検索結果がさらにフィルタリングされ指定した件数より少ない件数が返ってくる場合がある。クエリーで検索し、指定件数分の結果が揃ったら、クエリーフィルターでさらに絞る、というのはDynamoDBに限らずよくあるのでおそらく同じ動作なんだと思う。
まぁこれは
Limit
を指定してもフィルターを指定すると思ったとおりの件数は返ってこないですよ、という注意事項。DynamoDBのページネーション
DynamoDBのQuery/Scanは読み込みサイズが1MBを超えるとそこで帰ってくる。
Limit
を指定した場合も途中で帰ってくる。
まだ全てのデータを読み込み終えてない場合、次ページを取得するための開始地点がレスポンスに含まれる。
レスポンスに含まれるLastEvaluatedKey
を次回Query/Scan時のリクエストにExclusiveStartKey
として含めれば次ページを取得できる仕組みになっている。
LastEvaluatedKey
は実際にはプライマリーキー(パーティションキーあるいはパーティションキーとソートキーの組み合わせ)のオブジェクトである。
なのでLastEvaluatedKey
でなく、レスポンスに含まれるアイテムの何番目かをぶち込めば次回の読み込み開始位置はその項目の次からになる。なった。これを利用して、
ReadableStream
にpushしている途中でReadableStream
の内部バッファが一杯になった場合は、入り切らなかった次のアイテムを読み込み開始位置としてExclusiveStartKey
に設定することにした。ReadableStreamのハイウォーターマーク
ReadableStream
は内部バッファを持っていて、そのサイズ=閾値をハイウォーターマーク(HWM)という。
オブジェクトモードの場合はデフォルトで16
に設定されている。
データをpush
し続けてHWMに達すると、
push
がfalse
を返す_read
が呼ばれなくなるそれで内部バッファに空きがでると再び
_read
が呼ばれるらしい。
またpush
がfalse
を返しても内部バッファには詰め込まれてた気がする。たしか。HWMが上がるんだったかな・・・ということで、Query/Scanで取得したデータをpushしてる最中に
false
を返した場合はそこで処理を中断し、次のアイテム以降は残念ながら次回のQuery/Scan時に再取得することにした。諸々踏まえて
最終的にこんな感じの実装になった。
切り貼りしてたのでこのままでは動かないかも。雰囲気を感じ取って欲しい。class DynamoDBStream extends stream.Readable { : _read(size) { // 何度も呼ばれるので if (!this.#querying) { this.#querying = true; this.#params.Limit = size || this.hwm; this._query(this.#params); } } // HWMに達するまで再帰的にQueryを実行する _query(params) { const recursiveQuery = (params) => { // AWS.DynamoDB.DocumentClient.query() this.#ddb.query(params, (err, data) => { : let abort = false; for (const item of queryResult.Items) { // HWMに達したらそのitemまで処理済みとし次回の開始位置を指定して中断 if (!this.push(item)) { params.ExclusiveStartKey = item; abort = true; break; } } // 中断したら次の読み込み要求を待つ if (abort) { this.#querying = false; // 次ページがなかったら処理終了 } else if (typeof queryResult.LastEvaluatedKey === "undefined") { this.push(null); this.#querying = false; // 中断せず、次ページがあれば続けて次ページを取得 } else { params.ExclusiveStartKey = queryResult.LastEvaluatedKey; recursiveQuery(params); } }); } recursiveQuery(params); } : }余談: DynamoDBのコスト
リクエスト単位で課金される。また、結果整合性のある読み込みが一番安い。
Limit
を小さく指定しすぎるとリクエスト数が増えてしまい、ちゃりんちゃりん課金されてしまう。かと言って、あんまり
Limit
を大きくしてしまうと、詰め込みきれなかったデータが無駄になってしまうのでうまく調整しないといけない。頻繁にリクエストが細分化してしまう場合は、いっそのことStreamとは別にバッファを持ったほうがいいかもしれない。
- 投稿日:2020-07-31T21:05:03+09:00
React Contextのメモ
親コンポーネントのstateの値を、孫コンポーネントのpropsの値として使う場合はこのような書き方になる。
index.jsimport React from "react"; import ReactDOM from "react-dom"; /* 親 -> 子 -> 孫 -> .. の順に値を渡すことをバケツリレーと呼ぶ(親のstateの値を孫やひ孫のpropsにする) */ class Grandchild extends React.Component { render() { return( <div>{this.props.text}</div> ) } } class Child extends React.Component { render() { return( <Grandchild text={this.props.text} /> ) } } class Parent extends React.Component { constructor(props) { super(props); this.state = {"text": "まんち"}; } render() { return( <div><Child text={this.state.text} /></div> ) } } ReactDOM.render(<Parent />, document.getElementById("app"));バケツリレーをせずに、親コンポーネントから直接孫やひ孫のコンポーネントに値を渡すのがContext(子コンポーネントにも渡せる)
関数コンポーネントの場合はpropsが存在しないのでこの書き方一択になる?index.jsimport React, {useState, createContext, useContext} from "react"; import ReactDOM from "react-dom"; var Context = createContext(); var GrandChild = function() { //受け取った値が文字列ならその文字列が、配列ならその配列がそのまま渡される var arr = useContext(Context); return( <div> <p>{arr[0]}</p> <p>{arr[1]}</p> </div> ) } var Parent = function() { var [cnt, addCnt] = useState(0); var [numState, switchNumState] = useState("偶数"); function onClick(num) { num += 1; addCnt(num); if (num % 2 === 0) { numState = "偶数"; } else { numState = "奇数"; } switchNumState(numState); } return( <> <div> {/* providerコンポーネントの作成 value=渡す値 渡す値は文字数字などの単体の値やオブジェクトでも良い */} <Context.Provider value={[numState, cnt]}> <GrandChild /> </Context.Provider> <input type="button" value="連打しろ!!" onClick={() => onClick(cnt)} /> </div> </> ) } ReactDOM.render(<Parent />, document.getElementById("app"));
- 投稿日:2020-07-31T18:35:10+09:00
Firebaseでデータの取得、追加、更新を行う
Firebaseでデータの読み込み、書き込み、更新を行う
目次
1.目的
2.データ操作の基本
3.データ取得
4.データ追加
5.データ更新
6.まとめ
1.目的
備忘録として
[前提条件]
・Firebaseの構築に関しては、もう終わっている。
(Firestoreの設定も含めて)2.データ操作の基本
FirestoreにアクセスするためのAPIは既に用意されている。
データ操作の基本として、以下の構文が基本となる。
firebase.firestore() − ①
.collection('[コレクション名]') − ②
.where('[フィールド名]' ,'[比較演算子]' ,[検索する値]) − ③
.orderBy('オーダーをかけるフィールド名' ,'[asc or desc]') − ④
.limit([整数]) − ⑤上記が基本構造となる。
必須項目は ①、②
任意項目は ③、④、⑤また、whereはメソッドチェーンで繋げることができる。
この時、重要となるのが、「Firebaseは'AND'条件のみサポートしている」という点である。
現在は、'OR'条件は利用できない。また、利用できる比較演算子は以下の通り。
・'=='
・'>'
・'>='
・'<'
・'<='
・'in'
・'array-contains'
・'arrat-contains-any'上記の中で説明が必要なのは、以下だと思う。
・'array-contains'
・'arrat-contains-any''array-contains' は、配列形式で持っているデータの中から、完全一致する文言を探す。
こんな書き方
.where('[コレクション名]' ,'array-contains' ,'りんご')'arrat-contains-any' は上記の拡張版で配列形式で持っているデータの中から、完全一致する文言を複数個探す。
こんな書き方
.where('[コレクション名]' ,'array-contains-any' ,['りんご', 'みかん'])3.データ取得
データ操作の基本を踏まえ、取得は以下の様な記載方法となる。
firebase.firestore()
.collection('[コレクション名]')
.where('[フィールド名]' ,'[比較演算子]' ,[検索する値])
.orderBy('オーダーをかけるフィールド名' ,'[asc or desc]')
.limit([整数])
.get();記載しているのはjsなので、結果を待たない。
上記メソッドはpromise関数なので、「then()」を使うなり、「async await」を使うなりして取得する。4.データ追加
firebase.firestore()
.collection('[コレクション名]')
.add(
{
'name':'フルーツ',
'number':1,
available:true,
'fruit':[
'りんご',
'みかん'
],
'creater':[{
'生産者':'AA農場',
'電話番号':'080-0000-0000'
}],
[{
'生産者':'BB農場',
'電話番号':'090-1111-1111'
}]
}
);こんな感じ。
色々な型を使える。
[]は配列indexが自動でインクリメントされて生成される。
{}はkeyとvalueを指定できる。例えば「'creater'」は以下の様に保存される。
[0] => 生産者 => 'AA牧場'
電話番号 => '080-0000-0000'
[1] => 生産者 => 'BB牧場'
電話番号 => '090-1111-1111'ドキュメントIDは、何も指定しなければ自動生成される。
ドキュメントIDとは、そのデータを表す一意の特定を行うために作成されるID。実は追加のメソッドは「add()」だけでなく「set()」も使えるが、多分使わない方が無難。
この辺は説明すると長くなるので、一旦省略。もしも、ドキュメントIDを指定したい場合は、set()を利用する必要があるが、その場合は・・・いずれ書くかもしれません。
5.データ更新
firebase.firestore()
.collection('[コレクション名]')
.doc(ドキュメントID)
.update(
{
available:false,
}
);上記で追加したデータを更新すると考えたときに、他のフィールドは変更なく、「available」フィールドのみ更新される。
更新でも「set()」メソッドを使えるが、使わない方が無難。理由は(ry
6.まとめ
まずは基本を覚えて、
.get()
.add()
.update()を覚えれば、基本的にはfirestoreを使える。
- 投稿日:2020-07-31T17:54:10+09:00
jsonをcsv(カンマ区切り、項目行あり)に変換する(構文破壊に対応)
以下の記事にあるJSON変換用関数、すごくわかりやすくて重宝しているのですが、
文字列をダブルクオーテーションで囲んでないので、
「あいうえお"」や「あい,うえお」のような、構文破壊系の文言が来ると死にます。https://qiita.com/_shimizu/items/38e6d75afcacec25eb7e
ので、見出し行以外を全てダブルクォーテーションで囲んでカンマによる構文破壊を防ぎ、
ダブルクォーテーション一つだけの文字列は二つにすることで、そちらの構文破壊も防ぐようにします。おまけで、見出し行を出力するかどうかを選択できるようにしました
実装
/** * Convert json to csv * Supports syntactic destruction * * @params array-object records * @params boolean isHeaderDraw [default true] * @return string * @link https://qiita.com/_shimizu/items/38e6d75afcacec25eb7e * @link https://qiita.com/qwe001/items/60f6cb264110490d7d90 */ function json2csv(records, isHeaderDraw) { var isHeaderDraw = isHeaderDraw !== undefined && isHeaderDraw === false ? false : true; if(! records || records.length === 0){ return ""; } var header = Object.keys(records[0]).join(',') + "\n"; var body = records.map(record => { record = Object.keys(record).map(key => { var field = record[key]; if (field) { field = field.replace(/\"/g, "\"\""); //field = field.replace(/\r?\n/g, " "); // 改行を無効にしたい場合 } return field; }).join("\",\""); return "\"" + record + "\""; }).join("\n"); return isHeaderDraw ? header + body : body; }使い方
var a = [ {id:1, name:"test", address:"tokyo"}, {id:2, name:"hoge", address:"構文破壊1\""}, {id:3, name:"hello", address:"構文,破壊2"}, {id:4, name:"world", address:"saitama"} ]; json2csv(a); //output id,name,address "1","test","tokyo" "2","hoge","構文破壊1""" "3","hello","構文,破壊2" "4","world","saitama" // 見出し行が要らへんとき json2csv(a, false); //output "1","test","tokyo" "2","hoge","構文破壊1""" "3","hello","構文,破壊2" "4","world","saitama"バグなどあったら教えてくれると助かります。
- 投稿日:2020-07-31T17:51:23+09:00
Javascriptを一から勉強する(JavaScriptの基本事項編)
一時期勉強していたJavascriptを1からざっくり勉強し直すための記事です.他人に教えることが3倍ぐらい勉強したことになるという理論がモチベーションです.
自分と同じようにJavascriptを一からざっくり勉強したい人にはおすすめですが,それ以外の人は閲覧注意(間違った解釈が多いかもしれないので).https://www.javadrive.jp/javascript/ini/index1.html
↑を参考に勉強していきますJavaScriptとは?
https://techacademy.jp/magazine/8801 から引用
JavaScriptはプログラミング言語であり、ユーザー側のWebブラウザと、Webサイトまたはウェブサービスの相互間のやりとりを、円滑にするために使われています。
実質的に、私たちがブラウジングで体験できることのすべては、ブラウザの中で使われているJavaScriptの処理によるものです。
Webサイトの外観のデザインや使い心地から、Netflixのサイトに表示される映画の題名のようなサイト内の情報表示に至るまで、JavaScriptが使われています。
つまりWeb系言語で,HTMLの拡張機能的な何かで,UXに重要なプログラミング言語ってことかなるほど.
スクリプトの記述
- 大文字小文字は区別する(Num=num)
- スクリプト内の空白と改行は無視される(var n= 0とvar num=0は同じ)
- 文の区切りにはセミコロンを付ける(var num=0;)
基本的にはProcessingと一緒やな,大文字小文字の区別はコンピュータ君頭いいなと思えばいいし,改行や空白はめんどくさがりには大変うれしくてありがとうございましたやし,セミコロンは日本語の句読点と同じ役割やし簡単やな,次.
スクリプトが実行されるタイミング
Script要素が表れるとHTMLの記述の中にあろうが実行される
<p>HTML文の解析中</p> <p>この次にスクリプトが実行される</p> <script type="text/javascript"</p> document.write("<p>スクリプトが出力</p>"); </script</p> <p>スクリプトが終了</p> <p>HTML文の解析を続行</p>なるほど(なるほど),実行したい順番にコードを書かないといけないことね
分散して記述されたスクリプトの扱い
分散してても1つのスクリプトに記述されたように実行される
<p>XHTMLの文1</p> <script type="text/javascript"> var num = 10; </script> <p>XHTMLの文2</p> <script type="text/javascript"> num = num * 10; </script> <p>XHTMLの文3</p> <script type="text/javascript"> document.write("<p>結果は" + num + "です</p>"); </script>だとしても
<script type="text/javascript"> var num = 10; num = num * 10; document.write("<p>結果は" + num + "です</p>"); </script>のようなスクリプトとして実行されるらしい.分散されてても変数はそのファイル内でちゃんと共有されてるのよって感じですね
特殊な文字の入力(エスケープシーケンス)
入力できない文字を他の文字の組み合わせで表現したものをエスケープシーケンスといいます。
(¥b=改行,¥'=シングルクオーテーションなど)
'It's a pen'
だと意味の違うシングルクオーテーションが文の中にもあってバグるけど'It¥'s a pen'
にするとバグらなくなるらしい.時々必要になる秘密のコマンドみたいなもんだね.ということで今日はここまで.初心者が作ったノートは果たして本当に初心者に分かりやすいものなのかは神のみそしる.
- 投稿日:2020-07-31T17:41:20+09:00
kintoneで性別判定をしてみよう
はじめに
- ES2015以降の構文を含むため、Chromeでのみ動作確認をしています
- サンプルコードのため実際の使用は自己責任でお願いします?♂️
作るもの
- 名前を元に性別を自動判定する
フォーム構成
フィールド名(フィールドコード) 種別 姓(姓) 文字列(1行) 名(名) 文字列(1行) LastName(LastName) 文字列(1行) FirstName(FirstName) 文字列(1行) 性別(性別) ドロップダウン API
今回は GenderizeというAPIを使って性別判定を行います。
使用方法は簡単で、https://api.genderize.io/?name=●● と叩くと推定性別、パーセンテージが返ってきます。
ただし、日本語には対応していませんのでアルファベットで渡してあげる必要があります。
また、国の指定もできるので今回はcountry_id=JPというパラメータを付与します。
このパラメータが結構重要で、mika(みか)という日本名は概ね女性につけられますが、
英語圏だとmichaelの略名だったりする関係で、パラメータなしだと男性判定、パラメータありだと女性判定になります。
よって外国人も含む場合は国名などのフィールドを用意してパラメータを変化させることを要件に追加するのもありでしょう。カスタマイズ
今回はレコード追加画面を保存したタイミングで性別判定を行い、ドロップダウンに性別を反映します。
gender.js(() => { 'use strict'; kintone.events.on('app.record.create.submit', event => { const record = event.record; const firstName = record.FirstName.value; const apiUrl = `https://api.genderize.io/?name=${firstName}&country_id=JP`; return kintone.proxy(apiUrl, 'GET', {}, {}).then((args) => { const res = JSON.parse(args[0]); const gender = res.gender; record.性別.value = gender === 'male' ? '男' : '女'; return event; }); }); })();留意点
- Genderize APIは1日1000件までが無料範囲内です。
- 実際の運用では、Possibilityに基づき、閾値を設定する必要がありそうです。
- 今回のコードにはエラー処理を含めていません。
- 投稿日:2020-07-31T17:31:44+09:00
【Nuxt.js】Nuxt文法編:fetch
? この記事はWP専用です
https://wp.me/pc9NHC-vM前置き
非同期通信に使えるfetchをご紹介?
APIからデータを取得する方法は2つ!
fetch
asyncData2つの違いについても解説しました???
⬇️asyncDataの記事はこちら
https://wp.me/pc9NHC-utasyncDataとの違い
値のセット先が違う
asyncData:直接コンポーネントにセット
fetch:Vuexのstoreに格納?呼び出されるタイミングが違う
asyncData:インスタンス作成前
fetch:インスタンス作成後The Nuxt.js fetch hook is called after the component instance is created on the server-side: this is available inside it.
https://ja.nuxtjs.org/guides/features/data-fetching/
【翻訳】
Nuxt.jsフェッチフックは、
サーバー側でコンポーネントインスタンスが
作成された後に呼び出されます。
これは、その内部で使用できます。違いはこれくらいです!
他はほとんど変わりません?基本的にはasyncDataと同じ
使い方・書き方
asyncDataとほとんど一緒です?
Promiseを返すか
async/awaitを使うかです?
詳しい書き方はasyncDataの記事をご覧ください?thisが使えない
thisを通してコンポーネントのインスタンスに
アクセスすることができません?使用箇所
pages内コンポーネントのみ使用可能
fetch
fetchとは
? 続きはWPでご覧ください?
https://wp.me/pc9NHC-vM
- 投稿日:2020-07-31T16:17:13+09:00
【SharePoint】JSリンクのフィールド名(内部名・プロパティ名)がわかりづらい
SharePointのJSリンクの改修で、フィールド名がわかりづらく結構手間取ったのでそれについてまとめ
経緯
SharePointにJSリンクというのがあり、Excelでいう条件付き書式を実現するのに使われている(らしい)。
今回対応したのはSharePoint製社内イントラのリストで共通で使っていたJSリンクが、特定のリストで使えないというイレギュラー。原因を調べたところ、そのリストについてはフィールドの内部名が他と異なっていたことが判明。このイレギュラーなリストに対応するために既存のJSリンクのフィールド名のみ修正した差分を作成することになった。
わかりづらい点1: 表示上のフィールド名とJSで取得する内部名が違う
今回の例では表示上のフィールド名は
投稿日
、
表示上のフィールド名とJSで取得する内部名が違う、具体的には問題のフィールドは表示上は
大本の原因は以下の2つ
- フィールドの内部名は作成時の作成時のフィールド名をもとに決定され、以降フィールド名を変えても内部名は変わらない
- 全角文字は文字化けしていてそのままでは何のフィールドなのかわからない
厄介なのは後者で、ただでさえ読めない置き換えられた文字が、場合によってはさらにパーセントエンコーディングされていたりもするという…。
どうせ置き換えるならパーセントエンコーディングいらない文字列にしてくれればいいのに…。内部名の取得
列の内部名はというと、列の編集画面のアドレスから見つかる
(詳細はSharePoint 2013 で列の内部名を調べる方法 | idea.toString();を参照)
今回の問題のフィールドの場合、アドレス
http://example.com/(中略)&Field=%5Fx767b%5F%5Fx6821%5F%5Fx65e5%5F
となっていたので、
Field=
に続く%5Fx767b%5F%5Fx6821%5F%5Fx65e5%5F
が内部名になる。フィールド名が半角文字なら内部名も文字化けせずフィールド名そのままなのでこれで話は終わりになるところだが、今回は見ての通り全角文字なので案の定文字化けている。
パーセントエンコーディングのデコード(必要なら)
今思えば今どきのブラウザなら表示の際にはパーセントエンコーディングをデコードしてくれている気もするのだが、今回1のように何かの拍子でパーセントエンコーディングされることがあるので、その場合デコードする必要がある。
const str = `%5Fx767b%5F%5Fx6821%5F%5Fx65e5%5F`; const internalname = decodeURI(str); console.log(internalname); // "_x767b__x6821__x65e5_"が返ってくるこれをデコードすると
_x767b__x6821__x65e5_
という内部名が手に入る。本来の列名の取得
JSリンクで扱う分にはこれで十分だが、やっぱりこれだと何の列だかわからなくてモヤモヤするので変換してみる。
平たく言うとUnicodeらしい。(詳細は日本語で作成した列の内部名から列名を調べる方法 - Qiitaを参照)せっかくなのでUnicode(¥uxxx形式)を文字列へアンエスケープ | JavaScript逆引き | Webサイト制作支援 | ShanaBrian Websiteを参考に内部名の変換用に関数を作って試してみた。
function decodeInternalname(internalname) { const re = /_x([0-9a-f]{4})_/g; const arr = internalname.match(re); let result = ''; for (str of arr) { result += String.fromCharCode(str.replace(re, '0x$1')); } return result; }; // 問題のフィールド名を確認する const internalname = '_x767b__x6821__x65e5_'; const result = decodeInternalname(internalname); console.log(result); // "登校日"が返ってくるどうやら
投稿日
というフィールドをつくるはずが登校日
と変換間違いしたらしい。そのまま表示されてたら気になるから結果的に文字化けしててよかったと思った。
わかりづらい点2: ピリオドを含むプロパティ名が存在する。
今回だけならともかく、同様のイレギュラーが今後も全くないとは言えない(実際もう1件あった)ので、差分の作成に先駆けて、簡単に列名を変えられるように列名を定数に切り分けるという改修をした。
具体的には仮にフィールド名が
FieldName
だとすると、ctx.CurrentItem.FieldName
といったようになっているのを複数個所修正する必要があったので、これをvar FIELD_NAME = "FieldName"; ctx.CurrentItem[FIELD_NAME];といった感じで列名を定数に切り出し、'FIELD_NAME'一か所だけ変えれば済むようにした。
問題
そこで問題となったのが以下のようなコード。
ctx.CurrentItem.FieldName.descこのフィールド名を変数に置き換えようとすると、以下のようにしたくなるが、これはエラーになる
var FIELD_NAME = "FieldName"; (中略) ctx.CurrentItem[FIELD_NAME].desc // 間違い結論としては、
FieldName.desc
はこれ全体で一つのプロパティ名であって、FieldName
オブジェクトのプロパティdesc
ではない。
そのため正しくは以下のようにする必要があった。var FIELD_NAME = "FieldName"; (中略) ctx.CurrentItem[fieldName + ".desc"]詳細
SharePointのリストは1フィールドが複数の値を持っていることがある。
具体的な例としてはハイパーリンク型のフィールドで、これはURLと説明文の2つの値を持っている。
この場合のプロパティ名はURLがFieldName
、説明文がFieldName.desc
になっているらしい。GASのようなクラスの詳細がわかるリファレンスがほしいのだけれど見つからなかったので、Chromeのコンソールで調べたところ以下のように確認できた。
console.log(location.pathname) //リストを開いていることを確認。 //"/path/to/list/AnyView.aspx" console.log(GetCurrentCtx().ListData.Row[0]) // リストの1行目を取得 //{(中略), FieldName: "https://example.com", FieldName.desc: "リンクの説明", (中略)}その他参考サイト
リストおよびライブラリの列の種類とオプション - SharePoint
decodeURI() - JavaScript | MDN
String.fromCharCode() - JavaScript | MDN
この時はブラウザで確認せず、アドレスをその場でコピぺして作業をしたのでパーセントエンコーディングされてしまったらしい。 ↩
- 投稿日:2020-07-31T16:13:36+09:00
ウェブサイト作成用備忘録・1号:background-image の疑似アニメーション制御その1
日々の学習のアウトプットの為、自主学習の際に工夫した内容を記録していきます。
今回は background-image プロパティの疑似アニメーション制御について
background-image プロパティは transition プロパティや animation プロパティ等のアニメーション制御には対応していません。
そこで、自分なりに試行錯誤することで、疑似的なアニメーション制御に成功したので、その方法などを記録していきます。
方法1:JavaScript の動的制御で画面を暗転させ、background-image を変更してから setTimeout() メソッドで画面の暗転を解除する
記述例
HTML
<html> <body id="background" class="none"> <div id="hide_screen" class=""></div> <button id="change_screen" class="" type="button">背景変更</button> </body> </html>CSS
#hide_screen { min-height: 100%; min-width: 100%; background-color: #000; position: fixed; z-index: 2 transition: all .5s; -webkit-transition: all .5s; } #hide_screen.none { opacity: 0; pointer-events: none; } #background { background-image: url(背景画像1) } #background.change { background-image: url(背景画像2) }javascript
jQuery(document).ready(function(){ $("#change_screen").click(function(){ $("#hide_screen").removeClass("none"); setTimeout(function(){ $("#background").toggleClass("change"); },100); setTimeout(function(){ $("#hide_screen").addClass("none"); },200); });解説
1・背景色を黒に設定した空の div タグで画面全体を覆い隠す。
2・予めクラス none を設定し、透過処理の初期設定を行う。
3・背景変更ボタンをクリックすると、none クラスが解除され、画面全体が暗転する。
4・次に、setTimeout() で、最初に背景変更ボタンを押してから1秒後に toggleClass メソッドで body タグの change クラスを切り替え、画面暗転後に背景画像を変更する。
5・最後に setTimeout() で最初に背景変更ボタンを押してから2秒後に再び none クラスが追加され、画面の暗転が解除される。
今回はこれで以上になります。
あくまで自分用の備忘録ですが、他の方の参考になれば幸いです。
- 投稿日:2020-07-31T15:57:11+09:00
[React]React コンポーネントでJSXが複数行になる場合になぜ()で囲むのか?
はじめに
今更ですが、みなさんReactコンポーネントを作る際に「なんで、JSXが複数行になる場合にのみ()で囲まないといけないのか?
」と疑問に思ったことはないでしょうか?
僕は夜も眠れないくらい気になったので、ちょっと調べてみました。
↓こんな場合の()です!
hello.jsimport React from 'react'; const element = ( <h1> Hello, {formatName(user)}! </h1> );結論
最初に結論からいうと、ReactでJSXが複数行になる場合には()で囲んだ方が良いです!
主な理由
- 可読性向上のため
- フォーマットを揃えるため
これだけだとまだ眠れないと思うので、調査した内容をまとめます。
まず、あの()は何であるか
グループ化演算子の()です。
みなさんもよく
(1 + (2 * 3))
などで利用する評価の優先順位を制御する演算子ですね!
ちょっと形式が変わるだけで人間すぐに混乱してしまいます。この()で囲うことで、複数行のJSXも1つの式(Expression)として解釈されるようにしているんですね!
参考: MDN グループ化演算子
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/GroupingReact公式ドキュメントの見解
読みやすさのため JSX を複数行に分けています。必須ではありませんが、複数行に分割する場合には、自動セミコロン挿入の落とし穴にはまらないように括弧で囲むことをおすすめします
可読性の向上と自動で文末にコロンが挿入されるのを防ぐために括弧で囲むのをオススメしてます。
参考:JSX の導入
https://ja.reactjs.org/docs/introducing-jsx.html実際に()で囲わないとどうなるのか試してみる
では、実際に()で囲わないとどうなるのか試してみましょう。
今回は、下記のReact公式からリンクされているCodepenのコードを利用して色々試してみました。参考:CodePen
https://ja.reactjs.org/redirect-to-codepen/components-and-props/rendering-a-component例1: JSXを変数宣言時に代入
hello.jsvar hello = <div> <h1>Hello</h1> </div>; const element = <Hello name="Sara" />; ReactDOM.render(element, document.getElementById('root'));=> 問題なく動作する。
例2: JSXをアロー関数でリターン
hello.jsconst Hello = (props) => <div> <h1> Hello, {props.name} </h1> </div> const element = <Hello name="Sara" />; ReactDOM.render(element, document.getElementById('root'));=> 問題なく動作する。
例3(OKパターン): JSXをfunction内でリターン
hello.jsfunction Hello(props) { return <div> <h1> Hello, {props.name} </h1> </div> } const element = <Hello name="Sara" />; ReactDOM.render(element, document.getElementById('root'));=> 問題なく動作する。
ただし、下記のようにreturnで改行された場合は当然ですが、returnで関数が終了してしまうので動作しません。
例3(NGパターン): JSXをfunction内でリターン
hello.jsfunction Hello(props) { return <div> <h1> Hello, {props.name} </h1> </div> } const element = <Hello name="Sara" />; ReactDOM.render(element, document.getElementById('root'));このようにみていくとreturn文が登場するような
スタイルガイドではどうなっているか?
JavaScript Standard Styleでの推奨の書き方
JavaScript Standard Style で推奨の記載は下記です。
推奨以外の書き方以外はerrorになるようにeslintrc.json
で定義されています。eslintrc.json"react/jsx-wrap-multilines": ["error", { "declaration": "parens-new-line", "assignment": "parens-new-line", "return": "parens-new-line", "arrow": "parens-new-line", "condition": "parens-new-line", "logical": "ignore", "prop": "ignore" }]変数宣言時に代入
hello.jsvar hello = ( <div> <p>Hello</p> </div> );JSXをアロー関数でリターン
hello.jsvar hello = () => ( <div> <p>World</p> </div> );JSXをfunction内でリターン
hello.jsfunction hello() { return ( <div> <p>Hello</p> </div> ); }参考:
JavaScript Standard Styleでの方針
- https://github.com/standard/standard/issues/710
- https://github.com/standard/standard/commit/ccaf4390d9ae0829fdd31b2d69df143e9138e77dEsLint React Pluginでの推奨の書き方
EsLint React PluginでもJSXが複数行になる場合には()で囲もうという方針ですね。
ただEsLint React Pluginではデフォルトでの設定がparens
となっている点が異なります。(JavaScript Standard Style ではparens-new-line
となっています。)Prevent missing parentheses around multiline JSX (react/jsx-wrap-multilines)
Wrapping multiline JSX in parentheses can improve readability and/or convenience.参考:jsx-wrap-multilines.md
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-wrap-multilines.mdデフォルト設定
default{ "declaration": "parens", "assignment": "parens", "return": "parens", "arrow": "parens", "condition": "ignore", "logical": "ignore", "prop": "ignore" }
parens
とparens-new-line
の違いどちらも()で囲おうというのは同じなのですが、
parens-new-line
の方が少し厳しいです。どちらもOK
hello.jsvar hello = ( <div> <p>Hello</p> </div> );どちらもNG
hello.jsvar hello = <div> <p>Hello</p> </div>;
parens
ではOKだが、parens-new-line
でNGhello.jsvar hello = (<div> <p>Hello</p> </div>);最後に
ReactでJSXが複数行になる場合には()をつけましょう!
夜、()に悩まずに眠れるようになった人が少しでも増えたなら、幸いです。
- 投稿日:2020-07-31T15:51:17+09:00
【PHP】CSVを作成し、ZIPに圧縮してダウンロード
はじめに
前回の記事【PHP】Excelで文字化けしないCSVファイル作成の続き?というより最終的にやりたかったことを備忘録として記事にします。
非同期通信で、DBからテーブルごとのCSVを作成し、ZIPに圧縮してダウンロードさせる!!!
ZIPに圧縮まではすんなりできたのですが(前回記事は多少躓きましたが…)、ダウンロードしたZIPファイルが開けない/解凍できない現象に悩まされました。
非同期をjQueryでやっていたんですが上手くいかず、XMLHttpRequestに変えたら成功しました。
何がダメだったんだろう。。。コード
ルーティングとかは省きます。
クライアント側 JSvar request = new XMLHttpRequest(); request.open('GET', '/getZip', true); request.responseType = "blob"; request.onload = function (oEvent) { var blob = request.response; var objectURL = window.URL.createObjectURL(blob); // リンクを作成し、JavaScriptからクリック var link = document.createElement("a"); document.body.appendChild(link); link.href = objectURL; link.download = 'ZIPファイル名'; link.click(); document.body.removeChild(link); }; request.send(null);サーバ側 PHP// ZipArchiveクラス初期化 $zip = new ZipArchive(); // Zipファイルパス $zipFileNm = 'ZIPファイル名(拡張子付)'; $zipFilepath = 'ZIPファイルパス'.$zipFileNm; // Zipファイルオープン $result = $zip->open($zipFilepath, ZIPARCHIVE::CREATE); if($result !== true){ //ZIPファイル作成失敗時の処理 } // CSV作成 前回記事関数 $csvFilePath = createCSV('CSVファイル名', 'データカラム', 'データ本体'); // CSVファイルをZIPに追加 $zip->addFile($csvFilePath, 'CSVファイル名'); // Zipファイルクローズ $zip->close(); // HTTPヘッダを設定 mb_http_output( "pass" ); header("Pragma: public"); header('Content-Type: application/force-download;'); header('Content-Length: '.filesize($zipFilepath)); header("Content-Disposition: attachment; filename=$zipFileNm"); ob_end_clean(); // ファイル出力(ダウンロードさせる) readfile($zipFilepath); // Zipファイル削除 if(!unlink($zipFilepath)){ //ZIPファイル削除失敗時の処理 } // CSVファイルの削除 $delFileName = "CSVファイル保存パス/*.csv"; foreach(glob($delFileName) as $val){ if(!unlink($val)){ // CSVファイル削除失敗時の処理 } }最後に
だらだら長いコードだと思います。
あと、これで本当に良いのかな~と疑問。処理は動くけども。こうした方が良い等ありましたら、アドバイスください!!!
- 投稿日:2020-07-31T14:29:53+09:00
Typescriptを勉強しよう①~導入
Typescriptとは
TypescriptとはMicrosoft社が開発(2012年公開)したJavascriptの進化版言語である。
JavaScriptの欠点を補うために作られた言語で、Webアプリケーション開発、大規模開発での注目が高まっている。
Typescriptで書かれたコードはJavascriptにコンパイル(変換)され、コンパイルされたJavascriptが動作する仕組みである。なぜTypescriptを使うのか
JavaScriptと同じ環境で動かすことが可能
Typescriptはコンパイルし、JavaScriptに変換できるので、JavaScriptが動かせる環境なら、TypeScriptは動かせる。
そのため、開発環境のハードルは低くい。型を宣言するためエラーを未然に防げる
Typescriptは静的型システムである。
型が指定できると、変数に違った型のデータが入り、プログラムが動作しないことがある。この変数にはこの型のデータが入ると、その変数に入るデータを予測しやすくなる。
また、Editorによっては型が違うとエラー表示されるため、開発効率が上がる。TypescriptはES5にコンパイルされる。
Javascriptは毎年アップデートされているため、ES6ではブラウザで認識されず表示されないことがあります。ES5(2009年)では基本的にどのブラウザでも認識される。なので、Typescriptで書いたコードは基本どのブラウザでも表示される。
*補足*
静的型システム
Typescriptがコンパイルされる時に、型がチェックされる。変数の値がNumberなのか、Stringなのかそれ以外なのかすべてチェックされる。これが静的型システムがである。
型が違う場合(本来StringではないといけないところがNumberになっている)エラーが出力される。開発環境を整えましょう
手順
- ターミナルを開く *私はWindowsに内蔵されているCMDを使用した
Nodeがインストールされているか確認*node -vを実行すると確認できる
→インストールされていない場合 Nodeダウンロードターミナル上で npm install -g typescript を実行し、Typescriptのインストールする
Editarを開く*Typescriptのサポートが内蔵されているVscodeがオススメ
ファイルを作製(拡張子はts)
Typescriptを記述する。このように: string型を指定する
let greeting: string = 'こんにちは';
console.log(greeting);Typescriptをコンパイルする
Editor内のターミナルを開く、Vscodeならメニューバーのターミナルの新しいターミナルを選択するターミナル上で tsc ファイル名 を実行
実行前
実行後
問題発生
tsc : このシステムではスクリプトの実行が無効になっているため、ファイル C:\Users\user\AppData\Roaming\npm\tsc.ps1 を読み込むことができません。
詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=135170) を参照してください原因
実行ポリシーが初期値(Restricted)なので、制限を解除する必要がある。
解決方法
手順
- スタートからWindows PowerShellを開く
- PowerShell上でPowerShell Get-ExecutionPolicyを実行すると、現在の設定を確認できる。 恐らく、Restrictedとなっているはず...確認出来たら
- Set-ExecutionPolicy RemoteSignedを実行すると、実行ポリシーの変更が表示される
- 実行ポリシーの変更 実行ポリシーは、信頼されていないスクリプトからの保護に役立ちます。実行ポリシーを変更すると、about_Execution_Policies のヘルプ トピック (https://go.microsoft.com/fwlink/?LinkID=135170) で説明されているセキュリティ上の危険にさらされる可能性があります。実行ポリシーを変更しますか? [Y] はい(Y) [A] すべて続行(A) [N] いいえ(N) [L] すべて無視(L) [S] 中断(S) [?] ヘルプ (既定値は "N"): Y ###Yを入力する
- 再度、PowerShell Get-ExecutionPolicy で設定を確認するとRemoteSignedに変更されています
- 投稿日:2020-07-31T14:29:53+09:00
Typescriptを勉強しよう①~コンパイルしよう
Typescriptとは
TypescriptとはMicrosoft社が開発(2012年公開)したJavascriptの進化版言語である。
JavaScriptの欠点を補うために作られた言語で、Webアプリケーション開発、大規模開発での注目が高まっている。
Typescriptで書かれたコードはJavascriptにコンパイル(変換)され、コンパイルされたJavascriptが動作する仕組みである。なぜTypescriptを使うのか
JavaScriptと同じ環境で動かすことが可能
Typescriptはコンパイルし、JavaScriptに変換できるので、JavaScriptが動かせる環境なら、TypeScriptは動かせる。
そのため、開発環境のハードルは低くい。型を宣言するためエラーを未然に防げる
Typescriptは静的型システムである。
型が指定できると、変数に違った型のデータが入り、プログラムが動作しないことがある。この変数にはこの型のデータが入ると、その変数に入るデータを予測しやすくなる。
また、Editorによっては型が違うとエラー表示されるため、開発効率が上がる。TypescriptはES5にコンパイルされる。
Javascriptは毎年アップデートされているため、ES6ではブラウザで認識されず表示されないことがあります。ES5(2009年)では基本的にどのブラウザでも認識される。なので、Typescriptで書いたコードは基本どのブラウザでも表示される。
*補足*
静的型システム
Typescriptがコンパイルされる時に、型がチェックされる。変数の値がNumberなのか、Stringなのかそれ以外なのかすべてチェックされる。これが静的型システムがである。
型が違う場合(本来StringではないといけないところがNumberになっている)エラーが出力される。開発環境を整えましょう
手順
- ターミナルを開く *私はWindowsに内蔵されているCMDを使用した
Nodeがインストールされているか確認*node -vを実行すると確認できる
→インストールされていない場合 Nodeダウンロードターミナル上で npm install -g typescript を実行し、Typescriptのインストールする
Editarを開く*Typescriptのサポートが内蔵されているVscodeがオススメ
ファイルを作製(拡張子はts)
Typescriptを記述する。このように: string型を指定する
let greeting: string = 'こんにちは';
console.log(greeting);Typescriptをコンパイルする
Editor内のターミナルを開く、Vscodeならメニューバーのターミナルの新しいターミナルを選択するターミナル上で tsc ファイル名 を実行
実行前
実行後
問題発生
tsc : このシステムではスクリプトの実行が無効になっているため、ファイル C:\Users\user\AppData\Roaming\npm\tsc.ps1 を読み込むことができません。
詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=135170) を参照してください原因
実行ポリシーが初期値(Restricted)なので、制限を解除する必要がある。
解決方法
手順
- スタートからWindows PowerShellを開く
- PowerShell上でPowerShell Get-ExecutionPolicyを実行すると、現在の設定を確認できる 恐らく、Restrictedとなっているはず...確認出来たら
- Set-ExecutionPolicy RemoteSignedを実行すると、実行ポリシーの変更が表示される
- 実行ポリシーの変更 実行ポリシーは、信頼されていないスクリプトからの保護に役立ちます。実行ポリシーを変更すると、about_Execution_Policies のヘルプ トピック (https://go.microsoft.com/fwlink/?LinkID=135170) で説明されているセキュリティ上の危険にさらされる可能性があります。実行ポリシーを変更しますか? [Y] はい(Y) [A] すべて続行(A) [N] いいえ(N) [L] すべて無視(L) [S] 中断(S) [?] ヘルプ (既定値は "N"): Y ###Yを入力する
- 再度、PowerShell Get-ExecutionPolicy で設定を確認するとRemoteSignedに変更されています
- 投稿日:2020-07-31T13:59:06+09:00
JavaScript(jQuery)で画像をリサイズする方法(JS以外でも可)
画像をリサイズするJS以外でも使用可
画像をクリックしたら画像の縦横比を維持した状態で指定のサイズに画像をリサイズする
$('img')on.('click', function(){ //画像のsrcを取得 let src = $(this).attr('src'); let width = 400;//任意の数値 let height = 400;//任意の数値 //元の画像サイズ let originalWidth = $(this).width(); let originalHeight = $(this).height(); //割合計算(小数点が出る場合はコンソールで注意が出る場合も。その場合はMath.ceil()などを使用して切り上げる) let rate = originalWidth / originalHeight; height = (width / rate); if(rate < 1){ height = width; width = width * rate; } //リサイズした画像をwidth、heightともに追加 $('body').append(`<div><img src="${src}" width="${width}" height="${height}"></div>`); });
- 投稿日:2020-07-31T12:37:27+09:00
TrelloみたいなカンバンUIを作りたいので、Javascriptのドラッグ&ドロップについて調べてみた
Trelloのようなタスク管理で、タスクをドラッグ&ドロップで移動する操作がありますよね。
今までドラッグ&ドロップの処理を書いたことがないので、調べてみました。作ったサンプル
APIについて調べる
以下、調べていく過程を記載します。
MDNによると、JavascriptのAPIがちゃんと用意されているようです。
https://developer.mozilla.org/ja/docs/Web/API/HTML_Drag_and_Drop_API以下のAPIを使えば、考えているものが作れそうに思えました。
- ドラッグを開始した時はondragstart
- ドラッグしている項目が、ドロップ対象に入るとondragover
- ドロップした時はondrop
- ドラッグしている項目が、ドロップ対象から外れるとondragleaveドラッグするデータは、ドラッグ開始時にdataTransferオブジェクトというのを使うらしい。
以下、MDNからサンプルコードを引用function dragstart_handler(ev) { ev.dataTransfer.setData("text/plain", ev.target.innerText); ev.dataTransfer.setData("text/html", ev.target.outerHTML); ev.dataTransfer.setData("text/uri-list", ev.target.ownerDocument.location.href); }そして、ドロップする場所で、同じくdataTransferオブジェクトを受け取るらしい。
以下、MDNからサンプルコードを引用function drop_handler(ev) { ev.preventDefault(); var data = ev.dataTransfer.getData("text/plain"); ev.target.appendChild(document.getElementById(data)); }reactで実装
なんだ、意外とカンタンにできそうだと思ったので、reactでちょっとサンプルを実装してみます。
TaskListとTaskというコンポーネントを作って、登録したタスクが移動できるか試してみます。
ドラッグ&ドロップAPIの動作確認することが目的なので、つくりが雑なのは悪しからず。タスク一覧を表示するTaskList
const TaskList = ({ status, title, tasks }) => { const { globalState, dispatch } = useContext(StateContext) const handleDragOver = (e) => { e.preventDefault() if (e.dataTransfer) { console.log('drop ok') } } const handleDrop = (e) => { e.preventDefault() const data = e.dataTransfer.getData('text/plain').split(',') dispatch({ type: 'MOVE_TASK', payload: { id: Number(data[0]), prevStatus: data[1], newStatus: status } }) } const handleDragLeave = (e) => { e.preventDefault() console.log('dragleave') } return ( <div className="box" id={status} onDrop={handleDrop} onDragOver={handleDragOver} onDragLeave={e => handleDragLeave(e)}> <div className="box-title">{title}</div> {status === 'beforeWork' && <button className="new-task">課題を作成</button>} {tasks && tasks.map((task, idx) => ( <Task key={idx} {...task} /> ))} </div> ) } export default TaskList個別のタスクを表示するTask
const Task = ({ id, name, status }) => { const handleDragStart = (e) => { e.dataTransfer.setData('text/plain', `${e.target.id.replace('task-', '')},${status}`) e.dataTransfer.effectAllowed = 'move' } return ( <div className="task" draggable="true" id={`task-${id}`} onDragStart={e => handleDragStart(e)}> <div className="task-name">{name}</div> </div> ) } export default Taskこんな感じで、ドラッグ&ドロップAPIの動作が確認できました。
前からドラッグ&ドロップの処理が気になってたので、今回調べたのはいい機会でした。
今回のサンプルコードはここに上げてあります。
https://github.com/koyoukai/kanban-ui-sampleドラッグ&ドロップAPIを使うなら、こうした方がいい的な改善等ありましたら、
ぜひご指摘ください。
- 投稿日:2020-07-31T02:39:01+09:00
Vue.js の Composition API における this.$refs の取得方法
Vue.js でテンプレート内の DOM 要素や子コンポーネントの参照は、旧来の Options API だと
this.$refs
で取得できました。
では、Composition API ではどうなっているのでしょうか。答えは 公式サイトに書いてあります。
公式サイトより引用<template> <div ref="root"></div> </template> <script> import { ref, onMounted } from 'vue' export default { setup() { const root = ref(null) onMounted(() => { // the DOM element will be assigned to the ref after initial render console.log(root.value) // <div/> }) return { root } } } </script>
ref
関数で作った変数(上記の場合はroot
)をテンプレート内の要素の ref 属性に与えます。
初回レンダリング後(onMounted
のタイミング)、変数のvalue
プロパティに DOM 要素の参照が代入される、という流れです。TypeScript で型をつける
さて、このまま終わってしまうのも味気ないので TypeScript ではどう書くのか見ていきましょう。
Vite で作ったプロジェクトをベースにして検証しました。1DOM 要素の場合
<template> <img ref="imgRef" alt="Vue logo" src="./assets/logo.png" /> </template> <script lang="ts"> import { defineComponent, ref, onMounted } from 'vue' export default defineComponent({ setup() { const imgRef = ref<HTMLImageElement>() // 1: 型を指定 console.log(imgRef.value) // 2: undefined が出力される onMounted(() => { console.log(imgRef.value) // 3: <img> console.log(imgRef.value?.clientHeight) // 4: 0 が出力される imgRef.value?.addEventListener('load', () => { console.log(imgRef.value?.clientHeight) // 5: 200 が出力される }) }) return { imgRef } }, }) </script>
ref
関数は引数なしの場合、ジェネリクスで指定した型と undefined のユニオンになります。2
すなわち上記の場合はRef<HTMLImageElement | undefined>
型です。- レンダリング前は undefined です。
- レンダリング後だと img 要素の参照が取得できます。
- この時点では画像データはまだ読み込まれていないので高さは 0 です。
また、imgRef.value
はHTMLImageElement | undefined
型なので、?.
を使用しています。気になる場合はif (!imgRef.value) return
などを書いて、undefined の可能性を除外してしまいましょう。- 画像が読み込まれると、正しいサイズが取得できます。
コンポーネントの場合
<template> <HelloWorld ref="componentRef" msg="Hello Vue 3.0 + Vite" /> </template> <script lang="ts"> import { defineComponent, ref, onMounted } from 'vue' import HelloWorld from './components/HelloWorld.vue' export default defineComponent({ components: { HelloWorld, }, setup() { const componentRef = ref<InstanceType<typeof HelloWorld>>() // 1: 型を指定 onMounted(() => { if (!componentRef.value) return // 型から undefined をなくす console.log(componentRef.value) // 2: Proxy {…} console.log(componentRef.value.$el) // 3: #text console.log(componentRef.value.msg) // 4: Hello Vue 3.0 + Vite console.log(componentRef.value.count) // 5: 0 componentRef.value.count++ // 6: バッドプラクティス! console.log(componentRef.value.count) // 1 (増えている) }) return { componentRef } }, }) </script>
- コンポーネントの実態はコンストラクタなので、このような型になります。
- インスタンスの Proxy オブジェクトが入っています。
- Vue 3 ではテキストノード(
<template>
開きタグと最初の DOM 要素の間)になります。3- props のプロパティが直接生えており、値が取得できます(
componentRef.value.$props.msg
でも同じ)- data も同様です(
componentRef.value.$data.count
でも同じ)- 外から data の値を変えることもできてしまいますが、大変危険なのでやめましょう。
まとめ
以前にくらべると少し手間は増えましたが、型の恩恵を受けるためなので仕方ないですね。4
今までコンポーネント ref はほとんど使っていませんでした。唯一使いそうな
$el
がテキストノードに変更されてしまい、さらに使いどころが分からなくなりました。誰か教えて下さい。
参考になるか分かりませんが、リポジトリはこちら https://github.com/jay-es/composition-api-refs ↩
tsconfig.json
で"strictNullChecks": true
にしている場合("strict": true
でも OK)。
false になっていると| undefined
にはなりません。 ↩Vue 2 +
@vue/composition-api
の頃はコンポーネント全体の DOM 要素が取得できました。
テンプレートのルートに複数の要素を置けるようになったことが関係していると思われます。 ↩実は Vue 2 +
@vue/composition-api
の場合、setup
関数の第二引数にrefs
が生えています。なんとなく理由は分かりますよね。
ただし、型情報には存在しないためas
などでごまかさないといけないのでオススメできません。 ↩
- 投稿日:2020-07-31T00:21:24+09:00
Angularで特定のルートにデータを持たせる
はじめに
Angularでは、Routeという個々のルートを定義するオブジェクトがあり、そこにユーザーが追加定義できるdataというプロパティがあります。これを使って、ActivatedRouteを介してコンポーネントに何らかのデータを渡すことができます。
環境
Angular 9.1.7
1.特定のパスにデータを持たせる
ルーティングを設定するモジュールでRouteオブジェクトにdataというプロパティを持たせます。
app-routing.module.tsimport { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: 'notes', data: { isNotShowFooter: true, }, loadChildren: () => import('./notes/notes.module').then((m) => m.NotesModule), }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule { }2.ActivatedRouteを使ってコンポーネントにデータを渡す
Routerのイベントを監視(Subscribe)し、ActivatedRouteのプロパティのfirstChildにあるdata、つまり現在表示されているコンポーネントのRouteオブジェクトのdataプロパティを読み取ります。
app.component.tsexport class AppComponent { activatedRouteData: Observable<Data>; constructor(private router: Router, private activatedRoute: ActivatedRoute) { this.router.events.subscribe(event => { if ((event instanceof NavigationEnd)) { this.activatedRouteData = this.activatedRoute.firstChild.data; } }); } }以上でコンポーネントにRouteのdataを渡すことができました。
使用例:ヘッダーやフッターを非表示にしたい時
- 最上層のAppModuleでヘッダーやフッターを読み込んでおり、特定のモジュールで非表示にしたい場合、data内のプロパティを使うことで切り替え、非表示にすることができます。
app.component.html<ng-container *ngIf="activatedRouteData | async as data"> <app-footer *ngIf="!data.isNotShowFooter"></app-footer> </ng-container>参考記事