20210115のJavaScriptに関する記事は19件です。

難しく考えずに、簡単にfirebaseに画像をアップロードしよう!

今回は、firebaseのStorageを利用して、firebase上に画像をアップロードしていきます。

また、その画像を使ってユーザーのプロフィール画像(photoURL)に変更を反映させます。

内容的には、そこまで難しくないのでぜひご覧ください!

流れとしては以下のようになります。

1, 画像をアップロードするinputタグを作成
2, 画像をアップロードした際に、firebaseのStorageに保管(この時、userごとにフォルダを作成していきます)し、firebaseのユーザー情報を更新
3, アップロードする前の画像を破棄
4, firebaseのルール設定
5, Web上に反映

このような流れになっております。

全部を理解しなくてもいいと思います。

しっかり流れを把握しつつ、一緒に説明を見ていきましょう!

1, 画像をアップロードするinputタグを作成

今回は、<input type="file">としてやっても良いのですが、僕の大好きなBuefyを用いて使っていきます。

Buefyの使い方が分からないという方は、こちらの記事初心者必見!サイト制作は楽してなんぼ。CSSフレームワークBuefyの紹介!!に書いてあるので、ぜひ時間がある方は目を通してください。

App.vue
               <b-upload type="file" style="margin-top: 1rem" @change.native="uploadImage">
                  <a class="button is-info">
                    <b-icon
                      pack="fas"
                      icon="upload"
                      size="medium"
                      style="margin-right: 0.5rem; margin-bottom: 0.05rem; vertical-align: middle"
                    ></b-icon>
                    プロフィール画像を変更する
                  </a>
                </b-upload>

@change.nativeで画像がアップロードされたかを検知しています。

inputの場合は<input type="file" @change="uploadImage" />でOKです。

2, firebaseのストレージに保管し、firebaseのユーザー情報を更新

methodsにてuploadImageという関数を定義し、以下のように記述します。

また、画像ファイルは、imagesフォルダにユーザーID(uid)ごとにファルダを作成し、そこに画像ファイルを格納します。

images/
 ├ uid/
 │ └ image.jpg

イメージとしてはこんな感じです。

ユーザー情報を更新するのにはupdateProfileを使用します。

ここで、注意なのですがupdateProfileはfirebase.auth().currentUserで取得したuserで行わないと更新されません。firebase.auth().onAuthStateChanged()でやらないようにお願いします。ちなみに理由は分かりません!

これらを踏まえて、以下の記述を参考に書いてください。

App.vue
<script>
  methods: {
      async uploadImage(event) {
      event.preventDefault() //要素のデフォルトのイベントを無効
      event.stopPropagation() //子要素のイベントが親要素にも伝播することを防ぐ
      const userStorageRef = firebase.app().storage().ref()

      // type="file"を指定されたinput要素のchangeイベントは「ファイルのリスト」を返す
      const file = event.target.files[0]

      // ファイルが存在しないか、ファイル形式が"image/*"ではないとき
      if (!file || !file.type.match(/image\/*/)) {
        alert('不適切なファイル形式です')
        return false
      }

      await userStorageRef
        // images/uid/に画像ファイルを保管
        .child(`images/${this.user.uid}/${file.name}`)
        .put(file)
        .then(() => {
          // 保管した画像ファイルのURLを取得
          userStorageRef
            .child(`images/${this.user.uid}/${file.name}`)
            .getDownloadURL()
            .then(async imageURL => {
              // ユーザーを取得
              // 注意: firebase.auth().onAuthStateChangedでやらないように!
              const user = firebase.auth().currentUser
              await user
                .updateProfile({
                  photoURL: imageURL
                })
                .then(async () => {
                  // vuex store state userの情報更新
                  this.$store.dispatch('changeUserPhotoURL', imageURL)
                  // これを行わいと変更内容が反映されない
                  // また、一番最後に行う
                  user.reload()
                })
            })
        })
    }
  }
</script>

3, アップロードする前の画像を破棄

次に、変更する前の画像はもう使わないので、これを消去します。

firebaseのStorageには、ファルダにあるファイルを一括で消去する機能が携わっていません。なので、listAll()という関数を用いて、ファイルの配列を取得し、1個1個消去していきます。
また、新しい画像をアップロードする前に行うということにも注意してください!
※これよりも良いやり方を知っている方は、ぜひコメント欄にて教えて頂くと幸いです。

App.vue
<script>
  methods: {
      async uploadImage(event) {
      event.preventDefault() //要素のデフォルトのイベントを無効
      event.stopPropagation() //子要素のイベントが親要素にも伝播することを防ぐ
      const userStorageRef = firebase.app().storage().ref()

      // type="file"を指定されたinput要素のchangeイベントは「ファイルのリスト」を返す
      const file = event.target.files[0]

      // ファイルが存在しないか、ファイル形式が"image/*"ではないとき
      if (!file || !file.type.match(/image\/*/)) {
        alert('不適切なファイル形式です')
        return false
      }

    // 追加
    await userStorageRef
        .child(`images/${this.user.uid}`)
        .listAll()
        .then(result => {
          for (let i = 0; result.items.length; i++) {
        userStorageRef.child(`images/${this.user.uid}/${result.items[i].name}`).delete()
          }
        })
        // ファイルがない場合はスルー(初期値はstorageに保存していないため)
        .catch(() => {})
    }

   //ここまで

    await userStorageRef
        // images/uid/に画像ファイルを保管
        .child(`images/${this.user.uid}/${file.name}`)
        .put(file)
        .then(() => {
          // 保管した画像ファイルのURLを取得
          userStorageRef
            .child(`images/${this.user.uid}/${file.name}`)
            .getDownloadURL()
            .then(async imageURL => {
              // ユーザーを取得
              // 注意: firebase.auth().onAuthStateChangedでやらないように!
              const user = firebase.auth().currentUser
              await user
                .updateProfile({
                  photoURL: imageURL
                })
                .then(async () => {
                  // vuex store state userの情報更新
                  this.$store.dispatch('changeUserPhotoURL', imageURL)
                  // これを行わいと変更内容が反映されない
                  // また、一番最後に行う
                  user.reload()
                })
            })
        })
  }
</script>

4, firebaseのルール設定

次に、Firebase Consoleの画面を開き、左側の「Storage」という項目を開いてください。

そして、「Rules」を開いてください。

今回のルール設定は、ユーザー本人かつ認証済みかつ画像ファイルであれば変更を行えるというルール設定を行います。

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /images/{uid}/{allPaths=**}  {
      allow read, write: if request.auth.uid == uid && request.auth != null && request.resource.contentType.matches('image/.*')
    }
  }
}

request.auth.uid == uid:ユーザー本人かの確認
request.auth != null:認証済みかの判断
request.resource.contentType.matches('image/.*'):画像ファイルであるかの確認

5, Web上に反映

後は、user情報を取得してWeb上に反映するだけです!

一応、書いておきますね。

App.vue
firebase.auth().onAuthStateChanged((user)) => {
  console.log(user.photoURL)
})

このようにしてユーザーの画像ファイルを更新できると思います!

最近、毎日知らないことだらけの連続で頭がパンクしそうです(笑)

でもめげずに頑張りたいと思います!

ぜひ皆さんも一緒に頑張りましょう!!

以上、「難しく考えずに、簡単にfirebaseに画像をアップロードしよう!」でした!

良かったら、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

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

【Vue.js】初めてのコンポーネント。Vueインスタンスを再利用。

index.html
<div id="app">
  <hello-component></hello-component>
  <hello-component></hello-component>
  <hello-component></hello-component>
  <hello-component></hello-component>
  <hello-component></hello-component>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>

<script>
  Vue.component('hello-component', {
    data: function() {
     return {
      number: 13
     }
    },
    template: '<p>Hello{{ number }}</p>'
  })

  var app = new Vue({
    el: '#app',
  })
</script>

コンポーネントの中では、dataは関数である必要があるらしい。

Hello13

Hello13

Hello13

Hello13

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

JavaScript,PHP,Javaのrandom関数をまとめてみる

今回はrandom関数を各言語ごとにまとめてみます。
なんでrandom関数なのかというとミニゲームなど作成時に使用する頻度が高く、好きな関数の一つだからです。
言語ごとに微妙に違いがあり、備忘録として記事にしておきます。

ちなみにrandom関数にもアルゴリズムがあるので「擬似乱数」であって、
「真乱数」ではないところに注意。

JavaScriptのMath.random()

JavaScriptのMath.random()関数は0以上1未満の値を返します。
※0は含むが1は含みません!

let num = Math.floor(Math.random() * n) + m;
// n = 上限値 , m = 下限値

Math.random()は浮動小数点で返ってきますので、Math.floor()で小数点以下を切り捨てます。
「n」は上限値を設定し、「m」で下限値を設定します。

例 ) 1〜10の乱数を生成する

let num = Math.floor(Math.random() * 10) + 1;
  1. Math.random()によって0以上〜1未満の乱数が生成される
  2. 10を乗算することによって0以上〜10未満の乱数となる
  3. Math.floor()によって小数点以下を切り捨て(ここで0〜9のいずれかの整数になっている)
  4. 1を足してあげることで1〜10のいずれかの整数が生成されます。

注意すべき点は1未満の数字を生成する点です。

PHPのrandom_int()

PHPで擬似乱数を生成するためにはrandom_int()関数を使用します。

$num = random_int(int $min, int $max);

第一引数に下限値を渡し、第二引数に上限値を渡します。
min〜maxを含む乱数を整数値で返します。

例 ) 1〜10の乱数を生成する

$num = random_int(1, 10);

指定した数値を含むので簡単ですね。

JavaのRandom()クラス

Javaではjava.utilパッケージにあるRandom()クラスとnextInt()メソッドを使用します。

int num = new java.util.Random().nextInt(n);

