20200726のJavaScriptに関する記事は26件です。

クリックイベントを取り消す処理について

クリックしたらドロップダウンでメニューが出てくるのを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

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

やらかし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

少しだけ言い訳をしたい:sweat_smile:

いつもはJSONでファイル出力することが多いため、どうしてもそのまま出力できるObjectを利用することが多い。
そんな折、ファイル出力ないものがあったので、久しぶりにMapを使ったらずいぶん忘れていただけ。
今はもう大丈夫なんで:rolling_eyes:

答え合わせ

正解はこうなる。

terminal

何も出ないが正解:joy:

そもそも、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

何も出ない:joy:

よく見ると、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"

値がない:joy:

そういえば、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:⭕"

動いた:sob:

ちなみに、以下のように、値のセットを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

MapObjectと似ているように見えるが、使い方が異なるのでうろ覚えでの使用には注意!
特に存在チェックとセットメソッドを混在して利用する等すると泥沼。

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

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 -w

typescript 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)
typeAcquisition

compileOptions : 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 error
interface 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 error

class 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 log

9. 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?"; // error

Generic 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 error

11. 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]: second 

12. 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 
//hello

Method 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
// 1

13. 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 union

Union 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);
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

textareaの文字を色分けする(しているように見せる)方法を考えてみた

codepenやGASのエディタでは、入力された文字は次々と色分けされていきます。本記事ではそんな機能を、つまり入力された文字をそれぞれ色分けする機能を、JavaScriptのライブラリを駆使して「簡単に」再現してみようと思います。…再現と書きましたが、原理は根本的に異なるものであるということはあしからず。

sfdfg.png
サンプルのスクリーンショット
CSSを入力したら、こんな具合に色分けする。

必要なもの

  • 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>

この場合、.twrapposition: 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.js
var 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のありのままの大きさを測り、それを.twrapheightとして反映すれば良いのです。そして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.js
var 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のせいで余計な回り道をさせられています。この例に限った話ではないです。独自路線を突っ走り過ぎると嫌われますよ。

サンプル

http://topia.wdfiles.com/local--code/css-parser-1/1

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

【スクレイピングまとめ】| Python Node.js PHP Ruby Go VBA | 6種類の言語でヤフートップをスクレイピング

Python

動画

IMAGE ALT TEXT HERE

リポジトリ

https://github.com/yuzuru-program/scraping-python-yahoo

ソース

index.py
import 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

動画

IMAGE ALT TEXT HERE

リポジトリ

https://github.com/yuzuru-program/scraping-node-yahoo

ソース

package.json
{
  "dependencies": {
    "cheerio": "^1.0.0-rc.3",
    "node-fetch": "^2.6.0"
  }
}
index.js
const 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

動画

IMAGE ALT TEXT HERE

リポジトリ

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

動画

IMAGE ALT TEXT HERE

リポジトリ

https://github.com/yuzuru-program/scraping-ruby-yahoo

ソース

index.rb
require "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
end

Go

動画

IMAGE ALT TEXT HERE

リポジトリ

https://github.com/yuzuru-program/scraping-go-yahoo

ソース

index.go
package 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

動画

IMAGE ALT TEXT HERE

ソース

'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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

4連休だったからmarkdownに時間を追加してプレビューするvscode extension作った

感謝

https://github.com/showdownjs/showdown
上記repositoryをcloneして中身を編集して使いました
テストは高速、buildもタスクで一発だった。
素晴らしいリポジトリだった

https://github.com/kajirikajiri/showdown
オプションとか、正規表現とか追加したバージョン

概要

コメント 2020-07-26 190357.png

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になってしまった

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

【JavaScript】厳密等価演算子と等価演算子

厳密等価演算子(===)

比較対象が同じ型で同じ値である時trueを返す。

console.log(1 === 1); // true
console.log("test" === "test"); // true

 等価演算子(==)

暗黙的な型変換をした後に比較される

console.log(1 == "1"); // true

等価演算子(==)の使いどき

console.log(null == undefined); // => true

