- 投稿日:2020-07-26T23:50:16+09:00
クリックイベントを取り消す処理について
クリックしたらドロップダウンでメニューが出てくるのをjQueryで実装していたのですが、それを消すためのイベントについての話です。
https://gyazo.com/32762b0a4613e5b455de93f3db394050
右の▼または画像をクリックするとドロップダウンメニューが出てきて、その後どこかをクリックすると消える、という物です。
blurではできなかった。
ドロップダウンメニューが消えずにずっと残っていた。邪魔。
navbar.html.erb<li class="nav-item dropdown" id="nav-bar__dropdown"> <%= link_to '#',class: "ml-2 tip-accoount",id:'nav-bar__account' do %> <%= image_tag 'sample-account.png',class: "navbar__profile-icon" %> <span class="caret"> <%= icon('fas','caret-down') %> </span> <% end %> <div class="dropdown-menu dropdown-menu-right"> <%= link_to '管理ページ','#' ,class:"dropdown-item ajax-link" %> <%= link_to 'ログアウト','#' ,class:"dropdown-item ajax-link" %> </div> </li>navbar.js$("#nav-bar__dropdown").click(function(){ $("#nav-bar__dropdown").children(".dropdown-menu").toggleClass("show"); }); $("#nav-bar__dropdown").blur(function(){ $("#nav-bar__dropdown").children(".dropdown-menu").removeClass("show"); });結論:blurではなくfocusoutを使う
blurではその要素自体にしか適用されないので、子要素に適用するにはfocusoutを使わないといけない、とのことです。
navbar.js$("#nav-bar__dropdown").click(function(){ $("#nav-bar__dropdown").children(".dropdown-menu").toggleClass("show"); }); $("#nav-bar__dropdown").focusout(function(){ $("#nav-bar__dropdown").children(".dropdown-menu").removeClass("show"); });参考にした記事
https://developer.mozilla.org/ja/docs/Web/API/Element/blur_event
- 投稿日:2020-07-26T23:44:19+09:00
やらかしJS先生がみたMapをObject的に扱ってハマった話⭕❌
Overview
過去にバグになってしまったものを忘れないよう書き留めておくシリーズです。
今回の題材はMapです。以下は私が最初にさらりと書いたコードです。
これを基に❌な部分を修正していきます。const map = new Map(); if (!map["1"]) { map["1"] = "⭕"; } for(const {key, value} of map) { console.log(`key:${key}, value:${value}`); }Target reader
- この結果がわからない方
Prerequisite
- JavaScriptを一通り理解している
- 今回はChromeで実行したが、Node.js等、環境によって出力結果が異なることがあることに注意。
Body
少しだけ言い訳をしたい
![]()
いつもはJSONでファイル出力することが多いため、どうしてもそのまま出力できるObjectを利用することが多い。
そんな折、ファイル出力ないものがあったので、久しぶりにMapを使ったらずいぶん忘れていただけ。
今はもう大丈夫なんで答え合わせ
正解はこうなる。
terminal何も出ないが正解
そもそも、MapはMapにあるメソッドを使って値を扱い必要がある。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map
Map オブジェクトに対してオブジェクトプロパティを設定すると正しく動作しますが、混乱を催すことが考えられます。
要するに、Object的な扱いもできる故に、Mapにデータ入れたと思ったらハマりますよってことで。
詳しくは上記URL参照のこと。ということで、Object的な扱いを一つ一つ是正していく。
const map = new Map(); if (!map.has("1")) { // ❌ !map["1"] map.set("1", "⭕"); // ❌ map["1"] = "⭕"; } for(const {key, value} of map.entries()) { console.log(`key:${key}, value:${value}`); }これで正しくMapのデータ構造にデータを入れれたはず…答えはこうだ。
terminal何も出ない
よく見ると、
of map
ではなくof map.entries()
でkey-value
のペアを受け取らないといけなかった!const map = new Map(); if (!map.has("1")) { // ❌ !map["1"] map.set("1", "⭕"); // ❌ map["1"] = "⭕"; } for(const {key, value} of map.entries()) { // ❌ map console.log(`key:${key}, value:${value}`); }これで正しくMapのデータ構造にデータを入れれたはず…答えはこうだ。
terminal> "key:undefined, value:undefined"値がない
そういえば、
map.entries()
ってkey-value
のペアだからオブジェクトから取り出しではない!const map = new Map(); if (!map.has("1")) { // ❌ !map["1"] map.set("1", "⭕"); // ❌ map["1"] = "⭕"; } for(const [key, value] of map.entries()) { // ❌ map, ❌ {key, value} console.log(`key:${key}, value:${value}`); }これで正しくMapのデータ構造にデータを入れれたはず…答えはこうだ。
terminal> "key:1, value:⭕"動いた
ちなみに、以下のように、値のセットをObjectとして処理し、存在チェックをMapで処理すると…
const map = new Map(); map["1"] = "❌"; if (!map.has("1")) { map.set("1", "⭕"); } for(const [key, value] of map.entries()) { console.log(`key:${key}, value:${value}`); }terminal> "key:1, value:⭕"本来なら❌が出力され、⭕での上書きを防止するはずがそれを防げないという厄介なバグに発展するので要注意。
Conclusion
Map
はObject
と似ているように見えるが、使い方が異なるのでうろ覚えでの使用には注意!
特に存在チェックとセットメソッドを混在して利用する等すると泥沼。
- 投稿日:2020-07-26T22:25:39+09:00
Typescript書き方の速成まとめ
個人学習目的で作成していますが、間違ったところや捕捉などありましたがご指摘ください。
1. 概論・セットアップ
- transpile言語 (類似コンパイル言語)
- Javascriptと交換
- Typescriptの主な役目
- コンパイル時にタイプチェックを行うこと
Typescript setup & compile
# 初期化 npm init -y npm -i typescript [-g] %init tsconfig.json ./node_modules/.bin/tsc --init # compile tsc {filename} # watch & auto compile, 実用的ではない(glup推奨) tsc -wtypescript online play
https://www.typescriptlang.org/play
2. tsconfig
http://json.schemastore.org/tsconfig
● Top Level Properties
compileOptions ★ compileOnSave //boolean : セーブと同時にコンパイル(IDE) extends //relative path files //path(glob) include //path(glob) exclude //path(glob) typeAcquisitioncompileOptions : type
- TypeScript2からサポートするType Definition System関連オプション
- 何も設定しないと、自動で
./node_nodules/@types/*
をインポート
- ex:
./node_nodules/@types/react/*
,./node_nodules/@types/babel__*/*
- @typesは、コンパイル時のタイプチェックはもちろん、IDEのコードアシスト、シンタクスチェックなどでも使われる大事な定義ファイル
- typeRoots
- 設定すると、設定したパスだけインポート
- types
- 設定すると、配列内に指定したモジュール、または
./node_nodules/@types/
内のモジュール名から探す。[]
だと使わない- typeRootsはtypesは、どっちか一つだけ使う。
"typeRoots": { "description": "Specify list of directories for type definition files to be included. Requires TypeScript version 2.0 or later.", "type": "array", ... }, "types": { "description": "Type declaration files to be included in compilation. Requires TypeScript version 2.0 or later.", "type": "array", ... },compileOptions : target, lib
- target
- buildするバージョンを指定
- 指定しないと、基本esバージョン
- lib
- 基本type difinitionライブラリを指定
- 指定しないと、esバージョンに依存したライブラリを使用
- 指定すると、指定したライブラリのみを使う
"target": { "description": "Specify ECMAScript target version: 'ES3', 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ESNext'", "type": "string", ... }, "lib": { "description": "List of library files to be included in the compilation. Possible values are: 'ES5', 'ES6', 'ES2015', 'ES7', 'ES2016', 'ES2017', 'ES2018', 'ESNext', 'DOM', 'DOM.Iterable', 'WebWorker', 'ScriptHost', 'ES2015.Core', 'ES2015.Collection', 'ES2015.Generator', 'ES2015.Iterable', 'ES2015.Promise', 'ES2015.Proxy', 'ES2015.Reflect', 'ES2015.Symbol', 'ES2015.Symbol.WellKnown', 'ES2016.Array.Include', 'ES2017.object', 'ES2017.Intl', 'ES2017.SharedMemory', 'ES2017.String', 'ES2017.TypedArrays', 'ES2018.Intl', 'ES2018.Promise', 'ES2018.RegExp', 'ESNext.AsyncIterable', 'ESNext.Array', 'ESNext.Intl', 'ESNext.Symbol'. Requires TypeScript version 2.0 or later.", "type": "array", ... },compileOptions : outDir, outFile
"outDir": { "description": "Redirect output structure to the directory.", "type": "string" }, "outFile": { "description": "Concatenate and emit output to single file.", "type": "string" },compileOptions : module
- module
- compileされた結果物のモジュールシステムを指定
target
がes6だと、es6がデフォルトtarget
がes6じゃないとcommonjsがデフォルト- moduleResolution
- tsソースで使用されるモジュールを指定
- CommonJSの場合
Node
、それ以外はだいたいClassic
-pathsと baseUrl- 指定すると、該当パスのモジュールをロード
- 普通使わなくてOK。(細かいモジュール連携に必要)
- rootDirs
- ロードするモジュールのルートパス配列
- 普通使わなくてOK。(細かいモジュール連携に必要)
"module": { "description": "Specify module code generation: 'None', 'CommonJS', 'AMD', 'System', 'UMD', 'ES6', 'ES2015', 'ES2020' or 'ESNext'. Only 'AMD' and 'System' can be used in conjunction with --outFile.", "type": "string", ... }, "moduleResolution": { "description": "Specifies module resolution strategy: 'node' (Node) or 'classic' (TypeScript pre 1.6) .", "type": "string", ... }, "baseUrl": { "description": "Base directory to resolve non-relative module names.", "type": "string" }, "paths": { "description": "Specify path mapping to be computed relative to baseUrl option.", "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string", "description": "Path mapping to be computed relative to baseUrl option." } } }, "rootDirs": { "description": "Specify list of root directories to be used when resolving modules.", "type": "array", ... },3. TypeScript Basic Types
TypeScriptで定義した基本データタイプ
User Defined Typesも、基本データタイプからの拡張superset (ECMAScript Standard)
- boolean, number, string, null, undefined
- array (object, non-primitive)
- symbol (ecma6)
- 固有で修正不可能なデータとしてアサイン
- primitive値を指定
Subtypes
- undefined & nullは、すべてのタイプに対してのサブタイプ
- すべてのタイプに、undefinedとnullはアサインできる。
- しかし、compileOptionで,--strictNullChecks
を使うと、void
か、自分自身にだけアサインでできるようになる。
- その場合は、union typeで指定しなきゃいけない。
- ex :let union: string | null | undefined = 'str'
Additional Type
- void
- タイプがない、空の概念
- 関数のリターンタイプくらいで使う(リターンする値がない時)
- any
- 何のタイプにもなれる
- Anyは非推奨、TypeScriptを使う意味が薄れる。
- compileOptionで、エラーになるように指定も可能(
noImplicitAny
)- never
- 結果を返さないため、タイプを持たない。
- あんまり使うところがないが、関数のリターンタイプくらいで使う
- infinitely loop function
- absolutely throw Error
- absolutely return error('message')
- enum
- 列挙型(他の言語と同じ)
- 複数の変数に対して、一連の定数値をアサイン
enum Color {Red, Green, Blue}; let c: Color = Color.Red; let colorName: string = Color[c];
- tuple (object, non-privitive)
- 複合タイプを持つ配列
let x: [string, number] =['hello', 10];
- 値を持って使うときに、どういうタイプかチェックしない限りわからないため、使うのに注意は必要
- Union Type
- タイプの共用体
let someVar: string | number | boolean = false
Type Assigned by literal
- Literal値で、タイプを定義するのも可能。指定したLiteralのみ設定可能
let someVar: "a" | 5 | false = 5
Generic
パートで記述するkeyof (Indexed Type Query)
演算子の理解とつながる。4. var, let, const
VS var, let, const
- var
- ES5
- variable scope : function
- hoisting : O
- re-difinition : O
- let, const
- ES6
- variable scope : block
- hoisting : X
- re-difinitio : X
- varより、let, const推奨
- コード分析が直感的になる
- letとconstのタイプ推論
let a: string = "str"; //明示的string type
let b = "str"; //タイプ推論によるstring type
const c: string = "str"; //明示的string type
const d = "str"; //タイプ推論によるLiteral type
5. Type Assertion
- とある変数を参照する時、タイプを明示的に絞ること
- type castingとは違い、データを変換したりしない
- とある変数を、指定タイプであることを前提に使うという宣言
- もし使う場合があるなら、宣言に対しての信頼性が大事
- 使い方
- someVar as TYPE
- <TYPE>someVar (jsxと紛らわしいので、非推奨)
// 主に曖昧なタイプから絞るときに使う。 // 曖昧なタイプである時点で、ベストプラクティスではないので、参考までに見ることs let someVar: any = "some string"; let strLength: number =(someVar as string).length;6. Type Alias
特定のタイプに別称をつけて使うことができる。
あくまで、作られたタイプの参照を持つだけで、タイプを作ることではない。//alias to union type let varA: string | number = 0; varA = "A"; type StringOrNumber = string | number; let varB = 0; varB = "B";7. Interface
実装を持たず、ステート(プロパティ)とビヘイビアの形式の定義のみを記述した抽象データタイプ。
- インタフェースの抽象というのは、インスタンス化観点から抽象的であり具体を持たいないため、単体では実態を持てないという意味を内包している。
- 継承するクラスたちに対してのプロトコル(約束)の役目を果たす。interface basics
//interface interface Person { name: string; //--optional type age?: number; //-- function interface say(): string; } const person:Person = { name: "Mark", age: 34, say: (): string { return `hello. name=${this.name}`; } }interface - indexable type
indexのタイプとしては、stringか、numberを指定可能
interface Person { //--indexable type (number or string) [index: string]: string; } const person:Person = { name: "Mark" } person.age = "34"; person["age"] = "34"; //person.age = 34; //index type errorinterface NumIndex { //--indexable type (number or string) [index: number]: object; } const queue:NumIndex = {} queue[0] = new Object(); queue[1] = new Object(); //queue["abc"] = new Object(); //index type error //queue.abc = new Object(); //index type errorclass implements interface
interface IPerson { name: string; age?: number; say(): void; } class Person implements IPerson { name: string; constructor(name: string) { this.name = name; } say(): void { console.log(`hello. myname is ${this.name}.`); } } const person = new Person("Mark"); person.say();interface extends interface
intrface Person { name: string; age: number; } interface SalaryMan extends Person { job: string; }function with interface
interface funcPerson { (name: string, age?: number): void; } const sayPerson: funcPerson = function (name: string) { console.log(`hello. myname is ${name}.`); } sayPerson("Mark");8. Class
オブジェクトの初期ステート(プロパティ)とビヘイビアを記述したテンプレートであり、User Defined Data Type.
class basics
class Person { protected _name: string = null; private _age: number = null; set _age; } class SalaryMan extends Person { private _job: string = null; constructor(name: string) { super(); this.name = name; } } const man: SalaryMan = new SalaryMan("Mark");Abstract class
abstract class APerson { protected _name: string = "NoName"; abstract setName(name: string): void; } class Person extends APerson { setName(name: string): void { this._name = name; } } const person = new Person();readonly keyword & static keyword & private constructor
※ typescriptでは、anti-partternだという意見もあり
class Logger { private static singletonInstance: Logger; public readonly initTime: number; private constructor(){ this.initTime = new Date().getTime(); } public static getLogger = ():Logger => { if (Logger.singletonInstance === undefined) { Logger.singletonInstance = new Logger(); } return Logger.singletonInstance; } logInfo = (msg: string):void => { console.log(`logger-${this.initTime} : ${msg}`); } } Logger.getLogger().logInfo("first log"); setTimeout( (): void => { Logger.getLogger().logInfo("after 2sec log"); }, 2000 ); //Logger.getLogger().initTIme = 5; //error because readonly property //--output //logger-1590906992695 : first log //logger-1590906992695 : after 2sec log9. Generic
パラメータのデータタイプを、インスタンス化の後で明示するプログラミング手法(to-be-specified-later)
入力・出力のデータタイプを、任意のタイプに抽象化宣言する。
タイプチェックは、ランタイムの前にコンパイラでしてくれるが、内部動作メカニズムとしては、実際にどういうデータタイプで入力・出力されるかは、インスタンス化後、実際に呼び出されるときに確定される。Generic basics
function doPingPong<T>(message: T): T { return message; } console.log(doPingPong<string>("text")); console.log(doPingPong<number>(10)); console.log(doPingPong<object>({key: "value"}));Generic with class
class Code<T extends string | number, O> { private _code: T; private _data: O; constructor(code: T, data: O) { this._code = code; this._data = data; } getCode = (): T => { return this._code; } getData = (): O => { return this._data; } } const abc = new Code<string,string>("ABC", "data"); const oneTwoThree = new Code<number, object>(123, {});10. keyof -Lookup Types-
keyof
basics
- Indexed Type Lookup Query 演算子
- オブジェクトで、アクセスが許容されているプロパティのインデックスを
Literal Type
として算出する。Generic
と一緒に使うと有用//-- indexed type query from Interface interface IObj { keyA: number; keyB: number; } type restrictedAsKeysOfInterface = keyof IObj; let v1: restrictedAsKeysOfInterface = "keyA"; //same >> let v1: "keyA" | "keyB" = "keyA"; // let v2: restrictedAsKeysOfInterface = "key?"; // error //-- indexed type query from Object(typeof) const obj = {keyA : 0, keyB : 1}; type restrictedAsKeysFromObject = keyof typeof obj; //same >> let v1: "keyA" | "keyB" = "keyA"; // let v2: restrictedAsKeysOfInterface = "key?"; // errorGeneric with
keyof
- ランタイム前に、間違ったプロパティアクセスなどが検出できる。
function getProperty<T, K extends keyof T>(obj:T, key:K) { return obj[key]; } interface Person { name: string; age: number; } const person: Person = { name: "Mark", age: 35 } getProperty(person, "name"); getProperty(person, "age"); //getProperty(person, "unknown"); //unasignable type error11. Iterator
今までの巡回
Array巡回
// es3 for (var i = 0; i < array.length; i++) // es5 array.forEach() //breakができないので、anti pattern // es6 for (const item of array) // arrayのみ使えるObject巡回
// -- for in // 推奨されない。理由は以下 // - hello worldobject巡回時に使う。(arrayには柄はないはず) // - indexがnumberじゃなくstringで出る // - 配列内のプロパティも意図とは違って巡回できる可能性がある // - prototype chainのプロパティを巡回できる可能性もある // - 巡回の順序を保証しない // for ofが推奨される。 // -- objectを巡回するときには、for ofで以下のように使うことも可能 for (const prop of Object.keys(obj))example
const array = ['first', 'second']; const obj = { name: 'Mark', age: 35 }; // use for..of on Array for (const item of array) { console.log(typeof item + ', ' + item); } // use for..in on Array // item type is string. value is numeric string for (const item in array) { console.log(typeof item + ', ' + item); } // use for..of on Object => Error /* for (const item of obj) { console.log(typeof item + ', ' + item); } */ // use for..in on Object for (const item in obj) { console.log(typeof item + ', ' + item); } // use for..on using keys on Object for (const item of Object.keys(obj)) { console.log(typeof item + ', ' + item); }Symbol.iterator
概要
- プロパティ。巡回関数が具現されているとiterableなタイプになる。
- Array, Map, Set, String, Int32Array, Uint32Arrayなどには、内蔵された具現体があるので、iterableなタイプである。
- ただのobjectはiterableではない。
- Iteratorを使い、IterableなオブジェクトのSymbol.iterator関数を呼び出す。
target
- es3 or es5
- Arrayのみfor..ofを使える
- オブジェクトに使うとエラー
- es6
- SYmbol.iteratorを具現すると、どんなオブジェクトにもfor..ofを使える
typescriptのIteratorインタフェース
// lib.es6.d.ts interface IteratorResult<T> { done: boolean; value: T; } interface Iterator<T> { next(value?: any): IteratorResult<T>; return?(value?: any): IteratorResult<T>; throw?(e?: any): IteratorResult<T>; } interface Iterable<T> { [Symbol.iterator](): Iterator<T>; } interface IterableIterator<T> extends Iterator<T> { [Symbol.iterator](): IterableIterator<T>; }Iterable具現
class CustomIterable implements Iterable<string> { private _array: Array<string> = ['first', 'second']; [Symbol.iterator]() { var nextIndex = 0; return { next: () => { return { value: this._array[nextIndex++], done: nextIndex > this._array.length } } } } } const cIterable = new CustomIterable(); for (const item of cIterable) { console.log(item); } //[LOG]: first //[LOG]: second12. Decorator
- Decoratorを使うためには、config設定必要
- 各Decoratorパターンに対するシグニチャーを見ておくこと
Setting
$ mkdir ts-decorator $ cd ts-decorator $ yarn init -y $ yarn add typescript -D # setting tsconfig $ node_modules/.bin/tsc --init -- tsconfig.jsonのexperimentalDecoratorsをtrueに設定Class Decorator Example
function hello(constructFn: Function) { console.log(constructFn); } function helloFactory(show: boolean) { if (show) { return hello; } else { return null; } } @helloFactory(false) class Person {} @helloFactory(true) class Person2 {} //--output //$ node dist/Test.js //[Function: Person2]function editable(canBeEdit: boolean) { return function(target: any, propName: string, description: PropertyDescriptor) { console.log(canBeEdit); console.log(target); console.log(propName); console.log(description); description.writable = canBeEdit; } } class Person { constructor() { console.log('new Person()'); } @editable(true) hello() { console.log('hello'); } } const person = new Person(); person.hello(); person.hello = function() { console.log('world'); } person.hello();function addHello(constructorFn: Function) { constructorFn.prototype.hello = function() { console.log('hello'); } } @addHello class Person { constructor() { console.log('new Person()'); } } const person = new Person(); (<any>person).hello(); //使い方に短所があるが、ライブラリやフレームワークなどの開発にはいいパターンかも //--output //$ node dist/Test.js //helloMethod Decorator Example
function editable(canBeEdit: boolean) { return function(target: any, propName: string, description: PropertyDescriptor) { console.log(canBeEdit); console.log(target); console.log(propName); console.log(description); //descriptorのwritable属性がランタイム時に変わる description.writable = canBeEdit; } } class Person { constructor() { console.log('new Person()'); } @editable(true) hello() { console.log('hello'); } } const person = new Person(); person.hello(); // editableをtrueにしていたので、上書きされる。 // ※ editableをfalseにした場合は上書きされない。 person.hello = function() { console.log('world'); } person.hello(); // --output // true // Person {} // hello // { // value: [Function: hello], // writable: true, // enumerable: false, // configurable: true // } // new Person() // hello // world // ※上書きされた関数の結果。editableがfalseなら、結果は「hello」Property Decorator
function writable(canBeWrite: boolean) { return function(target: any, propName: string): any { console.log(canBeWrite); console.log(target); console.log(propName); return { writable: canBeWrite } } } class Person { @writable(false) name: string = 'Mark'; constructor() { console.log('new Person()'); } } const person = new Person(); console.log(person.name); // --output //TypeError: Cannot assign to read only property 'name' of object '#<Person>' //※ writable(true)にすると、エラーなく動作するParameter Decorator
function printInfo(target: any, methodName: string, paramIndex: number) { console.log(target); console.log(methodName); console.log(paramIndex); } class Person { private _name: string; private _age: number; constructor(name: string, @printInfo age: number) { this._name = name; this._age = age; } hello(@printInfo message: string) { console.log(message); } } //-- output // Person { hello: [Function] } // hello // 0 // [Function: Person] // undefined // 113. Type Inference
- タイプを明示しなかった場合のタイプ推論規則
- letは、基本データ・タイプで推論
- constはリタラル・タイプで推論
- objectタイプを使わないと、プロパティはletと同じように推論
- const person = {name:'Mark', age: 35}
- person => {name: string; age: number;}で推論される
- 大体は推論自体は簡単
- 単純な変数
- structuring, destructuring
- array, 関数のリターンに対しては推論が難しい場合が多い
ArrayのType Inference
const array1 = []; // any[] const array2 = ['a', 'b', 'c']; // string[] const array3 = ['a', 1, false]; // (string|number|boolean)[] ※ inferenced as union type class Animal { name: string; } class Dog extends Animal { dog: string; } class Cat extends Animal { cat: string; } const array4 = [new Dog(), new Cat()]; // (Dog | Cat)[]returnのType Inference
function hello(message: string | number) { if (message === 'world') { return 'world'; } else { return 0; } } // return type inference => ('world' | 0) // literal unionUnion TypeとType Guard
interface Person { name: string; age: number; } interface Car { brand: string; wheel: number; } //Type Guard //tell this is Person when return function isPerson(arg: any): arg is Person { return arg.name !== undefined; } function hello(arg: Person | Car) { if (isPerson(arg)) { //this is Person. not Car. console.log(arg.name); // console.log(arg.brand); //error } else { //this is Not Person. so this is Car. // console.log(arg.name); //error console.log(arg.brand); } }
- 投稿日:2020-07-26T20:39:59+09:00
textareaの文字を色分けする(しているように見せる)方法を考えてみた
codepenやGASのエディタでは、入力された文字は次々と色分けされていきます。本記事ではそんな機能を、つまり入力された文字をそれぞれ色分けする機能を、JavaScriptのライブラリを駆使して「簡単に」再現してみようと思います。…再現と書きましたが、原理は根本的に異なるものであるということはあしからず。
必要なもの
- highlight.js (JavaScriptライブラリ)
- JavaScript/CSSについての最低限の知識
0. はじめに知っておくべきこと
ご存知の方もいらっしゃるかもしれませんが、textarea内の文字をそれぞれ別の色に変えることはできません(textareaの文字色を赤にしてSVGフィルターをかけたspanを各文字の位置に重ねるという泥臭い方法はありますが、機能性も低くハイコスト・ローリターンです)。
ではどうするのか……以下のようにすればよいのです。
- textareaの上に「textareaではないがtextareaと同じスタイル」の要素Aを被せる
- textareaの文字色を透明にする
- textarea内の文章を要素Aに連動させる
- 要素A内に収められた文章をハイライト用ライブラリ(highlight.js)でじっくり料理する
1. 要素Aを作る
ここでいう要素Aは、highlight.jsを適用できるような要素でなければなりません。つまり
sample.html<html> <head> <link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/dracula.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-dark.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script> </head> <body> <!--以降のHTMLの例はここから--> <div class="twrap"> <textarea></textarea> <pre><!--要素A--> <code class="css"></code> </pre> </div> <!--ここまでのみ表示--> <script src="script.js"></script> </body> </html>こんな具合です。
2. 要素Aとtextareaの見た目を合わせる
まず我々には二つの道があります。
- textareaをスクロールさせる(textareaの高さを固定する)
- textareaをスクロールさせない(textareaの高さを可変にする)
本記事では「textareaをスクロールさせない」を選びます。
Tips あるいは失敗談
textareaをスクロールさせる方が見栄えは良いかもしれません。textareaのscroll量を要素Aに連動させるのは簡単ですし、以降の説明にある作業の多くが不要になりますからね。ですが、そのページがiPhoneからの閲覧を想定しているものならば、やめた方が良いです。iPhoneの慣性スクロールにおいては、上端あるいは下端を越えてその要素を引っ張ることができますが、そのうち上端を越える場合、JavaScript側ではscrollイベントが更新されず、そのscroll量はマイナスになってくれません。その間、textareaと要素Aの位置関係に齟齬が生まれてしまいます。まずはtextareaと要素Aのスタイルを合致させましょう。
style.css.twrap textarea { -webkit-appearance: none; border: 1px solid #aaa; border-radius: 0; box-sizing: border-box; font-size: .8rem; height: 100%; margin: 0; padding: 0 .25em; resize: none; } .twrap pre { margin: 0; padding: 0; pointer-events: none;/*要素Aを触れなくする*/ } .twrap pre code { border: 1px solid transparent; box-sizing: border-box; font-size: .8rem; height: 100%; margin: 0; padding: 0 .25em; white-space: pre-wrap; word-break: break-all; }3. textareaと要素Aを重ねる
一度textareaと要素Aの位置関係を確認してみましょう。
sample.html<div class="twrap"> <textarea></textarea> <pre><!--要素A--> <code class="css"></code> </pre> </div>この場合、
.twrap
にposition: relative;
を、要素Aにposition: absolute;
を設定しましょう。style.css.twrap { position: relative;/*new*/ } .twrap textarea { -webkit-appearance: none; border: 1px solid #aaa; border-radius: 0; box-sizing: border-box; font-size: .8rem; margin: 0; padding: 0 .25em; resize: none; } .twrap pre { left: 0;/*new*/ margin: 0; padding: 0; pointer-events: none; position: absolute;/*new*/ top: 0;/*new*/ } .twrap pre code { border: 1px solid transparent; box-sizing: border-box; font-size: .8rem; margin: 0; padding: 0 .25em; white-space: pre-wrap; word-break: break-all; }4. textarea内の文章を要素Aに連動させる&要素Aをハイライト
script.jsvar textarea = document.querySelector(".twrap textarea"); var dummy = document.querySelector(".twrap pre code"); textarea.oninput = function() { dummy.innerText = textarea.value + "\u200b";//textareaの値の最後が改行コードだった場合に対応するためのゼロ幅スペース hljs.highlightBlock(dummy); }5. textareaと要素Aをリサイズする
これをしないと、textareaと要素Aそれぞれの文字の位置がズレます。
Googleで検索すると、textareaのリサイズについての記事が幾つか散見されます。offsetHeightとscrollHeightの違いなどをもとにwhileループで最適の大きさを求めるものなどです。スマートですね。ですが本記事の場合、もっとコストの低い良いものがあるので、それらは使いません。
ではどうするのか……要素Aのありのままの大きさを測り、それを
.twrap
にheight
として反映すれば良いのです。そしてtextareaと要素Aにheight: 100%;
をかけておけば、それらの大きさは.twrap
の大きさに依存するようになります。下準備:スタイルを合わせる
style.css.twrap { position: relative; } .twrap textarea { -webkit-appearance: none; border: 1px solid #aaa; border-radius: 0; box-sizing: border-box; font-size: .8rem; height: 100%;/*new*/ margin: 0; padding: 0 .25em; resize: none; } .twrap pre { height: 100%;/*new*/ left: 0; margin: 0; padding: 0; pointer-events: none; position: absolute; top: 0; } .twrap pre code { border: 1px solid transparent; box-sizing: border-box; font-size: .8rem; height: 100%;/*new*/ margin: 0; padding: 0 .25em; white-space: pre-wrap; word-break: break-all; } .twrap pre code.resizing { height: unset;/*new*/ }リサイズ用のコードを入れる
script.jsvar twrap = document.querySelector(".twrap"); var textarea = twrap.querySelector("textarea"); var dummy = twrap.querySelector("pre code"); resizeTA(); textarea.oninput = function() { dummy.innerText = textarea.value + "\u200b";//textareaの値の最後が改行コードだった場合に対応するためのゼロ幅スペース hljs.highlightBlock(dummy); resizeTA(); } function resizeTA() { dummy.classList.add("resizing");//ありのままの大きさに戻す twrap.style.height = (dummy.scrollHeight + 20) + "px";//念の為に20pxほどマージンを取っている dummy.classList.remove("resizing"); }6. textareaの文字色と要素Aの背景色を透明にし、highlight.jsでfont-weight: bold;にされたspanをnormalにする
Tips あるいは失敗談
本記事ではtextareaと要素Aのフォントをmonospace系にしていますが、これは等幅フォントの利便性を鑑みた結果です。monospace系は文字サイズさえ同じならば、如何な文字も、あるいは太字やイタリック体であろうと、同じ幅になるためです。いえ、そうなる予定でした。しかしiPhoneの場合、太字にすると幅が少しだけ大きくなります。PCやAndroidだと同じ幅なのに…。そんなわけで、太字も無理やり通常の太さに戻してしまっているので、必ずしもmonospace系にこだわる必要はないでしょう。style.css.twrap { position: relative; } .twrap textarea { -webkit-appearance: none; border: 1px solid #aaa; border-radius: 0; box-sizing: border-box; caret-color: #000;/*new*/ color: transparent;/*new*/ font-family: monospace;/*new*/ font-size: .8rem; height: 100%; margin: 0; padding: 0 .25em; resize: none; } .twrap pre { height: 100%; left: 0; margin: 0; padding: 0; pointer-events: none; position: absolute; top: 0; } .twrap pre code { background: transparent;/*new*/ border: 1px solid transparent; box-sizing: border-box; font-family: monospace;/*new*/ font-size: .8rem; height: 100%; margin: 0; padding: 0 .25em; white-space: pre-wrap; word-break: break-all; } .twrap pre code.resizing { height: unset; } .twrap pre code span { font-weight: normal; }あとは各要素の大きさに影響を与えない範囲でデザインすれば完成です。
おわりに
iOSを開発されている方々に一言物申したいですね。iOSには面白い機能がたくさんあり、彼らの独創性・発想力、目を見張るものがあると思います。利便性を謳いたくなるのも分かります。ですがその前に、計画性と協調路線の存在にしっかりと目を向けてもらいたいですね。ここまでで挙げた通り、iOSのせいで余計な回り道をさせられています。この例に限った話ではないです。独自路線を突っ走り過ぎると嫌われますよ。
サンプル
- 投稿日:2020-07-26T20:17:44+09:00
【スクレイピングまとめ】| Python Node.js PHP Ruby Go VBA | 6種類の言語でヤフートップをスクレイピング
Python
動画
リポジトリ
https://github.com/yuzuru-program/scraping-python-yahoo
ソース
index.pyimport urllib.request as request from bs4 import BeautifulSoup req = request.Request( "https://www.yahoo.co.jp", None, {} ) instance = request.urlopen(req) soup = BeautifulSoup(instance, "html.parser") li = soup.select('main article section ul')[0].select('li') for m in li: print(m.text) print(m.select("a")[0].get("href")) print()Node.js
動画
リポジトリ
https://github.com/yuzuru-program/scraping-node-yahoo
ソース
package.json{ "dependencies": { "cheerio": "^1.0.0-rc.3", "node-fetch": "^2.6.0" } }index.jsconst fetch = require('node-fetch'); const cheerio = require('cheerio'); const main = async () => { // https://www.yahoo.co.jp/にリクエスト投げる const _ret = await fetch('https://www.yahoo.co.jp/', { method: 'get', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36', }, referrer: '', }).catch((err) => { console.log(err); }); if (_ret.status !== 200) { console.log(`error status:${_ret.status}`); return; } // jqueryチックに使えるように変換 const $ = cheerio.load(await _ret.text()); const _li = $('main article section ul').eq(0).find('li'); // ヤフートップニュースを表示 _li.map(function (i) { console.log(_li.eq(i).text()); console.log(_li.eq(i).find('a').attr()['href']); console.log(); }); }; main();PHP
動画
リポジトリ
https://github.com/yuzuru-program/scraping-php-yahoo
ソース
index.php<?php require_once './phpQuery-onefile.php'; function my_curl($url) { $cp = curl_init(); /*オプション:リダイレクトされたらリダイレクト先のページを取得する*/ curl_setopt($cp, CURLOPT_RETURNTRANSFER, 1); /*オプション:URLを指定する*/ curl_setopt($cp, CURLOPT_URL, $url); /*オプション:タイムアウト時間を指定する*/ curl_setopt($cp, CURLOPT_TIMEOUT, 30); /*オプション:ユーザーエージェントを指定する*/ curl_setopt($cp, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'); $data = curl_exec($cp); curl_close($cp); return $data; } $url = 'https://www.yahoo.co.jp'; $doc = phpQuery::newDocument(my_curl($url)); $ul = $doc->find('main article section')->find("ul:eq(0)"); for ($i = 0; $i < count($ul->find("li")); ++$i) { $li = $ul->find("li:eq($i)"); echo $li[0]->text(); echo "\n"; echo $li[0]->find("a")->attr('href').PHP_EOL; echo "\n"; } ?>phpQuery-onefile.php
https://github.com/yuzuru-program/scraping-php-yahoo/blob/master/phpQuery-onefile.php
Ruby
動画
リポジトリ
https://github.com/yuzuru-program/scraping-ruby-yahoo
ソース
index.rbrequire "nokogiri" require "open-uri" doc = Nokogiri::HTML(open("https://www.yahoo.co.jp")) test = doc.css("main article section ul")[0].css("li") test.each do |li| puts li.content puts li.css("a")[0][:href] puts endGo
動画
リポジトリ
https://github.com/yuzuru-program/scraping-go-yahoo
ソース
index.gopackage main import ( "fmt" "log" "net/http" "github.com/PuerkitoBio/goquery" ) func main() { req, _ := http.NewRequest("GET", "http://yahoo.co.jp", nil) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36") res, _ := new(http.Client).Do(req) if res.StatusCode != 200 { log.Fatalf("status code error: %d %s\n", res.StatusCode, res.Status) } doc, err := goquery.NewDocumentFromReader(res.Body) if err != nil { log.Println(err) } li := doc.Find("main article section ul").Eq(0).Find("li") li.Each(func(index int, s *goquery.Selection) { fmt.Println(s.Text()) tmp, err := s.Find("a").Attr("href") if err != true { log.Fatal(err) } fmt.Println(tmp + "\n") }) }VBA
動画
ソース
'Microsoft HTML Object Library 'Microsoft Internet Controls ' IEのプロセスを削除する関数 Function IeProcessKill() CreateObject("WScript.Shell").Exec ("taskkill.exe /F /IM iexplore.exe") Application.Wait Now + TimeValue("0:00:2") End Function 'ヤフートップスクレイピング Sub main() Dim ie As InternetExplorer ' IEプロセスを削除' Call IeProcessKill 'IE起動 Set ie = New InternetExplorer 'サイトを非表示 ie.Visible = False Debug.Print "読み込み中..." Debug.Print 'ヤフー ie.Navigate "https://www.yahoo.co.jp/" Do While ie.Busy = True Or ie.readyState < READYSTATE_COMPLETE Loop For Each tmp In ie.document.querySelector("main article section ul").getElementsByTagName("li") Debug.Print Trim(tmp.textContent) Debug.Print tmp.getElementsByTagName("a")(0).href Debug.Print Next tmp ' ブラウザ閉じる ie.Quit Set ie = Nothing End Sub
- 投稿日:2020-07-26T19:35:04+09:00
4連休だったからmarkdownに時間を追加してプレビューするvscode extension作った
感謝
https://github.com/showdownjs/showdown
上記repositoryをcloneして中身を編集して使いました
テストは高速、buildもタスクで一発だった。
素晴らしいリポジトリだったhttps://github.com/kajirikajiri/showdown
オプションとか、正規表現とか追加したバージョン概要
extension
https://marketplace.visualstudio.com/items?itemName=kajiri.kajiri
使い方
h1~5タグに{{number}}を追加します
numberは時間ですあとはextensionを実行するとプレビューが見れます(kajiri start)
github
https://github.com/kajirikajiri/vscode-extension
感想
タスクを管理できるものが欲しかった
trelloとかasanaほど高機能さは求めていなかった
plantumlのgantで管理しようかと思ったけど、betaだからどうなるかわからない感じだった
考えた結果、markdownでガントチャートみたいなものを作った
正規表現でh1~5タグの後ろの{{number}}を取得しています
あとはフロントでうまいことやってますgruntは知ってたけど。使い方知らなかった。お手軽だった。タスクランナー
他人のコードを読むのは勉強になったextensionはTypeScriptで書いた
最近TypeScript使ってるけど、vscodeの補完が便利になるし、すぐエラー出力してくれていい。
最近はobsidianをメモ帳に使ってます
https://obsidian.md/実行コマンドがkajiri startになってしまった
- 投稿日:2020-07-26T19:19:49+09:00
【JavaScript】厳密等価演算子と等価演算子
- 投稿日:2020-07-26T18:57:33+09:00
【JavaScript】非同期処理を操作する【ES6】
失敗例
const TakeRest = (ms) => { setTimeout(() => { console.log("休憩終わり"); }, ms); }; const active = () => { console.log("休憩開始"); TakeRest(2000); console.log("勉強する"); }; active();変数名がお粗末なのはスルーしてください。?♂️
上記のコードを実行すると「休憩開始」→「勉強する」→「休憩終わり」の順番で
console
に吐き出されます。これは非同期処理によるものです。これを理想どうり(「休憩開始」→「休憩終わり」→「勉強する」)にしたい。非同期処理のプロセスを待機させる「Promise構文」
const TakeRest = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const active = () => { console.log("休憩開始"); TakeRest(2000).then(() => { console.log("休憩終わり"); console.log("勉強する"); }); }; active();こうすることで、「休憩開始」→「休憩終わり」→「勉強終わり」という順番で同期的に動かすことができます。
解説
Promise
クラスオブジェクトの中でresolve
を含んだ非同期の関数を定義。setTimeout(resolve, ms)
の部分で、ms後resolve
を発火させます。- この
resolve
が発火した場合、then
の関数が実行されます。ちなみに、これはシンプルなコードです。実際には
reject
があったり、それに準ずるcatch
があったりします。await
というシンタックスシュガーを使ったものもあるので気になる方は、ググった方がよき。
- 投稿日:2020-07-26T17:44:13+09:00
作って理解JavaScript:JOKE開発記その7 - オブジェクトとクラス
今回のスコープ
前回から一か月近く経ってる。
今回の実装内容自体が手間のかかるものであったという理由もありますが主な理由はゲームなど他のことをしていたためです(ぉぃさて、前回のあとがきでも書いたように制御構造も一通りできて次は配列かと思ったのですが、配列はオブジェクトとクラスの仕組みを作ってからの方が作りやすいだろうと思ったため、今回の内容はオブジェクトとクラスです。具体的には以下の内容です。
- ステップ9:オブジェクト
- まずは連想配列的な意味でのオブジェクト
- Shorthand property namesやスプレッドもサポート
- 次にメソッド。というかthisの扱い
- ステップ10:クラス
- 昔ながらのprototype。合わせてnew
- 今どきのclass
JOKE実装で使っちゃったから継承もサポート- Arrayで使う予定なのでgetter/setterもサポート
ステップ9:オブジェクト
ステップ9段階のコードは以下にあります。
https://github.com/junjis0203/joke/tree/step0009オブジェクトリテラル
プログラム内でオブジェクトが定義できないと話が始まらないのでまずはオブジェクトリテラルです。
http://www.ecma-international.org/ecma-262/6.0/#sec-object-initializerオブジェクトの作成は以下のように行っています。
- 空のオブジェクトを作成しスタックにプッシュ(
MAKE_OBJECT
)- 名前を
PUSH
- 値を
PUSH
- オブジェクト、名前、値をスタックからポップしプロパティとして設定(
PROP_DEFINE
1)- 2.~4.をリテラルに書かれてるプロパティ分繰り返す
Assemblerの該当部分(後述するスプレッドの処理も含みます)
Vmの該当部分Shorthand property names
ES2015で導入されたShorthand property names、こういうやつですね。
const foo = 123; const bar = 'abc'; const obj = {foo, bar};構文解析的に
Identifier
の後ろに:
がなければShorthand property namesとみなすようにしました。
また、「展開」の処理はParser
内でしてしまうことでAssembler
やVm
は変更なしで動作するようにしました。ちなみにComputed property namesも対応はできますがJOKE実装では使ってないしサポートしてません
スプレッド
スプレッドの仕様見当たらないな(。´・ω・)?
とよく考えたらオブジェクトのスプレッドはES2018からでした。まあでも便利なので(JOKE実装で使ってるので)こちらはサポートします。
https://www.ecma-international.org/ecma-262/9.0/index.html#sec-object-initializerスプレッドで問題となるのは「プロパティの数がアセンブル時点でわからない」ということです。
初めに作成したオブジェクトリテラル実装では「プロパティの数をMAKE_OBJECT
の引数にし、VM実行時にスタックから引数分の名前と値を取り出す」ということをしており、スプレッドをなんとかこの仕組みに乗せれないかいろいろ試行錯誤したのですが2、結局「専用のCOPY_OBJECT命令を作った方が実装がシンプルになる」ということで既存部分を修正しました。
一度作ったものをなんとか使いまわそうとして変な実装になるよりかはそもそもの実装を変えてしまった方がいいという教訓を得ました。3プロパティへの代入
プロパティの参照はステップ1で作っているので飛ばして代入処理です。
「変数への代入」と「オブジェクトプロパティへの代入」で処理を分けたくなかったので「変数とはスコープオブジェクトのプロパティである」のような一般化をしようと思いましたがめんどくさいのでやめました(プロパティへの代入を行うMEMBER_ASSIGN命令4を作りました)。後述のsetterのことを考えるとちゃんと分けてよかったのかなと思います。余談ですがPythonやRubyは仕組みは違うもののどちらも「プロパティへの代入」はメソッド呼び出しとして行われます。いつものように「PythonやRubyってどうしてるっけ?」と参考にしようとしたら参考になりませんでした(笑)
this
さて
this
です。JavaScriptのthis
はややこしいことで有名ですがなんちゃって処理系なので「複雑な使い方」は当面サポートしない方向でいきます。つまり、
- オブジェクトからメソッドを取り出して関数的に呼ぶのはサポートしない
- Object.bindもサポートしない
- アロー関数でのthisバインドもサポートしない
- すなわち環境からthisを決定するのはサポートしない
this
に関する仕様(動作)はここら辺が参考になります。
http://www.ecma-international.org/ecma-262/6.0/#sec-function-calls仕様を正確に実装していませんが(ぉぃ、以下のように実装を行い
this
を実現しました。この処理はステップ10でもう少し改造が入ります。
- setProperty時に
value
がオブジェクトの場合はvalue
にthisValue
を設定する(関数はオブジェクトで定義されているのでthisValue
が設定される)- callFunctionでオブジェクトに設定されてるthisValueを取り出し
context
に設定、呼び出すthis
に対応する命令のTHISでは単にcontext
に設定されているthisValue
を取り出す(スタックにプッシュする)ステップ10:クラス
オブジェクトと関連が深いからということでクラスについてもまとめて説明します。JavaScriptの場合、「クラス」というと話は長くなりますがES2015で定義された
class
のこととします。
と言いつつ、実体は・・・なのですが、そもそもJOKEを作ってみようと思った因縁の機能(?)ですね。ステップ10段階のコードは以下にあります。
https://github.com/junjis0203/joke/tree/step0010prototype形式とnew
V8などの内部実装がどうなってるのかはわからないですが、とりあえずJOKEでは
class
で定義されたクラスもprototype形式に変換して動かすことにしました。
ところでVSCodeだとprototype形式で書いているとこんなことが言われます。最近のエンジンは「クラス形式」に変換されてるっぽいですね。ステップ10の前準備として「オブジェクト」に対応する
JokeObject
5を継承した(このために継承も実装することになりました)JokeFunctionを定義し、prototype
を初めから持たせておきます。こうしておけば「prototypeにメソッドを設定する」処理はステップ9で作ったプロパティへの代入がそのまま動きます。object.js抜粋export class JokeFunction extends JokeObject { constructor() { super(); this.setProperty('prototype', new JokeObject()); }
new
は「空のオブジェクトを作り、それをthis
としてコンストラクタ関数に渡す(MDNでの説明ページ)」というものなのでその通りに実装しました。そこまで考えていなかったのですが(ぉぃ、ステップ9で作った「contextにthisを入れておく」仕組みがうまく使いまわせました。vm.js抜粋case I.NEW: { const args = setupArguments(); const target = stack.pop(); const newObject = new JokeObject(); newObject.setProperty('constructor', target); callFunction({...context, thisValue: newObject}, target, args); stack.push(newObject); } break;ややこしいのはメソッド(prototypeに設定されているプロパティ)の取得処理です。JavaScriptで書いているため「constructorが設定されてなくても素のconstructorが取得される6」という点、「
this
がprototypeに設定されているのを書き換える必要がある(コメントに書いてあるようにこの実装は気に入ってません)」点などちゃんと動くまで苦労しました。object.js抜粋getProperty(name) { let value; // need because native "constructor" is found if not set if (this.names.includes(name)) { value = this.properties[name]; } if (!value && this.names.includes('constructor')) { const constructor = this.properties['constructor']; value = constructor.searchProperty(name); // change thisValue. any more better way? if (value instanceof JokeObject) { value = { ...value, thisValue: this }; } } return value; }class形式
さて
class
です。class
に関する動作仕様はこちらに書かれています。
http://www.ecma-international.org/ecma-262/6.0/#sec-runtime-semantics-classdefinitionevaluation
超長いし条件分岐もある( ゚Д゚)
というわけで読む気が起こるのも読み解くのも時間かかったわけですが、最終的に以下のように実装しました。仕様準拠ではないですが大体ちゃんと動作します。
- 構文解析の段階で
constructor
を分けておく- アセンブラではまず
constructor
を関数として定義しその後にprototype
にメソッドを設定していく。継承については後で説明します。それと// TODO: cache
とありますが当面効率は無視です(開発記その1の方針参照)こうすれば「prototypeで書いたのと同じこと」になるのでVMの変更はありません。
正確に言うとnew
付けないで関数呼び出しされたら例外投げるとかはあるのですがとりあえず保留です。継承
めんどくさそうなので作る気がなかった継承ですが、実装に使ってしまったので諦めて仕様を読んでみたところおもしろいことがわかりました。
http://www.ecma-international.org/ecma-262/6.0/#sec-class-definitionsClassHeritage : extends LeftHandSideExpressionそう、
extends
の後ろはLeftHandSideExpressionなのでハードコードなクラス名だけではなく変数でも関数呼び出しでも書けるのです!
つまり以下のコードは正しいJavaScriptプログラムです。class Foo {} const cls = Foo; class Bar extends cls {} const func = () => Foo; class Baz extends func() {}まあフレームワークの中ぐらいでしか使わない、というか今のところフレームワークの中でも見たことないですが(笑)
もう一つ文法的な話。
super
をconsole.log
しようとするとSyntaxError
になるの何故なのかと思っていたのですがBNFを見て納得しました。単独にsuper
と書く文法は存在しない(super()
やsuper.method()
しか正しい文法ではない)のですね。
http://www.ecma-international.org/ecma-262/6.0/#sec-super-keywordさて実装ですがAssemblerではスーパークラス(
extends
の後ろにあるもの)を評価しておき、Vmでは単にスタックに着かれているものをsuperClass
プロパティに設定しておきます。
スーパークラスのコンストラクタ呼び出しに対応するSUPER_CALLとスーパークラスのプロパティ参照7に対応するSUPER_PROPERTYでは設定されているsuperClass
を使ってスーパークラスにアクセスします。getter/setter
Array
のlength
は特別処理しようかなと考えていたのですがよく思うとgetter/setterとして一般化できるので実装しました。getter/setterに関する仕様はこちら。要するに
get
やset
を付けたメソッドを作ればいいようです(ちゃんと読み込むとそれだけではない気がする)
http://www.ecma-international.org/ecma-262/6.0/#sec-method-definitions-runtime-semantics-propertydefinitionevaluationVmでは
SET_PROPERTY
時にsetterがあるか、GET_PROPERTY
時にgetterがあるかを調べるようにしました。デバッグ機能
今までは
-d
オプション一つで「構文解析結果」「アセンブル結果」「今実行している命令」を出力していましたが出力が多くなってきたのでそれぞれ別オプションに分けました。
また、「今実行している命令」は追加情報として「今のスタック」を表示するようになりました。クラスまで来ると「動きがおかしいのだけどスタックに積まれてるデータどうなってるの?」ということが多くなってくるので役立っています(今までは都度console.log
を入れていた)あとがき
以上今回はオブジェクトとクラスの実装について説明しました。これでだいぶJavaScriptっぽいプログラムまで対応できるようになったと思います。
・・・「普通の使い方」についてはそこそこ動くと思うのですが「変わった書き方」や「仕様にも書かれている細かな文法エラー・実行時エラー」はあまりサポートできていません。まあそのうち。ともかく次は満を持して配列(メソッド含む)です。
ただその前に、map
などに渡すコードバックはアロー関数で書きたいのでアロー関数を実装する予定です。
ステップ10終わりに名前の見直しを行ったのでHEADでは
DEFINE_PROPERTY
。 ↩
// len is added in runtime
というコメントは試行錯誤してたときの残骸コメントですね・・・ ↩その際に大事なのは既存部分が壊れてないかを確認できるテストですね
↩
HEADでは
SET_PROPERTY
に名前変更。 ↩ステップ1で「とりあえずのオブジェクト表現」を作りそのまま進めてきましたがリファクタリングしました。Jokeと付いているのは素の
Object
と被るためです。 ↩JokeObjectは「JOKEを動かしているJavaScriptエンジン」での「Object(を継承したクラス)のインスタンス」のため、そちらが取得される。 ↩
使うことがあるかはわかりませんが仕様的には「スーパークラスのメソッド呼び出し」だけではなく、「(オーバーライドして
this
ではアクセスできないスーパークラスのプロパティ」を取ることができます。 ↩
- 投稿日:2020-07-26T17:16:27+09:00
Mildomのチャット取得
コメントビューア用。
OAuthが要るようなAPIは使わず、未登録で取得できるコメントを取得。
あくまで執筆時点の情報です。仕様が変わる可能性もあるのであしからず。ユーザのチャンネルURL
https://www.mildom.com/{ユーザID}
- 例:
https://www.mildom.com/10946091
手順
以下にWebSocket接続
wss://jp-room1.mildom.com/?roomId={ユーザID}
メッセージ送受信
send{ /** ユーザID。 */ "userId": 0, /** ユーザレベル。 */ "level": 1, /** ユニークなID。guest766863みたいな。 */ "userName": string, /** ユニークなID */ "guestId": string, /** リファラ */ "nonopara": string, /** ユーザID */ "roomId": number, /** コマンド。ここでは固定 */ "cmd": "enterRoom", "reConnect": 0, "nobleLevel": 0, "avatarDecortaion": 0, "enterroomEffect": 0, "nobleClose": 0, "nobleSeatClose": 0, "reqId": 1 }以降はメッセージが送られてくる。cmdがメッセージ種別。
cmd 内容 enterRoom 最初に受信する。チャットに入ったことを表す。 onBroadcast 規約文のメッセージが入ってる。 onAdd 不明。
他のユーザが入ってきた時のような気もする。onChat コメント
- enterRoom
receive{"admin": 0, "cmd": "enterRoom", "fobiddenGlobal": 0, "forbidden": 0, "reqId": 1, "rst": 0, "type": 2, "userCount": 590}
- onChat
receive{ /** メッセージの種別 */ "cmd": "onChat" /** メッセージ */ "msg": string, /** メッセージID */ "msgId": string, /** ユーザID */ "userId": number, /** アイコン画像のURL */ "userImg": string, /** ユーザ表示名 */ "userName": string, // 省略 }
- 投稿日:2020-07-26T17:10:10+09:00
2020年の11の必見のフロントエンドトレンド
こちらの記事は、Jonathan Saring 氏により2019年12月に公開された『 11 Must-Know FrontEnd Trends for 2020 』の和訳です。
本記事は原著者から許可を得た上で記事を公開しています。2020年の11の必見のフロントエンドトレンド
ランチ中のフロントエンドトークでスマートに見られる方法!
チームのランチトークでスマートに見られることは、最新のフロントエンドのトレンドを常に把握しておくための大きな理由であることは言うまでもない。 それは、あなたがより良い開発者になり、より良い技術とより良い製品を作るのに役にたつかもしれない。 たぶんね。
だから、いくつかの興味深い方向を示すことで、この名誉あるクエストを君が簡単に達成できるように少し時間をもらいたい。 すべてのコンセプトについて1から10まで説明するのではなく、そのコンセプトとそれがどのように有用であるか紹介しよう。最後にはさらなるリソースを書き記した。
例えば、マイクロフロントエンド、アトミックデザイン、Webコンポーネント、TypeScriptの台頭、ESM CDN、さらにはデザイントークンの概要についても簡単に説明している。 スクロールして、詳細を知りたいトピックをチェックしてみてほしい。 質問や提案がある場合は、下部にコメントを残してもらえればと思う。 それではごゆっくり!
1. マイクロフロントエンド
マイクロフロントエンドは、ランチの会話で最も話題になるフロントエンドトピックだ。
皮肉なことに、フロントエンド開発はコンポーネントのモジュール化された利点を享受しているが、それでもバックエンドマイクロサービスよりもはるかにモノリシックだ。
マイクロフロントエンドは、アプリの異なる部分で作業する異なるチームのために、フロントエンドアーキテクチャを異なるフロントエンドに分割するという約束をもたらしてくれる。 各チームは、マイクロフロントエンドのエンドツーエンドのライフサイクル全体で自律性を獲得できる。これらは(例えばBitなどのツールを使用することで)開発、バージョン管理、テスト、ビルド、レンダリング、アップデート、デプロイが独立して可能だ。
ここでコンセプト全体を説明する代わりに、@martcamwlerのブログで公開された@thecamjacksonによるこの素晴らしい投稿を読んでみてほしい 。 この記事は本当にすばらしく、この概念を掘り下げるために必要なすべてをカバーしているはずだ。
しかしながら、今日のエコシステムにはそれでもまだ足りないものがある。 多くの場合、分離されたフロントエンドの展開、バンドル、環境の違いなどの問題に悩まされる。しかし、Bitを使うことで個々のフロントエンド/コンポーネントの分離、バージョン管理、ビルド、テスト、アップデートができるようになる。 現時点では、これは主に複数のアプリケーションを操作するときに役立つ(コンポーネントを介して既存のアプリの一部を徐々にリファクタリングするためにすでに広く使用されているが)。
Bitが2020年にデプロイメントを開始すれば、独立したチームがスタンドアロンのフロントエンドを開発、構成、バージョン管理、デプロイ、アップデートできるようになる。 これにより、UIアプリを一緒に構成し、チームが独立した継続的なデプロイと増分アップグレードを使用して、シンプルな分離されたコードベースを作成できるようになる。 これらのフロントエンドを組み合わせることにより、最終的にアプリケーションができあがる。 Bitを使用して構成されたアプリは次のようになる。
Bitで構成されたUIアプリ
もっと詳しく:
2. アトミックデザイン
参考:アトミックデザインを30秒で理解する!
アトミックデザインはランチトークのもう1つの非常に興味深いトピックで、私はこれを純粋な方法論というよりも原理として考えるようにしている。
簡単に言えば、Brad Frost によって導入されたこの理論は、Webアプリケーションの構成 - 突き詰めると具体的なWebページと、原子(Atom)・分子(Molecule)・有機体(Organism)といった自然界の構成とを比較するものだ。 原子は分子を構成する。(例:テキスト入力「原子」 + ボタン「原子」 + ラベル「原子」=検索「分子」)。 分子は有機体を構成する。 有機体は1つのレイアウトテンプレートに存在しており、これはユーザーに配信される1つのWebページとして具現化される。
これは_ 視覚的な例を用いた30秒でわかる説明_だ。そこには、私が素晴らしい芸術的才能で描いたとても印象的な説明図があるから、どうぞオフィスのホワイトボードに書き写して使ってほしい?
アトミックコンポーネントの利点は、モジュール式の再利用可能なコンポーネントを介したモジュール式のUIアプリケーションを構築できるというだけではない。 このパラダイムに従うと、すべてのコンポーネントの役割とAPI、それらの階層、アプリケーションのビルドプロセスを効果的かつ効率的な方法で抽象化できる方法についてよりよく理解できるように構成を考える必要が出てくる。 次の記事を読んでほしい。
3. カプセル化されたスタイリングとShadow DOM
出典:developer.mozzila.org
コンポーネントの重要な側面はカプセル化だ。これにより、マークアップ構造、スタイル、および動作をページ上の他のコードから分離して、異なる部分が衝突しないようにできる。また、コードをきれいに保つことができる。 Shadow DOM API はこれの重要な部分であり、非表示の分離されたDOMを要素にアタッチする方法を提供してくれる。
今日、Shadow DOMは実際にブラウザーで長い間使用されている。 Shadow DOMを次のように考えることができる。 「DOMの中にあるDOM」 これは、元のDOMから完全に分離された、独自の要素とスタイルを持つ独自の分離DOMツリーだ。
これにより、非表示のDOMツリーが通常のDOMツリーの要素に接続することができるようになる。このShadow DOMツリーは、通常のDOMと同じ方法で、あなたが必要とする要素に接続できるシャドウルートから始まる。 これが主に意味することは、クラスに与える名前空間が必要ないということだ。 つまり名前の衝突やスタイルの流出のリスクがないということだ。 また、他にも利点がある。 これはWebコンポーネントのスタイルの真のカプセル化に対する当分約束されたソリューションであるとしばしば言われている。 もっと詳しく:
4. TypeScriptの台頭
最近、どんな会話でもTypeScriptがフロントエンド開発を支配するかのように言われている。 80%の開発者が次のプロジェクトでTypeScriptを使用または学習したいと認めているという報告がある 。
TypeScriptには欠点もあるが、コードは理解しやすく、実装が高速で、バグが少なく、必要なボイラープレートが少なくて済む。 TSで動作するようにReactアプリをリファクタリングしてみたい? やってみよう。 徐々に始めたい? Bitなどのツールを使って、アプリのコンポーネントを徐々にTSにリファクタリングしてみよう。そしてそれらを React-Typescriptコンパイラを使用してアプリとは独立にビルドしよう。 この方法では、コードを1コンポーネントずつ徐々にアップグレードできる。
もっと詳しく:
5. Webコンポーネント
基本的に、これは未来だ。 なぜかって? これらの純粋なWebコンポーネントはフレームワークにとらわれず、フレームワークなしでも、どんなフレームワークの標準的な構文でも機能するからだ 。 JS疲労から解放され、最新のブラウザでサポートされているからだ。 バンドルサイズと消費量が最適化されていて、VDOMレンダリングは驚くほど素晴らしいからだ。
これらのコンポーネントは、新しい種類のhtmlタグを定義できるJavascript API、レイアウトを指定するHTMLテンプレート、そしてもちろんコンポーネント固有のShadow DOMを提供するカスタム要素を提供する。
この分野でよく知られているツールは、Lit-html( Lit-elementも)だ。** StencilJS** 、SvelteJSそしてもちろんBitだ。どこでも直接共有できて、使うことができて、開発もできる再利用可能なモジュール式コンポーネントを提供してくれる。
私たちのUI開発の将来と、モジュール化、再利用性、カプセル化、および標準化の原則がコンポーネントの時代にどのように見えるかを考えるとき、Webコンポーネントががその答えだ。 もっと詳しく:
6. コンポーネントライブラリから動的コレクションへ
コンポーネントを動的コレクションとして編成しよう。再利用し、構成し、独立性を保つのだ。
コンポーネント駆動開発の登場はさまざまなツールを生み出した。 有名なツールの1つはホスティングプラットフォームの Bit.devならびにBitだ 。
厄介で高度に結合されたコンポーネントライブラリを構築するために一生懸命働く代わりに、ビットを使用して、既存のコンポーネント同士の分離、動的に再利用可能な共有コレクションへのエクスポートを継続的に行おう。
Bit(GitHub) を使うことによって、 UIコンポーネントの分離、バージョン管理、ビルド、テスト、更新を独立して行うことが可能だ。 Bitを使えば既存のアプリにあるコンポーネントの単離、リモートコレクションへの保存、場所を問わない使用のプロセスを合理化できる。 すべてのコンポーネントは、プロジェクトの外部でのビルド、テスト、レンダリングが可能だ。 また、アプリ全体ではなく、単一のコンポーネント(およびその依存コンポーネント)を更新することができる。
Bit.devプラットフォーム(または独自のサーバー)では、コンポーネントをリモートでホストし、異なるチーム向けに編成できるため、それを使ってすべてのチームが独自のコンポーネントの開発を行うことができる。 すべてのチームはコンポーネントを共有して再利用できるが、コンポーネントの独立性と制御は保つことができるのだ。
このプラットフォームは、すぐに使える共有コンポーネントのオールインワンエコシステムも提供している。UIコンポーネントを自動ドキュメント化し、インタラクティブなプレイグラウンドでコンポーネントをレンダリングし、npm/yarnを使用してコンポーネントをインストールするための組み込みレジストリを提供してさえくれる。 さらに、どんなリポジトリでも変更のために
`bit import`
でコンポートネントをインポートできる。短期的に見ると、これは以前のCDミュージックアルバムを介した音楽の共有プロセスをSpotify / iTunesが変えたのと同じように、コンポーネントを共有および作成するプロセスに革命をもたらしている。 これは、誰もがコンポーネントを共有して一緒に使用できるようにする動的かつモジュール式のソリューションだ。
長期的に見れば、Bitはマイクロフロントエンドへの道を開拓するのに役立つ。 なぜかって? それはUIアプリケーションの一部分のバージョン管理、テスト、ビルド、更新を独立して実行することができるようになっているからだ。 2020年にBitには独立したデプロイメント機能が導入され、最終的にはさまざまなチームがアプリの一部をエンドツーエンドで所有できるようになる。分離されたシンプルなコードベースを維持し、チームが慎重かつ継続的に段階的にUIの更新をビルド・デプロイし、フロントエンドを構成することができるようになるということだ。
7. 状態管理:ばいばい Redux? (いや...)
Reduxは倒すのが難しいビーストだ 。 フロントエンドがモジュール化されるにつれて、アプリの状態をグローバルに管理することの苦痛はより明確になるが、Reduxの有用性の高さは多くのチームにとって頼りになるソリューションだ。
では、2020年にReduxに別れを告げることになるのだろうか。 おそらく完全にそうなることはないだろう?
ただし、状態を処理するフレームワーク内の新機能(React hooks、Context-APIなど)の誕生は、グローバルストアのない未来への道を描いている。 Mobxのようなツール群は1年前に採用されたばかりだが、それらコンポーネント指向でスケーラブルな性質のおかげで、ますます人気が高まってきている。 ここでより多くの選択肢を見つけることができる。
参考:React Hooksを理解する — Dan Abramov
8. ESM CDN
ESモジュールは、ブラウザでJSのモジュールを操作するための標準規格であり、ECMAScriptによって標準化されている。 ESモジュールを使用すると、CDNなどを介して使用できるモジュールに機能を簡単にカプセル化できる。 Firefox 60のリリースにより、すべての主要ブラウザが ESモジュールをサポートするようになる。Node mteamはESモジュールサポートを Node.js に追加する作業に取り組んでいる。 また、WebAssemblyに向けてのESモジュール統合が今後数年間に対応されるだろう。 CDNを介して、モジュール式BitUIコンポーネントをアプリに組み込めることを想像してみよう...
9. プログレッシブWebアプリケーション。 まだまだ成長中。
プログレッシブWebアプリケーションは最新のテクノロジーを活用して、Webアプリとモバイルアプリのいいとこどりをしたものだ 。 これは、ウェブテクノロジーを使用して構築されたウェブサイトであるが、モバイルアプリのようなふるまい、UXをもつものであると考えればよい。 Service Worker、Cache API、Push APIの可用性向上とブラウザの近年の進歩により、Web開発者はモバイルユーザーのホーム画面にWebアプリをインストールし、プッシュ通知を受信させ、さらにはオフラインで機能させることさえ可能になった。
PWAは快適なユーザーエクスペリエンスを提供できるようにしているためか、すべてのネットワークリクエストはサービスワーカーを介して傍受される可能性があるため、アプリをHTTPS経由でホストして中間者攻撃を防止し、セキュリティを強化することが不可欠だ。 ここでFacebookの開発者Omer GoldbergによるPWAのベストプラクティスの概要の解説を読むことができる。
10. デザイナーと開発者の統合
プロダクトやチーム全体で一貫したUIを実現するコンポーネント駆動設計システムの誕生とともに 、デザイナーと開発者の間のギャップを埋めるための新しいツールが登場した。 とはいえこれは簡単な作業ではない。実際にはコード自体が唯一の真の情報源だが(コードこそがユーザーに実際にいきわたるものだ)、ほとんどのツールはデザイナー側からのギャップを埋めようとしている。 このカテゴリには、Framer、Figma、Invision DSMなどがある。
開発者側からの視点では、 Bit.devのようなプラットフォームが 、次世代のコンポーネントライブラリをホストし、共有コンポーネントの採用を支援している。 このプラットフォームは、実際のソースコードのレンダリングして視覚化してくれるため、デザイナーは開発者と共同で作業し、ソースコード自体について視覚的な方法でディスカッションできるようになる。
注意すべきもう1つの有望なアイデアは、 design-tokens だ 。 トークンをコードに配置することにより、デザイナーは外部のコラボレーションツールを介して単純なスタイリングの側面(色など)を直接制御できる。 これはBit.devのようなプラットフォームと統合されており、これまでよりも厳しいワークフローを作成できる。
11. Webアセンブリ — 未来へ?
Webアセンブリ(Wasm) はWeb開発に言語多様性をもたらし、JavaScriptによってもたらされた隔たりを埋めてくれる。 Wasmは、「スタックベースの仮想マシン用のバイナリ命令フォーマット」として定義されている。 Wasmは、C / C ++ / Rustのような高水準言語のコンパイル用のポータブルターゲットとして設計されており、クライアントアプリケーションおよびサーバーアプリケーションのWebへの展開を可能にしてくれる。
Eric Elliott は彼の投稿でそのコンセプトの利点を次のようにエレガントに概説している。
- JavaScriptの改善: wasmでパフォーマンスが重要なものを実装し、標準のJavaScriptモジュールのようにインポートできる。
- 新しい言語: WebAssemblyコードは、バイナリ形式で表されるAST(抽象構文ツリー)を定義できる。 テキスト形式で書いてデバッグ可能だ。つまり可読性が高い。
- ブラウザの改善: ブラウザはバイナリ形式を理解できる。これは、現在使用しているテキスト形式のJavaScriptコードよりもより高くい圧縮率をもつバイナリバンドルをコンパイルできることを意味する。 ペイロードが小さいほど、配信が速くなる。 また、コンパイル時の最適化のケースによっては、WebAssemblyバンドルもJavaScriptよりも高速に実行されるかもしれない!
- コンパイルターゲット: 他の言語がWebプラットフォームスタック全体でファーストクラスのバイナリサポートを取得する方法である。
なぜそれが有用で、どこで使用されるようになるのか、そしてなぜまだ目にしないのか、このコンセプトについてさらに理解するためには、この素晴らしい投稿とこの素晴らしいビデオを見ることをおすすめする 。
もっと詳しく
翻訳協力
Original Author: Jonathan Saring
Original Article: 11 Must-Know FrontEnd Trends for 2020
Thank you for letting us share your knowledge!この記事は以下の方々のご協力により公開する事が出来ました。
改めて感謝致します。
選定担当: @_masa_u
翻訳担当: @_masa_u
監査担当: @r_pg10
公開担当: @_masa_uご意見・ご感想をお待ちしております
今回の記事は、いかがだったでしょうか?
・こうしたら良かった、もっとこうして欲しい、こうした方が良いのではないか
・こういったところが良かった
などなど、率直なご意見を募集しております。
いただいたお声は、今後の記事の質向上に役立たせていただきますので、お気軽にコメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
みなさまのメッセージをお待ちしております。
- 投稿日:2020-07-26T16:48:06+09:00
javascript-ゲームを作ってオブジェクトを学ぶ-
この記事について
現役プログラマーさんから教えていただいたことをまとめている記事です。今回はJavaScriptの基礎を使ったゲームづくりを通して、教えていただいたことをまとめていきます。
関数
まずfunctionを使った足し算をする簡単な関数をみていきます。
sumNumberは関数名で、自由に名前をつけることができます。
a,bは引数で、後にから代入する具体的な数字を入れるための箱のようなものです。//aとbを足して、コンソールに表示する関数を定義 function sumNumber(a,b){ console.log(a+b); } //関数名とa,bに入る数字を記入すると、上の関数に代入されます。 sumNamber(3,4); //結果:7allow関数
上の関数をallow関数で書いてみます。
const sumNumber = (a,b) =>{ console.log(a+b); } sumNumber(3,4);先ほどのfunctionを使った関数を下のように書き換えています。
const 関数名 = (引数) => { 関数の中身 }
functionを使った関数に問題が見つかり、allow関数で回避することができるので、こちらを主に使っていくのがベターみたいです。
オブジェクト
オブジェクトを使うと、今までsumNumber(3,4)のように、数字や文字などを単体だった情報をまとめて管理できるようになります。
const user1 = { name:"ポチ", age:7, weight: 15 }このオブジェクトだとオブジェクト名はuser1です。nameやageなどの要素はプロパティと呼びます。プロパティは「 , (カンマ)」でつなぎます。最後のプロパティの後ろは何もつけないので注意!
オブジェクトは、オブジェクト名.プロパティ名 で呼び出すことができます。console.log(user1.name); console.log(user1.age);結果:ポチ
7◎関数の引数としてオブジェクトを渡すことができます!
name,age,weightを表示させる関数を作り,その引数に先ほどのオブジェクトuser1を代入してみます。const loginUser = (u) =>{ console.log(u.name); console.log(u.age); console.log(u.weight); } loginUser(user1);結果:ポチ
7
15関数の引数“u”が“user1”が置き換わるイメージです。初めの引数の“u”は、ただの数字を代入する箱のようなイメージで意味を持たないと考えようにすると、分かりやすくなると思います。
ゲームを作って覚えよう
私は、鬼滅の刃が好きなので、それぞれの剣士の階級を求めるプログラムを作りたいと思います。剣士の腕前を、腕力、速さ、技術に分けて、点数化し、その合計値を元に階級を表示していきます。
1.炭次郎のオブジェクトを定義
剣士のプロパティーは
・name(文字列)
・power(数字)
・speed(数字)
・technique(数字)
名前以外は、それぞれ1~100の数字。炭次郎のオブジェクト名は、kenshi1とします。
const kenshi1 = { name:"竈門炭治郎", power: 50, speed: 60, technique: 70 }2.剣士の階級を求める関数を作成する
剣士の階級はpowerとspeedとtechniqueの合計値で求めます。
合計値が250以上なら甲(キノエ)
合計値が150以上250未満なら丙(ヒノエ)
合計値が150未満なら丁(ヒノト)とします。//まず腕力、速さ、技術の合計値を求める関数を定義します。この時の、“kenshi”は引数で後で定義する具体的な数値を入れるためのただの箱のようなものです。 const calcSum = (kenshi) => { return kenshi.power + kenshi.speed + kenshi.technique; } //次に剣士のランクを求める関数を定義します。 const getRank = (kenshi) => { const sum = calcSum(kenshi); //rankは変化していくので、変数で定義します。文字列になるので、""をつけています。"階級"などの文字を入れてもOK let rank = ""; if(sum>=250){ rank = "甲(キノエ)"; }else if(sum>=150){ rank = "丙(ヒノエ)"; }else{ rank = "丁(ヒノト)"; } return rank; }3.それぞれのランクをconsoleに出力する
//炭次郎は定義したので、他2人のオブジェクトを定義します const kenshi2 = { name:"煉獄杏寿郎", power: 98, speed: 96, technique:80 } const kenshi3 = { name:"不死川玄弥", power: 20, speed: 40, technique:70 } //3人の名前とランクをコンソールに表示します console.log(kenshi1.name); console.log(getRank(kenshi1)); console.log(kenshi2.name); console.log(getRank(kenshi2)); console.log(kenshi3.name); console.log(getRank(kenshi3));
- 投稿日:2020-07-26T15:41:58+09:00
Springで勤怠サイトを作ってみる。【概略】
概要
評価面談でSpringを勉強していると言ってしまったばっかりに、
次回面談までに勤怠サイトを作ることに・・・。
実業務でSpringベースの開発経験はあるけども、全体的に素人に毛が生えたレベル。PCの調子が悪くて(なんかカリカリ音がしている)成果物が消える恐れがあるので、
面談時のエビデンスのためにも作業履歴としてQiitaに残そうかな、と。構成(想定)
- Webアプリ
- フロント:Vue.js
- バックエンド:Spring
- DB:PostgreSQL
※追記事項があり次第記載します環境
- Eclipse - Version: Oxygen.3a Release (4.7.3a)
- Atom
※追記事項があり次第記載します。
- 投稿日:2020-07-26T12:52:39+09:00
【Rails】JavaScriptが絡むテスト
はじめに
Capybaraでのテストで、JavaScriptが作動する処理をする場合には少し設定を変える必要があります。
今回は簡単な例としてクリックするとテキストが変わる処理についてテストしてみます。設定
まずはCapybaraを導入する必要がありますが、わからない方は過去に書いたこちらの記事を参照してください。
続いてhelperファイルに追記します。
spec_helper.rb# 最後の行に追加 ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) Capybara.javascript_driver = :selenium_chrome_headless※ なぜかjavascript_driverにwebkitを使うとうまくいきませんでした…
詳しい方いたらコメントいただけると助かります。テストファイル
spec/features
の下にファイルを用意します。sample_spec.rbrequire 'rails_helper' feature 'post', type: :feature do scenario 'jsが動作すること', js: true do # クリックの表示が存在する visit root_path expect(page).to have_content('クリック') # クリックの文字をクリックするとOKになる find('.js-class').click expect(page).to have_text("OK") end endjsを動作させたい場合は上述のようにscenarioの行に
js: true
を追記する必要があります。念のためviewファイルとjsファイルも載せておきます。
(HamlとjQueryを使ってます)index.html.haml.header = link_to "トップページ", root_path .js-class クリックsample.js$(function(){ $(".js-class").on("click", function() { $(this).text("OK"); }) })テスト実行
あとはターミナルから実行するだけです。
ターミナル$ bundle exec rspec spec/features/sample_spec.rb
- 投稿日:2020-07-26T11:40:22+09:00
LINE LIVEのチャット取得
コメントビューア用。
OAuthが要るようなAPIは使わず、未登録で取得できるコメントを取得。
あくまで執筆時点の情報です。仕様が変わる可能性もあるのであしからず。ユーザのチャンネルURL
https://live.line.me/channels/{ユーザID}
手順
動画IDの取得
- GET
https://live-api.line-apps.com/web/v4.0/channel/{ユーザID}
- レスポンス
{ /** ステータスコード。正常時200が返ってくるっぽい。 */ "apistatusCode": number, /** ユーザID */ "id": number, /** タイトル */ "title":string, /** 配信してたらtrue */ "isBroadcastingNow": boolean, "liveBroadcasts": { "hasNextPage": boolean, /** 配信してたら要素がある。してなかったら空配列。 */ "rows": { /** ステータスコード。正常時200が返ってくるっぽい。 */ "apistatusCode": number, /** ユーザID */ "channelId": number, /** 動画ID */ "id": number, /** 配信中なら"LIVE" */ "liveStatus": string, // 省略 }[], /** ステータスコード。正常時200が返ってくるっぽい。 */ "apistatusCode": number }, /** ステータスコード。正常時200が返ってくるっぽい。 */ "status": number, // 省略 }
- 細かくチェックするなら各階層のステータスコードチェックが要るが、正常系だけ作るなら無視でOK
- isBroadcastingNowがtrueかチェック
- liveBroadcasts.rows[0].idを動画IDとして取得
チャットのURLを取得
https://live-api.line-apps.com/web/v4.0/channel/{ユーザID}/broadcast/{動画ID}
{ "chat": { /** チャットのWebSocketURL */ "url": string, /** 配信中ならnull。たぶんstringになる。 */ "archiveURL": any, /** 配信者コメントのWebSocketURL */ "ownerMessageURL": string }, "status": 200, // 省略 }
- チャットのURLは以下のような感じ
wss://cast-chat-{数値}.line-apps.com/chat/app/{動画ID}/{配信ごとの英字。乱数っぽい}
チャット受信
- 取得したチャットURLでWebSocket接続する。レスポンスの種類はいっぱいあるので必要なもの以外は無視する。
- メッセージの共通の型は以下。
{ "type": string, "data": object }
- typeの種類(確認できたもの)
type 内容 entranceAnnouncement 入場した followStart フォローした pokeReceive (謎) gift ギフト giftMessage (giftとセットで飛んでくる) love guideMessage systemMessage bulk message コメント
- type: messageのdata
{ /** コメント */ "message": string, /** コメントしたユーザ */ "sender": { "id": number, "hashedId": string, /** ユーザ表示名 */ "displayName": string, /** アイコン画像のURL */ "iconUrl": string, "hashedIconId": string, "isGuest": boolean, "isBlocked": boolean, "isPremiumMember": boolean }, "sentAt": number, "isNGMessage": boolean, "key": string, "roomId": string }
- 投稿日:2020-07-26T11:21:12+09:00
React SpectrumでBuild a movie search app using React hooksを作ってみた
React Spectrumがすごい
React Spectrumがすごそうということで、実際に使って開発してみました。React Spectrum自体の設計のクオリティの高さは、Adobe製デザインシステム「React Spectrum」がすごいので紹介したいの記事が詳しいです。内部の作りはさておき、開発者視点で良かったと感じた点は以下です。
- Adobeが開発しているので信頼度が高い
- Componentのプロパティの統一感があり、わかりやすく使いやすい
- Flex/Gridが簡単にかける
- 基本的にタグで書いていく思想?でCSSを書かなくても大体いける。Flex/Gridもタグがある。
- classNameのプロパティは「UNSAFE_className」になっており、説明にも「最後の手段」と。
Build a movie search app using React hooksについて
2020年のフロントエンドマスターになりたければこの9プロジェクトを作れで1つめに紹介されていたプロジェクトです。元のサイトはこちら。フロントエンドマスターを目指した方なら作ってみたことがあるはず!React Spectrumを試すちょうどよい題材として活用させてもらいました。
React Spectrumを使ってカスタマイズ
完成したイメージ
こんな感じです。
(デザインがダサいと感じたら、それはReact Spectrumではなく私のセンスの問題です。)
スマホにするとこんな感じです。
文字サイズとかいい感じに変わってくれるところとか素晴らしいです。
ダークモードにするとこんな感じです。
本来はOSの設定に応じて切り替わりますが、Reactの勉強がてら切り替えボタンをつけてみました。文字色もうまく変えてくれています。
キャプチャではわかりませんが、検索APIを呼び出している間は、ProgressCircleがくるくる回ります。
中身の説明
リセットCSS
リセットCSSがなくても大丈夫かもしれませんが、hタグを使った時の余白が気に入らなかったので、リセットCSSを入れました。使用したのはmodern-css-resetです。はじめは、destyle.cssを使ってみたのですが、hタグで見た目の違いが表現できなくなってしまったので、適度にリセットしてくれるmodern-css-resetにしました。導入は簡単で、
yarn add modern-css-reset
して、index.jsにimport 'modern-css-reset';
するだけです。Flex,Gridで簡単にレイアウト
App.jsでレイアウトを作っていきます。レイアウトを実現するのにCSSを自分で書く必要はなく、用意されたタグだけで十分でした。
全体の構成は
- ヘッダー
- 検索部分
- ガイダンス部分
- コンテンツ部分
- フッター
です。
全体をFlexboxで並べて、それぞれのレイアウトの調整にさらにFlexboxやGridを使いました。コンテンツ部分は固定サイズで配置したかったのでGridにしています。React Spectrumのイメージがつかめるように、import部分とreturnのレイアウト部分のみ抜粋します。App.jsimport React, { useReducer, useEffect } from 'react'; import './App.css'; import HookedHeader from "./HookedHeader"; import Movie from "./Movie"; import Search from "./Search"; import { Provider, defaultTheme, Flex, View, Text, Grid, repeat, Footer, ProgressCircle, Link } from '@adobe/react-spectrum'; const App = () => { return ( <Provider theme={defaultTheme} colorScheme={colorSchemeStete.colorScheme}> {/* 全体をflexbox化する */} {/* ダークモードでも白地が見えないように画面の高さ分をコンテンツ領域で確保する */} <Flex direction="column" gap="size-100" minHeight="100vh"> {/* ヘッダー部 */} <HookedHeader text="HOOKED" switch={switchColorScheme} currentColorScheme={colorSchemeStete.colorScheme} /> {/* 検索部 中央寄せにする*/} <Flex direction="row" justifyContent="center"> <Search search={searchMethod} /> </Flex> {/* ガイダンス部 中央寄せにする */} <Flex direction="row" justifyContent="center"> <Text>Sharing a fwe of our favourite movies</Text> </Flex> {/* コンテンツ部 Grid化する */} <Grid columns={repeat('auto-fit', 'size-2400')} autoRows="size-2400" justifyContent="center" gap="size-200"> {loading && !errorMessage ? ( // ローディング表示 <View // 上下中央表示 alignSelf="center" // 左右中央表示(gridのrpeatを無視) justifySelf="center"> <ProgressCircle aria-label="Loading…" isIndeterminate /> </View> ) : errorMessage ? ( // エラーメッセージ表示 <View // 左右中央表示(gridのrpeatを無視) justifySelf="center"> <div className="errorMessage">{errorMessage}</div> </View> ) : ( // コンテンツ表示 movies.map((movie, index) => ( <View backgroundColor="gray-200"> <Movie key={`${index}-${movie.Title}`} movie={movie} /> </View> )) )} </Grid> {/* フッター リンクをつけてみる*/} <Footer alignSelf="center"> {/* Linkを使うとダークモードでも見やすく色が変わる */} <Link> <a href="https://www.freecodecamp.org/news/how-to-build-a-movie-search-app-using-react-hooks-24eb72ddfaf7/" target="_blank"> freeCodeCamp:How to build a movie search app using React Hooks </a> </Link> <br /> <Link> <a href="https://react-spectrum.adobe.com/react-spectrum/index.html" target="_blank"> React Spectrum:A React implementation of Spectrum, Adobe’s design system. </a> </Link> </Footer> </Flex> </Provider> ); }; export default App;ちなみにヘッダーコンポーネントも中身の配置を制御するのに、flexbox化しています。
HookedHeader.jsimport React from "react"; import { Header, Heading, View, Flex } from '@adobe/react-spectrum'; import ColorSchemeSwitch from './ColorSchemeSwitch'; const HookedHeader = (props) => { return ( <Header> {/* 背景色 */} <View backgroundColor="red-500"> {/* 文言を中央寄せ */} <Flex direction="row" justifyContent="center"> {/* h2と同等 */} <Heading level="2" > <font color="white">{props.text}</font> </Heading> <View position="absolute" right="size-0"> <ColorSchemeSwitch switch={props.switch} currentColorScheme={props.currentColorScheme}> </ColorSchemeSwitch> </View> </Flex> </View> </Header> ); }; export default HookedHeader;いちいち、classNameをつけて、cssを開いてflexにして、うまくいかないから、divを追加してみて、、、なんてことをしなくても、
<Flex>
タグや<Grid>
タグを並べていけばいいんです!しかも、設定すべき項目はパラメータ化してあるので、directionやjustifyContent、gapなど細かい調整もjsファイルだけでできてしまいます。プロパティの統一感
マニュアルの見やすさからくるものかもしれませんが、コンポーネントに指定できるプロパティが、
- 固有のプロパティ
- Event
- Layout
- Spacing
- Sizing
- Background
- Borders
- Positioning
- Accessibility
- Advanced
になどに体系だって定義されています。コンポーネントごとに、利用できる分類は異なりますが、分類の中では基本的に同じ内容のようです(Eventは違う)。すぐに覚えて使えるようになれました。
また、地味にいいのが、
DimensionValue
です。sizeなどを12px
とか1rem
とか指定するのではなく、Spectrumで定義されたDimensionValue
を使って指定します。具体的には、size-0
、size-10
、size-25
・・・といった値です。これらを使っていれば、Spectrum側で、デバイスに合わせた調整などを行ってくれます。指定する側もpxを使うかremを使うかemを使うかいちいち悩まなくて済みますね。同様に色指定もColorValue
があるので簡単に指定できます。文字色の指定は?
使っていて唯一わからなかったのは、文字色を自分で指定したいときです。ここはこの色にしたい!っていう箇所はやっぱり出てくるのではないかと思います。バックグラウンドカラーを変更するプロパティはあったのですが、文字色を変えるプロパティはぱっと見、見当たりませんでした。classを指定してCSSで書くというのは最後の手段のようなので、一旦、
<font>
タグで色を指定したのですが、これだと、ColorValue
も使えませんし、正しいやり方のようには思いません。そもそも、意味なく文字色だけ変えるなってことですかね。おわりに
Ract Spectrumはリリースされたばかりということで、まだ情報が少ないですが、公式のマニュアルが充実しているので特段困ることはなかったです。React歴数時間、CSSフレームワーク等はBootstrapを眺めたことがある程度ですが、非常に簡単でした。CSSを書かなくてよくて、且つ、タグも自然と標準化された書き方になっていくので、ストレスなく書けました。人気がでることに期待です!
参考
React Spectrum
freecodecamp:Build a movie search app using React hooks
- 投稿日:2020-07-26T11:21:12+09:00
Build a movie search app using React hooksをReact Spectrumで作ってみた
React Spectrumがすごい
React Spectrumがすごそうということで、実際に使って開発してみました。React Spectrum自体の設計のクオリティの高さは、Adobe製デザインシステム「React Spectrum」がすごいので紹介したいの記事が詳しいです。内部の作りはさておき、開発者視点で良かったと感じた点は以下です。
- Adobeが開発しているので信頼度が高い
- Componentのプロパティの統一感があり、わかりやすく使いやすい
- Flex/Gridが簡単にかける
- 基本的にタグで書いていく思想?でCSSを書かなくても大体いける。Flex/Gridもタグがある。
- classNameのプロパティは「UNSAFE_className」になっており、説明にも「最後の手段」と。
Build a movie search app using React hooksについて
2020年のフロントエンドマスターになりたければこの9プロジェクトを作れで1つめに紹介されていたプロジェクトです。元のサイトはこちら。フロントエンドマスターを目指した方なら作ってみたことがあるはず!React Spectrumを試すちょうどよい題材として活用させてもらいました。
React Spectrumを使ってカスタマイズ
完成したイメージ
こんな感じです。
(デザインがダサいと感じたら、それはReact Spectrumではなく私のセンスの問題です。)
スマホにするとこんな感じです。
文字サイズとかいい感じに変わってくれるところとか素晴らしいです。
ダークモードにするとこんな感じです。
本来はOSの設定に応じて切り替わりますが、Reactの勉強がてら切り替えボタンをつけてみました。文字色もうまく変えてくれています。
キャプチャではわかりませんが、検索APIを呼び出している間は、ProgressCircleがくるくる回ります。
中身の説明
リセットCSS
リセットCSSがなくても大丈夫かもしれませんが、hタグを使った時の余白が気に入らなかったので、リセットCSSを入れました。使用したのはmodern-css-resetです。はじめは、destyle.cssを使ってみたのですが、hタグで見た目の違いが表現できなくなってしまったので、適度にリセットしてくれるmodern-css-resetにしました。導入は簡単で、
yarn add modern-css-reset
して、index.jsにimport 'modern-css-reset';
するだけです。Flex,Gridで簡単にレイアウト
App.jsでレイアウトを作っていきます。レイアウトを実現するのにCSSを自分で書く必要はなく、用意されたタグだけで十分でした。
全体の構成は
- ヘッダー
- 検索部分
- ガイダンス部分
- コンテンツ部分
- フッター
です。
全体をFlexboxで並べて、それぞれのレイアウトの調整にさらにFlexboxやGridを使いました。コンテンツ部分は固定サイズで配置したかったのでGridにしています。React Spectrumのイメージがつかめるように、import部分とreturnのレイアウト部分のみ抜粋します。App.jsimport React, { useReducer, useEffect } from 'react'; import './App.css'; import HookedHeader from "./HookedHeader"; import Movie from "./Movie"; import Search from "./Search"; import { Provider, defaultTheme, Flex, View, Text, Grid, repeat, Footer, ProgressCircle, Link } from '@adobe/react-spectrum'; const App = () => { return ( <Provider theme={defaultTheme} colorScheme={colorSchemeStete.colorScheme}> {/* 全体をflexbox化する */} {/* ダークモードでも白地が見えないように画面の高さ分をコンテンツ領域で確保する */} <Flex direction="column" gap="size-100" minHeight="100vh"> {/* ヘッダー部 */} <HookedHeader text="HOOKED" switch={switchColorScheme} currentColorScheme={colorSchemeStete.colorScheme} /> {/* 検索部 中央寄せにする*/} <Flex direction="row" justifyContent="center"> <Search search={searchMethod} /> </Flex> {/* ガイダンス部 中央寄せにする */} <Flex direction="row" justifyContent="center"> <Text>Sharing a fwe of our favourite movies</Text> </Flex> {/* コンテンツ部 Grid化する */} <Grid columns={repeat('auto-fit', 'size-2400')} autoRows="size-2400" justifyContent="center" gap="size-200"> {loading && !errorMessage ? ( // ローディング表示 <View // 上下中央表示 alignSelf="center" // 左右中央表示(gridのrpeatを無視) justifySelf="center"> <ProgressCircle aria-label="Loading…" isIndeterminate /> </View> ) : errorMessage ? ( // エラーメッセージ表示 <View // 左右中央表示(gridのrpeatを無視) justifySelf="center"> <div className="errorMessage">{errorMessage}</div> </View> ) : ( // コンテンツ表示 movies.map((movie, index) => ( <View backgroundColor="gray-200"> <Movie key={`${index}-${movie.Title}`} movie={movie} /> </View> )) )} </Grid> {/* フッター リンクをつけてみる*/} <Footer alignSelf="center"> {/* Linkを使うとダークモードでも見やすく色が変わる */} <Link> <a href="https://www.freecodecamp.org/news/how-to-build-a-movie-search-app-using-react-hooks-24eb72ddfaf7/" target="_blank"> freeCodeCamp:How to build a movie search app using React Hooks </a> </Link> <br /> <Link> <a href="https://react-spectrum.adobe.com/react-spectrum/index.html" target="_blank"> React Spectrum:A React implementation of Spectrum, Adobe’s design system. </a> </Link> </Footer> </Flex> </Provider> ); }; export default App;ちなみにヘッダーコンポーネントも中身の配置を制御するのに、flexbox化しています。
HookedHeader.jsimport React from "react"; import { Header, Heading, View, Flex } from '@adobe/react-spectrum'; import ColorSchemeSwitch from './ColorSchemeSwitch'; const HookedHeader = (props) => { return ( <Header> {/* 背景色 */} <View backgroundColor="red-500"> {/* 文言を中央寄せ */} <Flex direction="row" justifyContent="center"> {/* h2と同等 */} <Heading level="2" > <font color="white">{props.text}</font> </Heading> <View position="absolute" right="size-0"> <ColorSchemeSwitch switch={props.switch} currentColorScheme={props.currentColorScheme}> </ColorSchemeSwitch> </View> </Flex> </View> </Header> ); }; export default HookedHeader;いちいち、classNameをつけて、cssを開いてflexにして、うまくいかないから、divを追加してみて、、、なんてことをしなくても、
<Flex>
タグや<Grid>
タグを並べていけばいいんです!しかも、設定すべき項目はパラメータ化してあるので、directionやjustifyContent、gapなど細かい調整もjsファイルだけでできてしまいます。プロパティの統一感
マニュアルの見やすさからくるものかもしれませんが、コンポーネントに指定できるプロパティが、
- 固有のプロパティ
- Event
- Layout
- Spacing
- Sizing
- Background
- Borders
- Positioning
- Accessibility
- Advanced
になどに体系だって定義されています。コンポーネントごとに、利用できる分類は異なりますが、分類の中では基本的に同じ内容のようです(Eventは違う)。すぐに覚えて使えるようになれました。
また、地味にいいのが、
DimensionValue
です。sizeなどを12px
とか1rem
とか指定するのではなく、Spectrumで定義されたDimensionValue
を使って指定します。具体的には、size-0
、size-10
、size-25
・・・といった値です。これらを使っていれば、Spectrum側で、デバイスに合わせた調整などを行ってくれます。指定する側もpxを使うかremを使うかemを使うかいちいち悩まなくて済みますね。同様に色指定もColorValue
があるので簡単に指定できます。文字色の指定は?
使っていて唯一わからなかったのは、文字色を自分で指定したいときです。ここはこの色にしたい!っていう箇所はやっぱり出てくるのではないかと思います。バックグラウンドカラーを変更するプロパティはあったのですが、文字色を変えるプロパティはぱっと見、見当たりませんでした。classを指定してCSSで書くというのは最後の手段のようなので、一旦、
<font>
タグで色を指定したのですが、これだと、ColorValue
も使えませんし、正しいやり方のようには思いません。そもそも、意味なく文字色だけ変えるなってことですかね。おわりに
Ract Spectrumはリリースされたばかりということで、まだ情報が少ないですが、公式のマニュアルが充実しているので特段困ることはなかったです。React歴数時間、CSSフレームワーク等はBootstrapを眺めたことがある程度ですが、非常に簡単でした。CSSを書かなくてよくて、且つ、タグも自然と標準化された書き方になっていくので、ストレスなく書けました。人気がでることに期待です!
参考
React Spectrum
freecodecamp:Build a movie search app using React hooks
- 投稿日:2020-07-26T11:05:13+09:00
指定時刻のエポック秒を得る方法の比較(moment.jsとshell)
目的
- 時刻や日時の計算は一度エポック秒に変換して加算、減算するのがもっとも間違いが少ないです。
- そのためJavaScriptでも指定時刻からエポック秒を得たいことがあります。サマータイムとか閏とか手で設定するのは悪夢。
- JavaScriptのデフォルトのDateで格闘するのも否定しませんが、時間かけずにさっさとコーディングしたいものです。
- それには
moment.js
を導入することにしています。- でもshellの
gdate
と互換の無い記述なのでその備忘録としての書いておきます。環境
- macOS
- moment.js 2.17
- bash 5.0.17
- date (GNU coreutils) 8.32
リンク
指定時間のエポック秒の取得
bashで取得
GNU
date
を使いたいのでbashのcoreutil
をインストールしておきます。詳しくは下記が面白いです。使い方も載っているサイトあります。$ gdate -d "2020-01-01 12:34:56" +%s 1577849696エポック秒を得る定番ですね。時間指定にはUNIX形式の指定が必要ですが柔軟性を持たせるのは後に載せました。
moment.js
- JavaScriptのmoment.jsライブラリを使います。
<script src="..."></script>
なんかでCDNで読めばいいと思います。- moment.jsはデフォルトではローカル時間で解析および表示されます。
JavaScriptで現在時間のエポック時間を取得
- ブラウザのコンソールで試してましょう。moment.jsが使えるようにおまじないをかけておきます(CDNで読みます)
//ブラウザ(Safari)でCDNでmoment.jsを読んでおきます。 // Macでは⌘+opt+Iでコンソール画面が出てきます。そこで入力します。 script = document.createElement('script').src = 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.0/moment-with-locales.js'; document.getElementsByTagName('head')[0].appendChild(script);なんかエラーが出るかもしれませんが、moment()と入力して時刻が出ればOK。出なければHTMLにしっかり書いてAdobe/Bracketなんかでどうぞ。
// ブラウザ(Safari)コンソールでテスト // 現在の時間をJavaScriptのDate()で取得します。 > var now = Date(); < undefined > now < "Sun Jul 26 2020 09:08:56 GMT+0900 (JST)" // moment()はDateクラスを読み書きできます > moment(now).unix() < 1595722136bashで現在時間の確認
bashで得られたエポック秒が復元できるか確認してみましょう。同じエポック時間で変換します。
$ gdate '-d @1595722136' 日 7 26 09:08:56 JST 2020OKです。
指定時刻のエポック秒を得る
moment.jsで指定時刻のエポック時間を得る
- moment()の時刻表現は非常にフレキシブルなのですが、shellの
gdate
と表現は似ていて違います。なので忘れます。- 私は使うのは下記に限っています。
表1 moment.jsのフォーマット(ここのリンクから改変)
Input Example Description YY 14 2 digit year MM 1..12 Month number HH 0..23 Hours (24 hour time) mm 0..59 Minutes ss 0..59 Seconds Z +12:00 Offset from UTC as +-HH:mm ZZ +0900 Offset from UTC as +-HHmm
- 使用例;
moment("200101 123456", "YYMMSS HHmmss").unix()
moment.unix(1577849696).format("YYMMDD HHmmss")
表2 GNU dateのフォーマット(ここのリンクから改変)
Input Example Description %y 00..99 last two digits of year %m 01..12 month %d e.g., 01 day of month %H 00..23 hour %M 00..59 minute %S 00..60 second %z e.g., -0400 +hhmm numeric time zone %:z e.g., -04:00 +hh:mm numeric time zone
- 使用例
gdate '+%y%m%d_%H%M%S'
で200726_095643を得ますJavaScriptで確認
// 第1引数で指定した時刻のフォーマットは第2引数で指定します。 // それをunix()でエポック秒にします。 > moment("200101 123456", "YYMMSS HHmmss").unix() < 1577849696 // エポック秒から指定フォーマットに戻します。 > moment.unix(1577849696).format("YYMMDD HHmmss") < "200101 123456" // もちろん可読性の高いフォーマットにできます。 > moment.unix(1577849696).format("YYYY-MM-DD HH:mm:ss Z") < "2020-01-01 12:34:56 +09:00"bashで確認
$ gdate '-d @1577849696' 水 1 1 12:34:56 JST 2020 # 可読性の高いフォーマットに $ gdate '-d @1577849696' +"%Y-%m-%d %H:%M:%S (%Z)%:z" 2020-01-01 12:34:56 (JST)+09:00指定したのは2020年01月01日12:34:56でしたので、正しく戻っています。ローカル時間帯の(JST)で戻っています。
bashで指定時間のエポック秒を得る
# gdateは入力フォーマットと出力フォーマットを別個に指定できないのでUNIX時間フォーマットで指定する必要があります。 $ gdate -d "2020-01-01 12:34:56" "+%s" 1577849696
- 他の時刻フォーマットでもbashなら他ツールをパイプで組み合わせて得られます。
$ echo "200101 123456" | fold -s -w2 |tr '\n' ' ' |awk '{print mktime(20$0)}' 1577849696
- 少しだけ解説。
- 文字列200101 123456を
fold -s -w2
でスペースを気にしないで二文字に分けます。- それを
tr '\n' ' '
で改行をスペースに分けて、awkのmktime()
でエポック秒に変換します。mktime()
で必要な最初の項目はは4桁年なので20を$0に加えています。- 最初、awkで200101を2020-01-01とかに変換すればいいかと考えましたがawkにはmktime()があるからそれ使えばいいじゃんと考え直した次第です。
自分の備忘録のために書きましたがどなたかのお役に立てれば嬉しいです。
- 投稿日:2020-07-26T11:05:13+09:00
指定時刻のエポック秒を得る方法(moment.jsとshell)
目的
- 時刻や日時の計算は一度エポック秒に変換して加算、減算するのがもっとも間違いが少ないです。
- そのためJavaScriptでも指定時刻からエポック秒を得たいことがあります。サマータイムとか閏とか手で設定するのは悪夢。
- JavaScriptのデフォルトのDateで格闘するのも否定しませんが、時間かけずにさっさとコーディングしたいものです。
- それには
moment.js
を導入することにしています。- でもshellの
gdate
と互換の無い記述なのでその備忘録としての書いておきます。環境
- macOS
- moment.js 2.17
- bash 5.0.17
- date (GNU coreutils) 8.32
リンク
指定時間のエポック秒の取得
bashで取得
GNU
date
を使いたいのでbashのcoreutil
をインストールしておきます。詳しくは下記が面白いです。使い方も載っているサイトあります。$ gdate -d "2020-01-01 12:34:56" +%s 1577849696エポック秒を得る定番ですね。時間指定にはUNIX形式の指定が必要ですが柔軟性を持たせるのは後に載せました。
moment.jsの準備
- JavaScriptのmoment.jsライブラリを使います。
<script src="..."></script>
なんかでCDNで読めばいいと思います。- moment.jsはデフォルトではローカル時間で解析および表示されます。
- ブラウザのコンソールで試してましょう。moment.jsが使えるようにおまじないをかけておきます(CDNで読みます)
//ブラウザ(Safari)でCDNでmoment.jsを読んでおきます。 // Macでは⌘+opt+Iでコンソール画面が出てきます。そこで入力します。 script = document.createElement('script').src = 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.0/moment-with-locales.js'; document.getElementsByTagName('head')[0].appendChild(script);なんかエラーが出るかもしれませんが、moment()と入力して時刻が出ればOK。出なければHTMLにしっかり書いてAdobe/Bracketなんかでどうぞ。
moment.jsで取得
// ブラウザ(Safari)コンソールでテスト // 現在の時間をJavaScriptのDate()で取得します。 > var now = Date(); < undefined > now < "Sun Jul 26 2020 09:08:56 GMT+0900 (JST)" // moment()はDateクラスを読み書きできます > moment(now).unix() < 1595722136bashで確認
bashで得られたエポック秒が復元できるか確認してみましょう。同じエポック時間で変換します。
$ gdate '-d @1595722136' 日 7 26 09:08:56 JST 2020OKです。
(本題)指定時刻のエポック秒を得る
moment.jsで指定時刻のエポック時間を得る
- moment()の時刻表現は非常にフレキシブルなのですが、shellの
gdate
と表現は似ていて違います。なので忘れます。- 私は使うのは下記に限っています。
表1 moment.jsのフォーマット(ここのリンクから改変)
Input Example Description YY 14 2 digit year MM 1..12 Month number HH 0..23 Hours (24 hour time) mm 0..59 Minutes ss 0..59 Seconds Z +12:00 Offset from UTC as +-HH:mm ZZ +0900 Offset from UTC as +-HHmm
- 使用例;
moment("200101 123456", "YYMMSS HHmmss").unix()
moment.unix(1577849696).format("YYMMDD HHmmss")
表2 GNU dateのフォーマット(ここのリンクから改変)
Input Example Description %y 00..99 last two digits of year %m 01..12 month %d e.g., 01 day of month %H 00..23 hour %M 00..59 minute %S 00..60 second %z e.g., -0400 +hhmm numeric time zone %:z e.g., -04:00 +hh:mm numeric time zone
- 使用例
gdate '+%y%m%d %H%M%S'
で200726 095643を得ますJavaScriptで確認
// 第1引数で指定した時刻のフォーマットは第2引数で指定します。 // それをunix()でエポック秒にします。 > moment("200101 123456", "YYMMSS HHmmss").unix() < 1577849696 // エポック秒から指定フォーマットに戻します。 > moment.unix(1577849696).format("YYMMDD HHmmss") < "200101 123456" // もちろん可読性の高いフォーマットにできます。 > moment.unix(1577849696).format("YYYY-MM-DD HH:mm:ss Z") < "2020-01-01 12:34:56 +09:00"bashで確認
$ gdate '-d @1577849696' 水 1 1 12:34:56 JST 2020 # 可読性の高いフォーマットに $ gdate '-d @1577849696' +"%Y-%m-%d %H:%M:%S (%Z)%:z" 2020-01-01 12:34:56 (JST)+09:00指定したのは2020年01月01日12:34:56でしたので、正しく戻っています。ローカル時間帯の(JST)で戻っています。
bashで指定時間のエポック秒を得る
# gdateは入力フォーマットと出力フォーマットを別個に指定できないのでUNIX時間フォーマットで指定する必要があります。 $ gdate -d "2020-01-01 12:34:56" "+%s" 1577849696
- 他の時刻フォーマットでもbashなら他ツールをパイプで組み合わせて得られます。
$ echo "200101 123456" | fold -s -w2 |tr '\n' ' ' |awk '{print mktime(20$0)}' 1577849696
- 少しだけ解説。
- 文字列200101 123456を
fold -s -w2
でスペースを気にしないで二文字に分けます。- それを
tr '\n' ' '
で改行をスペースに分けて、awkのmktime()
でエポック秒に変換します。mktime()
で必要な最初の項目はは4桁年なので20を$0に加えています。- 最初、awkで200101を2020-01-01とかに変換すればいいかと考えましたがawkにはmktime()があるからそれ使えばいいじゃんと考え直した次第です。
自分の備忘録のために書きましたがどなたかのお役に立てれば嬉しいです。
- 投稿日:2020-07-26T10:52:50+09:00
アロー関数式初心者覚書
初心者覚書です。時々JSを使おうとするとアロー関数のことを忘れているのでメモとして作りました。ほぼ参考を読み、箇条書きにしただけなので、参考サイトを見た方がわかりやすいです。
アロー関数とは
- アロー関数式は、メソッドでない関数に最適で、コンストラクタとして使うことはできない
- 関数を短く書きたい、thisを束縛したくないという2つの理由がある
- 通常関数にはthisがあり、thisが何を指すのかは、通常関数を実行したタイミングで決まるが、
- アロー関数には、それ自体が保有するthisはなく、関数の外のthisを関数内で参照できるだけです。レキシカルスコープのthisを参照します。つまり、アロー関数は定義したときに、thisが指すものがひとつに決まり、どうやって関数が実行されるかに左右されなくなります。
- アロー関数はnewすることができません。つまり、アロー関数はコンストラクタになることができない>通常関数はclassでextendsできますが、アロー関数はできない
arrow.jsconst materials=[ 'aaa', 'bbb', 'ccc' ]; console.log(materials.map(material => material.length));getTriangle.jslet getTriangle = function(base,height){ return base * height /2; }; console.log(getTriangle(10,2));getTriangle_arrow.jslet getTriangle = (base,height)=>{ return base * height /2; }; console.log(getTriangle(10,2));本文が一文
getTriangle_arrow_short.jslet getTriangle = (base,height)=> base * height /2; console.log(getTriangle(10,2));引数がない場合
getTriangle_arrow_nohikisu.jslet show = ()=>console.log('hi!'); show();@uttk さんに教えてもらったサイト、、
通常の関数とアロー関数の違いは「書き方だけ」ではない。異なる性質が10個ほどある。これを見ると巻き上げ、ジェネレータ関数、レキシカルスコープとかよくわからない。
regular() // エラーにならない function regular() {}アロー関数は巻き上げが起こりません:
arrow() // ReferenceError: Cannot access 'arrow' before initialization const arrow = () => {}これは、アロー関数の性質というよりかは、constの性質によるもので、通常関数の場合でも、constなどを使った定義では、巻き上げが起こりません:
regular() // ReferenceError: Cannot access 'regular' before initialization const regular = function () {}ジェネレータ関数
通常関数はジェネレータ関数を定義できますが、アロー関数はジェネレータ関数を定義する構文がそもそもありません。function* regular() { yield 1 }レキシカルスコープ変数
通常関数は、argumentsで引数リストを参照できますが、アロー関数ではargumentsが定義されていないため、引数リストを参照できません。アロー関数のargumentsはレキシカルスコープ変数のargumentsを参照するだけです。lexical.jsconst arguments = 'hoge' // レキシカルスコープ変数 function regular() { console.log(arguments) } const arrow = () => { console.log(arguments) } regular(1, 2, 3) //=> [Arguments] { '0': 1, '1': 2, '2': 3 } arrow(1, 2, 3) //=> hogeアロー関数はletやconstの仕様上、同じ関数名で定義を上書きすることができない
let arrow = () => {} let arrow = () => {} //=> SyntaxError: Identifier 'arrow' has already been declaredアロー関数でもvarで宣言すると、上書き可能:
var arrow = () => {} var arrow = () => {}可変長引数?
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parametersやばい、色々調べているだけで人生が終わりそう
- 投稿日:2020-07-26T10:06:49+09:00
HTML5のフォームバリデーションをSPAの中で使う方法
最近、素のHTML5やCSS3をなるべく活用することに興味があります。
こういったベースの技術の方がフレームワークよりも息が長いので、一度学んだことを長期間役に立てることができるためです。※もちろんフレームワークも使うときは使います。例えばNuxt.jsは大好きです。
今回は、HTML5のフォームバリデーション機能を、Nuxt.jsで構築したSPAの中で使ってみたいと思います。
課題
HTML5のフォームバリデーションは、通常はformのsubmitを実行したタイミングで実施されます。
でも、SPAではformをsubmitしません。
どうしましょう...解決策
form.reportValidity() を使います。
これは、submitせずにバリデーションだけをかけるためのメソッドです。※2020年7月時点のMDNによれば、IE以外の全ての主要ブラウザでサポートされているようです。安心して使えますね。
https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reportValidityコード例
VInputItem.vue<template> <form> <label> 数量 </label> <input type="number" v-model="quantity" min="0" max="999" @chenge="onChengeInput" /> </form> </template> <script> export default { data() { return { unitprice: 0, quantity: 0, } }, methods: { onChangeInput(event){ this.$el.reportValidity() }, }, } </script>ポイント
この例では、直近のform要素を取得するために
$el
を使っています。
$elはVue.jsでルート要素を取得するためのプロパティです。
このサンプルコンポーネントでは、template直下のルート要素がformなので、それを一発で取得できています。※コンポーネント内の要素の構造によっては、$refsを用いて参照する方法もあると思います。
- 投稿日:2020-07-26T09:55:04+09:00
【TypeScript/JavaScript】一定条件になるまでループする
for (;;) + break 版
let num = 0 for (;;) { num += 1 const ok = num >= 10 if (ok) break } console.log(num) // => 10do ... while 版
let num = 0 let ok = true do { num += 1 ok = num < 10 } while (ok) console.log(num) // => 10or
let num = 0 let ok = false do { num += 1 ok = num >= 10 } while (!ok) console.log(num) // => 10参考
備考
while (true) + break
は、以下の eslint のルールに抵触するため断念。
https://eslint.org/docs/rules/no-constant-condition
- 投稿日:2020-07-26T09:50:23+09:00
OPENRECのチャット取得
コメントビューア用。
OAuthが要るようなAPIは使わず、未登録で取得できるコメントを取得。
あくまで執筆時点の情報です。仕様が変わる可能性もあるのであしからず。ユーザのチャンネルURL
https://www.openrec.tv/live/{ユーザID}
- 例:
https://www.openrec.tv/live/pasta04
手順
ユーザIDを元に、動画IDを取得
- GET
https://public.openrec.tv/external/api/v5/movies?channel_ids={ユーザID}&sort=onair_status&is_upload=false
- レスポンス
{ /** 動画ID(文字列) */ "id": string, /** 動画ID(数値) */ "movie_id": number, /** 配信タイトル */ "title": string, /** 配信詳細 */ "introduction": string, /** 配信中ならtrue */ "is_live": boolean, // 残りは省略 }[]新しいものが配列の先頭に来るので、先頭の要素をチェックし、is_liveをチェック。
動画IDを元に、コメント受信
以下で受信
wss://chat.openrec.tv/socket.io/?movieId={動画ID(数値)}&EIO=3&transport=websocket
EIOの意味は不明。
メッセージは、以下の形式で送られてくる。[数値][JSON]
- 例
0{"sid":"hogehogehoge","upgrades":[],"pingInterval":25000,"pingTimeout":60000}数値の正確な意味は不明だが、以下のように思われる。
数値 意味 0 サーバから送られてくる初期データ。 2 クライアントからのping 3 サーバからのpong 40 0の直後に送られてくる。
この時はJSON部分が無いので切れ目の意図だろうか。42 コメント 0
{ "sid": string, "upgrades": [], /** クライアントから投げるpingの間隔のミリ秒表記。25秒とか。 */ "pingInterval": number, /** タイムアウト判定される時間のミリ秒表記。60秒とか。 */ "pingTimeout": number }42
["message", 以下のJSONを文字列化したもの]{ /** * メッセージタイプ * - 0: コメント * - 1: 現在の視聴者数 * - 10: refreshが必要かとか */ "type": 0 | 1 | 10, /** 動画ID(数値)と同じ */ "room": string, /** 以下はtype0の時のもの */ "data": { /** 動画ID(数値)と同じ */ "movie_id": number, "live_type": number, "onair_status": number, "user_id": number, "openrec_user_id": number, /** ユーザ表示名 */ "user_name": string, /** コメント */ "message": string, /** アイコン画像のURL */ "user_icon": string, // 省略 } }コメント受信をざっくり書くと以下になる。
- WebSocketで受信
- メッセージの数値部分が42なら処理続行
- JSON部分をパースして、typeを確認し、0なら処理続行
- dataから、コメントメッセージ、ユーザ名、アイコンURLを抽出
また、定期的にpingメッセージを送信する必要がある。
初期コメント
- GET
https://public.openrec.tv/external/api/v5/movies/{動画ID(文字列)}/chats?to_created_at={時刻 ISO 8601}&is_including_system_message=false
レスポンス
{ "id": number, "chat_type": number, /** コメント */ "message": string, /** 配信者は2。一般ユーザが0? */ "quality_type": 0 | 2, /** 投稿時刻。ISO 8601 */ "posted_at": string, /** 表示時刻?ISO 8601 */ "messaged_at": string, /** ユーザ情報 */ "user": { /** ユーザID */ "id": string, /** OpenREC内部でのユーザID */ "openrec_user_id": number, /** 表示名 */ "nickname": string, /** アイコン画像のURL */ "icon_image_url": string, /** アイコン画像のURL。Lサイズ */ "l_icon_image_url": string, // 省略 }, // 省略 }[]
- 投稿日:2020-07-26T08:50:36+09:00
LINE×GASで作成した順番取り予約LINE Botを改良
概要
耳鼻科の開業医をしています。先週、医院の順番取り予約システムのプロトタイプをGASを使ったLINE Botで作成しました。
1時間で出来る LINE×GASで順番取り予約システムの作成
今回こちらを改良して実際患者さんに使ってもらいました。実装
1.患者さんはLINEで現在の診察待ち状況が分かる
2.患者さんはLINEで診察の順番が取れる
3.スタッフはLINEで診察中患者を更新できる
4.受付時間以外は予約券が発券されない
5.休診日は予約券が発券されない
6.スタッフはLINEで発券番号と診察中患者を初期化できる今回は4~6の機能を追加しました。
概念図
バックエンドとしてGoogle Spread Sheetを利用し発券番号・診察中番号を管理。Google App Script(GAS)でLINE botと連携しました。
作成法はこちら
1時間で出来る LINE×GASで順番取り予約システムの作成
BOTを改良
1.フレックスメッセージに変更
フレックスメッセージはこちらを使うととても簡単に作成できます。
FLEX MESSAGE SIMULATOR予約券発券
2.休診日・利用時間外は発券しない
・休診日の土曜午後と日曜は発券しないようにします。
・利用時間(8:30-11:00 14:00-16:30)外は発券しないようにします。//現在日時を取得(HHmm形式) 例)08:30の場合、nowに0830が入る var today = new Date(); var now = Utilities.formatDate(today, "Asia/Tokyo", "HHmm"); //日月火水木金土が0~6の数字で入る var day = today.getDay(); //発券(患者) if (userMessage === "発券") { if(day === 0 || day === 6 && 1400 <= now ){//日曜または土曜午後の場合 messages[0].text = "土曜午後・日曜・祝日は休診日です"; }else if ((830 <= now && now < 1100) || (1400 <= now && now < 1630)) { // フレックスメッセージ(予約券) messages = [{ "type": "flex", "altText": "どい耳鼻咽喉科 予約券", "contents": { "type": "bubble", "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "どい耳鼻咽喉科 診察予約券", "weight": "bold", "color": "#1DB446", "size": "sm", "align": "center" }, { "type": "text", "text": String(getNumber()), "weight": "bold", "size": "5xl", "margin": "xxl", "align": "center" }, { "type": "separator", "margin": "xxl" }, { "type": "box", "layout": "vertical", "margin": "xxl", "spacing": "sm", "contents": [ { "type": "box", "layout": "horizontal", "contents": [ { "type": "text", "text": "・こちらの画面を受付でご提示下さい", "size": "sm", "color": "#555555", "flex": 0 } ] }, { "type": "box", "layout": "horizontal", "contents": [ { "type": "text", "text": "・遅れた場合予約券は無効になります", "size": "sm", "color": "#555555", "flex": 0 } ] }, { "type": "box", "layout": "horizontal", "contents": [ { "type": "text", "text": "・こまめに【待ち状況】をご確認下さい", "size": "sm", "color": "#555555", "flex": 0 } ] } ] }, { "type": "separator", "margin": "xxl" }, { "type": "box", "layout": "horizontal", "margin": "md", "contents": [ { "type": "text", "text": "医院電話番号", "size": "xs", "color": "#aaaaaa", "flex": 0 }, { "type": "text", "text": "047-496-1133", "color": "#aaaaaa", "size": "xs", "align": "end" } ] } ] }, "styles": { "footer": { "separator": true } } } }]; } else { messages[0].text = "現在発券時間外です。受付時間は午前8:30~11:00 午後2:00~4:30です。"; } }3.予約券番号・診察中番号の初期化
予約券番号はSpredSheetのセルA1値を、診察中番号はセルB1値を読み取っています。
こちらの仕組みについてはこちら
1時間で出来る LINE×GASで順番取り予約システムの作成特定のメッセージを受けると以下の関数が動いて、セルA1値を「1」にセルB1値を「0」に変更し初期化します。
//A1セル値を1にB1セル値を0に初期化する関数 function initialA1B1() { //1. 現在のスプレッドシートを取得 var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); //2. 現在のシートを取得 var sheet = spreadsheet.getActiveSheet(); //3A. 指定するセルの範囲(A1)を取得 var rangeA = sheet.getRange("A1"); //3B. 指定するセルの範囲(B1)を取得 var rangeB = sheet.getRange("B1"); // 4.初期化:セルに値をセットする場合はsetValueを使う rangeA.setValue(1); rangeB.setValue(0); }考察
今回実際にこのBotを患者さんに使って頂きました。土曜午前に受診した130名の患者さんのうち20-30名の方が予約システムを利用されました。特にトラブルなく運用できましたが順番に遅れる方が何名かいたようです。プッシュ機能を使い来院を呼びかけるようにすると良いのかもしれませんがすぐ無料枠を超えてしまうので何かほかに良い方法がないか考え中です。
- 投稿日:2020-07-26T07:15:27+09:00
Twicasのチャット取得
コメントビューア用。
OAuthが要るようなAPIは使わず、未登録で取得できるコメントを取得。
あくまで執筆時点の情報です。仕様が変わる可能性もあるのであしからず。ユーザの配信URL
https://twitcasting.tv/{ユーザID}
- 例:
https://twitcasting.tv/pasta04
手順
ユーザIDを元に、動画ID(movieID)を取得
- リクエスト
- GET
https://frontendapi.twitcasting.tv/users/{ユーザID}/latest-movie
- レスポンス
{ "update_interval_sec": 4, "movie": { "id": {movieID}, "is_on_live": true // 配信中かどうか } }動画IDを元に、チャットのURLを取得
- リクエスト
- POST
https://twitcasting.tv/eventpubsuburl.php
- body(form-data)
- movie_id: {movieID}
- レスポンス
{ "url": "wss://s202218134036.twitcasting.tv/event.pubsub/v1/streams/{movieID}/events?token={なんやかんや}&n={なんやかんや}" }チャットURLにWebSocketで接続する
1メッセージで複数コメントが取得できることがある。
ただの空配列が受信できることもある。
WebSocketでpingメッセージを送る必要は無さそう。受信できるメッセージは以下の型。
- コメント
{ "type": "comment", /** コメントのIDっぽい */ "id": number, /** コメント */ "message": string, "createdAt": number, /** 投稿ユーザ情報 */ "author": { /** Twicas内でのIDっぽい。乱数みたいなので見てもしょうがない。 */ "id": string, /** ユーザの表示名 */ "name": string, /** TwitterアカウントのScreenName */ "screenName": string, /** アイコン画像のURL */ "profileImage": string, "grade": number }, "numComments": number }[]
- ギフト
{ "type": "gift", "message": "", "item": { /** アイテム名。お茶とか */ "name": string, /** アイテム画像URL */ "image": string, "effectCommand": "" }, /** コメントのauthorと同じ */ "sender": { "id": string, "name": string, "screenName": string, "profileImage": string, "grade": number } }[]
- 投稿日:2020-07-26T00:27:56+09:00
イマドキな JavaScript で書かない・使わないもの
自分は趣味で JavaScript をしているため、お仕事などでは使わざるをえない場合もあるかもしれませんが、少なくとも理想的には使わない方が良いと思うものを書きます。
var
を書かない
let
もなるべく書かない- なるべく
const
を使うfunction
をなるべく書かない
- アロー関数またはクラスやオブジェクトのメソッドを使う
- 理論上どうしても必要な場合のみ
function
then()
を書かない
await
を使う- jQuery を使わない
- 純粋な WEB API を使用して DOM を走査する
fetch()
で通信する- XMLHttpRequest を使わない
fetch()
で通信する1. 対象環境
本記事では、IE 以外の主要ブラウザおよび Deno での JavaScript を想定しています。
Node.js は一部異なる場合があります。
IE 対応が必要な場合は、Babel 等のトランスコンパイラの使用を検討してください。
2.
var
を書かない
const
,let
は IE 11 ですら使用できます ※ 。※
for
文内でのlet
の挙動が異なるようです。2.1. なるべく
const
を使う(JavaScript は関数型言語ではありませんが) 関数型言語の概念から、変数を使わずになるべく定数だけを使用することでバグの発生リスクを抑えることができることが分かるため、なるべく
const
を使用してください。なるべく
const
で書くためのいくつかのテクニックがあります。
- 値を書き換えない
if
文の代わりに三項演算子等の使用を検討する
- 三項演算子
(c ? t : f)
- 論理和
||
const z = x || y;
(Falsy な値の注意が必要)- Null 合体演算子
??
- Optional Chaining 演算子
?.
- 配列などのコレクションの操作は
map()
やreduce()
等の反復メソッドをなるべく利用する- 別関数に分離して直接
return
する2.1.1. 値を書き換えないものはとにかく
const
MDN や Google の開発者向けの一部のヘルプページなどでも、サンプルコードで
let
が多用されている場合がありますが、値を書き換えないものは全てconst
にします。良くない例let response = await fetch(url); let json = await response.json(); let message = json.message;良い例const response = await fetch(url); const json = await response.json(); const message = json.message;2.1.2. 同じ変数を使いまわさず、別名の定数を作る
例えば、変数の値の形式を変換するような処理をしたいとき、意味的には中身が同じなため同じ変数名を使いたくなりますが、以下を参考にして別名の定数を作ってください。
- 英語の過去分詞や形容詞を付ける
- 変換後の型名を付ける
(※臨機応変に名前を使用してください。)
良くない例let html = '<div>nya</div>'; html = html.replace(/</g, '<').replace(/>/g, '>'); heml = new TextEncoder().encode(html);良い例const html = '<div>nya</div>'; const escapedHtml = html.replace(/</g, '<').replace(/>/g, '>'); const htmlUint8Array = new TextEncoder().encode(escapedHtml);2.1.3.
if
文の代わりに三項演算子等の使用を検討する
- 三項演算子
(c ? t : f)
- Null 合体演算子
??
- Optional Chaining 演算子
?.
良くない例const options = {}; let isYes = true; if ( 'something' in options && 'isYes' in options.something ) { isYes = options.something.isYes; } else { isYes = true; } let ynStr; if ( isYes ) { ynStr = 'Yes'; } else { ynStr = 'No'; }良い例const options = {}; const isYes = options.something?.isYes ?? true; const ynStr = (isYes ? 'Yes' : 'No');三項演算子は本来括弧
()
を必要としませんが、演算子の優先順位が分かりにくくなることがあるため、括弧()
を付けた方が良いと思います。参考「条件 (三項) 演算子 - JavaScript | MDN」
参考「Null合体演算子 - JavaScript | MDN」
参考「Optional chaining - JavaScript | MDN」Null 合体演算子
??
の代わりに論理和||
を使用する場合は、Falsy な値の注意が必要です。// const size = 0; console.log(size ?? 1024); // 出力: 0 console.log(size || 1024); // 出力: 1024 // const text = ''; console.log(text ?? '(default)'); // 出力: '' console.log(text || '(default)'); // 出力: '(default)'参考「論理和 (||) - JavaScript | MDN」
2.1.4. コレクションの操作は反復メソッドをなるべく利用する
例:
map()
: 配列を同じ length のまま変換するreduce()
: 配列から 1 つの値に変換する
- 文字列にしたい場合は
map()
とjoin()
を使用したほうが便利filter()
: 配列の一部を取り出して新しい配列を返す※他にも便利なメソッドがあります。
良くない例const dataArray = [{ id: 58 }, { id: 25 }, { id: 67 }, { id: 10 }]; let maxId = 0; for (const data of dataArray) { maxId = Math.max(maxId, data.id); }良い例const dataArray = [{ id: 58 }, { id: 25 }, { id: 67 }, { id: 10 }]; const maxId = dataArray.map(data => data.id).reduce((acc, id) => Math.max(acc, id), 0);参考「反復メソッド - Array.prototype - JavaScript | MDN」
2.1.5. 別関数に分離して直接
return
する
const
を使用するかどうかに関わらず、機能を適切に別関数やメソッドに分割することはソースコードの品質を向上させ、バグの削減など保守性も良くなります。良くない例const profiles = [{ id: 22, name: 'kitty' }, { id: 1, name: 'puppy' }, { id: 33, name: 'bunny' }]; let profilesHtml; if ( profiles.length !== 0 ) { profilesHtml = '<div>' + profiles.map(profile => '<span>ID: ' + profile.id + ', Name: ' + profile.name + '</span>').join('<br>') + '</div>'; } else { profilesHtml = '<div>No Profile</div>'; }良い例// const getProfilesHtml = profiles => { if ( profiles.length !== 0 ) { return '<div>' + profiles.map(profile => '<span>ID: ' + profile.id + ', Name: ' + profile.name + '</span>').join('<br>') + '</div>'; } else { return '<div>No Profile</div>'; } }; // const profiles = [{ id: 22, name: 'kitty' }, { id: 1, name: 'puppy' }, { id: 33, name: 'bunny' }]; const profilesHtml = getProfilesHtml(profiles);2.2.
const
が使えないと勘違いされがちなもの2.2.1. 配列は
const
でも中身を書き換えられる配列の中身を操作したいために
let
にしているコードを見かけますが、配列の宣言はconst
でも中身を書き換えられます。配列をまるごと書き換えることはできなくなります。
const array = [2, 5, 8]; array.push(15); // array: [2, 5, 8, 15] array = [3, 6, 9, 18]; // TypeError2.2.2.
for...in
やfor...of
はconst
を使える
for...in
やfor...of
でconst
を使用すると、ループのたびに別の定数として扱われます。for (const property in object) { console.log(property); } for (const value of array) { console.log(value); }2.3.
for (;;)
はlet
を使用する当たり前に見えますが、経験上、そもそも
for (;;)
で書きたくなる処理は反復メソッド等で処理することがむずかしい場合が多いです。
for (;;)
で書きたい処理に関しては無理にconst
で実現しようと思わず、let
を使用していいと思います。3.
function
をなるべく書かない
function
はthis
の挙動が特殊なため、特別な理由がない限りは使用しない方が混乱を避けられます。単にコールバック関数を生成したい場合はアロー関数を使用してください。
クラスやオブジェクトのメソッドの定義では
function
を表記せずに定義でき、this
に関する混乱は起きにくいです。(※メソッド単体を取り出すような処理をすると混乱が起きます。)
以前はオブジェクトでのメソッド定義で
function
が必要でしたが、今では不要です。参考「クラス - JavaScript | MDN」
参考「メソッドの定義 - オブジェクト初期化子 - JavaScript | MDN」
addEventListener()
ではthis
を使用せず、コールバック関数の引数event
からevent.currentTarget
でイベントがアタッチされた要素を取得できます。(どうしても
function
でないと実現不可能な場合もあります。)参考「JavaScript の デコレータ の使い方 - Qiita」
4.
then()
を書かないIE 以外の主要ブラウザでは Promise に対応しており、そのいずれもが
async
,await
をサポートしています。
then()
は使用せずにawait
を使用してください。参考「Promise - JavaScript | MDN」
参考「await - JavaScript | MDN」5. jQuery を使わない
jQuery は昔は便利なライブラリでしたが、今では使用するメリットがあまりありません。
jQuery のごく一部の機能を利用するためだけに、WEB ページの読み込み時間を長くするよりも、純粋な Web API を使用するか、より複雑な WEB ページに向いているライブラリの React 等を使用してください。
ソースコードの意味的な構造を小さくできることは可読性が上がり、保守性などの点から見ても良いことですが、jQuery というサイズの大きなライブラリを読み込んだうえで、WEB API の長いメソッド名を
$
1 文字に置き換えることは意味的には規模が変わらず、メリットになりません。ID やクラス名で要素を取得したいということが分かっているなら、
getElementById()
やgetElementsByClassName()
を使用したほうが高速に動作します。jQuery のように複雑なセレクタを用いて要素を取得したい場合は、
querySelector()
かquerySelectorAll()
を使用してください。参考「Document.querySelector() - Web API | MDN」
参考「Document.querySelectorAll() - Web API | MDN」
jQuery.ajax()
を使わなくてもfetch()
で簡単に通信できます。参考「WindowOrWorkerGlobalScope.fetch() - Web API | MDN」
6.
XMLHttpRequest
を使わない
fetch()
で簡単に通信できます。