int型で返ってくるため変数はint型で宣言します。
nextInt()の引数が上限値となり、0〜上限値未満の値を返します。
JavaScriptと同じく指定した上限値自体は含みません!

例 ) 1〜10の乱数を生成する

public class Main {
 public static void main(String[] args) {
  int num = new java.util.Random().nextInt(10) + 1;
 }
}

上限値を含まない点に注意して値を渡しましょう。

まとめ

Javascript
0以上1未満の浮動小数点を生成。
Math.floor()とセットで覚える。

PHP
下限値、上限値指定した値を含む整数値を返す。

Java
Random()クラスを使用する。
nextInt()メソッドの引数に上限値を渡す。
0以上「上限値」未満の整数を返す。

こぼれ話

訓練校で「なぜ1未満なのか?」と先生に質問しました。
先生の答えはこうだった

「なぜでしょうね〜、、、」

何か意図があって1未満なのかなと思ったが、
そんなの言語作った人にしかわからんよな。と思いました。

それ以降、定義済の関数や変数、定数に疑問を抱くのはやめましたw

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

JavaScript Puppeteerで絶対ハマらないブラウザ操作2021

はじめまして。マーティンです。Twitterフォロワー20人記念記事、爆誕です。
スクレイピングライブラリはいろいろありますがブラウザ操作用ライブラリはPuppeteer一択じゃぁ!
個人的にはPythonよりJSでブラウザ操作する方が好みですが、このPuppeteerというライブラリは少々くせ者ですのでハマりやすいところを解説します。
JavaScriptの文法を理解した初心者の方向けです。

目次

1.そもそもPuppeteerとは
2.始め方
3.おすすめ起動オプションと注意点
4.ページ遷移
5.よく使うpageのメソッドと注意点

1. そもそもPuppeteerとは

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

Nodeで動くChromium操作用のライブラリ。「ヘッドレス」なので、デスクトップにChromiumを表示させずに動かすこともできるということですね。
Chromiumというやつがオープンソースで、それをPuppeteerで操作するという流れ。自分のパソコンに入ってるChromeを操作するわけではない。Chromiumはpuppeteerをインストールすると一緒についてくる。

Async AwaitのおかげでPuppeteerを使うのはめちゃくちゃ簡単になりました。
Puppeteerは操り人形という意味らしい。

2. 始め方

$npm install puppeteer

installするものはこれだけです。
Puppeteerで何かするときは以下の雛形を用います。

const puppeteer = require('puppeteer');

const URL = "https://example.com";

(async () => {
  const browser = await puppeteer.launch();//あとでオプション設定するよ
  const page = await browser.newPage();
  await page.goto(URL);//あとでオプション設定するよ

 //ここでなんかするよ

  await browser.close(); //開発時はコメントアウトしておくと良いよ
})(); //即実行する関数よ

3. おすすめ起動オプションと注意点

オプションの指定が可能です。

Puppeteerはデフォルトでヘッドレスが有効になっていますが、無効に設定できます。
slowMoを設定しないと処理が超高速で行われてしまいます。非同期処理に自身がない方は必ずslowMoしましょう。非同期処理に自身がある方も基本的にslowMoしたほうが良いかと。slowMo:50くらいにするといい感じの速度でブラウザが操作されるので見ていて面白い。

またwindowサイズを決定することも重要です。レスポンシブデザインのサイトでは閲覧するブラウザのwindowサイズによってHTMLの要素が変化する場合があるため、windowサイズを明示的に固定することでこれを防ぎます。
defaultViewportを設定しましょう。defaultViewportを設定することによりHMTLページのサイズを指定します。下にある --window-size はHTMLページのサイズではなくブラウザのサイズのみ指定します。defaultViewportなしではレスポンシブデザインによるHMTL要素の変化を防止できません。

goto(URL)するときはwaitUntilも一緒につけちゃいましょう。
https://pptr.dev/#?product=Puppeteer&version=v5.5.0&show=api-pagegotourl-options
ページ遷移を待ってくれます。

//開発時はheadless:falseが良い
await puppeteer.launch({
  headless:false,
  slowMo:50,
  args: ['--window-size=1900,1080'],
  defaultViewport: {
    width:1900,
    height:1080
  }
});

await page.goto(URL,{"waitUntil": 'networkidle0'})