上記のように、nullとundefinedを比較した時はtrueを返すので、その比較が必要な場合は便利かもしれない。

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

【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というシンタックスシュガーを使ったものもあるので気になる方は、ググった方がよき。

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

作って理解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

オブジェクトの作成は以下のように行っています。

  1. 空のオブジェクトを作成しスタックにプッシュ(MAKE_OBJECT
  2. 名前をPUSH
  3. 値をPUSH
  4. オブジェクト、名前、値をスタックからポップしプロパティとして設定(PROP_DEFINE1
  5. 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内でしてしまうことでAssemblerVmは変更なしで動作するようにしました。

ちなみにComputed property namesも対応はできますがJOKE実装では使ってないしサポートしてません:stuck_out_tongue:

スプレッド

スプレッドの仕様見当たらないな(。´・ω・)?
とよく考えたらオブジェクトのスプレッドは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でもう少し改造が入ります。

  1. setProperty時にvalueがオブジェクトの場合はvaluethisValueを設定する(関数はオブジェクトで定義されているのでthisValueが設定される)
  2. callFunctionでオブジェクトに設定されてるthisValueを取り出しcontextに設定、呼び出す
  3. thisに対応する命令のTHISでは単にcontextに設定されているthisValueを取り出す(スタックにプッシュする)

ステップ10:クラス

オブジェクトと関連が深いからということでクラスについてもまとめて説明します。JavaScriptの場合、「クラス」というと話は長くなりますがES2015で定義されたclassのこととします。
と言いつつ、実体は・・・なのですが、そもそもJOKEを作ってみようと思った因縁の機能(?)ですね。

ステップ10段階のコードは以下にあります。
https://github.com/junjis0203/joke/tree/step0010

prototype形式とnew

V8などの内部実装がどうなってるのかはわからないですが、とりあえずJOKEではclassで定義されたクラスもprototype形式に変換して動かすことにしました。
ところでVSCodeだとprototype形式で書いているとこんなことが言われます。最近のエンジンは「クラス形式」に変換されてるっぽいですね。

prototype.jpg

ステップ10の前準備として「オブジェクト」に対応するJokeObject5を継承した(このために継承も実装することになりました)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-definitions

ClassHeritage :
    extends LeftHandSideExpression

そう、extendsの後ろはLeftHandSideExpressionなのでハードコードなクラス名だけではなく変数でも関数呼び出しでも書けるのです!
つまり以下のコードは正しいJavaScriptプログラムです。

class Foo {}

const cls = Foo;
class Bar extends cls {}

const func = () => Foo;
class Baz extends func() {}

まあフレームワークの中ぐらいでしか使わない、というか今のところフレームワークの中でも見たことないですが(笑)

もう一つ文法的な話。superconsole.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

Arraylengthは特別処理しようかなと考えていたのですがよく思うとgetter/setterとして一般化できるので実装しました。

getter/setterに関する仕様はこちら。要するにgetsetを付けたメソッドを作ればいいようです(ちゃんと読み込むとそれだけではない気がする)
http://www.ecma-international.org/ecma-262/6.0/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation

VmではSET_PROPERTY時にsetterがあるか、GET_PROPERTY時にgetterがあるかを調べるようにしました。

デバッグ機能

今までは-dオプション一つで「構文解析結果」「アセンブル結果」「今実行している命令」を出力していましたが出力が多くなってきたのでそれぞれ別オプションに分けました。
また、「今実行している命令」は追加情報として「今のスタック」を表示するようになりました。クラスまで来ると「動きがおかしいのだけどスタックに積まれてるデータどうなってるの?」ということが多くなってくるので役立っています(今までは都度console.logを入れていた)

あとがき

以上今回はオブジェクトとクラスの実装について説明しました。これでだいぶJavaScriptっぽいプログラムまで対応できるようになったと思います。
・・・「普通の使い方」についてはそこそこ動くと思うのですが「変わった書き方」や「仕様にも書かれている細かな文法エラー・実行時エラー」はあまりサポートできていません。まあそのうち。

ともかく次は満を持して配列(メソッド含む)です。
ただその前に、mapなどに渡すコードバックはアロー関数で書きたいのでアロー関数を実装する予定です。


  1. ステップ10終わりに名前の見直しを行ったのでHEADではDEFINE_PROPERTY。 

  2. // len is added in runtimeというコメントは試行錯誤してたときの残骸コメントですね・・・ 

  3. その際に大事なのは既存部分が壊れてないかを確認できるテストですね:grinning: 

  4. HEADではSET_PROPERTYに名前変更。 

  5. ステップ1で「とりあえずのオブジェクト表現」を作りそのまま進めてきましたがリファクタリングしました。Jokeと付いているのは素のObjectと被るためです。 

  6. JokeObjectは「JOKEを動かしているJavaScriptエンジン」での「Object(を継承したクラス)のインスタンス」のため、そちらが取得される。 

  7. 使うことがあるかはわかりませんが仕様的には「スーパークラスのメソッド呼び出し」だけではなく、「(オーバーライドしてthisではアクセスできないスーパークラスのプロパティ」を取ることができます。 

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

Mildomのチャット取得

コメントビューア用。
OAuthが要るようなAPIは使わず、未登録で取得できるコメントを取得。
あくまで執筆時点の情報です。仕様が変わる可能性もあるのであしからず。

ユーザのチャンネルURL

  • https://www.mildom.com/{ユーザID}
    • 例: https://www.mildom.com/10946091

手順

  1. 以下にWebSocket接続

    • wss://jp-room1.mildom.com/?roomId={ユーザID}
  2. メッセージ送受信

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,
  // 省略
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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コンポーネントのスタイルの真のカプセル化に対する当分約束されたソリューションであるとしばしば言われている。 もっと詳しく:

shadow DOMを使う

4. TypeScriptの台頭

最近、どんな会話でもTypeScriptがフロントエンド開発を支配するかのように言われている。 80%の開発者が次のプロジェクトでTypeScriptを使用または学習したいと認めているという報告がある 。

TypeScriptには欠点もあるが、コードは理解しやすく、実装が高速で、バグが少なく、必要なボイラープレートが少なくて済む。 TSで動作するようにReactアプリをリファクタリングしてみたい? やってみよう。 徐々に始めたい? Bitなどのツールを使って、アプリのコンポーネントを徐々にTSにリファクタリングしてみよう。そしてそれらを React-Typescriptコンパイラを使用してアプリとは独立にビルドしよう。 この方法では、コードを1コンポーネントずつ徐々にアップグレードできる。

もっと詳しく:

TypeScriptが2019年にフロントエンドを作成する最良の方法である理由

LyftでのTypeScript

SlackでのTypeScript

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の更新をビルド・デプロイし、フロントエンドを構成することができるようになるということだ。

再利用可能なコードコンポーネントをチームとして共有する・Bit

UIコンポーネント設計システム・Bit

teambit / bit

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のようなプラットフォームと統合されており、これまでよりも厳しいワークフローを作成できる。

UIコンポーネント設計システム:開発者ガイド

UIデザインシステムとコンポーネントライブラリ:物事が壊れるところ

2020年にデザインシステムを構築するための7つのツール

11. Webアセンブリ — 未来へ?

Webアセンブリ(Wasm) はWeb開発に言語多様性をもたらし、JavaScriptによってもたらされた隔たりを埋めてくれる。 Wasmは、「スタックベースの仮想マシン用のバイナリ命令フォーマット」として定義されている。 Wasmは、C / C ++ / Rustのような高水準言語のコンパイル用のポータブルターゲットとして設計されており、クライアントアプリケーションおよびサーバーアプリケーションのWebへの展開を可能にしてくれる。

Eric Elliott は彼の投稿でそのコンセプトの利点を次のようにエレガントに概説している。

  • JavaScriptの改善: wasmでパフォーマンスが重要なものを実装し、標準のJavaScriptモジュールのようにインポートできる。
  • 新しい言語: WebAssemblyコードは、バイナリ形式で表されるAST(抽象構文ツリー)を定義できる。 テキスト形式で書いてデバッグ可能だ。つまり可読性が高い。
  • ブラウザの改善: ブラウザはバイナリ形式を理解できる。これは、現在使用しているテキスト形式のJavaScriptコードよりもより高くい圧縮率をもつバイナリバンドルをコンパイルできることを意味する。 ペイロードが小さいほど、配信が速くなる。 また、コンパイル時の最適化のケースによっては、WebAssemblyバンドルもJavaScriptよりも高速に実行されるかもしれない!
  • コンパイルターゲット: 他の言語がWebプラットフォームスタック全体でファーストクラスのバイナリサポートを取得する方法である。

なぜそれが有用で、どこで使用されるようになるのか、そしてなぜまだ目にしないのか、このコンセプトについてさらに理解するためには、この素晴らしい投稿この素晴らしいビデオを見ることをおすすめする 。

WebAssemblyが必要な理由:Brendan Eichへのインタビュー


もっと詳しく

Reactでのコード再利用を最大化する

2020年のトップ13のReactコンポーネントライブラリ

2020年のトップ11のAngular開発者ツール

2020年のトップ11のVue開発者ツール

翻訳協力

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でもご意見を受け付けております。
みなさまのメッセージをお待ちしております。

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

javascript-ゲームを作ってオブジェクトを学ぶ-

この記事について

現役プログラマーさんから教えていただいたことをまとめている記事です。今回はJavaScriptの基礎を使ったゲームづくりを通して、教えていただいたことをまとめていきます。

関数

まずfunctionを使った足し算をする簡単な関数をみていきます。
sumNumberは関数名で、自由に名前をつけることができます。
a,bは引数で、後にから代入する具体的な数字を入れるための箱のようなものです。

//aとbを足して、コンソールに表示する関数を定義
function sumNumber(a,b){
    console.log(a+b);
}
//関数名とa,bに入る数字を記入すると、上の関数に代入されます。
sumNamber(3,4);

//結果:7

allow関数

上の関数を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-26 15.29.08.jpg

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

Springで勤怠サイトを作ってみる。【概略】

概要

評価面談でSpringを勉強していると言ってしまったばっかりに、
次回面談までに勤怠サイトを作ることに・・・。
実業務でSpringベースの開発経験はあるけども、全体的に素人に毛が生えたレベル。

PCの調子が悪くて(なんかカリカリ音がしている)成果物が消える恐れがあるので、
面談時のエビデンスのためにも作業履歴としてQiitaに残そうかな、と。

構成(想定)

  • Webアプリ
    • フロント:Vue.js
    • バックエンド:Spring
    • DB:PostgreSQL


※追記事項があり次第記載します

環境

  • Eclipse - Version: Oxygen.3a Release (4.7.3a)
  • Atom


※追記事項があり次第記載します。

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

【Rails】JavaScriptが絡むテスト

はじめに

Capybaraでのテストで、JavaScriptが作動する処理をする場合には少し設定を変える必要があります。
今回は簡単な例としてクリックするとテキストが変わる処理についてテストしてみます。

36a2e16cd12c7ebb3b25be633051a96a.gif
railsのバージョンは5.2.3を使用しています。

設定

まずは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.rb
require '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
end

jsを動作させたい場合は上述のように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

fcbc5bed345b38c37a9394684c68513a.png
うまくテストが通ってます。

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

LINE LIVEのチャット取得

コメントビューア用。
OAuthが要るようなAPIは使わず、未登録で取得できるコメントを取得。
あくまで執筆時点の情報です。仕様が変わる可能性もあるのであしからず。

ユーザのチャンネルURL

  • https://live.line.me/channels/{ユーザID}

手順

  1. 動画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として取得
  2. チャットの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}/{配信ごとの英字。乱数っぽい}
  3. チャット受信

    • 取得したチャット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
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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ではなく私のセンスの問題です。)
image.png

スマホにするとこんな感じです。
文字サイズとかいい感じに変わってくれるところとか素晴らしいです。
image.png

ダークモードにするとこんな感じです。
本来はOSの設定に応じて切り替わりますが、Reactの勉強がてら切り替えボタンをつけてみました。文字色もうまく変えてくれています。
image.png

キャプチャではわかりませんが、検索APIを呼び出している間は、ProgressCircleがくるくる回ります。
image.png

中身の説明

リセット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.js
import 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">
              freeCodeCampHow 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 SpectrumA React implementation of Spectrum, Adobes design system.
          </a>
          </Link>
        </Footer>
      </Flex>
    </Provider>
  );
};

