分割代入でオブジェクトにある特定の値を取得してくる

今回は分割代入でオブジェクトにある特定のキーの値を取得してくる方法を見ていきたいと思います。

経緯

reduxのコードを見ている時に分割代入をしているコードを見かけて挙動が気になった。

従来の方法と分割代入の比較

nameを取得する

const person = {
  name: "ジロリン丸",
  address: "Tokyo",
}

// 従来
const name = person.name
console.log(name) // ジロリン丸

// 分割代入
const { name } = person
console.log(name) // ジロリン丸

複数階層の時

この中のbrotherを取得する

const person = {
  name: "ジロリン丸",
  address: "Tokyo",
  family: {
    brother: 2,
    sister: 1
  },
  favoriteFoods: ["ramen", "ziro"]
};

// 従来
const brother = person.family.brother;
console.log(brother) // 2

// 分割代入
const { family: { brother } } = person;
console.log(brother) // 2

以上になります。

まとめ

分割代入は便利ではありますが、読みやすさという点では最初理解しずらいところはあるかもしれません。それを踏まえても便利な点が多いので積極的に使っていこうと思います。

ReactのライフサイクルやPureComponentについて

今回はReactのライフサイクルやPureComponent辺りについて紹介したいと思います。

早速ですが、ライフサイクルはMountingUpdatingUnmountingError Handlingがあります。(今回の記事ではUnmountingとError Handlingについては紹介しません。)

Mounting

reactjs.org

以下のメソッドは、コンポーネントインスタンスを作成してDOMに追加される時に呼び出されます。呼び出される順番は以下の順番です。

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

Updating

reactjs.org

以下のメソッドはコンポーネントが再レンダリングされる時に呼ばれます。

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

ここで重要なのはshouldComponentUpdateです。 コンポーネントがrenderされる前に呼ばれます。 このメソッドは、nextProps, nextStateを受け取り現在のpropsとstateを比較します。更新するときはtrueを更新が必要ない場合はfalseを返します。

注意したいのは、React.Componentの場合はshouldComponentUpdateはデフォルトでtrueを返すということです。 なのでReact.Componentの場合はshouldComponentUpdateでロジックを書く必要があります。

簡単な例

type Props = {
  name: string;
  age: number;
}