学生向けコーナー(需要あるのか?

Googleにログインした状態でブラウザ操作したいときがありますがPuppeteerでGoogleにログインすることは基本的にできません。詳しくは以下の記事がよくまとまっていますのでご覧あれ。
https://qiita.com/vicugna-pacos/items/a52e22d08856d1041316
自動でブラウザを操作していることがバレて弾かれちゃいます。
しかしなんと、教育用Googleアカウント: xxxxxx@ed.jp等を持っている学生ならそのアカウントを使ってGoogleにログインできます。謎ですね!ところが一つ問題点があります。
Headless trueでGoogleにログインしたい場合は必ずUserAgentを設定しなければならないということです。(個人的にハマったところ)
Puppeteerではフォームに値を入力するとき、フォームのHTML要素を指定します。よってheadless falseでブラウザを起動させてからデベロッパーツールを使ってHTML要素を調べるわけです。
headless falseではこちらが指定した通りGoogleにログインできるのですが、trueにした途端できなくります。原因はGoogleログインフォームとUserAgentの関係。
下を試してみると...

//headless trueのとき
console.log(await browser.userAgent())
// => Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4298.0 Safari/537.36

//headless falseのとき
console.log(await browser.userAgent())
// => Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4298.0 Safari/537.36

headlessがfalseの場合はuserAgentに「Chrome」とあり,headless trueにするとuserAgentに「HeadlessChrome」とあることからheadlessか否かによってUserAgentが異なることがわかります。
UserAgentが異なると何が問題なのでしょうか?
Googleのログイン画面を見てみましょう。

[headlessがtrue UserAgentがHeadlessChromeの場合]
headless.png
[headlessがfalse UserAgentがChromeの場合]
chrome.png

ご覧の通りHeadlessChromeの場合とChromeの場合でGoogleのログインフォームのHTML構造が異なるため、メールアドレスを打ち込む際の(後に述べますが)inputフィールドを指定するセレクターの違いが生まれることがエラー発生の原因でした。

で解決策は?

Headless trueの状況下でUserAgentをChromeに無理やり設定することで解決!UserAgentはご自分のChromiumで「UserAgent 確認」と検索するといろいろなUserAgent確認用サイトがヒットしますのでそこで調べてください。

await page.setUserAgent("あなたのUserAgent")

4. ページ遷移

非同期処理って何?って方はこちらの記事が非常に分かりやすいので参考までに。
https://qiita.com/soarflat/items/1a9613e023200bbebcb3
https://qiita.com/klme_u6/items/ea155f82cbe44d6f5d88

ページ遷移はPuppeteerの肝。
「async await つけるだけでしょ」と思っていると少し危険です。
Puppeteerでページ遷移を待つときには page.waitForNavigation()が使われますが、これがよく誤用されがち。

waitForNavigationはページ遷移の前に宣言

await Promise.all([
    page.waitForNavigation(),
    page.click("button")
]);

buttonタグをクリックするpage.click()よりもあとに宣言された場合、たまにエラーが発生します。clickによるページ遷移がwaitForNavigationが呼ばれる前に完了した場合、waitForNavigationはページ遷移を認識できない。

waitForNavigationよりwaitForSelector

waitForNavigationにはいくつかオプション指定ができますが、どのオプションを持ってしても確実にページ遷移の非同期処理が行われるという保証はありません。そこで登場するのがwaitForSelector。

await Promise.all([
  page.waitForSelector("input[name='email']"),
  page.click("button")
])

読んで字の如く、指定したセレクターが出現するのを待ちます。セレクターの指定方法はquerySelectorと同じです(後述)。
waitForSelectorを使えば非同期処理エラーはまずないだろうと思いますが、それでも不安な方はwaitForTimeoutを使いましょう。

await Promise.all([<省略>])

await page.waitForTimeout(500) ///500ミリセカンド待つ

余談ですが少し前まで使われていたwaitForは非推奨となりましたので代わりにwaitForTimeoutを使いましょう。

waitFor is deprecated and will be removed in a future release. See https://github.com/puppeteer/puppeteer/issues/6214 for details and how to migrate your code.

5. よく使うpageのメソッドと注意点

page.click(selector[, options])

ページ内のHMTL要素をCSSセレクターを用いて指定します。
CSSセレクターはどうやって指定するのか?
デベロッパーツールを開いてお目当てのHTMLタグを右クリック、「Copy」=>「copy selector」で一発です。オプションはあまり使わないので言及しません。

page.type(selector,text[,options])

指定したCSSセレクターにテキストを入力します。

page.evaluate(pageFunc[,...args])

具体例を見てみましょう。

let text = await page.evaluate((selector) => {
  return document.querySelector(selector).innerHTML;
},"div > h1")

page.evaluateの第一引数にpageFuncとして渡すアロー関数を、第二引数にセレクター"div > h1"を渡していることがわかります。第二引数に渡したセレクターがpageFuncの引数selectorに代入され処理が実行されています。
注意したいことが3つ。
1つ目がpageFuncはブラウザ内で実行される関数であるため、スクリプトファイル内で定義された変数を直接関数内で使うことはできないこと。しっかりpage.evaluateの第二引数として変数をわたしてあげましょう。
2つ目がreturnをすること。returnしないとtextに値がを代入されません。
3つ目はmapとforeachについて。evaluate内でquerySelectorAllするときはmapを使いましょう。
return document.querySelectorAll("div").map(() => (do stuff here))
mapは新しい配列を返すがforeachは配列を書き換えるだけ。またmap内でもreturnを忘れないこと。
初歩的なことですが、よく忘れてしまうので自戒も込めて。

 その他

const URL = "https://------"のところで行末にセミコロンをつけないとエラーになる
・Puppeteerを使ってGoogleForm自動回答マシーンを作りたい方はhttps://stackoverflow.com/questions/59328036/how-to-select-an-opion-from-a-google-forms-popup-dropdown-puppeteer-nodejs 
が参考になります。
・Puppeteerでブラウザ操作を定期的に行いたいwindowsユーザーの方にはタスクスケジューラの使用をおすすめします。.batファイルでnode実行コマンド記述->タスクスケジューラで定期的に.batファイルを実行。
・間違っているところがあれば指摘していただけると幸いです。

最後まで読んでいただきありがとうございました。

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

AtCoder Beginners Selection 解答例【JavaScript】

AtCoder Beginners SelectionをJavaScriptで解きました。
競技プログラミングはC++で解くのがスタンダードですが、JavaScriptが好きなのであえてJavaScriptを使って解いています。
JavaScriptは標準入力がちょっと面倒なので、競プロのJavaScript標準入力まとめ【パターン別】から確認してください。

※問題の解説はこちらの記事を参考にしてください。

問題 1 : A - Product

https://atcoder.jp/contests/abc086/tasks/abc086_a

解答例

function main(input) {
  var args = input.split(" ");
  var a = parseInt(args[0], 10);
  var b = parseInt(args[1], 10);

  (a * b) % 2 === 0 ? console.log("Even") : console.log("Odd");
}
main(require("fs").readFileSync("/dev/stdin", "utf8"));

問題 2 : A - Placing Marbles

https://atcoder.jp/contests/abc081/tasks/abc081_a

解答例

function main(input) {
  return console.log(input.split("1").length - 1);
}
main(require("fs").readFileSync("/dev/stdin", "utf8"));

問題 3 : B - Shift only

https://atcoder.jp/contests/abc081/tasks/abc081_b

解答例

function main(input) {
  var args = input.split("\n");
  var a = args[1].split(" ").map((n) => parseInt(n, 10));

  var sum = 0;
  while (a.every((n) => n % 2 === 0)) {
    a = a.map((n) => n / 2);
    sum++;
  }
  console.log(sum);
}
main(require("fs").readFileSync("/dev/stdin", "utf8"));

問題 4 : B - Coins

https://atcoder.jp/contests/abc087/tasks/abc087_b

解答例

function main(input) {
  var args = input.split("\n");
  var a = parseInt(args[0], 10);
  var b = parseInt(args[1], 10);
  var c = parseInt(args[2], 10);
  var x = parseInt(args[3], 10);

  var sum = 0;
  for (let i = 0; i <= a; i++) {
    for (let j = 0; j <= b; j++) {
      for (let k = 0; k <= c; k++) {
        if (500 * i + 100 * j + 50 * k === x) sum++;
      }
    }
  }
  console.log(sum);
}
main(require("fs").readFileSync("/dev/stdin", "utf8"));

問題 5 : B - Some Sums

https://atcoder.jp/contests/abc083/tasks/abc083_b

解答例

function main(input) {
  const args = input.split(" ");
  const N = parseInt(args[0], 10),
    A = parseInt(args[1], 10),
    B = parseInt(args[2], 10);

  const nums = Array.from(new Array(N), (v, i) => ++i).filter((i) => {
    var n = i.toString().split("").reduce((p, c) => p + parseInt(c), 0);
    return n >= A && n <= B;
  });
  console.log(nums.reduce((p, c) => p + c, 0));
}

main(require("fs").readFileSync("/dev/stdin", "utf8"));

問題 6 : B - Card Game for Two

https://atcoder.jp/contests/abc088/tasks/abc088_b

解答例

function main(input) {
  var args = input.split("\n");
  var n = parseInt(args[0], 10);
  var a = args[1].split(" ").map((n) => parseInt(n, 10)).sort((a, b) => b - a);

  var sum = 0;
  for (let i = 0; i < n; i++) {
    if (i % 2 === 0) sum += a[i];
    else sum -= a[i];
  }
  console.log(sum);
}

main(require("fs").readFileSync("/dev/stdin", "utf8"));

問題 7 : B - Kagami Mochi

https://atcoder.jp/contests/abc085/tasks/abc085_b

解答例

function main(input) {
  var args = input.split("\n");
  var n = parseInt(args[0], 10);
  var d = args.slice(1, args.length - 1).map((n) => parseInt(n, 10)).sort((a, b) => a - b);

  var sum = 1;
  for (let i = 1; i < n; i++) {
    if (d[i - 1] < d[i]) sum++;
  }
  console.log(sum);
}

main(require("fs").readFileSync("/dev/stdin", "utf8"));

問題 8 : C - Otoshidama

https://atcoder.jp/contests/abc085/tasks/abc085_c

解答例

function main(input) {
  var args = input.split(" ");
  var n = parseInt(args[0], 10);
  var y = parseInt(args[1], 10);

  var ans = "-1 -1 -1";
  for (let i = 0; i <= n; i++) {
    for (let j = 0; j <= n; j++) {
      if (i + j > n) continue;
      if (10000 * i + 5000 * j + 1000 * (n - i - j) === y) {
        ans = `${i} ${j} ${n - i - j}`;
        break;
      }
    }
  }

  console.log(ans);
}

main(require("fs").readFileSync("/dev/stdin", "utf8"));

問題 9 : C - 白昼夢

https://atcoder.jp/contests/abc049/tasks/arc065_a

解答例

function main(input) {
  var reverse = (s) => {
    return s.split("").reverse().join("");
  };

  var S = reverse(input.split("\n")[0]);
  var WORDS = ["dream", "dreamer", "erase", "eraser"].map(reverse);

  var index = 0;

  while (true) {
    var w = WORDS.find((w) => S.startsWith(w, index));
    if (!w) break;
    index += w.length;
  }

  if (S.length === index) console.log("YES");
  else console.log("NO");
}
main(require("fs").readFileSync("/dev/stdin", "utf8"));

問題 10 : C - Traveling

https://atcoder.jp/contests/abc086/tasks/arc089_a

解答例

function main(input) {
  input = input.trim().split("\n");
  var n = parseInt(input[0], 10);

  var list = [[0, 0, 0]];
  for (var i = 1; i <= n; i++) {
    var t = Number(input[i].split(" ")[0]);
    var x = Number(input[i].split(" ")[1]);
    var y = Number(input[i].split(" ")[2]);
    list.push([t, x, y]);
  }
  for (var i = 0; i < n; i++) {
    dt = list[i + 1][0] - list[i][0];
    dx = Math.abs(list[i + 1][1] - list[i][1]);
    dy = Math.abs(list[i + 1][2] - list[i][2]);

    dis = dx + dy;
    if (dis > dt || dt % 2 != dis % 2) {
      console.log("No");
      return false;
    }
  }
  console.log("Yes");
}
main(require("fs").readFileSync("/dev/stdin", "utf8"));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

functions.phpからスタイルシートやjsを読み込む

functions.phpからスタイルシートやjsを読み込む

WordPressのwp_head( )とwp_footer( )という関数を読み込んでいく。

そのためには以下をする必要がある。

wp_enqueue_style( )はwp_head()で読み込むcssを追加するための関数で、
wp_enqueue_script( )はwp_footer( )で読み込むjsを追加するための関数です。

下記の内容をfunction.phpに記述。

/**
* CSSとJavaScriptの読み込み
*
* @codex https://wpdocs.osdn.jp/%E3%83%8A%E3%83%93%E3%82%B2%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC
*/
function my_script_init()
{
wp_enqueue_style('fontawesome', 'https://use.fontawesome.com/releases/v5.8.2/css/all.css', array(), '5.8.2', 'all');
wp_enqueue_style('my', get_template_directory_uri() . '/css/style.css', array(), '1.0.0', 'all');
wp_enqueue_script('my', get_template_directory_uri() . '/js/script.js', array( 'jquery' ), '1.0.0', true);
}
add_action('wp_enqueue_scripts', 'my_script_init');

これでstyle.cssとscript.jsがそれぞれ読み込まれるようになりました!

そして、それぞれheader.phpとfooter.phpに以下のように書いていきます。

header.php

<meta property="og:description" content="">
<meta name="twitter:card" content="summary_large_image">

<!--
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css?ver=5.8.2">
<link rel="stylesheet" href="./css/style.css">
ここを置き換える
-->

<?php wp_head(); ?>
<link rel="icon" href="./img/icon-home.png">

footer.php

<div class="floating">
  <a href="#"><i class="fas fa-chevron-up"></i></a>
</div>

<!--
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="js/script.js"></script>
ここを置き換える。jqueryはWPデフォで読み込まれるので消してOK。
-->

<?php wp_footer(); ?>
</body>
</html>

これで読み込み完了です。
以上、ありがとうございました。

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

[JavaScript] 2次元配列のデータをMapの配列に変換する関数

目的

GAS(Google Apps Script)でGoogleスプレッドシートのデータをgetValues()で2次元配列として取り出し後、加工してsetValues()で書き戻す、というのはよくあると思う。

ただ、2次元配列のままでは列の追加や削除、入れ替えなどの変更に対して弱いのでこれまではObjectに変換してから処理していたのだが、Objectではキー(プロパティ)の順序が保証されない、ということを知ったので、順序が保証されるMapに変換することに。

ということで、1行目がヘッダである2次元配列と、Mapの配列を相互に変換する関数を作成した。

できたもの

// 2次元配列(第1行はヘッダ)をMapの配列に変換
function tableToMap(data){
  const headers = data[0];
  const transpose = a => a[0].map((_, c) => a.map(r => r[c]));
  return data.slice(1).map((e) => new Map(transpose([headers, e])));
}

// Mapの配列を2次元配列(第1行はヘッダ)に変換
function mapToTable(data){
  return [[...data[0].keys()], ...data.map((e) => [...e.values()])]
}

エラー処理等はしてないので適当に呼び出し側で行ってください。?

2次元配列 → Mapの配列

const table = [
  ["col1", "col2", "col3", "col4"],
  [1, 2, 3, 4],
  [5, 6, 7, 8],
  ["a", "b", "c", "d"]
];
const maps = tableToMap(table);

image.png

Mapの配列 → 2次元配列

mapToTable(maps);

image.png

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

p5.jsでランダムウォークを実装する

クリエイティブコーディング、ジェネラティブアート...呼び方はさまざmですが、今回は有名な表現方法であり比較的簡単な「ランダムウォーク」をp5.jsで作ってみようと思います。

こんな感じ
https://editor.p5js.org/mtoutside/sketches/ZdmmhEN9i
wip_p5js_210115.gif

ランダムウォークとは

Wikipediaには

ランダムウォーク(英: random walk)は、次に現れる位置が確率的に無作為(ランダム)に決定される運動である。日本語の別名は酔歩(すいほ)、乱歩(らんぽ)である。グラフなどで視覚的に測定することで観測可能な現象で、このとき運動の様子は一見して不規則なものになる。

と書いてありました。オブジェクトが無作為の方向に進んでいく、ランダムウォーキングするからランダムウォークといいます。そのままですね。

ランダムウォーク実装の考え方

ロジックとしては、

  1. 各方向を定義
  2. random関数を使用して、進む方向を決定
  3. 進む
  4. 2~を繰り返し

だいたいこんな感じの考え方です。

開発環境

p5.jsはJavaScriptのライブラリなので、基本ブラウザとテキストエディタがあれば動きます。手っ取り早く触ってみたかったら

https://editor.p5js.org/

がおすすめです。なんの用意も要らずブラウザから動かせて便利です。

コード

先に全体のコードを載せます。

randomWalk.js
const NORTH = 0;     // 上
const NORTHEAST = 1; // 右上
const EAST = 2;      // 右
const SOUTHEAST = 3; // 右下
const SOUTH = 4;     // 下
const SOUTHWEST = 5; // 左下
const WEST = 6;      // 左
const NORTHWEST = 7; // 左上
let direction;

const stepSize = 1;
let diameter = 1;

let posX, posY;

function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();
  fill(0, 40);

  posX = width / 2;
  posY = height / 2;
}