export default App;

ちなみにヘッダーコンポーネントも中身の配置を制御するのに、flexbox化しています。

HookedHeader.js
import 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-0size-10size-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

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

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ではなく私のセンスの問題です。)
image.png

スマホにするとこんな感じです。
文字サイズとかいい感じに変わってくれるところとか素晴らしいです。
image.png

ダークモードにするとこんな感じです。
本来はOSの設定に応じて切り替わりますが、Reactの勉強がてら切り替えボタンをつけてみました。文字色もうまく変えてくれています。
image.png

キャプチャではわかりませんが、検索APIを呼び出している間は、ProgressCircleがくるくる回ります。
image.png

中身の説明

リセット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.js
import 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">
              freeCodeCampHow 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 SpectrumA React implementation of Spectrum, Adobes design system.
          </a>
          </Link>
        </Footer>
      </Flex>
    </Provider>
  );
};

export default App;

ちなみにヘッダーコンポーネントも中身の配置を制御するのに、flexbox化しています。

HookedHeader.js
import 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-0size-10size-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

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

指定時刻のエポック秒を得る方法の比較(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()
< 1595722136

bashで現在時間の確認

bashで得られたエポック秒が復元できるか確認してみましょう。同じエポック時間で変換します。

$ gdate '-d @1595722136'
日  7 26 09:08:56 JST 2020

OKです。

指定時刻のエポック秒を得る

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()があるからそれ使えばいいじゃんと考え直した次第です。

自分の備忘録のために書きましたがどなたかのお役に立てれば嬉しいです。

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

指定時刻のエポック秒を得る方法(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()
< 1595722136
bashで確認

bashで得られたエポック秒が復元できるか確認してみましょう。同じエポック時間で変換します。

$ gdate '-d @1595722136'
日  7 26 09:08:56 JST 2020

OKです。

(本題)指定時刻のエポック秒を得る

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()があるからそれ使えばいいじゃんと考え直した次第です。

自分の備忘録のために書きましたがどなたかのお役に立てれば嬉しいです。

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

アロー関数式初心者覚書

初心者覚書です。時々JSを使おうとするとアロー関数のことを忘れているのでメモとして作りました。ほぼ参考を読み、箇条書きにしただけなので、参考サイトを見た方がわかりやすいです。
アロー関数とは

  • アロー関数式は、メソッドでない関数に最適で、コンストラクタとして使うことはできない
  • 関数を短く書きたい、thisを束縛したくないという2つの理由がある
  • 通常関数にはthisがあり、thisが何を指すのかは、通常関数を実行したタイミングで決まるが、
  • アロー関数には、それ自体が保有するthisはなく、関数の外のthisを関数内で参照できるだけです。レキシカルスコープのthisを参照します。つまり、アロー関数は定義したときに、thisが指すものがひとつに決まり、どうやって関数が実行されるかに左右されなくなります。
  • アロー関数はnewすることができません。つまり、アロー関数はコンストラクタになることができない>通常関数はclassでextendsできますが、アロー関数はできない
arrow.js
const materials=[
'aaa',
'bbb',
'ccc'
];

console.log(materials.map(material => material.length));

参考:アロー関数を説明

getTriangle.js
let getTriangle = function(base,height){
return base * height /2;
};
console.log(getTriangle(10,2));
getTriangle_arrow.js
let getTriangle = (base,height)=>{
return base * height /2;
};
console.log(getTriangle(10,2));

本文が一文

getTriangle_arrow_short.js
let getTriangle = (base,height)=> base * height /2;
console.log(getTriangle(10,2));

引数がない場合

getTriangle_arrow_nohikisu.js
let 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.js
const 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

やばい、色々調べているだけで人生が終わりそう

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

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を用いて参照する方法もあると思います。

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

【TypeScript/JavaScript】一定条件になるまでループする

for (;;) + break 版

let num = 0

for (;;) { 
  num += 1
  const ok = num >= 10
  if (ok) break
}

console.log(num) // => 10

do ... while 版

let num = 0
let ok = true

do {
  num += 1
  ok = num < 10
} while (ok)

console.log(num) // => 10

or

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

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

OPENRECのチャット取得

コメントビューア用。
OAuthが要るようなAPIは使わず、未登録で取得できるコメントを取得。
あくまで執筆時点の情報です。仕様が変わる可能性もあるのであしからず。

ユーザのチャンネルURL

  • https://www.openrec.tv/live/{ユーザID}
    • 例: https://www.openrec.tv/live/pasta04

手順

  1. ユーザ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をチェック。

  2. 動画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,
            // 省略
        },
        // 省略
    }[]
    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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で順番取り予約システムの作成