class User extends React.Component<Props> {
  shouldComponentUpdate(nextProps:Props) {
    if(this.props.name === nextProps.name) {
      return true
    } else {
      return false
    }
  }
  /* 以下省略 */

例えば親のコンポーネントの状態だけが変更し、子のコンポーネントのpropsやstateに変化がない場合に子のコンポーネントも一緒にマウントされます。そうするとコンポーネントの描画速度が遅くなります。ですのでReact.Componentで書く場合は、ちゃんとshouldComponentUpdateを使って描画パフォーマンスを最適化してあげる必要があります。

React.PureComponent

次に最初の方で挙げたReact.PureComponentについてです。

reactjs.org

結論を先に言うとReact.PureComponentshouldComponentUpdateの処理が実装済みとなっています。ですのでReact.PureComponentを使うのが良いでしょう。ですがここで注意したいのは、React.PureComponentのshouldComponentUpdateはshallow compareするので以下のケースの場合、常にマウントされる結果となります。

class App extends React.PureComponent {
  render() {
    return (
      <div className="App">
        <User families={{ brother: 2, sister: 1 }} name="sato" age={20} />
      </div>
     );
  }
}

propsにはつねに新しいオブジェクトを受け取っています。 shallow compareなのでプロパティと値が正しくてもshouldComponentUpdateの戻り値はtrueを返してしまいます。今はオブジェクトの階層は深くありませんが、もっと階層がある場合などこのような場合などを考慮するとPureComponentの受け取るpropsやstateなどはシンプルなものにすると良いでしょう。また深い階層でpropsやstateの値が変更されるとわかっている場合は、forceUpdate()を使うのも一つの方法です。

Shallow Compareについて

reactjs.org

Pure.Componentがリリースされる前は、Shallow Compareを使用していました。このアドオンがPure.Componentの役割を果たしていました。

以下のように使います。(公式サイトから引用)

// インポートする方法

import shallowCompare from 'react-addons-shallow-compare'; // ES6
var shallowCompare = require('react-addons-shallow-compare'); // ES5 with npm

shallowCompareメソッドには同じようにnextPropsnextStateを渡してあげます。そうすることでshallowCompareが比較してくれます。

export class SampleComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

まとめ

Reactのコンポーネントの描画する仕組みを知っておくことで、最適な描画パフォーマンスを実現できると思います。 今回紹介したもの以外にReact.memorecomposeのpureなどがあるので次はこれらについて紹介したいと思います。

JavaScriptの非同期処理とスコープの関係性について

今回非同期処理スコープの関係性について紹介したいと思います。

唐突ですが以下のコードをご覧になってください。

function countdown() {
  let i;
  for (i = 5; i >= 0; i--) {
    setTimeout(function() {
      console.log(i === 0 ? "GO!" : i);
    }, (5 - i) * 500);
  }
}
countdown();

出力結果はどうなると思いますか?

結果は以下になります。

f:id:top_men:20181103122242p:plain

このコードのポイントとしては、変数iがfor文の外にあるということです。setTimeoutが実行される時にはiの値が-1になっているのでsetTimeoutが参照する値が2行目のiつまり-1になります。

では以下のコードではどうなるのでしょう?

function countdown() {
  for (let i = 5; i >= 0; i--) {
    setTimeout(function() {
      console.log(i === 0 ? "GO!" : i);
    }, (5 - i) * 500);
  }
}
countdown();

結果は以下となります。

f:id:top_men:20181103163708p:plain

こちらは意図としたように動きます。 先ほどのコードとの違いは、変数iをforループの制御文の箇所で利用しています。 JavaScriptの処理系はループの各ステップで新しい独立した変数iのコピーを作成します。setTimeoutに渡された関数が実行されるときに、自分の独自のスコープになる変数から値を受け取ることになります。

言葉ではなかなかわかりづらいので下記のコードをみてみてください。

// 1回目
{
  let i = 5
  setTimeout(function() {
    console.log(i === 0 ? "GO!" : 5); // 5
  }, (5 - 5) * 500);
}

// 2回目
{
  let i = 4
  setTimeout(function() {
    console.log(i === 0 ? "GO!" : 4); // 4
  }, (5 - 4) * 500);
}

// 3回目
{
  let i = 3
  setTimeout(function() {
    console.log(i === 0 ? "GO!" : 3); // 3
  }, (5 - 3) * 500);
}

// 4回目
{
  let i = 2
  setTimeout(function() {
    console.log(i === 0 ? "GO!" : 2); // 2
  }, (5 - 2) * 500);
}

// 5回目
{
  let i = 1
  setTimeout(function() {
    console.log(i === 0 ? "GO!" : 1); // 1
  }, (5 - 1) * 500);
}

// 6回目
{
  let i = 0
  setTimeout(function() {
    console.log(i === 0 ? "GO!" : 0); // "GO!"
  }, (5 - 0) * 500);
}

setTimeout関数が実行される時には上記のように各ステップで新しくコピーされた変数iを参照していることになります。

まとめ

どうしてこのような結果になるのか最初理解するのに時間が掛かりました。JavaScriptにおける非同期処理スコープについてはちゃんと理解しておく必要があります。 間違いがあれがご指摘ください!

参考記事・書籍

js-next.hatenablog.com

www.oreilly.co.jp

TypeScriptの型の互換性について

今回はTypeScriptの型の互換性について取り上げます。

型の互換性・・・??

簡単に言うと例えば、ある型の変数であるHogeに、別の型であるFugaが代入できれば、型の互換性があると言えます。逆に代入できなければ型の互換性がないとなります。

この型の互換性がどのように決まるかは言語によって異なります。TypeScriptの場合は、構造的部分型(Structural Subtyping)が採用されています。

言葉だけで捉えるとなんだか難しそうですね・・・ 実際にコードを見て確認します。

interface Tree {
  age: number
}

class Person {
  age: number
}

const tanaka: Tree = new Person();
tanaka.age = 20;
console.log(tanaka.age); // 20

上記のようにPersonクラスがTreeを実装していなくてもメンバーが同じなのでコンパイルエラーにはならないのです。 これを構造的部分型(Structural Subtyping)というのです。

一方でC#Javaの場合は、PersonクラスがTreeインターフェースを実装していないといけないので上記のコードではエラーになります。(自分はC#Javaを触ったことがないので別の機会でお試しください。。。) このようなものを名目上の型付けと呼びます。

また以下の例はどうでしょうか?

interface User {
  name: string;
}

let x: User;
let y = { name:"hoge", address: "Tokyo" }
x = y;

こちらは正常にコンパイルされます。 変数yが変数xのプロパティ(ここでいうとname)をもっているので割り当てが可能になります。

逆にyxを代入した時はどうでしょうか?

interface User {
  name: string;
}

let x: User;
let y = { name:"hoge", address: "Tokyo" }
y = x;

お察しかもしれませんが、yに対応するプロパティをxnameしか持っていないのでコンパイルエラーになります。

関数の場合はどのように型を評価しているのでしょうか?

以下のような関数があります。

let hoge = (a: number) => 0;
let fuga = (b: number, c: string) => 0;
fuga = hoge;

結果としては、fugaにhogeを割り当て可能です。注意する点としては、引数の名前が異なっていても型のみが考慮されるのです。 hogeの関数の引数名はaですが、fugaの関数の引数名はbcです。 上記の例でみたオブジェクトの例では、プロパティ名が異なっていると正しく割り当てされません。

またfuga = hogeのように引数が切り捨てられるのが許される理由としては、関数の引数が無視されることがJavaScriptでよくみられるからだそうです。

例えば、以下の例です。

const names = ["田中", "佐藤", "中村"];
const result = names.some(name => name === "田中"); // true

someメソッドのコールバック関数のパラメーターは以下の3つを受け取ります。