function draw() {
 background(255, 5);
  for(let i = 0; i <= mouseX; i++) {
    direction = int(random(0, 8));
    diameter = random(1, 3);

    if(direction === NORTH) {
      posY += stepSize;
    } else if(direction === NORTHEAST) {
      posX += stepSize;
      posY += stepSize;
    } else if(direction === EAST) {
      posX += stepSize;
    } else if(direction === SOUTHEAST) {
      posX += stepSize;
      posY -= stepSize;
    } else if(direction === SOUTH) {
      posY -= stepSize;
    } else if(direction === SOUTHWEST) {
      posX -= stepSize;
      posY -= stepSize;
    } else if(direction === WEST) {
      posX -= stepSize;
    } else if(direction === NORTHWEST) {
      posX -= stepSize;
      posY += stepSize;
    }

    // 画面の端まで到達したら反対側の端へ移動
    if(posX > width)  posX = 0;
    if(posX < 0)      posX = width;
    if(posY < 0)      posY = height;
    if(posY > height) posY = 0;    

    // 円を描く
    ellipse(posX + stepSize / 2, posY + stepSize / 2, diameter, diameter);
  }
}

function keyReleased() {
  if(keyCode === DELETE || keyCode === BACKSPACE) clear();
}

以下ポイントを解説していきます。

方向を定義

ランダムで進む方向をここで定義しています。全部で8方向です。

const NORTH = 0;     // 上
const NORTHEAST = 1; // 右上
const EAST = 2;      // 右
const SOUTHEAST = 3; // 右下
const SOUTH = 4;     // 下
const SOUTHWEST = 5; // 左下
const WEST = 6;      // 左
const NORTHWEST = 7; // 左上

random関数で進む方向を決定

for(let i = 0; i <= mouseX; i++) {
    direction = int(random(0, 8));    
    // 省略
  }

全8方向のなかからランダムで方向を決定するので、
direction = int(random(0, 8));
のようにして進む方向を決めます。あとはif文で各方向へ進む処理を書いていきます。

今回はマウスのX座標に応じて進むスピードを変えています。画面(描画エリア)の右に行くほどスピードが速くなります。

for(let i = 0; i <= mouseX; i++) { // iの終了条件をマウスのx座標にしている

まとめ

というわけでランダムウォークを実装してみました。クリエイティブコーディング系の本とかにもだいたい載っている有名な表現です。ロジックもシンプルなので簡単に実装できるかと思います。

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

node.jsでTCPで通信 (server側の入力をclient側でエコーバックする)

node.jsでTCP通信をする方法を調べていると

  1. cilent側で文字を入力して
  2. それをserver側で折り返して
  3. それをclient側でコンソールに表示

というサンプルが山ほど見つかるのですが、

  1. server側で文字入力をして
  2. それをclient側で折り返して
  3. それをserver側でコンソールに表示

という例が見つからなかったので、書いてみました。

server.js

var net = require('net');

var server = net.createServer(conn => {
    console.log('connected.');

    conn.on('data', data => {
        console.log('server-> ' + data);
    });

    conn.on('close', () => {
        console.log('closed');
    });

    process.stdin.resume()
    process.stdin.on('data', data => {
        conn.write(data)
    });

}).listen(3000);

console.log('listening on port 3000');

client.js

var net = require('net');

var client = new net.Socket();
client.setEncoding('utf8');

client.connect('3000', 'localhost', () => {
    console.log('connected');
});

client.on('data', data => {
    console.log('client-> ' + data);
    client.write(data);
});

client.on('close', () => {
    console.log('client-> connection is closed');
});

以上です

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

node.jsでTCP通信 (serverからの送信内容をclientでエコーバックする)

node.jsでTCP通信をする方法を調べていると

  1. cilent側で文字を入力して
  2. それをserver側で折り返して
  3. それをclient側でコンソールに表示

というサンプルが山ほど見つかるのですが、

  1. server側で文字を入力して
  2. それをclient側で折り返して
  3. それをserver側でコンソールに表示

という例が見つからなかったので、書いてみました。

server.js

var net = require('net');

var server = net.createServer(conn => {
    console.log('connected.');

    conn.on('data', data => {
        console.log('server-> ' + data);
    });

    conn.on('close', () => {
        console.log('closed');
    });

    process.stdin.resume()
    process.stdin.on('data', data => {
        conn.write(data)
    });

}).listen(3000);

console.log('listening on port 3000');

client.js

var net = require('net');

var client = new net.Socket();
client.setEncoding('utf8');

client.connect('3000', 'localhost', () => {
    console.log('connected');
});

client.on('data', data => {
    console.log('client-> ' + data);
    client.write(data);
});

client.on('close', () => {
    console.log('closed');
});

以上です

参考
https://nodejs.org/api/net.html#net_net_createserver_options_connectionlistener

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

ちょっと高度なJavaScriptの話

はじめに

今回は、初心者から中級者になるためのJavaScriptを学んでいきましょう。

JavaScriptを全く勉強したことがないという人には以下の記事がおすすめです。

【JavaScriptの超基本】ファイルのインポートやエクスポートについて簡単に解説

【JavaScriptの超基本】コールバック関数について簡単に解説

【JavaScript】プリミティブ型とオブジェクト型を理解したい

それでは、頑張っていきましょう。

配列やオブジェクトのちょっと便利な構文

それではJavaScriptのちょっと便利に構文について学んでいきましょう。

オブジェクトのkeyとvalueに変数を指定

JavaScriptではオブジェクトのkeyとvalueに変数を指定することができます。以下のコードです。

const keyName = "bar";
const baz = 3
const obj = {foo: 1, [keyName]: 2, baz: baz};
console.log(obj)

{ foo: 1, bar: 2, baz: 3 }

上記の例のように、オブジェクトのkeyに変数を指定するときは[]で囲い、valueに変数を指定するときはそのまま代入します。

keyNameの値であるbarがオブジェクトのkeyに、bazの値である3がオブジェクトのvalueになっていることが分かると思います。

プロパティ名のショートハンド

次は、プロパティ名のショートハンドと呼ばれる書き方です。

変数を{}で囲うことで、変数名をkey・値をvalueに持つオブジェクトを生成することができます。

const organization = 'unreact';
const obj = {organization};
console.log(obj);

{ organization: 'unreact' }

プロパティ名のショートハンドという書き方はES2015から導入された構文で、React開発においても頻繁に使用されます。ぜひともマスターしておいて下さい。

配列の分割代入

他の言語でも頻繁に使われますが、JavaScriptにおいても配列の分割代入は可能です。

配列の分割代入で値を受け取る際は、受け取る側も配列にしておきます。

const [n, m] = [1, 2];
console.log(n, m);

1 2

上記の例では、nに配列の1つ目の要素である1が代入され、mに配列の2つ目の要素である2が代入されます。

JavaScriptの配列には順番が存在することを考えれば、値を受け取る側の順番が、値を与える側の順番に対応するというのは当然の挙動ですよね。

オブジェクトの分割代入

当然ですが、オブジェクトの分割代入も可能です。

const onj = {organization: 'unrect', age: 1};
const {organization, age} = onj;
console.log(organization, age);

unrect 1

オブジェクトの分割代入においては、受け取る側に{}を準備して、その中にオブジェクトのkey名を書きます。

そのkey名に対して、オブジェクトの中で対応するvalueがそれぞれ格納されていきます。

つまり上記の例では、organaizationというオブジェクトのkeyに対応するunreactというvalueが、受け取る側の{}の中のkey名であるorganaizationに代入されることになります。

配列と違いオブジェクトには順番が存在しないため、分割代入においてはkey名で指定することが必要になります。

上記の例では、オブジェクトの分割代入を行った際の変数名が、オブジェクトのkey名になってしまっています。

以下のようにすることで、分割代入で生成する変数名を任意の名前にすることができます。

const onj = {organization: 'unrect', age: 1};
const {organization: group, age} = onj;
console.log(group, age);

unrect 1

上記の例では、organization: groupの部分で、オブジェクトのorganaizationに対応するvalueであるunreactを、groupという変数名で受け取っています。

また以下のようにすれば変数名に初期値を割り当てることができます。

const onj = {age: 1};
const {organization: group = 'unreact', age} = onj;
console.log(group, age);

上記の例では、オブジェクトにはorganaizationが存在しないため何もしなければgroupにはundefinedが格納されます。

それを防ぐために、='unreact'とすることで初期値を設定しています。

オブジェクトの分割代入はReactでpropsを受け取る際などに頻繁に利用されます。ぜひとも覚えておきましょう。

スプレッド構文

それでは次にスプレッド構文をまとめていきます。スプレッド構文とは...をつけることでオブジェクトや配列の中身を展開する構文です。

const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5, 6];