「LINE Bot+APIで表現してアウトプット」 概念図.png

BOTを改良

1.フレックスメッセージに変更
フレックスメッセージはこちらを使うととても簡単に作成できます。
FLEX MESSAGE SIMULATOR

システム利用時間
IMG-1197.jpg

現在の待ち状況
IMG-1200.jpg

予約券発券
IMG-1203.jpg
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名の方が予約システムを利用されました。特にトラブルなく運用できましたが順番に遅れる方が何名かいたようです。プッシュ機能を使い来院を呼びかけるようにすると良いのかもしれませんがすぐ無料枠を超えてしまうので何かほかに良い方法がないか考え中です。

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

Twicasのチャット取得

コメントビューア用。
OAuthが要るようなAPIは使わず、未登録で取得できるコメントを取得。
あくまで執筆時点の情報です。仕様が変わる可能性もあるのであしからず。

ユーザの配信URL

  • https://twitcasting.tv/{ユーザID}
    • 例: https://twitcasting.tv/pasta04

手順

  1. ユーザIDを元に、動画ID(movieID)を取得

    • リクエスト
      • GET https://frontendapi.twitcasting.tv/users/{ユーザID}/latest-movie
    • レスポンス
      {
          "update_interval_sec": 4,
          "movie": {
             "id": {movieID},
             "is_on_live": true  // 配信中かどうか
           }
      }
    
  2. 動画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={なんやかんや}"
      }
    
  3. チャット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
    }
}[]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