  • 配列の中の値
  • 各要素のインデックス
  • 要素を格納している配列

このパラメーターを全て使う時もあるとはありますが、使わないケースも多いです。このことから引数の切り捨てられるのが許されるようです。

まとめ

このようにTypeScriptには構造的部分型が取り入れられています。オブジェクトや関数などによって型の評価の仕方が異なるのです。 次回はジェネリクスのことについて取り上げたいと思います。

JavaScriptにおけるfor文のまとめ

今回は言語において基本中の基本であるfor文について取り上げます。

for

forというのはfor three daysなど期間を表す意味として使われます。 このようにfor文は決められた回数のなかで何か繰り返し処理を書きたい時に使います。これとは対照的なものとしてwhileなどが挙げられます。

(例)

const names = ["香川", "南野", "中島", "堂安", "柴崎"];
for (let i = 0; i < names.length; i++) {
  console.log(`サッカー日本代表選手${i}`);
}

// 実行結果
/*
香川
南野
中島
堂安
柴崎
*/

for in

オブジェクトの中から何かを取り出したい時 (例)

const obj = {
  name: "太郎",
  age: 23,
  address: "Tokyo"
}

for(let item in obj){
  console.log(item);
}

// 実行結果
/*
name
age
address
*/

for of

配列の中から何か値を取得したい時

(例)

const fruits = ["apple", "orange", "grape", "banana"]
for(fruit of fruits){
  console.log(fruit);
}

// 実行結果
/*
apple
orange
grape
banana
*/

forEach

配列の一つ一つのデータに対してコールバック関数を適用することができます。

const numbers = [0, 1, 2, 3, 4, 5];
numbers.forEach((value, index, array) => {
  ...処理内容
})

コールバック関数の引数は最大で3つ受け取れます。 第一引数は、配列の値、第二引数はインデックス番号、第三引数はforEachの対象となっている配列になります。

注意する点としては、コールバックの関数内でbreakやcontinueは記述することができないということです。

まとめ

for offorEachは配列に対して行うものですが、forEachの場合コールバック関数を使うことで配列内の 値を何かしたいといった時に便利です。一つ一つの構文がどのような役割をもっているかを理解しておくことは大切です。

Javascriptで簡単な画像プレビュー機能を作成してみる

プロフィール画像をアップロードした時などプレビューできるできると便利ですよね? 今日は簡易的なものですが、作っていきます。

こちらアップロードした後の画面です。

f:id:top_men:20181010193215p:plain

準備

必要なものはブラウザとエディタです。それだけ。

見た目を整える

まずは、簡単な見た目の部分から作ります。

<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <meta name='viewport' content='width=1, initial-scale=1.0'>
  <meta http-equiv='X-UA-Compatible' content='ie=edge'>
  <title>ファイルプレビュー機能について</title>
  <link href="https://use.fontawesome.com/releases/v5.0.6/css/all.css" rel="stylesheet">
  <link rel='stylesheet' href='style.css'>
</head>
<body>
  <div class='wrap'>
    <div>
      <label class="file_photo_wrap" for="file_photo">
        <i class="fas fa-camera fa-4x"></i>
        <input type='file' id="file_photo">
      </label>
      <div class="preview_img" id="preview">
        <img src='' class="preview_img_content" id="preview_img_data" alt=''>
        <span class="updated_text">アップロード済み</span>
      </div>
    </div>
  <script src="main.js"></script>
</body>
</html>

アイコンを使いたかったのでFontawesomeを読み込んでいます。

.wrap {
  justify-content: center;
  display: flex;
}

.file_photo_wrap {
  display: block;
  width: 90px;
  height: 90px;
  padding: 50px;
  margin: 100px auto 0 auto;
  border-radius: 50%;
  background: #4f4fe9;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 1;
  transition: opacity .5s;
}

.file_photo_wrap:hover {
  opacity: .6;
}

input[type="file"] {
  display: none;
}

.fa-camera {
  color: #fff;
  cursor: pointer;
}
.preview_img {
  display: none;
  border: 1px solid #ccc;
  width: 160px;
  height: 100px;
  padding: 10px;
  margin-top: 20px;
}

.preview_img.is-active {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.preview_img_content {
  height: 100%;
}

.updated_text {
  position: absolute;
  bottom: -30px;
}

プレビュー機能を作成

const target_el = document.getElementById("file_photo");
target_el.addEventListener("change", (e) => {
  const files = target_el.files;
  if (e.target.files.length) {
    const reader = new FileReader();
    reader.readAsDataURL(files[0]);
    reader.onload = () => {
      document.getElementById("preview").classList.add("is-active");
      document.getElementById("preview_img_data").setAttribute("src", reader.result);
    }
  }
})

このコードではまず2行目でinputタグのchangeイベントを取得します。

target_el.addEventListener("change", (e) => {}

その後3行目のfilesでファイル情報を取得しています。

 const files = target_el.files;

ファイルの数が1つでもあればif文の中を通ります。 5行目のFileReader オブジェクトを使用するとユーザのコンピュータ内にあるファイルを非同期的に読み込むことが出来ます。

以下参考にしてみてください。

developer.mozilla.org

FileReader.readAsDataURL()は 指定されたBlobオブジェクトを読み込みます。 BlobはBinary Large Object の略になります。Blob は文字列や巨大な画像、音声ファイル、動画ファイルなどを扱うことができます。

FileReader.onloadファイルの読み込みが成功した時に呼ばれるイベントです。

プレビュー画像のimgタグのsrc属性にreader.resultという値が入ります。 このresultというプロパティには、ファイルのデータを示すデータURLが入ります。

とても簡単なものではありますが、これで完成です。

プレビュー機能があるだけでUXの向上に繋がると思います。

JavaScriptにおけるシャローコピーとディープコピーについて

JavaScriptにおけるシャローコピー(shallow copy)ディープコピー(deep copy)について簡単にまとめました。 shallowは日本語で「浅い」という意味になります。

シャローコピー

シャローコピーはざっくり言うと参照元のオブジェクトとコピー先のオブジェクトどちらも同じメモリを参照していることをいいます。

簡単な例

const obj = {
  name: "hoge",
  age: 24
}

const obj2 = obj
obj.name = "fuga";
console.log(obj2.name) // fuga
console.log(obj.name) // fuga

同じメモリを参照しているのでobj2nameを変更するobjnameも変更されてしまいます。

ディープコピー

ディープコピーとは、オブジェクトのみのコピーではなく、オブジェクトメモリ上のデータの両方をコピーします。コピー元のプロパティを変更しても、コピー先のプロパティは変更されません。

スプレッド演算子を使ってディープコピーをしてみたいと思います。

const newObj = { ...obj }
newObj.name = "taro"
console.log(newObj.name) // taro
console.log(obj.name) // hoge

ちゃんとできているように見えます。

では次のケースはどうでしょうか? familiesプロパティのオブジェクトを追加しました。

const obj = {
  name: "hoge",
  age: 24,
  families: {
    brothers: 3,
    sisters: 1
  }
}

const newObj = { ...obj }
newObj.families.brothers = 5
console.log(newObj.families.brothers) // 5
console.log(obj.families.brothers) // 5

コピー元とコピー先のプロパティどちらも変更されてしまっています。 スプレッド演算子シャローコピーだということがわかります。

JSON.stringifyJSON.parseを使う方法があるそうですが、undefinedや関数が定義されていると、プロパティがなくなってしまうのです。 下の例をみてください。

const person = {
  name: "二郎",
  age: 20,
  run: () => {console.log("すとすと")} 
}

person2 = JSON.parse(JSON.stringify(person))
console.log(person2) // {name: "二郎", age: 20}

関数runがありません・・・

もっと簡単な方法はないかと思っていたところ、ありました。 lodashを使用する方法です。

github.com

const _ = require('lodash'); // 追加

const obj = {
  name: "hoge",
  age: 24,
  families: {
    brothers: 3,
    sisters: 1
  },
   run: () => {console.log("すとすと")} 
}

const newObj = _.cloneDeep(obj)
console.log(newObj); // { name: 'hoge', age: 24, family: { brother: 2, sister: 1}, run: [Function: run] }
newObj.families.brothers = 10000;
console.log(newObj.families.brothers); // 1000
console.log(obj.families.brothers); // 3

ちゃんとディープコピーができています。 これからもlodashとは仲良くしていきたいと思います。

簡単ではありますが、JavaScriptにおけるシャローコピー、ディープコピーについて紹介しました。

参考URL

kurochan-note.hatenablog.jp

kuroeveryday.blogspot.com