console.log(arr2);

const obj1 = {organaization: 'unreact'};
const obj2 = {...obj1, age: 1};

console.log(obj2);

[ 1, 2, 3, 4, 5, 6 ]
{ organaization: 'unreact', age: 1 }

配列のスプレッド構文はES2015から導入され、オブジェクトのスプレッド構文はES2018で導入されました。

使用例としては、オブジェクトや配列をコピーする際などに用いられます。

具体例を解説する前に、値渡しと参照渡しについて学んでおきましょう。

参照渡し

これは他の言語でも同じですが、配列やオブジェクトをそのまま代入すると値渡しではなく参照渡しが行われます。

どういうことか分からないと思うので、具体例で解説します。以下が参照渡しが行われる例です。

const arr1 = [1, 2, 3];
const arr2 = arr1;
arr2.push(4);

console.log(arr1);

[ 1, 2, 3, 4 ]

上記の例のように、arr2.push(4)を行ってarr2に対して変更を行うと、arr1にも変更が加えられます。

なぜこのような挙動をするのかというと、オブジェクトや配列は参照渡しが行われるからです。

const arr1 = [1, 2, 3]を行うと、配列のためのメモリ領域が確保され、そのポインタ(参照)がarr1に渡されることになります。

この状態でconst arr2 = arr1を行うと、配列のために新たなメモリ領域が確保されるのではなく、arr1に代入されている配列の参照がそのままarr2に渡されることになります。

つまり、arr1arr2は同じメモリ領域の配列を指し示すことになります。

その状態でarr2.push(4);を行うと、そのメモリ領域の配列自体に変更が加えられるため、同じ参照をもつarr1の値も変更することになります。

これを回避するために、スプレッド構文を用いた値渡しが行われます。

値渡し

上記の参照渡しを回避するためには、arr2のために新たに配列のメモリ領域を確保する必要があります。

そのために、下記のようにスプレッド構文を用いることがよくあります。

const arr1 = [1, 2, 3];
const arr2 = [...arr1];
arr2.push(4);

console.log(arr1);

[ 1, 2, 3 ]

このようにすれば、値をコピーして渡すことができます。const arr2 = [...arr1]の部分で新たに配列を生成し、そのメモリ領域を確保した後に、その新たに確保したメモリ領域の参照をarr2に渡しています。

シャローコピーについて

先程配列をスプレッド構文でコピーして値渡しを行う方法について解説しましたが、この方法は少し問題を抱えています。

というのも、スプレッド構文による値渡しは、一段回までの深さしか行えないからです。

つまり、オブジェクトや配列がネストされていた場合は、そのネストされたオブジェクトや配列に対しては参照渡しを行ってしまいます。

以下の具体例で見てみましょう。

const obj1 = {
  organaization: "unreact",
  nest: { group: "react" },
};
const obj2 = {...obj1};
obj2.nest.group = 'React';

console.log(obj1);

{ organaization: 'unreact', nest: { group: 'React' } }

上記の例では、const obj2 = {...obj1}の部分でスプレッド構文による値渡しを行っていますが、ネストされたオブジェクトである{ group: "react" }に関しては参照渡しが行われてしまいます。

そのため、obj2.nest.group = 'React'を行いobj2に対してネストされたオブジェクトの値を変更すると、obj1のネストされたオブジェクトの値まで書き換えられてしまいます。

このシャローコピーを回避するために、いくつかの方法があります。

最も有名なのはJson.parse/stringfyを使う方法でしょうか。

ディープコピーを行う

それでは、このシャローコピーを回避してディープコピーを行う方法について解説していきます。

最も有名なのは、以下のようにJson.parse/stringfyを行う方法です。

const obj1 = {
  organaization: "unreact",
  nest: { group: "react" },
};
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.nest.group = "React";

console.log(obj1);

{ organaization: 'unreact', nest: { group: 'react' } }

JSON.parse(JSON.stringify(obj1))の部分で、オブジェクトを一度JSON文字列に変換した後、再びJavaScriptのオブジェクトに変換しています。

この方法はお手軽で高速にディープコピーを行うことができますが、強引であるがゆえに弊害もあります。

下記の記事に、弊害について分かりやすくまとめてありました。

JavaScriptのDeepCopyでJSON.parse/stringifyを使ってはいけない

かいつまんで説明すると、この方法はオブジェクトのプロパティにDate関数undefinedが存在する場合に上手く動きません。

このような問題があるため、現環境でディープコピーを行う際はLodashというライブラリのcloneDeep()を用いる方法が推奨されています。

Lodashでディープコピー

Lodashを使えば簡単にディープコピーを行うことができます。

import _ from "lodash";

const obj1 = {
  organaization: "unreact",
  nest: { group: "react" },
};
const obj2 = _.cloneDeep(obj1);
obj2.nest.group = "React";

console.log(obj1);

{ organaization: 'unreact', nest: { group: 'react' } }

const obj2 = _.cloneDeep(obj1);の部分でディープコピーを行っています。凄く簡単ですよね。

これからディープコピーを使用する際は積極的に使っていきましょう。

関数型プログラミングっぽく書こう

JavaScriptはかなり関数型プログラミングのパラダイムに対応しています。

関数型プログラミングとは、先行する式の評価を後続の式に適用し、それをつなげていくことで最終的な評価値を得るプログラミング手法です。

ifやfor文による制御構造による手続き書き連ねていく命令型プログラミングのパラダイムとは異なり、関数型プログラミングはy = f(x)のような数学的な式であるべき状態を宣言します。

React開発ではこの関数型プログラミングのような書き方が非常に好まれるため、積極的に使用していく必要があります。例えば、ReactのJSXの中ではを使用することができず、全てを用いてプログラムを記述する必要があります。

ifやforのようなではなく、値を返すを組み合わせてあるべき状態を宣言していく、そんな宣言的(Declarative)な書き方を学んでいくことで、少しレベルアップできるはずです。

それでは頑張っていきましょう。

ショートサーキット評価

最初にショートサーキット評価、またの名を短絡評価についてまとめていきます。

短絡評価とは&&||などの論理演算子が左から右に評価される性質を利用して、右辺が評価されるかどうかを左辺に委ねる方法です。

&&を使用した場合、左辺がtruthyのとき右辺を返し、||を使用した場合は左辺がfalsyなときに右辺を返します。

以下で具体例を見ておきましょう。

const organaization = "" || "UnReact";
console.log(organaization);

UnReact

上記の例では、"" || "UnReact"の部分でショートサーキット評価を行っています。

||は左辺がfalsyのときに右辺を返し、左辺がtruthyのときは左辺を返す(短絡する)式です。

空文字はfalsyな値のため、右辺が返ってきてorganaizationに代入されています。

ちなみに、JavaScriptにおいてfalsyな値はfalse0NaNnullundefined''(空文字)のみで、それ以外は全てtruthyな値になります。

以下の例のように、ショートサーキット評価は繋げて書くことができます。

const organaization = 'Uniqlo' && 'Softbank' && 'Toyota' && 'UnReact';
console.log(organaization);

UnReact

&&は値が左辺がtruthyなとき右辺に評価を委ねる記法です。そのため、上記の例では一番右側まで評価されてUnReactが返ってきます。

このショートサーキット評価はif文の代わりに使うことができます。

ifなどのと異なり、値を返すなのでReact開発で頻繁に利用されます。

Nullish Coalescing

次はNullish Coalecing、通称Null合体演算子をやっていきましょう。

Nulllish Coalescingはショットサーキット評価の||と似ています。||は、左辺がfalsyな値のときに右辺を評価するという記法ですが、Nullish Coalescingは??を用いて左辺がnullまたはundefinedのときに右辺を評価するという記法です。

具体例は以下になります。

const organaization = null ?? "UnReact";
const language = undefined ?? "React";
console.log(organaization);
console.log(language);

UnReact
React

このように??を用いて、左辺がnullまたはundefinedのときに右辺を評価するという記法がNullish Coalescingです。

ちなみに、上記のパターンだとNullish Coalescingの??の部分をショートサーキット評価の||に変えても特段違いはありません。

しかし、ショートサーキット評価の||は0や空文字が左辺に入った際も右辺を評価してしまうため、思わぬバグを生む可能性があります。

そのため、nullまたはundefinedのときに右辺を評価するということを明示的に示したい場合にNullish Coalescingを使用することになります。

ちなみにNullsish CoalescingはES2020で追加されたものなので、Node.js 14.0以降で実行することになります。

Optional Chaining

それでは、Optional Chainingについて解説していきます。

こちらもES2020から追加された記法で、端的に書くならオブジェクトに対するプロパティアクセスを用いた際に、各参照が正しいかどうかを明示的に確認せずアクセスすることができるものです。

もしも途中のプロパティが存在していなかったら、式が短絡してundefinedが返ってきます。

この説明では何がなんだか分からないと思うので、きちんと順を追って解説していきます。