イマドキな 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 の挙動が異なるようです。

参考「let - JavaScript | MDN

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, '&lt;').replace(/>/g, '&gt;');
heml = new TextEncoder().encode(html);
良い例
const html = '<div>nya</div>';
const escapedHtml = html.replace(/</g, '&lt;').replace(/>/g, '&gt;');
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]; // TypeError

2.2.2. for...infor...ofconst を使える

for...infor...ofconst を使用すると、ループのたびに別の定数として扱われます。

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 をなるべく書かない

functionthis の挙動が特殊なため、特別な理由がない限りは使用しない方が混乱を避けられます。

単にコールバック関数を生成したい場合はアロー関数を使用してください。

参考「アロー関数 - JavaScript | MDN

クラスやオブジェクトのメソッドの定義では function を表記せずに定義でき、this に関する混乱は起きにくいです。

(※メソッド単体を取り出すような処理をすると混乱が起きます。)

以前はオブジェクトでのメソッド定義で function が必要でしたが、今では不要です。

参考「クラス - JavaScript | MDN
参考「メソッドの定義 - オブジェクト初期化子 - JavaScript | MDN

addEventListener() では this を使用せず、コールバック関数の引数 event から event.currentTarget でイベントがアタッチされた要素を取得できます。

参考「Event - Web API | MDN

(どうしても 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() で簡単に通信できます。

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