まず、前提としてオブジェクトの存在しない要素に対してプロパティアクセスを行うとundefinedが返ってきます。

const obj = { organization: "UnReact" };
console.log(obj.employee);

undefined

そして、この返ってきたundefinedに対してプロパティアクセスを行うとエラーが発生します。

const obj = { organization: "UnReact" };
console.log(obj.employee.age);

at ModuleJob.run (internal/modules/esm/module_job.js:110:37)
at async Loader.import (internal/modules/esm/loader.js:179:24)

この前提を踏まえた上で、次のようなケースを考えます。

const arr = [
  { organaization: "UnReact", employee: { name: "poocomaru", age: 23 } },
  { organaization: "Softbank", employee: null },
];

arr.map((n) => {
  console.log(n.employee.age);
});

at ModuleJob.run (internal/modules/esm/module_job.js:110:37)
at async Loader.import (internal/modules/esm/loader.js:179:24)

上記のコードでは、会社についての情報を格納したオブジェクトの配列を作成して、それをmapで回しながらプロパティにアクセスしています。

このコードは、2つ目のオブジェクトに対してのn.employee.ageを行う部分でエラーが発生してしまいます。

というのも、n.employeeの実行結果がnullであり、そのnullに対してプロパティアクセスを行ってしまったために発生するエラーです。

Optional Chainingの実装以前はifによる条件分岐でnullやundefinedチェックを行い、エラーを回避していました。

しかし、Optional Chainingを使用すれば、以下のコードのように簡単にエラーを回避することができます。

const arr = [
  { organaization: "UnReact", employee: { name: "poocomaru", age: 23 } },
  { organaization: "Softbank", employee: null },
];

arr.map((n) => {
  console.log(n?.employee?.age);
});

23

undefined

変更が加わったのはn?.employee?.ageの部分です。通常のプロパティアクセス.から、Optional Chainingである?.に変更しています。

この変化により、nullに対してのプロパティアクセスが短絡されてundefinedが返ってくるようになり、nullに対してプロパティアクセスを行ったが故のエラーが発生しなくなります。

終わりに

今回はちょっと高度なJavaScriptの文法について取り扱いました。

少しでも皆様のお役に立てれば幸いです。

ここまで読んで頂きありがとうございました。

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

【JavaScript】ディープマージを自前実装してみた

先に実行結果を。

// 元データ
const test1 = {
  a: { aa: 1, ab: 2, ac: [31, 32, 33], },
  b: { ba: 4, bb: [51, 52, 53], bc: 6, },
  c: { ca: [71, 72, 73], cb: 8, cc: 9, },
}

// 差分
const test2 = {
  a: { ab: 20, ac: [], },
  b: { ba: 40, bb: [51], },
  c: { ca: [71, 72, 73, 74, 75], cc: '9' },
  d: { da: ['a', 'b', 'c'], db: 'd', dc: 'e', },
}

// マージ結果
deepMerge(test1, test2);
{
  a: { aa: 1, ab: 20, ac: [] },
  b: { ba: 40, bb: [ 51 ], bc: 6 },
  c: { ca: [ 71, 72, 73, 74, 75 ], cb: 8, cc: '9' },
  d: { da: [ 'a', 'b', 'c' ], db: 'd', dc: 'e' }
}

実装はこんな感じ。

// メインの関数
const deepMerge = (base, difference) => {
  const merged = JSON.parse(JSON.stringify(base));
  const diff = JSON.parse(JSON.stringify(difference));
  merger(merged, diff);
  return merged;
}

// これが再帰的に呼び出される
const merger = (merged, diff) => {
  for (const key of Object.keys(diff)) {
    if (!(key in merged)) {
      // パラメータの追加
      merged[key] = diff[key];
      continue;
    }
    if (typeof diff[key] === 'object' && diff[key] !== null) {
      // 配列の場合は要素数を合わせる
      if (Array.isArray(diff[key])) {
        merged[key] = merged[key].slice(0, diff[key].length)
      }
      // オブジェクトの場合は再帰呼び出し
      merger(merged[key], diff[key]);
    } else {
      // パラメータの更新
      merged[key] = diff[key];
    }
  }
}

あとがき

うーん、後から考えてみれば別に作る必要なかったかも。

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

MDNを読みながらクロージャを理解する

目的

JavaScriptを勉強しているとクロージャという概念にぶち当たります。
難しいです。
私もMDNや参考書、その他ネット上の記事を見ましたが理解するのに時間がかかりました(完全には理解できていないかもしれない)。
そこで、MDNを見ながら、自分のような初学者にとってクロージャの何が難しいのかを記しつつ、そういった部分の理解への足掛かりになりやすい記事を書きたいと思いこの記事を書きました。

クロージャとは?

MDNには以下のように説明されています。

クロージャは、組み合わされた(囲まれた)関数と、その周囲の状態(レキシカル環境)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。
引用元:https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures

・・・?

周囲の状態への参照の組み合わせ?

内側の関数から外側の関数スコープへのアクセスを提供?

難しいですね。

クロージャを理解するのに必要な知識

MDNのクロージャの記事を読むために前提となる知識があります。

それはスコープについてです。

スコープとは変数や関数を使える有効範囲のことを言います。

グローバルスコープローカルスコープなどの意味、そしてある関数スコープ内で変数を使用したい場合、そのスコープ内で宣言された変数か、そのスコープ外で宣言された変数しか使用できないということを理解できていればクロージャを理解できるのではないかと思います。

最初のコードを眺めてみる

MDNのクロージャのページに書かれているコードを引用します。
一緒に考えていきましょう。

function init() {
 var name = 'Mozilla'; // name は、init が作成するローカル変数

 function displayName() { // displayName() は内部に閉じた関数
    alert(name); // 親関数で宣言された変数を使用
  }
  displayName();
}
init();

引用元:MDN Web Docs クロージャ

ここではinit関数を定義しています。
init関数がすることは以下です。

・ローカル変数nameを宣言し文字列Mozillaを代入
・displayName関数を定義
・displayName関数を実行

displayName関数がすることは以下です。

・displayName関数外のスコープであるinit関数スコープで宣言されたローカル変数nameを引数で受け取り、alertで表示する

このコードをJSFiddleなどで実行するとMozillaとアラート表示されると思います。

MDNでこのコードとともに説明していることは、あるスコープ内で変数を使用したい場合、そのスコープ内で宣言された変数か、そのスコープ外で宣言された変数しか使用できないということです。

今回の場合だとdisplayName関数スコープ内で、外のスコープであるinit関数スコープで宣言されたローカル変数nameを使用していますね。

2番目のコードを眺めてみる

function makeFunc() {
  var name = 'Mozilla';
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

引用元:MDN Web Docs クロージャ

ここではmakeFunc関数を定義しています。
先ほどのinit関数との違いは以下です。

・displayName関数を実行するのではなく返していること

makeFunc関数を定義後はグローバルスコープで変数myFuncを宣言、makeFunc関数を実行して変数myFuncにdisplayName関数を代入しています。
最後に変数myFuncを関数呼び出ししてdisplayName関数を実行しています。

結果的にはinit()関数を実行した時と同じようにMozillaとアラート表示されることになります。

ここでのポイントは、変数myFuncはmakeFunc関数の外で実行されたのに、makeFunc関数スコープのローカル変数であるname変数の内容が表示できている点です。

あれ??
と思いませんか。

先ほど私は
あるスコープ内で変数を使用したい場合、そのスコープ内で宣言された変数か、そのスコープ外で宣言された変数しか使用できない
と書きました。

しかしここではグローバルスコープ上でmakeFunc関数スコープ内で宣言されたローカル変数nameを使用できています。
つまり、あるスコープで宣言された変数をそのスコープ外から使用しているということですね。

これは何故可能なのか?

それはdisplayName関数がmakeFunc関数の中で作られた関数であるため、その環境(ローカル変数nameが使える)を保持しているからです。

このように、ある関数が作られた時にその環境が関数の中で保持されていることクロージャと言います。

・・・

まだピンとこないかもしれません。

ある関数が作られた時にその環境を関数の中で保持するということをよりイメージしやすいコードが次のコードです。

3番目のコードを眺めてみる

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2)); // 7 と表示される
console.log(add10(2)); // 12 と表示される

引用元:MDN Web Docs クロージャ

クロージャの性質を利用すると動的な関数を作ることができます。
動的な関数とは何でしょうか?

コードを見てみましょう。
このmakeAdder関数がまさに動的な関数を作ることができる関数です。

makeAdder関数の引数に5を渡している時には 5 + y を返す関数が作られ、グローバル変数add5に代入される。引数に10を渡している時には 10 + y を返す関数が作られ、グローバル変数add10に代入される。

このmakeAdder関数のように、引数に渡す値によって内部処理が変わってくるなど、状況によって異なる関数を作る関数を動的な関数と言います。

ここのどの部分にクロージャが関わっているか?

add5関数を作った時にはローカル変数xに5が保持され、add10関数を作った時にはローカル変数xに10が保持されています。

つまり、動的な関数は、ある関数が作られた時にその環境が関数の中で保持されるというクロージャの性質を利用して作られているということです。

まとめ

・クロージャとは、ある関数が作られた時にその環境が関数の中で保持されていること。
・クロージャを使えば、あるスコープで宣言された変数をそのスコープ外から使用することができる。
・クロージャを使えば、動的な関数を作れる。
・MDNを読むのは難しい。

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

なぜだ!?actionsの引数で、どうしてもundefiendになってしまった時の話

皆さんこんにちは!

今回は、エラー備忘録として書いていきます。

同じエラーに苦しんでいる方の参考になればなと思います。

まず、初めに以下の二つのコードを見ていただきたいと思います。

sotre/index.js
changeUserPhotoURL({ commit }, {updatePhotoURL}) {
      commit('changeUserPhotoURL', updatePhotoURL)
    },
sotre/index.js
changeUserPhotoURL({ commit }, updatePhotoURL) {
      commit('changeUserPhotoURL', updatePhotoURL)
    },

実行結果は

{updatePhotoURL}undefiend
updatePhotoURLにはしっかりと値は受け取れてます。

なぜ、このような結果になるのかは分かりません。。。

分かる方がいたら、ぜひコメント欄にて教えて頂くと幸いです。

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

webpack Vue.js TypeScript を使った環境構築まで (WSL)

概要

サービスを作るにあたって、Vue.js+TypeScriptでさくっと作れないか模索する準備として、環境構築したので
まとめてみました。調べながらやっており、手順としては冗長。

やりたいこと

JavaScriptとWebサーバーだけで良い感じに開発する アプリサーバーは無し

前提環境

  • Ubuntu 18.04.2 LTS
  • node v12.13.1
  • yarn 1.19.2
  • Apache/2.4.29 (Ubuntu)

リポジトリ

ディレクトリ構成

この記事時点での最終は下記になる。
Apacheのルートディレクトリを public 配下としておく nginxでも何でもいいが

.
├── .babelrc
├── .browserslistrc
├── .gitignore
├── package.json
├── public
│   ├── index.html
│   └── js
│       └── app.js
├── src
│   ├── Hello.vue
│   └── app.ts
├── tsconfig.json
├── webpack.config.js
└── yarn.lock

HTML作成

こんな感じで作成し、ブラウザで表示させる jsはまだない

public/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>TEST</title>
    <script src="js/app.js"></script>
  </head>
  <body>
    <div id="app">test app</div>
  </body>
</html>

yarn初期化

yarn init -y

package.jsonが自動で作成される。

package.json
{
  "name": "test5",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}

webpack準備

yarnによるwebpackのインストール

yarn add --dev webpack webpack-cli
package.json
{
  "name": "test4",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "webpack": "^5.13.0",
    "webpack-cli": "^4.3.1"
  }
}

node_modules というディレクトリが作られ、webpack利用に必要なパッケージがインストールされる。
これらの詳細の情報は、同時に作成されたyarn.lockに記録される。

テスト用のjsの準備

src/app.js
let test = ()=> {alert("webpack test");};
test();

webpackの設定ファイル準備

webpack.config.js
const path = require('path');

const env = process.env.NODE_ENV || 'development';

module.exports = {
  entry: './src/app.js',
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, 'public/js'),
  },
  mode: env,
}

webpack実行

npxとは、パッケージ管理しているnodeモジュールを簡単に実行できる仕組み
node_modules/.bin 以下の実行ファイルを実行できる。

npx webpack --mode=development
asset app.js 802 bytes [emitted] (name: main)
./src/app.js 49 bytes [built] [code generated]
webpack 5.14.0 compiled successfully in 137 ms
src/js/app.js
/*
 * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
 * This devtool is neither made for production nor for readable output files.
 * It uses "eval()" calls to create a separate source file in the browser devtools.
 * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
 * or disable the default devtool with "devtool: false".
 * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
 */
/******/ (() => { // webpackBootstrap
/*!********************!*\
  !*** ./src/app.js ***!
  \********************/
eval("let test = ()=> {alert(\"webpack test\");};\ntest();\n\n//# sourceURL=webpack://test5/./src/app.js?");
/******/ })()
;
【参考】webpackのmodeオプション

下記のようにproductionモードで実行すると、出力結果が異なる 不要な記述が省略されていることが分かる

npx webpack --mode=production # もしくはmodeを付けない場合はproductionで実行される
dist/app.js
console.log("webpack test");

https://webpack.js.org/configuration/mode/

babelの導入

yarn add --dev @babel/core @babel/preset-env babel-loader core-js

webpackの設定に、jsファイルはbabelを通すように設定

webpack.config.js
  module: {
    rules: [
        {
          test: /\.js$/,
          loader: 'babel-loader',
        }
      ]
  },

babelの設定用にファイルを2つ追加する

.babelrc
{
    "presets": [[ "@babel/env", { "modules": false } ]],
    "env": {
       "test": {
            "presets": [[ "@babel/env", { "targets": { "node": "current" } } ]]
        }
    }
}
.browserslistrc
> 1%

再び、webpackを実行すると、ES5に対応した書き方に変換されていることがわかる。

public/js/app.js
/*
 * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
 * This devtool is neither made for production nor for readable output files.
 * It uses "eval()" calls to create a separate source file in the browser devtools.
 * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
 * or disable the default devtool with "devtool: false".
 * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
 */
/******/ (function() { // webpackBootstrap
/*!********************!*\
  !*** ./src/app.js ***!
  \********************/
eval("var test = function test() {\n  alert(\"webpack test\");\n};\n\ntest();\n\n//# sourceURL=webpack://test5/./src/app.js?");
/******/ })()
;

IEでエラーが出ないことを確認

jsをVueのものに書き換えていく

src/app.js
import Vue from 'vue';
import Hello from './Hello.vue';

document.addEventListener('DOMContentLoaded', () => {
  new Vue(Hello).$mount('#app');
});

vueの単一コンポーネントファイルを追加

src/Hello.vue
<template>
  <div>
    <p>{{ greeting }} World!</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      greeting: ''
    };
  },
  created() {
    this.greeting = 'hello';
  }
}
</script>

webpack 実行

npx webpack --mode="development"
asset app.js 239 KiB [emitted] (name: main)
runtime modules 916 bytes 4 modules
cacheable modules 223 KiB
  modules by path ./src/ 2.13 KiB
    ./src/app.js 152 bytes [built] [code generated]
    ./src/Hello.vue 1.04 KiB [built] [code generated]
    ./src/Hello.vue?vue&type=template&id=184cbba9& 197 bytes [built] [code generated]
    ./src/Hello.vue?vue&type=script&lang=js& 336 bytes [built] [code generated]
    ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/Hello.vue?vue&type=template&id=184cbba9& 267 bytes [built] [code generated]
    ./node_modules/babel-loader/lib/index.js!./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/Hello.vue?vue&type=script&lang=js& 172 bytes [built] [code generated]
  modules by path ./node_modules/ 221 KiB
    ./node_modules/vue/dist/vue.runtime.esm.js 218 KiB [built] [code generated]
    ./node_modules/vue-loader/lib/runtime/componentNormalizer.js 2.71 KiB [built] [code generated]
webpack 5.14.0 compiled successfully in 2064 ms

ブラウザで問題なく表示されていることを確認

単一コンポ―ネント CSS対応

yarn add --dev style-loader css-loader

webpackにcss用の設定を追加

webpack.config.js
 module: {
    rules: 
    [
        {
          test: /\.vue$/,
          loader: 'vue-loader'
        },
        {
          test: /\.js$/,
          loader: 'babel-loader',
        },
        {
          test: /\.css$/,
          loader: 'vue-style-loader',
          loader: 'css-loader',
        }
      ]
  },
npx webpack --mode="development"
asset app.js 257 KiB [compared for emit] (name: main)
runtime modules 1.19 KiB 5 modules
cacheable modules 232 KiB
  modules by path ./src/ 3.39 KiB 9 modules
  modules by path ./node_modules/ 229 KiB
    ./node_modules/vue/dist/vue.runtime.esm.js 218 KiB [built] [code generated]
    ./node_modules/vue-loader/lib/runtime/componentNormalizer.js 2.71 KiB [built] [code generated]
    ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built] [code generated]
    ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]
webpack 5.14.0 compiled successfully in 2071 ms

CSSが app.js に埋め込まれ、適用されるようになった。

Typescript対応

yarn add --dev typescript ts-loader

app.js → app.tsへ

内容はとくにいじらず

Hello.vueをts対応

Hello.vue
<script lang="ts">

webpack設定

webpack.config.js
--
entry: './src/app.ts',
--

        {
          test: /\.tsx?$/,
          exclude: /node_modules/,
          use: [
              {
              loader: 'ts-loader',
              options: {
                  appendTsSuffixTo: [/\.vue$/]
              }
              }
          ]
        }
--
[参考]

"moduleResolution": "node",
"allowSyntheticDefaultImports": true
の2行が無いと、import Vue from 'vue' のようなインポートができなかった。Typescript初心者なのであとで調べる。

TypeScript設定ファイルの準備

tsconfig.json
{
    "compilerOptions": {
        "target": "es5",
        "module": "es2015",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "sourceMap": true,
        "baseUrl": "./",
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true
    },
    "include": [
        "./src/*.vue",
        "./src/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}

試しに、型をつけてみる

Hello.vue
-略-
<script lang="ts">
export default {
  data() {
    return {
      greeting: ''
    };
  },
  created() {
    this.greeting = 'hello';
  },
  computed:{
      computed_greeting():string{
          return this.greeting;
      }
  }
}
</script>
-略-

これでwebpackが通り、TypeScriptが扱えている

npx webpack --mode="development"
asset app.js 257 KiB [compared for emit] (name: main)
runtime modules 1.19 KiB 5 modules
cacheable modules 233 KiB
  modules by path ./src/ 3.57 KiB 9 modules
  modules by path ./node_modules/ 229 KiB
    ./node_modules/vue/dist/vue.runtime.esm.js 218 KiB [built] [code generated]
    ./node_modules/vue-loader/lib/runtime/componentNormalizer.js 2.71 KiB [built] [code generated]
    ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built] [code generated]
    ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]
webpack 5.14.0 compiled successfully in 4685 ms

ためしに型に合っていない値を返すと、こける

src/Hello.vue
-略-
  computed:{
      computed_greeting():string{
          return 1;
      }
  }
-略-
npx webpack --mode="development"
asset app.js 257 KiB [emitted] (name: main)
runtime modules 1.19 KiB 5 modules
cacheable modules 232 KiB
  modules by path ./src/ 3.56 KiB 9 modules
  modules by path ./node_modules/ 229 KiB
    ./node_modules/vue/dist/vue.runtime.esm.js 218 KiB [built] [code generated]
    ./node_modules/vue-loader/lib/runtime/componentNormalizer.js 2.71 KiB [built] [code generated]
    ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built] [code generated]
    ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]

ERROR in /home/obana/js/vue/test5/src/Hello.vue.ts
[tsl] ERROR in /home/obana/js/vue/test5/src/Hello.vue.ts(19,11)
      TS2322: Type 'number' is not assignable to type 'string'.

.gitignore

git管理にあたり、作成 とりあえずnodeモジュール除外すれば良いか。

node_modules/

ここまでで、一応開発できるようになったものの、
まだまだ準備したいところなので、続きは別でまとめていく。

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

JavaScriptの__proto__とは一体なんなのか?

私はJavaScriptを使用している時に、理解せずにずっと放置していた。
__proto__と言う謎の物体。これを理解せずに放置している人は多いと思う。
今回はそれをこの記事をきっかけに理解を試みた。

__proto__とは一体なんなのか?

空のオブジェクトを作成したはずなのに、__proto__が出てくる。

Objects.prototypeだった

JavaScriptでは作成された全てのオブジェクトの__proto__に対して、
自動的にObjects.prototypeプロパティが付属している。

Objects.prototypeをみてみると。

__proto__Objects.prototypeが全く同じプロパティを持っていることがわかる。
これはxObjects.prototypeから同じ__proto__を継承していると言うことである!

新しい機能を割り当てることも可能

Object.prototypeを変更することで、その後に作成される全てのオブジェクトにも変更が含まれる
新しい関数を与えてみると。

このように新しい機能を割り当てると、その後のオブジェクトではその機能を持つことが可能となる。

オブジェクトだけじゃない__proto__

オブジェクトだけが__proto__を持っているわけではなく、他にも存在する

  • Array.prototype
  • Number.prototype
  • String.prototype
  • Boolean.prototype

最後に

この__proto__の機能を使うことは非推奨なので、使う際には十分注意をしていただきたい。

developer.mozilla
参考

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

Vuexでオブジェクトのプロパティの値の変更を検知したいときの対処法!!

今日は、Vuexについての記事を書いていこうかなと思います。

Vuexでオブジェクトのプロパティの値を変更しても、値の変更は検知されません。(これで3,4時間くらい時間食われた)

これは、Vue本来の仕様にになっておりどうしようもすることはできません。

では、どうやってプロパティの変更を検知してくれるのでしょうか?

結論から言うと、オブジェクト自体を書き換えるという動作をします。

めんどくさいですよね。

でも大丈夫!

同じようなことで頭を抱える人に、とっておき解決策をご用意しております。

ぜひ皆さんのお力になれればなと思います!!

それでは、順を追って説明していきます。

オブジェクトのコピーを作成

今回は、userというオブジェクトを例に挙げて説明していきます。

store/index.js
state: {
    user: {}, // user情報
  },

changeUserPhotoURL(state, payload) {
      let user = Vue.util.extend({}, state.user)
    },

Vue.util.extendでコピーを作成し、第一引数には肩を定義します。第二引数には、コピーする対象を選択します。

プロパティの値を変更し、元のオブジェクトに代入

今回は、ユーザーのプロフィール画像(photoURL)を変更します。

store/index.js
state: {
    user: [], // user情報
  },

changeUserPhotoURL(state, payload) {
      let user = Vue.util.extend({}, state.user)
      user.photoURL = payload
      state.user = user
    },

state.user = userこの部分は、元のオブジェクト(state.user)にコピー、編集したオブジェクト(user)を代入しています。

ページの読み込み

this.$router.go()

最後に、routerなどを使ってページのリロードを行いましょう!

このようにして、オブジェクトのプロパティの値の変更を検知してくれます。

いかがだったでしょうか?

これで時間を潰すなんてもったいないですよね??

なので、この記事を参考にしてぜひ学習を進めていってください!

以上、「Vuexでオブジェクトのプロパティを変更したいときの対処法!!」でした!

良ければ、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

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

JavaScriptの引数と戻り値の基礎中の基礎

プロゲートのJavaScript 学習コース |||でつまずいた箇所がありましたので記事に整理してみました。
関数の引数と戻り値の理解に難しさを感じられている方のお力になれれば幸甚です。

JavaScriptは関数から理解するのが段々と酷になってきました。

もともとインフラ周りのエンジニアをやっておりましたので、関数の概念自体は理解しておりましたが実際にコードで書くとなるとやはり難しいです(笑)

何らかの数値や文字列を格納する変数と同じ入れ物であり、実行する処理を格納するのが関数であるという理解で差し支えありません。

単純に関数を定義して使うだけならば、条件分岐で習ったIf文に近しい下記のような書き方ですが、引数と戻り値という概念が加わるので混乱することがあります。

関数の定義とは

const introduce = fuction(){
  console.log("こんにちは")
}
introduce();

定数introduceを定義してfunction(){};の{}内に処理を記載します。
これだけで関数の出来上がりです。ここまでを「関数の定義」と呼びます。

『定数名();』と記載することで関数内の処理が実行されます。
処理であるconsole.log("こんにちは");が関数introduceに格納されており、introduce();と書くことでそのまま中の処理が実行されて「こんにちは」と表示されます。

※JavaScriptのES6バージョンから「function()」の部分を「() =>」とするだけで関数が定義できるようになっており、この関数の書き方をアロー関数と呼びます。

ややこしくなるかもですが、JavaScriptの仕様で予め用意された関数も存在しており、オリジナルの関数を使いたい場合に定義が必要になります。

私と同様に引数と戻り値によって苦しむ初学者の方もいらっしゃると思いますので、こちらで私の体験談を踏まえながらどのように学習したのかをご紹介させて頂きます。

引数とは関数に与える追加情報のようなものであり、戻り値とは呼び出し元で受け取る関数の処理結果のことです・・・と言われてもピント来ませんよね。

正確に理解しようとして調査して考えていても難しいと思いますので、手を動かして「こういうものだ」と一種のおまじないだと思ってコードを書いてみると理解できるようになります。

関数の定義とは

const greet = (name) => {
  console.log(`こんにちは、${name}さん`);
};
greet('田中');

上記のコードで「こんにちは、田中さん」と表示されます。

ロジックとしてはアロー関数の() =>{};にある()の中に引数としてnameを記載します。
そして定義したgreet関数を呼び出す際に()内に値を入れてgreet('田中');と記載することで、引数nameに渡されます。

すると、greet関数内のconsole.log(こんにちは、${name}さん);のnameにも値が反映されて、greet関数を実行すると上記の処理結果になります。

戻り値とは

const add = (a, b) => {
  return a + b;
};
const sum = add('1,3');
console.log(sum);

上記のコードで「4」と表示されます。

「return 値」で指定するとその値が戻り値として、呼び出し元であるadd関数に戻ります。上記のコードでは、add('1,3')が関数を呼び出しており、そのまま戻り値に置き換わります。

つまり、return 「a + b;」のaとbにadd('1,3')の1,3が入るので4が呼び出し元であるadd関数に戻ります。

そして、定数sumの中に代入されて、sumを実行して4が出力されるという仕組みです。

プロゲートで私が理解するのに時間がかかった問題「6.戻り値とは」のコードを参考に引数と戻り値の応用例を解説致します。

引数と戻り値を扱った応用コード

const half = (number) => {
  return number / 2;
};
const result = half (130);

console.log(`130の半分は${result}です`);

上記のコードで「130の半分は65です」と表示されます。

half関数を定義して引数にnumberを指定してありますね。
そうしたら、returnでnumber / 2を戻り値として返しています。

half(130)にてhalf関数の引数に130を加えたので、numberに130が代入されます。
ここで私は躓きましたが、もっと引数の扱い方が定着していればスムーズでした。
result定数にそのまま代入しているので、出力結果は「130の半分は65です」になります。

理解をしようとするよりも「習うよりも慣れろ」的な話だと思います。
数学の勉強方法に近しいものがあり、新しい公式を使って取り敢えず問題を解きながら覚えるのが習得時間の短縮に繋がりました。

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

2つの帽子を知る

2つの帽子

2つの帽子とは、Kent Beckが考案したリファクタリングを行う作業を区分した例えです。

Kent Beckは、ソフトウェア開発でリファクタリングするときは下記の二つに作業を区分すべきと言っています。

機能追加

機能追加時に気をつけること

  • 既存のコードを変更してはいけない
  • 単に機能を拡張することに専念する

リファクタリング

リファクタリング時に気をつけること

  • 機能追加を行ってはいけない
  • コードの再構築に専念する
  • テストの追加をしてはいけない(テストケース漏れの追加, インターフェース変更への対象は例外)

帽子のかぶり方

普通、人は帽子を一つしかかぶらないはずです (僕が知る限りでは)
そのため2つ帽子があるときは、どっちをかぶるか考えないとです。

ユースケース

  • 新しい機能を追加するタスクが発生
  • 既存のコードの構造を少し変更するだけで簡単に機能追加ができることを発見
  • リファクタリングの帽子をかぶる
  • コードの改修が終了
  • 機能追加の帽子をかぶる



  • 機能追加をしていたら少し分かりづらい箇所があることに気づく
  • リファクタリングの帽子をかぶる

いつリファクタリングをすべきか

三度目の法則

  • 初めは単純な作業を行う
  • 2度目に以前と似たようなことをしていると気づいたら、意識はしつつ作業を続行する
  • 3度目に同じようなことをしていると気付いたらリファクタの開始

つまり、仏の顔も三度までということですね。。w

参考文献

今回参考にした文献は下記なのですが、こちらとてもいい本なのでおすすめです。
初版は、Javaでの解説だったのですが、第2版はjavaScriptを利用しています。
javaScriptの為のリファクタリング本ではないので、そこは気を付けてください。

リファクタリング(第2版): 既存のコードを安全に改善する

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