20200321のJavaScriptに関する記事は30件です。

React Native でいろんなメニューを持ったアプリを作る

最終形

  • タブメニューで画面が切り替わる
  • どの画面からでもアクセスできるスライドタイプのメニューを持つ
  • イベントトリガーでポップアップ画面が差し込まれる

以下の動画のようになります。
output.gif

このような複数の画面パターンを持ったアプリを実装していきます。

ナビゲーターの構成

今回の実装では、 React Navigation を使っています。
バージョンは v5 です。

スライドメニュー(Drawer)、ポップアップ画面はタブメニューに覆いかぶさる形で実装したいので、
DrawerNavigator > StackNavigator > TabNavigator となる構成で実装します。

それぞれの中身を見ていきます。

DrawerNavigator

const DrawerNavigator = () => {
  return (
    <Drawer.Navigator
      drawerContent={() => (
        <View style={styles.container}>
          <Text>Drawer</Text>
        </View>
      )}
    >
      <Drawer.Screen name="StackNavigator" component={StackNavigator} />
    </Drawer.Navigator>
  );
};

スクリーンは1つのみの定義で、StackNavigatorを呼び出しています。
これにより、自動的にStackNavigatorの内容が表示されます。

Drawer の表示内容は、drawerContentに定義します。

StackNavigator

const StackNavigator = () => {
  const navigation = useNavigation();
  return (
    <Stack.Navigator mode="modal" headerMode="screen">
      <Stack.Screen
        name="TabNavigator"
        component={TabNavigator}
        options={{ headerShown: false }}
      />
      <Stack.Screen
        name="ModalScreen"
        component={ModalScreen}
        options={{
          headerLeft: () => (
            <HeaderBackButton
              labelVisible={false}
              backImage={() => (
                <View style={{ paddingHorizontal: 8 }}>
                  <Icon name="md-close" style={{ fontSize: 18 }} />
                </View>
              )}
              onPress={() => navigation.goBack()}
            />
          ),
        }}
      />
    </Stack.Navigator>
  );
};

TabNavigatorとポップアップ画面を定義しています。
mode="modal"の場合は下から浮き上がる形で、mode="card"の場合は右から押し出すように画面が挿入されます。

TabNavigatorと同階層にModalScreenを配置することで、ポップアップ画面がタブに隠れないようにしています。

TabNavigator

const TabNavigator = () => {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Tab1" component={TabScreen1} />
      <Tab.Screen name="Tab2" component={TabScreen2} />
      <Tab.Screen name="Tab3" component={TabScreen3} />
    </Tab.Navigator>
  );
};

アプリからの呼び出し

const App: React.FunctionComponent = () => {
  return (
    <NavigationContainer>
      <DrawerNavigator />
    </NavigationContainer>
  );
};

NavigationContainerの子要素として、DrawerNavigatorを呼び出します。
これで完成です。

おわりに

React Navigation では、どちらの Navigator を上位にするかで挙動が異なります。

今回紹介したように、覆いかぶさるポップアップ画面も実装できますし、一方で選択タブ内で詳細画面などの別画面を表示することも可能です。(その場合はタブメニューが隠れない)

どのような画面構成にしたいかを設計し、どのページをどのナビゲーターに配置するかを考える必要があります。

今回は、簡単ではありますがその実装方法について紹介しました。
不明点や間違いなどがありましたらコメントお願いします。

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

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

Class Component + TypeScript の Vue アプリケーションを Composition API で作り直したので思ったことを書く

はじめに

少し前に作って、更新が滞っていたアプリケーションに機能を追加したかった。
ちょうどよかったので Vue + Class Decorator + TypeScript の構成だったが、 Vue + CompositionAPI + TypeScript に作り直した。
この記事では、その過程で思ったことや考えたことを共有したい。

作成しているアプリケーションは以下。

作り直す前

  • コンポーネント定義はクラススタイル
    • vue-class-componentでやってた
  • Vuex もクラススタイル
    • vuex-module-decoratorsでやってた

とりあえず型がついていたのでよかった。

作り直すにあたって考えたこと

Vuexいる?

なんで Vuex を使いたいのかを考えた。

  • 共有したい状態を中央管理したい
    • composition-api でできる
  • タイムトラベルデバッグがしたい
    • 個人的にはそんなに使わない
  • コンポーネントに注入できる
    • vuex-module-decorators 使ってたら結局 store は注入しない
    • コンポーネントの注入された共有された状態を無秩序に触れると困ることが多いのでむしろ明示的にインポートするほうがいい。

Vuex いらない。

composition-api を使って状態を共有する方法はいくつかの記事があるけどかんたんに紹介する。

export const useState = ()=> {
  return {
    state: reactive({
       count: 0
    })
  }
}

上記の場合は useState がコールされるたびに新しく状態が生成されるので、コンポーネント間で独立したものになる。

const state = reactive({
  count: 0
})
export const useState = ()=> ({ state })

上記の場合は useState がコールされると state が返却されるが、この state はモジュール内で定義されたものなので、コンポーネント間で共有される。

状態を変更するためのインターフェースを提供したければ普通に状態を変更する関数を公開すればいい。

const _state = reactive({
  count: 0
})
// 公開する状態はreadonly
const state = computed(()=> _state)

// 注入された状態を更新する
export const increment = (state) => () => state.count++
export const decrement = (state) => () => state.count--

export const useState = ()=> ({ 
  state,
  increment: increment(_state),
  decrement: decrement(_state)
})

場合にもよるが、状態を変更する関数は上記のように変更対象の状態を注入できるようにしておくと試験がしやすい。

import { increment } from "./composition"
describe("useState", ()=> {
  describe("increment", ()=> {
     test("count を 1 増やす", ()=> {
        const mockState = { count: 100 }
        increment(mockState)()
        expect(mockState.count).toBe(101)
     })
  })
})

これだとあんまり効果が実感できないが、例えば 1000 以上は増えちゃいけないみたいな要件があるときには試験しやすい。

Class Component と Vue の相性悪くない?

基本的にコンポーネントはコンポーネントが持っている状態を、ユーザからのアクションに応じて変更する。そして、この状態に応じてUIが更新される。

ここから考えると、自分の状態とそれを変更するロジックをひとまとめにする class をコンポーネント定義に使うのはとても自然に感じてた。

実際 Class Component は this の型付けもうまくできていたしはじめは使いやすかった。
ただ、これを複数のクラスやコンポーネントとの連携を考える急に難しくなると感じた。

OOPではオブジェクト間でのメッセージングでソフトウェアを組み立てていくけど、実際はコンポーネント定義の糖衣構文として使っているだけなので、そういうメッセージングを行う頭で考えてしまうと設計を間違える。

まぁ、これ自体はそういう頭で考えなければいいのだが、コンポーネントを定義するクラスに他のクラスに依存させたいときはまた難しい。
OOPでこれを行うときはコンストラクタインジェクションなどで依存を注入するなどがポピュラーだと思うが、 Vue のコンポーネント定義ではコンストラクタ定義ができない。
そのため、直接クラス内でインスタンスを生成する必要がある。

こんなふうにしたいけどできない。
class HogeComponent extends Vue {
  constructor(private domain: DomainClass) {
  }
  hoge() {
    this.state = this.domain.foo("bar")
  }
}

そのために provide, inject の仕組みを Vue は用意してくれているが、コンポーネントからコンポーネントへの提供を想定しているので試験などやりにくい。

class コンポーネント使いにくい。

composition-api を使ってあげるとこの辺がうまく解決できる。
重要なのは、 setup メソッドは 所謂 main に相当するものであると考えることだと思う。
つまり、 setup にはロジックを記述せずに依存インポートと、composition 関数への依存注入に責任をもたせる。

composition
// 依存はダイレクトにモジュールをインポートしないで引数で注入する
export useHoge = (domainLogic)=> {
  const state = ref(null)
  const hoge ()=> {
    state.value = domainLogic("bar")
  }
  return {
    state, hoge
  }
}
component
<script>
import { domainLogic } from "@/domain"
import { useHoge } from "./composiotons/"
export default defineComponent({
  setup() {
    // 依存の解決だけ行う
    return { ...useHoge(domainLogic) }
  }
})
</script>

こうすることで composition 単体での試験がとてもやりやすくなる。

describe("useHoge", ()=> {
  describe("hoge", ()=> {
     const mockFn = jest.fn()
     mockFn.mockImplementation(()=> "foo")
     const { hoge, state } = useHoge(mockFn)
     hoge()
     expect(state).toBe("foo")
     expect(mockFn.mock.calls[0][0]).toBe("bar")
  })
})

おわりに

Class Componet のスタイルから、 Composition API のスタイルに書き換えたときに考えたことを記載した。
基本的に試験の容易性を高めることが、結果的に拡張性・保守性を高めることにつながると考えているので、やや偏っていたかもしれないが参考になれば嬉しい。

これ以外に composition-api を使ったプロジェクトでのテストについて以下の記事を作成しているので、よければそちらも参考にしてほしい。

https://qiita.com/sterashima78/items/b72c722a3043dcfd99bd

また、関心を分離しながらプログラムを書くために、プレーンなJSから composition-api を使うまでのステップアップしていくさまを以下で紹介している。
https://qiita.com/sterashima78/items/e5518dabbfccdf6a205d

composition-api は適切に利用すれば Vue の抱えていた多くの問題をすると思うので、ぜひいろんな人に使い込まれてベストプラクティスが構築されていってほしいと思う。

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

【JS/Vue】VueXの浅い理解

注意

VueXについては、下記の記事を読むのが一番です。
vuexをまだ理解していない全人類に捧ぐvuexを利用したコードの図解 - Qiita

VueX

VueXの構成

  • Actions
  • Mutations
  • State

以下全体図

image.png
Vuex とは何か? | Vuexより引用

全体の流れ

  1. stateを確認
  2. actionを呼び出し
  3. mutationsへcommit
  4. stateを更新

this.$store.dispatch('xxx') でコンポーネント内でアクションをディスパッチできます。あるいはコンポーネントのメソッドを store.dispatch にマッピングする mapActions ヘルパーを使うこともできます(ルートの store の注入が必要です):
アクション | Vuexより引用

store

アプリケーションの 状態(state) を保持するコンテナです。
storeの状態を変更する際は、Mutationsにコミットをすることで store を変更することが可能です。

stateの中にstoreが存在しています。
templateから$storeにアクセスすることができます。
$sotre.state

actionの呼びだし方法

dipatch

this.$store.dispatch('メソッド名')

mapActions

methods: {
  ...mapActions(['login'])
}

actionで定義されるcommit

actionの定義をする際、下記のようにmutationsへcommitをします。
その際のcommitが少しとっつきづらいです。

// 実装
actions: {
  toggleSideMenu ({ commit }) {
    commit('toggleSideMenu')
  }
}

// 本当は
actions: {
  toggleSideMenu (context) {
    context.commit('toggleSideMenu')
  }
}

上記で記載の通り、context.commitを呼び出しています。
詳しい解説は下記がわかりやすかったです。
vuexで登場する分割代入の説明 - Qiita
分割代入 - JavaScript | MDN

Flux

VueXは、Fluxがベースになっています。
Fluxを実装したのが、Reduxで、そこからVueXが生まれました。

image.png
flux/examples/flux-concepts at master · facebook/flux · GitHubより引用

Fluxとはなんなのか - Qiita

全ての状態を持つべきか

公式ドキュメントでも述べられている通り、全てを置かなくても良いみたいです。

Vuex を使うということは、全ての状態を Vuex の中に置くべき、というわけではありません
ステート | Vuexより引用

参考記事

VueとVuexの間の値の連携の仕方 - Qiita
2018年Vue.jsとVuexを使ってる人には必ず知っていてほしい開発やメンテナンスの際に役立つ設計とTipsとサンプルコード - Qiita

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

Pay.jpを用いた購入機能の実装

概要

Pay.jpを用いた購入機能の実装を備忘録としてまとめます。
修正点ありましたらご指摘お願いいたします。
Transaction(取り引き)テーブルおよびProduct(商品)テーブルをもとに作っていきます。

前提

  • Pay.jpのアカウント作成済み
  • Pay.jpにてクレジットカード登録機能は実装済み
  • ビューはHamlで記載
  • deviseにてログイン済み

手順

  1. Transactionテーブルを作成(購入済みの場合SOLD OUTを表示させるため)
  2. アソシエーションの設定
  3. Transactionコントローラーを作成
  4. 環境変数の設定
  5. ルーティングの設定
  6. マークアップ:購入内容確認画面
  7. マークアップ:購入完了画面
  8. マークアップ:購入済みの場合SOLD OUTを表示
  9. 購入データの確認

Transactionテーブルを作成

今回は購入済みの場合SOLD OUTを表示させるためにTransactionテーブルを作成します。

$ rails g model Transaction
db/migrate/20200000000000_create_transactions.rb
class CreateTransactions < ActiveRecord::Migration[5.2]
  def change
    create_table :transactions do |t|
      t.references :product, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.timestamps
    end
  end
end

マイグレートを実行

$ rails db:migrate

アソシエーションの設定

transaction.rb
class Transaction < ApplicationRecord
  belongs_to :user, optional: true
  belongs_to :product, optional: true
end

Transactionコントローラーを作成

$ rails g controller transactions
transactions_controller.rb
class TransactionsController < ApplicationController
  require 'payjp'
  before_action :set_card, only: [:pay_index, :pay]
  before_action :set_product

  def pay_index
    @top_image = @product.images.first
    @card = @set_card.first
    if @card.blank?
      redirect_to controller: "cards", action: "new"
    else
      Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"]
      customer = Payjp::Customer.retrieve(@card.customer_id)
      @default_card_information = customer.cards.retrieve(@card.card_id)
    end
  end

  def pay
    @card = @set_card.first
    Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
    Payjp::Charge.create(
    :amount => @product.price,
    :customer => @card.customer_id,
    :currency => 'jpy',
  )
  redirect_to action: 'done', product_id: @product
  end

  def done
    @top_image = @product.images.first
    Transaction.create(product_id: @product.id, user_id: current_user.id)
  end

  private

  def set_card
    @set_card = Card.where(user_id: current_user.id)
  end

  def set_product
    @product = Product.find(params[:product_id])
  end

end

環境変数の設定

コントローラ内のENV["PAYJP_PRIVATE_KEY"]は環境変数でテスト秘密鍵を設定し読み込む。
今回はdotenvとgonを利用する。

dotenv:Railsの環境変数管理
gon:JSにてRailsで定義した環境変数を使用
参考:https://qiita.com/3443/items/44202ff6504210592570#comments

※gonはCard登録機能にて使用したため今回は関係ありません

ルーティングの設定

routes.rb
Rails.application.routes.draw do
  devise_for :users
  root "products#index"
  resources :users, only: [:edit, :update]
  resources :products
  resources :cards, only: [:new, :show, :destroy] do
    collection do
      post 'pay_show', to: 'cards#pay_show'
      post 'pay', to: 'cards#pay'
    end
  end

  ##今回の該当箇所
  resources :transactions, only: [:index] do
    collection do
      get 'pay_index', to: 'transactions#pay_index'
      post 'pay', to: 'transactions#pay'
      get 'done', to: 'transactions#done'
    end
  end

end

マークアップ:購入内容確認画面

Image from Gyazo

pay_index.html.haml
.transaction-pay
  .transaction-pay__content
    %h2.transaction-pay__title 購入内容の確認
    .transaction-pay__item
      .transaction-pay__item-box
        = image_tag @top_image.image.url, alt:"商品画像", class: "transaction-pay__item-image"
      .transaction-pay__item-detail
        %p.transaction-pay__item-detail--name
          = @product.name
        .transaction-pay__item-detail-price
          .transaction-pay__item-detail-price--text.transaction-pay__item-detail-price--text
            = @product.price
          .transaction-pay__item-detail-price--shipping (税込)送料込み
    .transaction-pay__table
      .transaction-pay__table-inner
        .transaction-pay__table-form
          .transaction-pay__table-content
            .transaction-pay__table-pay
              %p.transaction-pay__table-pay--title 支払い金額
            .transaction-pay__table-price
              %p.transaction-pay__table-price--title%p.transaction-pay__table-price--title
                = @product.price
          .transaction-pay__table-way
            %h3 支払い方法
            .transaction-pay__table-register
              - if @default_card_information.blank?
                %i.fas.fa-plus-circle
                %span.icon-register
                = link_to "登録してください", new_card_path
              - else
                = "**** **** **** " + @default_card_information.last4
                - exp_month = @default_card_information.exp_month.to_s
                - exp_year = @default_card_information.exp_year.to_s.slice(2,3)
                = exp_month + " / " + exp_year
          .transaction-pay__table-buy
            = form_tag(action: :pay, method: :post, product_id: @product) do
              %button.transaction-pay__table-buy-button 購入する

マークアップ:購入完了画面

Image from Gyazo

done.html.haml
.transaction-done
  .transaction-done__content
    .transaction-done__text
      購入が完了しました!
    .transaction-done__image
      = image_tag @top_image.image.url, alt:"商品画像", class: "transaction-done__image--img"
    .transaction-done__title
      = @product.name
    .transaction-done__price
      .transaction-done__price--text
        = @product.price
      .transaction-done__price--info
        (送料込み)

マークアップ:購入済みの場合SOLD OUTを表示

Image from Gyazo

show.html.haml
.product-show
  .product-show__main
    .product-show__content
      .product-show__top-content
        .product-show__item-box
          .product-show__item-box--name
            = @product.name
          .product-show__item-box__body
            .product-show__item-box__body--top-img
              = image_tag @top_image.image.url, alt:"トップ画像", class: "product-show__item-top-img"
            .product-show__item-box__body--list
              - @images.each do |image|
                .product-show__item-box__body--sub-img
                  = image_tag image.image.url, alt:"サブ画像", class: "product-show__item-sub-img"
          .product-show__item-box--price
            %span 
            = "#{@product.price}円"
            .product-show__item-box--price-detail
              %span.product-show__item-box--price-detail-text
                (税込)
              %span.product-show__item-box--price-detail-text
                = @product.delivery_charge
          .product-show__item-box--item-detail
            = @product.name
          -# 商品出品者であれば表示させない
          - if user_signed_in? && (current_user.id == @product.user_id)
          - else
            .product-show__transaction
              .product-show__transaction-box
                -# 商品購入済みであればSOLD OUT
                - if @product_id.present?
                  .product-transaction-btn
                    SOLD OUT
                - else
                  = link_to "購入画面に進む", pay_index_transactions_path(product_id: @product), class: "product-transaction-btn"

購入データの確認

購入ができていれば下記のURLにて履歴が確認できます。
https://pay.jp/d/charges

以上です

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

【JS/Vue】ライフサイクル

主に下記のUdemyのコースをベースに書きました。
Vue.js + Firebaseで作るシングルページアプリケーション | Udemy

Vueライフサイクル

ドキュメントに書かれているライフサイクルの図は下記の通りです。

image.png
Vue インスタンス — Vue.jsより引用

大まかに定義すると,下記の流れのようになっており、それぞれに作成前、後が存在しています。

  1. Vueインタンス作成
  2. マウント
  3. DOM アップデート
  4. Vueインタンス削除

またこのライフサイクルでは、ライフサイクルフックで処理を追加できるようになっています。
ライフサイクルフックとは、上図で書かれてる beforeCreatecreated などの赤枠で囲われているものをさします。
この中で色々実装することで、細かい挙動の変更を行うことができます。

Vueインタンス作成

beforeCreatecreatedがインスタンスの作成に該当します。
下記のような実装でインスタンスを作成することができます。

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

マウント

beforeMountmountedが該当します。
定義については下記がわかりやすかったです。

既存のDOM要素をVue.jsが生成するDOM要素で置き換えること。
Vue.js入門 2章 Vue.jsの入門(コンストラクタ、マウント、data) - Qiitaより引用

仮想DOM

beforeUpdateupdatedが該当します。
つまり変更差分のところだけ、アップデートしようという考え方です。

なぜ仮想DOMという概念が俺達の魂を震えさせるのか - Qiita
仮想DOMは本当に“速い”のか? DOM操作の新しい考え方を、フレームワークを実装して理解しよう - エンジニアHub|若手Webエンジニアのキャリアを考える!

実験

下記のように html, js を定義して、 console上でメッセージを書き換えると、beforeUpdateupdatedが動いていることがわかります。

<div id="app">
  {{ message }}
</div>
const vm = new Vue({
  el: '#app',
  data() {
    return {
      message: 'こんにちは'
    }
  },
  beforeUpdate() {
    console.log('再描画前')
  },
  updated() {
    console.log('再描画後')
  }
})

window.vm = vm

image.png

便利なサイト

下記のサイトで色々 js の実装とかを試せます。
めちゃ便利です。
JSFiddle - Code Playground

その他参考にした記事

Vueのライフサイクルを完全に理解した - Qiita

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

JavaScriptでカジュアルに日付操作する

JSでちょっとしたDate操作をしたのでメモ。

YYYYMMDD文字列を取得

toISOString() メソッドは、簡潔な拡張表記の ISO 形式 (ISO 8601) の文字列を返します。これは、常に 24 文字または 27 文字の長さになります (それぞれ、YYYY-MM-DDTHH:flag_mm:ss.sssZ または ±YYYYYY-MM-DDTHH:flag_mm:ss.sssZ)。タイムゾーンは常に 0 UTC オフセットになり、接尾辞 "Z" で表記されます。

const date = new Date()
date.toISOString().split('T')[0] // 2020-03-04
date.toISOString().split('T')[0].replace(/-/g, '') // 20200304

日付の操作

1日足す

const date = new Date()
date.setDate(date.getDate() + 1)
new Date(date)

もしくはgetTimeメソッドで

1970 年 1 月 1 日 00:00:00 UTC から指定した日時までの経過時間をミリ秒で表した数値。

を取得できるので普通に足し算すればいいです。

const date = new Date()
date.getTime() // 1584792437135
const date = new Date()
const tomorrow = new Date(date.getTime() + 1000 * 60 * 60 * 24)

1ヶ月足す

const date = new Date()
date.setMonth(date.getMonth() + 1)
new Date(date)

フォーマット

const date = new Date()
const formatString = 'YYYY年MM月DD日 HH時mm分'

formatString
  .replace('YYYY', date.getFullYear())
  .replace('MM', date.getMonth() + 1)
  .replace('DD', date.getDay() + 1)
  .replace('HH', date.getHours())
  .replace('mm', date.getMinutes())

日付作成

指定期間のdateの配列を作成する。

const createDays = (since, until) => {
  const results = [since];
  while (results[results.length - 1] !== until) {
    const date = new Date(results[results.length - 1]);
    const next = date.setDate(date.getDate() + 1);
    const ymd = new Date(next).toISOString().split('T')[0];
    results.push(ymd);
  }
  return results;
};
createDays('2020-04-01', '2020-05-31')
// ["2020-04-01", "2020-04-02", "2020-04-03", "2020-04-04"... "2020-05-30", "2020-05-31"]

時間作成

const createTimes = (from, to, interval) => {
  let results = [from];
  while (results[results.length - 1] !== to) {
    const [hh, mm] = results[results.length - 1].split(':');
    const date = new Date(
      1000 * 60 * (60 * Number(hh) + Number(mm) + interval)
    );
    const hhmm = date
      .toISOString()
      .split('T')[1]
      .slice(0, 5);
    results.push(hhmm);
  }
  return results;
};
createTimes('10:00', '12:30', 10)
//  ["10:00", "10:10", "10:20", "10:30", "10:40", "10:50", "11:00", "11:10", "11:20", "11:30", "11:40", "11:50", "12:00", "12:10", "12:20", "12:30"]

Dateのおさらい

生成

new Date()
new Date('2020-04-01')
new Date(10000)
new Date(1995, 11, 17)

Get

date = new Date('2020-03-21T12:19:04.441Z')
date.getDate() // 21
date.getDay() // 6
date.getFullYear() // 2020
date.getHour() // 21
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptにおけるthisの挙動について

対象読者

  • JavaScriptのオブジェクトについて理解している初学者
  • thisの挙動がいまいちわからないJavaScript初学者

そもそもJavaScriptにおけるthisって何?

JavaScriptにおけるthisとは、自分自身を表すオブジェクトのことを挿します。重要なポイントは呼び出し元によって変わる点です。どういうことか以下に記載していきます。

グローバルオブジェクトを指すthis

sayFoo.js
const sayFoo = function () {
  console.log(this["foo"]);
}
foo = "foo";// 予約後であるconst,letをつけない場合はグローバルオブジェクトとして定義される
sayFoo();

sayFoo.jsでは、sayFoo()を呼び出した場合、グローバルオブジェクト(windowオブジェクト)として変数fooを宣言しています。よって、thisの対象はグローバルオブジェクトをさす挙動になっています。
this["foo"]の[]で書く方法はブラケット表記法と呼ばれる書き方です。
ブラケット表記法

インスタンス変数を指すthis①

sayFooInstance.js
const sayFoo = function () {
  console.log(this["foo"]);
}  
foo = 'foo';
const sayFooInstance = {
  foo: "I'm Instance Object",
  sayFoo
}
sayFooInstance.sayFoo();

sayFooInstance.jsでは、sayFooInstanceオブジェクトにて関数sayFooを呼び出しています。sayFooInstance内部からの呼び出しなので、this["foo"]foo: "I'm Instance Object"を参照しています。

インスタンス変数を指すthis②

myObjInstance.js
function MyObj(id) {
  this.id = id;
}
MyObj.prototype.printId = function (id) {
  console.log(this.id);
}
const newMyObj = new MyObj(5);
newMyObj.printId(2);// MyObjでidをthisに入れているため、2ではなく5と表示される

実行結果
スクリーンショット 2020-03-21 20.21.42.png

オブジェクトのプロトタイプに関数を定義して、実行しても、2が表示されず、5が表示されます。
なぜなら、MyObj関数をnewを使ってインスタンス化する際にthis.id = idで変数idをMyObj関数に代入しているため、printIdが実行されても、this.idの値に代入されないので、実行結果が5と表示されます。

ちなみに以下の様にClassを使っても同じ形になります。

インスタンス変数を指すthis③

myClass.js
class MyClass {
  constructor(id) {
     this.id = id;
  }
  print(id) {
    console.log(this.id);
  }
}
const myClass = new MyClass(5);
myClass.print(3);

実行結果
スクリーンショット 2020-03-21 20.21.42.png

コンストラクターを使ってインスタンス生成時にthis.idの値を代入します。
インスタンス変数を指すthis②でご紹介した通り、インスタンス生成時にthis.idにid値を代入しているため、実行結果は5と表示されます。

クラスについてはMDN Web docに記載があります。
クラス MDN Web doc

ちなみに、オブジェクト内部で関数を入れ子にした場合はどうなるでしょうか

インスタンス変数の内部で関数が入れ子になっている場合のthis

sayFooInstanceNest.js
const nestFunction = {
  nestFunc1: function () {
    console.log(this);// nestFunctionを参照
    const nestFunc2 = function () {
      console.log(this);// グローバルオブジェクトを参照
      const nestFunc3 = function () {
        console.log(this);// グローバルオブジェクトを参照
      }();
    }();
  }
}
nestFunction.nestFunc1();// 実行

実行結果は以下の画像の通り。
スクリーンショット 2020-03-21 19.14.17.png

入れ子にした場合のthisは、グローバルオブジェクトを参照します。

ちなみに以下の形{}();と書かれているコードをみて「ん?」と思った人もいると思います。
以下の書き方は即時実行関数式と呼ばれ、定義されるとすぐに実行されるJavaScriptの関数となります。

sokuji.js
const nestFunc3 = function () {
          ・・・中略・・・
}();// <= 波括弧の隣に丸括弧をつけることで、定義されるとすぐに実行される即時実行関数となります。

MDN Web Docs 用語集 IIFE (即時実行関数式)

thisは理解してしまえば、あとは応用が効く

内容に不備があったり、誤字脱字などあれば、コメントをいただければと思います。

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

[Javascript] 要素の外側をクリックした時にremoveする

概要

例えばモーダルウインドウには×で閉じるボタンがレイアウトされている事が一般的ですが、
ユーザビリティを考えると、ボタン以外でも閉じられるようにしている事が望ましいですよね。
だからといって、どこでもクリックすれば閉じられるような処理では、
そもそも閉じるボタンの意味がなくなってしまいます。

今回はクリックイベントで付与したクラスを、
反応させたくない要素の外側かを判定して取り除く処理になります。
あくまで私が調べた結果ですので、もっとスマートな書き方があるかもしれません。

制御部分のコード

Javascript
const docOpen = document.getElementById('クラスを付与した要素');
const elemBody = document.querySelector('body');

elemBody.addEventListener('click', e => {
  if(e.target !== docOpen) {
    docOpen.classList.remove('active');
  }
});

解説

elemBody.addEventListener('click', e => {
  if(e.target !== docOpen) {
     docOpen.classList.remove('active');
  }
});

今回は全体の指定をbodyにしています。
body要素をクリックした時に、
event.targetプロパティでクリックした要素を取得し、
開いているdocOpenの要素と違う要素なのかを判定しております。

こうする事で、例えば、ドロワーメニュー等を開いた時に、
メニュー部分までクリックしたら閉じてしまうといった動作を防いでいます。

要素が違うのならばremoveされます。

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

vue cli: lazy loadって?やっておいたほうがいい理由を解説

備忘録です。

vue cliでは、vue-routerを使うことで、いとも簡単にpathがつくれます。
つまりabout, contact, infoなど、プロジェクト内にいくつかページを設定したいときに、

blog.jp/about
blog.jp/contact
blog.jp/info

とそれぞれのリンクをつくれるようになります。

vue cliでプロジェクトを作成するときにvue-routerを含むよう設定しておけば、routerというフォルダが自動的に作成され、そのなかにあるindex.jsからrouterの作成・編集が行えるようになります。この記事はvue-routerについてではないので、細かいところは割愛します。

(vue-routerのドキュメントは、ここから確認できます)

このときlazy loadを設定しておかないと、ウェブサイトを開いたときに、個々のページで使われる全てのJSが一斉に読み込まれてしまいます。

なぜlazy loadするべきか

一見、「別にいいじゃん」と思うかもしれません。ところがたとえばページが重い場合、「ページに必要となるJSを全部ロードしてからページを開く」なんてことをしていると、動作が鈍くなりかねません。

lazy loadであれば、ユーザーがページにクリックした際に、そのページのJSファイルがロードされるよう設定ができます。なので、サイトにアクセスした瞬間から重すぎてページがロードしない、なんて事態を防げます。そうすることで、ユーザーはページ内をスイスイ回遊することができます。

方法

たとえばAboutというページがあるとします。
まずはlazy loadをしなかった場合のコードを見てみましょう。
About.vueは、通常通りimport About from〜というコードでimportされています。

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import About from "../views/About.vue";//ここでAbout.vueをインポート

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home
  },
  {
    path: "/about", //パスをつくる
    name: "About", //vueファイル内でアクセスできる名前をつける
  }
];

一方でlazy loadをした場合は、routesのなかにあるcomponentというプロパティを通して、About.vueをインポートします。

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";//ここでAbout.vueをインポートしない


Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home
  },
  {
    path: "/about", //パスをつくる
    name: "About", //vueファイル内でアクセスできる名前をつける
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue") // ここでlazy loadをしている!
  }
];

componentを定義することで、ページが呼び出されたときにだけ、About.vueが読み込まれるようになります。

ちなみに/* webpackChunkName: "about" */これはなんぞや?と思うかもしれません。

lazy loadのコードを書いたあと、Chrome Developer Tools?networkを開き、Aboutのページをクリックしてみると、app.jsとは別に0.jsというファイルがロードされていることが確認できるかと思います。つまりwebpackChunkNameを設定しないと、デフォルトとして数字でファイル名が登録されてしまうのです。

about.jsとわかりやすく表示できるよう、/* webpackChunkName: "about" */を付け加えておきましょう。

vue.jsの公式ドキュメントにも記載されています。

以上です?

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

vue cli: lazy loadって?やっておいたほうがいい理由&やり方を解説

備忘録です。

vue cliでは、vue-routerを使うことで、いとも簡単にpathがつくれます。
つまりabout, contact, infoなど、プロジェクト内にいくつかページを設定したいときに、

blog.jp/about
blog.jp/contact
blog.jp/info

とそれぞれのリンクをつくれるようになります。

vue cliでプロジェクトを作成するときにvue-routerを含むよう設定しておけば、routerというフォルダが自動的に作成され、そのなかにあるindex.jsからrouterの作成・編集が行えるようになります。この記事はvue-routerについてではないので、細かいところは割愛します。

(vue-routerのドキュメントは、ここから確認できます)

vue-routerで新たなpathをつくる際にlazy loadを設定しておかないと、ウェブサイトを開いたときにウェブサイト全体で使われる全てのJSが一斉に読み込まれてしまいます。

なぜlazy loadするべきか

一見、「別にいいじゃん」と思うかもしれません。ところがページが重い場合、「ページに必要となるJSを全部ロードしてからページを開く」なんてことをしていると、ページの動作が鈍くなりかねません。

lazy loadであれば、ユーザーがページにクリックした際に、そのページのJSファイルがロードされるよう設定ができます。なので、サイトにアクセスした瞬間から重すぎてページがロードしない、なんて事態を防げます。そうすることで、ユーザーはページ内をスイスイ回遊することができます。

方法

たとえばAboutというページがあるとします。
まずはlazy loadをしなかった場合のコードを見てみましょう。
About.vueは、通常通りimport About from〜というコードでimportされています。

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import About from "../views/About.vue";//ここでAbout.vueをインポート

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home
  },
  {
    path: "/about", //パスをつくる
    name: "About", //vueファイル内でアクセスできる名前をつける
  }
];

一方でlazy loadをした場合は、routesのなかにあるcomponentというプロパティを通して、About.vueをインポートします。

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";//ここでAbout.vueをインポートしない


Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home
  },
  {
    path: "/about", //パスをつくる
    name: "About", //vueファイル内でアクセスできる名前をつける
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue") // ここでlazy loadをしている!
  }
];

componentを定義することで、ページが呼び出されたときにだけ、About.vueが読み込まれるようになります。

ちなみに/* webpackChunkName: "about" */これはなんぞや?と思うかもしれません。

lazy loadのコードを書いたあと、Chrome Developer Tools?networkを開き、Aboutのページをクリックしてみると、app.jsとは別に0.jsというファイルがロードされていることが確認できるかと思います。つまりwebpackChunkNameを設定しないと、デフォルトとして数字でファイル名が登録されてしまうのです。

about.jsとわかりやすく表示できるよう、/* webpackChunkName: "about" */を付け加えておきましょう。

vue.jsの公式ドキュメントにも記載されています。

以上です?

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

[Rails]非同期のいいね機能実装

ユーザー(user)が出品した商品(item)にいいねできる機能を実装してます。

Likeモデル、テーブル作成

rails g model Like

like.rb
class Like < ApplicationRecord
end
XXXXXXXXXXX_create_likes.rb
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.integer     :user_id
      t.integer     :item_id
      t.timestamps
    end
  end
end

rails db:migrateマイグレーションファイル実行

モデル

通常はuserとitemは1対多の関係ですが、いいね機能実装時に関してはlikeテーブルが加わるので、多対多の関係になります。

user.rb
  has_many :likes, dependent: :destroy
  has_many :like_items, through: :likes, source: :item
item.rb
  has_many :likes, dependent: :destroy
  has_many :liking_users, through: :likes, source: :user

dependent: :destroyはいいねを外した時に、中間likesテーブルにある該当userとitemのレコードを一緒に削除してくれます。

:like_itemsはuserがどのitemをいいねしているのかを取得
:liking_usersはitemがどのuserによっていいねされているのか取得

through: :likesは多対多の関係で中間likeテーブルを経由するための関連付けで記述

source:オプションは関連付け元の名前を指定するため記述

like.rb
class Like < ApplicationRecord
  belongs_to :item, counter_cache: :likes_count
  belongs_to :user
end

counter_cahce: :likes_countはリレーションされているlikeの数の値をリレーション先のlikes_countというカラムの値に入れるという意味です。なのでlikes_countカラムをitemsテーブルに追加しましょう。

itemsテーブルにlikes_countカラム追加

rails g migration AddLikes_countToItems

XXXXXXXXXXX_add_likes_count_to_items.rb
class AddLikesCountToItems < ActiveRecord::Migration[5.2]
  def change
    add_column :items, :likes_count, :integer
  end
end

rails db:migrateマイグレーションファイル実行

ルーティング設定

routes.rb
resources :items
  member do
    post   '/like/:item_id' => 'likes#like',   as: 'like'
    delete '/like/:item_id' => 'likes#unlike', as: 'unlike'
  end

いいねをつける時→like 外す時→unlike
as:でルーティングに名前を付けれる。この二つはlike_path,unlike_pathとして使えるようになります。

コントローラー

rails g contoller likes

likes_controller.rb
class LikesController < ApplicationController
  before_action :set_variables

  def like
    like = current_user.likes.new(item_id: @item.id)
    like.save
  end

  def unlike
    like = current_user.likes.find_by(item_id: @item.id)
    like.destroy
  end

  private
  def set_variables
    @item = Item.find(params[:item_id])
    @id_name = "#like-link-#{@item.id}"
  end
end

@id_nameは非同期で使用します。

ビュー

items/show.html.haml
  .option
    = render partial: 'likes/like', locals: { item: @item }

renderを使用し、部分テンプレートへ誘導
likesディレクトリに部分テンプレート_like.html.hamlファイルを作成

likes/_like.html.haml
.option__like{:id => "like-link-#{@item.id}"}
  - if current_user.likes.find_by(item_id: item.id)
    = link_to unlike_item_path(@item.id, @item.id), method: :delete, remote: true, class: "option__like-on" do
      .fas.fa-star
      .option__like-on__text いいね!
      .option__like-on__count
        =item.likes.count
  - else
    = link_to  like_item_path(@item.id, @item.id), method: :post, remote: true, class: "option__like-off" do
      .fas.fa-star
      .option__like-off__text いいね!
      .option__like-off__count
        =item.likes.count

いいねボタンのビューを記述します。

{:id => "like-link-#{@item.id}"}をつけることで@itemのボタンであることを指定します。

remote: trueをつけることでリンクを押した時、ajaxを発火させます。

=item.likes.countでいいねされた数を表示します。

いいねボタンの非同期化

like.js.hamlunlike.js.hamlファイル作成

likes/like.js.haml
$("#{@id_name}").html('#{escape_javascript(render("likes/like", item: @item  ))}');

likes/unlike.js.haml
$("#{@id_name}").html('#{escape_javascript(render("likes/like", item: @item  ))}');

コントローラーで定義した@id_nameを指定し、escape_javascriptで先ほど作成した_likeファイルを埋め込んでます。

Sass

item.show.scss
          .option {
            display: flex;
            justify-content: space-between;
            &__like {
              &-on {
                text-decoration: none;
                padding: 11px 10px;
                border-radius: 40px;
                color: #3CCACE;
                border: 1px solid #ffb340;
                display: flex;
                line-height: 16px;
                .fas.fa-star {
                  padding-right: 5px;
                }
                &__text {
                  padding-right: 5px;
                }
              }
              &-off {
                text-decoration: none;
                padding: 11px 10px;
                border-radius: 40px;
                color: #333;
                border: 1px solid #f5f5f5;
                display: flex;
                line-height: 16px;
                background: #f5f5f5;
                .fas.fa-star {
                  padding-right: 5px;
                }
                &__text {
                  padding-right: 5px;
                }
              }
            }
          }

sassの説明は省略します。

完成イメージ

Image from Gyazo

ユーザーがいいねした商品一覧を表示させたい方はこちらをご覧ください

間違えている部分があったらぜひコメントよろしくお願いします!!

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

Vue.js 自分用

vue-eyecatch.jpeg
自分用になります。

テンプレート構文

methodsの使い方

    <div id="app">
        <p v-once>{{ message }}</p> <!-- v-onceを使用 dataプロパティからmessageのValueを呼び込む -->
        <p>{{ sayHi() }}</p> <!-- 関数を呼ぶ -->
        <p>{{ message }}</p> <!-- この記述が最適 -->
    </div>


    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data:{
                message: 'Hello World',
            },
            methods:{
                sayHi: function(){
                    return this.message = 'Hello Vue.js' //thisを忘れない
                }
            }
        });
    </script>

dataのバインディング v-bind

 <div id="app">
        <a v-bind="twitterObject">Twitter</a> <!-- v-bind -->
    </div>


    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data:{
                twitterObject:{
                    href: 'https://twitter.com',
                    id:3
                },
            },
        });
    </script>

countUp v-on

<div id="app">
   <p>現在のクリック回数は{{ number }}です。</p> <!-- dataのnumberの数を取得 -->
   <!-- v-on:click -->
   <button v-on:click="countUp(2)">カウントアップ</button> <!-- countUp関数に引数(2)を -->
</div>


<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    new Vue({
        el: '#app',
        data:{
            number: 0,
        },
        methods:{
            countUp: function(times){ //times 引数の2
                this.number += 1 * times // this
            }
        }
    });
</script>

countUp 省略記法

<button @click="countUp">カウントアップ</button> <!-- @記法で v-on:を省略 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data:{
                number: 0, //初期値の設定
            },
            methods:{
                countUp: function(){
                    this.number += 1; //this 関数内
                }
            }
        });
    </script>

mouseover

<div id="app">
    <p v-on:mousemove="changeMousePosition(1, $event)">マウスオーバ- <!-- 第一引数に値を、第二引数にイベント -->
    <span v-on:mousemove.stop>反応しないでください~~</span></p> <!-- stopをつけると反応しない -->
    <p>りんご:{{ x }}個, みかん:{{ y }}個</p>
</div>


<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    new Vue({
        el: '#app',
        data:{
            x: 0, //初期値
            y: 0, //初期値
        },
        methods:{
            changeMousePosition: function(divideNumber, $event){
                this.x = event.clientX + divideNumber; 
                this.y = event.clientX + divideNumber;
            }
        }
    });
</script>

アラート機能

<div id="app"> 
    <input type="text" v-on:keyup.enter="myAlert"> <!-- イベントkeyupに.enter -->
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            methods:{
                myAlert: function(){ // methods内に 関数宣言
                    alert('アラート');
                }
            }
        });
    </script>

非同期処理 v-model

スクリーンショット 2020-03-22 0.15.27.png

form の input 要素 や textarea 要素、 select 要素に双方向 (two-way) データバインディングを作成するには、v-model ディレクティブを使用することができます。

<div id="app">
    <input type="text" v-model="message"> <!-- v-model  -->
    <h1>{{ message }}</h1> <!-- data要素 -->
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data:{
                message: 'こんにちは',
            },
            methods:{
                countUp: function(){
                    this.number += 1;
                }
            }
        });
    </script>

https://jp.vuejs.org/v2/guide/forms.html

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

知識ゼロから始めるTypeScript 〜応用編〜

はじめに

クラス編の続きです。
知識ゼロから始めるTypeScriptシリーズのラストパート応用編です。

予習はこちらから:point_down:
知識ゼロから始めるTypeScript 〜基本編〜
知識ゼロから始めるTypeScript 〜関数編〜
知識ゼロから始めるTypeScript 〜クラス編〜

様々な応用パターンを見ていきましょう。

型の互換性

  • 型の互換性は常にコンパイラーによって評価されている
  • 型の互換性が無いと警告で教えてくれる
  • 型の互換性があると型が上書きされる

any型string型を代入してみる

let fooCompatible: any
let barCompatible: string = 'TypeScript'

console.log(typeof fooCompatible) // undefined

fooCompatible = barCompatible // 代入できる any型はstring型に互換性がある

console.log(typeof fooCompatible) // string

string型number型を代入してみる

そうすると互換性が無いのでエラーが起きる

let fooInCompatible: string
let barInCompatible: number = 1

fooInCompatible = barInCompatible // エラー string型にnumber型は互換性が無い

ジェネリクス型

  • 抽象的な型宣言
  • 引き数の型戻り値の型が一致している必要がある
  • 扱う型が違うだけで同じ処理をしている関数を使い回す事で、型の実装コストが削減できる

こんな感じでやってることは同じなのに扱う型だけ違う時に大活躍

// 型だけ違うけどやってることはほとんど一緒な関数
const echo = (arg: number): number => {
   return arg
}

const echo = (arg: string): string => {
   return arg
}

上記の関数をまとめてジェネリクスで定義してみる
型定義は慣習的にTとすることが一般的らしい
関数の引き数の前に<T>を定義する
関数を実行するときは<>内に実行したい型を定義する

// ジェネリクスで定義
const echo = <T>(arg: T): T => {
  return arg
}

// 実行
console.log(echo<number>(100)) // 100
console.log(echo<string>('Hello')) // Hello
console.log(echo<boolean>(true)) // true

クラスの場合はクラス名の後ろにジェネリクス型を定義する

class Mirror<T> {
  constructor(public value: T) {}

  echo(): T {
    return this.value
  }
}

console.log(new Mirror<string>('Hello').echo()) // Hello
console.log(new Mirror<number>(123).echo()) // 123

型アサーション

  • 型の互換性がある時に、手動で型を変換させることができる
  • 互換性がある場合のみに限定される

これはコードを見た方が分かりやすいかも

以下の様にany型で定義された変数namelengthメソッドを使用して、変数lengthに代入する場合、本来であれば変数lengthnumber型である必要があるのにany型で定義されてしまう。

let name: any = 'foo'

let length = name.length

length = 'foo' // 文字列を代入してもlengthはany型

厳密な型定義がTypeScriptの醍醐味なのでこれは避けたい。

これを実現するのが型アサーション
変数を()で括りasの後ろに型を手動で定義する
こうすることで変数lengthは本来あるべき姿(number型)になる

let length = (name as string).length

length = 'foo' // lengthはnumber型なのでコンパイルエラーになる

ちなみ下記の書き方は非推奨

JSXの記法と似ているのが理由らしい

// この書き方は非推奨
let length = (<string>name).length

length = 'foo' // lengthはnumber型なのでコンパイルエラーになる

constアサーション

  • 設定した値の再代入しかできない
  • データの値を書き換えないことをコンパイラーに伝える
let nickName = 'foo' as const // as constを定義

nickName = 'buzz' // コンパイルエラー 

これ普通にconstで定義すればよくない?と思いつつ

オブジェクトに対しても設定できるのが便利そう
オブジェクトの場合はreadonly修飾子が設定される
どれだけネストされていてもreadonlyになる

let profile = {
  name: 'foo',
  height: 175
} as const

// readonlyなのでエラーが発生する
profile.name = 'foo'
profile.height = 175

Nullable Types

  • 不確定な状態を持ちうる型
  • ある値を設定したい時に、必ずしも設定したい値は確定しているとは限らない
  • とりあえずまだわからないという状態を設定しておきたい

TypeScriptにはコンパイルオプションがある
コンパイルオプションはtsconfig.jsonで管理している

tsconfig.json"strictNullChecks"falseに設定すると、どんな変数にもnullを許容できる状態になる

tsconfig.json
{
  "compilerOptions": {
  ~ 省略 ~
  "strictNullChecks": false
}
let profile: { name: string; age: number } = {
  name: 'foo',
  age: null //nullでもエラーにならない
}

しかし、これは秩序のないコードになってしまう

このような場合はunion型を使う

let profile: { name: string; age: number | null } = {
  name: 'foo',
  age: null
}

tscofig.jsonでグローバルな設定をするのではなく、union型を使用して局所的に対応する

インデックスシグネチャ

オブジェクトにプロパティを新規で追加する度に型を定義する手間を防ぐ

基本的な書き方は[index: string]: 型

interface Profile {
  // オブジェクトのバリューには以下の型のいずれかを許容する
  [index: string ]: string | number | boolean
}

// 空のオブジェクトを設定
let profile: Profile = {}

profile.name = 'buzz'
profile.age = 30
profile.nationality = 'Japan'

console.log(profile) // { name: 'buzz', age: 30, nationality: 'Japan' }

まとめ

応用の使い所がまだわからないけど、完璧より前進:runner_tone3:

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

魔法JS☆あれい 第7話「includesのsliceと向き合えますか?」

登場人物

丹生(にゅう)あれい
魔法少女「魔法(マジカル)JS(女子小学生)☆あれい」として活動中。

イテレー太
正体不明の魔法生物。

第1部「ミューテーター・メソッド編」
* 第1話「popでpushした、ような……」
* 第2話「shiftはとってもunshiftって」
* 第3話「もうsortもreverseも怖くない」
* 第4話「fillも、spliceも、copyWithinもあるんだよ」

第2部「アクセサ・メソッド編」
* 第5話「joinなんて、concatなわけない」
* 第6話「indexOfなの絶対lastIndexOf」

includes()

イテレー太「さあ、惰性で戦いを続けるよ!」
あれい「堂々と言うんじゃねえよこの駄犬」
イ「さて、今回僕が魔法世界から召喚したデータは……これだ!」

items = [
  'ウォ一タ一', 'ワォ一リ一', 'ウォ一リ一',
  'ウォ一ソ一', 'ウォ一リ一', 'ウ才一リ一',
  'ウォ一リ一', 'チャ一リ一', 'チャ一ハン'
];

あ「前回と一緒じゃねえか。どれだけ引っ張るんだこのネタ」
イ「……果たして、本当にそうかな?」
あ「何だと」
イ「じゃあ、この配列の中に、『ウォーリー』という文字列があるかどうかを判定して、truefalseのいずれかの値をreturnして敵を倒してよ!」
あ「面倒くせえなあ……」

return items.includes('ウォーリー');
// false

あ「ねえのかよ」
イ「伸ばし棒(ー)に見える文字は全部漢数字の一だよ!」
あ「どれだけ手の混んだ嫌がらせだよ」

解説

includes() メソッドは、特定の要素が配列に含まれているかどうかを true または false で返します。与えられた要素が見つかるかどうかを計算するために、 SameValueZero (ゼロの同値) アルゴリズムを使用します。

MDNより)

includes() メソッドは、ECMAScript 2016から追加された新しいメソッドです。「検索する値、開始位置」という引数を指定でき、指定した値が配列に含まれていればtrue、含まれていなければfalseを返します。また、開始位置を指定した場合は、その位置以降の要素のみを検索対象にします。

slice()

イ「じゃあ、次に魔法世界から召喚したデータは……これだよ!」

items = [
  'ウォーリー', 'ウォーリー', 'ウォーリー',
  'ウォーリー', 'ウォーリー', 'ウォーリー',
  'ウォーリー', 'ウォーリー', 'ウォーリー'
];

あ「もう全部ウォーリーじゃねえか」
イ「この中から、3番目、4番目、5番目のウォーリーだけを取り出して、それをreturnして敵を倒してよ!」
あ「他のウォーリーとそのウォーリーは何が違うんだよ」
イ「さあ、急いで!」
あ「面倒くせえなあ……」

return items.slice(2, 5);
// ['ウォーリー', 'ウォーリー', 'ウォーリー']

イ「やったね、あれい! 効いてるよ!」
あ「敵もよく判別がつくな」

解説

slice() メソッドは begin から end まで選択された配列の一部をシャローコピーして、新しい配列オブジェクトを返します (end は含まれません)。元の配列は変更されません。

MDNより)

このメソッドは「開始位置、終了位置」という引数を指定できます。しかし、実際に返される値は「開始位置の要素」から「終了位置の直前の要素」であることに注意してください。また、終了位置を指定しない場合は配列の末尾までの要素が、開始位置も指定しない場合はすべての要素が返ります。
この特徴を利用して、引数を指定しないことで、配列をコピーして新しい配列を作成する、という用途にも使用できます。


これにて、第2部「アクセサ・メソッド編」完結です。次回からは、第3部「イテレーション・メソッド編」がスタートします。
さて、魔法JS☆あれいは、魔法(JavaScript)の力でこの世界を守ることが出来るのか!? 次回に続く!

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

【paizaスキルチェック用】配列内の数値を少ない順に並べ替えるJavaScript

数値の並べ替えなのにsort()の中身を入れずに使い、変な並べ替えのまま採点に進むという凡ミスを3回やらかしたので備忘録。

コード

array.sort(
function(x,y){
return (x < y ? -1 : 1);//降順の場合は-1と1を逆にする
}
);


普通に記事あったわ...

JavaScriptで配列内の数値を昇順・降順でソート(並び替え)するTips

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

Javascript での、時計の作り方。

Javascriptのコードで時計を作る。

html5での記述は、
スクリーンショット 2020-03-21 16.36.15.png
この、p id="watch" 以下に、時計の値を入れる。
その式は、
スクリーンショット 2020-03-21 16.50.08.png
window.addEventListener('load', でロードイベントを開始、
id="watch" の中身をリスニングし、window.setInterval(function(){ でインターバルを、とじカッコである、,1000); で一秒ごとに繰り返し動く、インターバルをセット、(setTimeout(function(){},1000);)は1度だけのタイマー、インターバル、リピート=true、の状態で開始される。
関数である、new Date(),getHours(),getMinutes(),getSeconds()はそれぞれ、電波時計の時間をキャッチするもので、new Date()、で現在時間を取得。get~で時間、分、秒をとり、2桁に、するために、自作関数、zero2D()の中に入れて2桁表示に変える。

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

複数画像投稿で盛大に自爆した時の確認事項[備忘録]

はじめに

某フリマアプリの模倣アプリを開発中、出品機能実装で複数画像の登録に死ぬほど手を焼いたので、備忘録として掲載します。

誤った記述などあればご指摘いただけると幸いです。

開発環境・前提

Ruby 2.5.1p57
Ruby on rails 5.2.3
jquery-rails 4.3.5
haml-rails 2.0.1
sass-rails 5.1.0
CarrierWave 2.1.0

完成コード

先に完成コードを載せておく。

image.rb
  belongs_to :item, optional: true
  validates_presence_of :item
  validates :content, presence: true
  mount_uploader :content, ImageUploader
item.rb
  belongs_to :brand, optional: true
  belongs_to :user, optional: true
  belongs_to :category,  optional: true
  has_many :images, dependent: :destroy
  accepts_nested_attributes_for :images, allow_destroy: true
items_controller.rb
def new
    @item = Item.new
    @brands = Brand.all
    @category_parent_array = ["指定なし"]
    Category.where(ancestry: nil).each do |parent|
      @category_parent_array << parent.name
    end
    @item.images.build
  end

  def create
    @item = Item.new(item_params)

    if @item.save!
      @image = @item.images.create
      redirect_to :root
    else
      render :new
    end
  end

private
  def item_params
    params.require(:item).permit(
      :name, :description, :condition, :price, 
      :fee, :brand_id, :area, :shipping_days, 
      images_attributes: [:content, :id, :_destroy]
      ).merge(user_id: current_user.id, category_id: params[:category_id], brand_id: params[:item][:brand_id])
  end
new.html.haml
#画像投稿フォームの記述部分
.main-items
    = form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            .image-wrapper__image-box__js.js-file_group{data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content"
              .js-remove
                %span.js-remove__text
                  削除

モデルへのmout_uploaderの記述

before

image.rb
mount_uploaders :content, ImageUploader

after

image.rb
mount_uploader :content, ImageUploader

mount_uploaderとするかmount_uploadersか。

error.message
NoMethodError (undefined method `map' for #<ActionDispatch::Http::UploadedFile......> #省略
Did you mean?  tap):

app/controllers/items_controller.rb:68:in `create'

mount_uploadersにすると、デフォルトでmapメソッドが使われてしまう。

つまり、1つのfile_fieldに複数の画像データが入っている配列である必要があるのだ。

そういう時は、file_fieldにmultiple: trueを記載する必要がある。

multiple: true

multiple: true を記述すると、一つのfile_fieldに複数画像をアップロードしようとする。

修正前の記述

new.html.haml
= form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            .image-wrapper__image-box__js.js-file_group{data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required" 
              .js-remove
                %span.js-remove__text
                  削除

inputタグのtype[file]部分のHTML(検証)

multiple有り
<input multiple="multiple" class="image-wrapper__image-box__js__label__file js-file" 
id="item_images_attributes_0_content" type="file" 
name="item[images_attributes][0][content][]">

multiple無し
<input class="image-wrapper__image-box__js__label__file js-file"
 id="item_images_attributes_0_content" type="file"
 name="item[images_attributes][0][content]">

デフォルトで設定されるname属性が変わる

私の場合は、各file_fieldに一つずつ保存させるようなコードを書いていたのにもかかわらず、multipleの記述をしてしまっていて、エラーが起きた。

labelタグのfor属性

labelタグのfor属性は、連動させたい子要素のidの値を記述する必要がある。

修正前のlabel部分の記述
html.haml
            %label.image-wrapper__image-box__js__label
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required" 
              .js-remove
                %span.js-remove__text
                  削除
修正後
html.haml
            = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required" 
              .js-remove
                %span.js-remove__text
                  削除

labelタグの性質をよく理解せずに使っていた。
修正前の記述だと、検証でみてみるとわかるが、labelタグにfor属性が付与されておらず、inputタグのid属性(ここでは、item_images_attributes_0_content)に対応しておらず、不具合がおきた。

後からわかったことだが、、hidden_fieldの記述を消してやれば、%labelのままでも支障はないことがわかった。

hidden_fieldの記述

修正前の記述

new.html.haml
= form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            .image-wrapper__image-box__js.js-file_group{data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required"
                = i.hidden_field :item_id, value: @item.id
              .js-remove
                %span.js-remove__text
                  削除

imagesのitem_idをparamsに送るための記述をしていたが、idや外部キーはデフォルトで送られるようになっているため、必要なかった。逆に、これがあることによって、:contentが入っていない空のfile_fieldがhidden_fieldと共にparamsに送られてしまうため、validationに引っかかってしまう。

最後に

チーム開発で商品出品機能を担当したことにより、HTML&CSS, jQuery, Rubyについての知識がかなり深まった。欲張りな性格なので、いろんな記事のいいとこ取りをしようとした結果、こんなにもの何重もの罠を自分で仕掛けて自分でハマるということになってしまった。次からは是非とも一つ一つの用法や性質を理解した上で、実装していきたい。

でも、何かしらの初学者ってこういう風に泥臭く成長していくのかなぁとも思った。

諦めたら、そこで試合終了だよ。

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

React開発環境を作る2

https://qiita.com/daga_0301/items/a7aa8bed488b13f5db64の続き

まずはファイル構成から

│  .babelrc
│  .eslintrc.json
│  package-lock.json
│  package.json
│  webpack.config.js
│
├─public
│      index.html
│
└─src
        index.css
        index.js

webpack

webpackは今回使うツールの中で最上位のツール
javaScriptのモジュールやらcssやらを一つのファイルにまとめてくれます。
だから、htmlでこのようなことをしなくてよくなる。

<link rel="stylesheet" href="./css/bootstrap.min.css">
<link rel="stylesheet" href="./add.css">
<script src="./js/jquery-3.4.1.min.js"></script>
<script src="./js/bootstrap.min.js"></script>

webpackはwebpack.config.jsファイルで設定をします。

module.exports = {
    entry: { //バンドルする元ファイルの場所を指定
        app : "./src/index.js" 
    },
    output: { //バンドルした成果物のファイル名と場所を指定
        filename: "bundle.js",
        path: __dirname + "/public"
    },
    devServer : {//サーバーを立ててwebpackを起動するときの設定
        historyApiFallback: true,
        contentBase : __dirname + '/public',
        port:8080,
        publicPath:'/'
    },
    devtool:"#inline-source-map",
    module:{ //モジュールの設定
        rules:[
            {
            test:/\.js$/, //.jsと付くファイルを対象にする
            enforce:"pre", //最初に実行することを指定
            exclude: /node_modules/, //node_modules/にあるファイルは除外
            loader:"eslint-loader" //eslint-loderを使う
        },
        {
            test:/\.css$/, //.cssと付くファイルを対象にする
            loader:["style-loader","css-loader"]
        },{
            test:/\.js$/,
            exclude:/node_modules/,
            loader:'babel-loader'
        }]
    }
};

babel

babelはes6,es7で書いたjavascriptをes5に変換してくれます。
現在、javascriptはes6まで策定されていますが、ブラウザによってはes5しか対応していないものもあります。
import,class,letなどがes6になります。
この問題を解決してくれる素晴らしいツール、使わないわけにはいかない。
babelの設定ファイルは.babelrc
「.」から始まるファイルは設定によってはエクスプローラで表示されないので注意してください。

{
    "presets": ["env","react"]
}

eslint

eslintはタイプミスをチェックしたり、コードスタイルのルールを設定してくれます。
vsCodeにも組み込めるみたいなのでそっちで設定してもいいかも、
設定項目が馬鹿みたいに多いので動きさえすればいいのなら使わなくてもよい。
設定ファイルは.eslintrc.json

{
    "env":{
        "browser":true,
        "es6":true
    },
    "parserOptions":{
        "sourceType":"module",
        "ecmaFeatures":{
            "experimentalObjectResetSpread":true,
            "jsx": true
        }
    },
    "extends" : ["eslint:recommended","plugin:react/recommended"],
    "plugins" :["react"],
    "rules":{
        "no-console":"off"
    }
}

css-loader style-loader

css-loaderとstyle-loaderはwebpackでcssファイルをバンドルするときに必要
css-loaderはcssファイルを読み取って、style-loaderがstyleタグを出力するらしい(←よくわかってない
設定ファイルはありません。

終わり

今回はここまで
次はreactを動かしてみます。

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

魔法JS☆あれい 第6話「indexOfなの絶対lastIndexOf」

登場人物

丹生(にゅう)あれい
魔法少女「魔法(マジカル)JS(女子小学生)☆あれい」として活動中。

イテレー太
正体不明の魔法生物。

第1部「ミューテーター・メソッド編」
* 第1話「popでpushした、ような……」
* 第2話「shiftはとってもunshiftって」
* 第3話「もうsortもreverseも怖くない」
* 第4話「fillも、spliceも、copyWithinもあるんだよ」

第2部「アクセサ・メソッド編」
* 第5話「joinなんて、concatなわけない」

indexOf()

イテレー太「この話、別に魔法少女ものっていう設定じゃなくてもいいよね」
あれい「第6話にして我に返ってんじゃねえよこの駄犬」
イ「さ、気を取り直して、今回僕が魔法世界から召喚したデータは……これだ!」

items = [
  'ウォーター', 'ワォーリー', 'ウォ一リ一',
  'ウォーソー', 'ウォーリー', 'ウ才ーリー',
  'ウォーリー', 'チャーリー', 'チャーハン'
];

あ「なんなんだこのデータは」
イ「この配列の中から、『ウォーリー』という文字列が前から何番目にあるのか? を探し出して、returnして敵を倒してよ!」
あ「そしてなんなんだその注文は」
イ「さあ、早く!」
あ「面倒くせえなあ……」

return items.indexOf('ウォーリー');
// 4

イ「配列魔法を使うとウォーリーも簡単に見つかるね!」
あ「ところで、3番目のやつは違うのか?」
イ「あ、あれは伸ばし棒(ー)じゃなくて漢数字の一なんだよ」
あ「まぎらわしいな」
イ「ちなみに、漢字とカタカナが混じってる文字列は、ダブルクリックしても一部しか選択されないよ! みんなもやってみてね!」
あ「何の豆知識だよ」

解説

indexOf() メソッドは引数に与えられた内容と同じ内容を持つ配列要素の内、最初のものの添字を返します。存在しない場合は -1 を返します。

MDNより)

String(文字列)オブジェクトに対するindexOf()メソッドは有名ですが、配列にもほぼ同じメソッドが用意されています。配列内を検索し、指定した値が最初に現れる場所(インデックス)を返します。

lastIndexOf()

イ「ところで、さっきの配列にはもう一つ『ウォーリー』が隠れているよ!」
あ「興味ねえよ」
イ「それじゃあ、今後はさっきの配列を後ろから検索して、『ウォーリー』という文字列が何番目にあるのか? を探し出して、returnして敵を倒してよ!」
あ「データの召喚をサボってんじゃねえよ」
イ「さあ、早く!」
あ「面倒くせえなあ……」

return items.lastIndexOf('ウォーリー');
// 6

イ「配列魔法を使うとどこに隠れてても一発で見つかるね!」
あ「今回は手抜き感が半端ないな」

解説

lastIndexOf() メソッドは配列中で与えられた要素が見つけられた最後の添字を返します。もし存在しなければ -1 を返します。配列は fromIndex から逆向きに検索されます。

MDNより)

配列内を最後から検索し、指定した値が最初に現れる場所(インデックス)を返します。配列内に指定した値と同じものが1つしかない場合は、結果はindexOf() と同じになります。


さて、魔法JS☆あれいは、魔法(JavaScript)の力でこの世界を守ることが出来るのか!? 次回に続く!

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

Chrome DevTools の便利な機能を何もわかっていなかった(Debugger編)

はじめに

以前の記事でDevToolsのドキュメントを読んで学びがあったため続編。
日々フロントエンドで戦っているエンジニアには当然の知識かもしれませんが
バックエンドからたまに出てくるくらいの方には発見があるかもしれません。

「Javascript」の項目あたりからいくつかピックアップします。
https://developers.google.com/web/tools/chrome-devtools/javascript/

※上記リファレンスのデモページが使いやすいためサンプルとして利用します

活用できていなかった機能

ブレークポイントの使い分け

DevtoolsのSourcesタブ上にソースを表示して
該当行をクリックしてやると青色の矢印が表示され、ブレークポイントを置けます。
実行時にはここで処理が一時停止し、関連する変数や処理の確認ができます。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png
debuggerステートメントをソースに書いたときにも同じ動作になります。
この状態からステップ実行してデバッグができます。

ここまでは流石に知っていましたが、画面の右下を見るとさらにメニューがあります。
例えば今回のデモにはボタンのクリックイベントによって発火する処理があるため
ここをデバッグするためにはイベントリスナー用のブレークポイントを設定します。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png

この状態でデモ上のボタンをクリックすると、ソースの15行目で処理が一時停止しました。
他にもDOMの変更やXHRの内容に応じて動作するブレークポイントなどがあるようです。

条件付きブレークポイント

ブレークポイントを置きたい行をクリックではなく右クリックすると
「条件付きブレークポイント(Conditional breakpoint)」を選択できます。
ここでは addend1 の値が1を超える場合のみ一時停止させる設定をしています。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png
設定が完了すると該当行に黄色の矢印が表示されます。
同じ処理が何度も呼ばれるが特定の条件下でのみ確認したい というときに使えそうです。
(粘り強くステップ実行で進めても良いですが、非常に手間…。

ソースのブラックボックス化

ブレークポイントからステップインなどで細かく処理を進めていると
サードパーティのライブラリのようなデバッグの必要のないコードに入り込むときがあります。
ソースコード上で右クリックするとメニュー上でブラックボックス化が可能です。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png
これによって、デバッガがこのスクリプトを無視するようになります。
ブラックボックス化されたスクリプトは↓このように設定されていきます。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png

ログの出力

条件付きブレークポイントと同様に行を右クリックするとログポイントの設定メニューがあります。
試しに3つの変数を記載して確定すると、この行にも黄色い矢印が設定されます。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png
該当行が実行されるときにはconsoleタブに変数の中身が出力されています。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png

変数の監視

ログポイントでは該当行実行時の変数の中身を表示することができましたが
特定の変数をしばらく追い続けたい状況もあると思います。
Sourcesタブにある「Watch」には監視したい変数を設定します。

変数名を設定しておくと、処理が一時停止されるたびに内容が更新されます。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png
「Scope」にも同様の表示がありますが、実行箇所ごとに表示が大きく変化するため
継続的に変化を追うためには「Watch」で監視をした方が良いと思います。

もっと頻繁に変化する値をデバッグしたい場合は
Consoleタブにある目玉ボタン(Live Expression)を利用します。
静止画なのでわかりませんが、記載した処理が250msごとに実行され表示されます。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png
ロジックを左右するローカル変数の変化などの監視はできませんが
タイマー処理や座標の調整などのデバッグに活用できそうです。

ブラウザ上でのコード編集

ブレークポイントで一時停止している状態においては
consoleタブ上で実行時のローカル変数にも触れることができます。
例として、32行目でコードを一時停止しておきます。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png
この状態でconsoleタブに移動すると、ローカル変数の内部を表示できました。
直後に「こう変えたら正しい挙動になるのでは…?」という検証もできます。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png

ちなみにSourcesタブ上で直接ソースを書き換えてしまうことも可能です。
Demo__Get_Started_Debugging_JavaScript_with_Chrome_DevTools.png
大元のソースとブラウザを何度も行き来する必要がなくなりました。

まとめ

console.log()alert()を埋めていた日々は何だったんでしょう。
まだまだ効率の良い開発を追求できそうです。

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

JavaScriptでクラスを定義する方法!

JavaScriptを記述する場合、基本的には専用のjsファイルを作成して、処理を記述するのが一般的ですね。
しかし、何も考えずに処理を記述すると下記のように関数(function)が増えていってしまいます。

function() {
alert('処理1');
}
function() {
alert('処理2');
}
function() {
alert('処理3');
}

処理内容が小さければ、上記のような記述でも問題ありませんが、処理の内容が大きいと可読性やメンテナンス性が損なわれます。

大規模なプロジェクトを経験した方であれば、JavaScript地獄なんて言葉を聞いた方も多いと思います。

私も別のプログラマーが作った10万ステップのJavaScriptの改修を任され、とても苦労した経験があります。

そこで今回は、JavaScriptをクラス定義で記述し、可読性やメンテナンス性に優れたコーディング方法を紹介します。

JavaScriptでクラスを定義する方法

もっともシンプルな中身のないクラスを定義する方法は簡単で、ただ空の関数リテラルを代入するだけです。

var Member = function() {};

そして、インスタンス化するには、以下のように記述します。

var member = new Member();

ただし、空の関数リテラルを代入しただけなので、このインスタンス化したクラスは意味がありませんね。

そこで、もう少し一般的な使い方を考慮してクラス定義していきます。

プロパティとメソッドを持つクラスを定義する方法

プロパティとメソッドを持つクラス定義は、下記のとおりです。

var Member = function(firstName, lastName) {
    // 名前(名)
    this.fistName = fistName;
    // 苗字(姓)
    this.lastName = lastName;

    /**
     * 名前を返却.
     * @return string 苗字(姓)と名前(名)を連結して返却
     */
    this.getName = function() {
        return this.lastName + ' ' + this.fistName;
    }
}

インスタンス化するには、以下のように記述します。

// 苗字(姓)と名前(名)を引数にクラスをインスタンス化する
var member = new Member('一郎', '鈴木');

// 名前を出力する
alert(member.getName());

以上で、苗字(姓)と名前(名)を保持し、連結した名前を返却するメソッドのクラスを定義することができました。

しかし、このクラス定義には以下の問題点があります。

  • メソッドの数に比例して”無駄”なメモリを表示する

つまり実際の開発現場では、上記のようなクラス定義は行ってはダメです!!!

ではJavaScriptでクラス定義は意味ないの?

JavaScriptには、上記のような問題を解決する方法が用意されています。

実際の開発現場では、下記の方法でクラス定義を行いましょう!

JavaScriptでクラス定義する場合はprototypeを付加する!

var Member = function(firstName, lastName) {
    // 名前(名)
    this.fistName = fistName;
    // 苗字(姓)
    this.lastName = lastName;
}

// prototypeを付加してメソッドを定義
Member.prototype.getName = function() {
    return this.lastName + ' ' + this.fistName;
}

名前(名)と苗字(姓)は今までどおり関数リテラルを記述すればOKです。

注目は、getNameメソッドを定義するときに「prototype」を付加するところです。この「prototype」を付加することで、”参照”されるようになります。

つまり、複数インスタンス化した場合でも、関数オブジェクトは暗黙的な参照を持つことになり、無駄に作られることはありません。

つまりメモリの消費を抑えることができるようになります。

ちなみに関数を定義する際に下記のように毎回prototypeを付加するのは大変ですね。。

var Member = function(firstName, lastName) {
    this.fistName = fistName;
    this.lastName = lastName;
}
Member.prototype.getName1 = function() {
    return this.lastName + ' ' + this.fistName;
}
Member.prototype.getName2 = function() {
    return this.lastName + ' ' + this.fistName;
}

そのため、複数の関数リテラルを定義する場合は、以下のように書き換えることができます。

var Member = function(firstName, lastName) {
    this.fistName = fistName;
    this.lastName = lastName;
}
Member.prototype.getName1 = {
    getName1 : function() {
        return this.lastName + ' ' + this.fistName;
    },
    getName2 : function() {
        return this.lastName + ' ' + this.fistName;
    }
}

まとめ:JavaScriptでクラス定義する場合は、prototypeを付加して無駄なメモリを消費しないようにしよう!

JavaScriptでクラスを定義することで、可読性やメンテナンス性に優れたプログラムを記述することができます。

そして、prototypeを付加することで無駄なメモリを消費せず、スマートな定義ができるようなります。

以上、JavaScriptでクラスを定義する方法でした。

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

JavaScript Multi-dimensional Array;

JavaScript (Node.js) で AtCoderの問題を解いていて困ったが、現時点で日本語の情報がなかったので後学者のためのメモ。
そもそもJavaScriptでAtCoderやってる人自体少ないので需要はあまりなさそうですが、興味ある人だけ読んでください。

例えば a = [[0, 0], [0, 0]] というArrayを作って、a[0][0] = 1とupdateしたいとする。

まずは失敗例

var a = new Array(2).fill(new Array(2).fill(0));

a[0][0] = 1;
console.log(a);
// [ [ 1, 0 ], [ 1, 0 ] ]
// a[0], a[1] は呼び出すメモリ上でidが"完全に同じ"なので、a[0][0]とa[1][0]も単に"値が等しいのではなく全く同じもの"になる。

これは Python3でいうところの

a = [[0] * 2] * 2

と同じ。
これを解決するには、
a[0], a[1]をそれぞれ別に作成する必要がある。
Python3だと、

a = [[0] * 2 for _ in range(2)]

と書けばよかったが、JavaScriptだとちょっと大変。
いろいろ試して、一応以下のように書くと良いことが解った。

var a = new Array(2);

for (var i = 0; i < 2; i++) {
  a[i] = new Array(2).fill(0);
}

a[0][0] = 1;
console.log(a);
// [ [ 1, 0 ], [ 0, 0 ] ]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【REACT】カンタンなSPAサイトの仕組み

JavaScript学習をはじめてから2ヶ月目でSPAに触れてみた所感

基本的なweb制作のスキルはすでに獲得していて、次のステップにJavaScriptを選択してみました。
理由をカンタンに説明すると、web制作をより深く学習していく中で、SPA(シングルページアプリケーション)という技術への興味が高ぶったからです。
そこで、実際にJavaScriptを本格的に学びはじめてからSPAに触れてみた所感を共有できればと思い記事を投稿しようと思います。
これからSPAを学ばれる方に少しでも参考になれば幸いです。

今回使用する技術

・React Router
 →Routeコンポーネント、Linkコンポーネント

1.ディレクトリの作成

※既に環境構築済みの想定
 →まだの方はこちらから
sample(好きなディレクトリ名)> src> App.jsとindex.js のみのシンプルな構造

App.js
import React from 'react';

function App() {
 return (
   <h1>hello</h1>
 );
}

export default App;

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
    <App />, 
    document.getElementById('root')
);


2.React Routerのインストール

・VSコード内のターミナルに"npm install react router-dom"とコマンドを入力してインストールを行います。
すると、package.jsonの"dependencies"内に"react-router-dom"がインストールされたことがわかります。

package.json
{
  "name": "sample",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "gh-pages": "^2.2.0",
    "react": "^16.13.0",
    "react-dom": "^16.13.0",
    "react-router-dom": "^5.1.2",
    "react-scripts": "3.4.0"
  },

となれば問題ないかと思います。

3.コンポーネントの追加

SPAサイトの特徴である複数のページを要せずとも、ページ遷移ができる仕組みはコンポーネントが支えています。
ということで、src> components> Top.jsxとAbout.jsxの2つの関数コンポーネントファイルを作成し、とりあえずh1要素のみ入力しておきます。

Top.jsx
import React from 'react';

function Top() {
    return(
        <h1>TOP</h1>
    );
}


export default Top;

About.jsx
import React from 'react';

function About() {
    return(
        <h1>ABOUT</h1>
    );
}


export default About;

4.ルーティングの設定

実際の表示ページとなるApp.jsに以下の要領でルーティング設定を行います。

App.js
import React from 'react';
import {
  BrouserRouter as Router,
  Route
} from "react-router-dom";
import Top from './components/Top';
import About from './components/About';

function App() {
 return (
  <Router>
     <Route path="/" exact component={Top} />
     <Route path="/about/" exact component={About} />
   </Router>
 );
}

export default App;

package.jsonで読み込んだreact-router-domから①BrouserRouterをasキーでRouterとして②Routeの2つを読み込みます。

そして、function App内ではRouterを親要素。Routeをその中の子要素として記入します。

そのRoute内の“path”にURLセット。
“exact”の役割はpathが完全一致した時のみ表示できるようにします。
“component”はpathにセットしたURLをクリックする時に表示したいコンポーネントを指定できます。

5.いざ、表示の切り替え

最後の仕上げです。
通常、ページの切り替えはaタグで行われますが、Linkタグを使うことで、非同期処理が実現できます。

App.js
import React from 'react';
import {
  BrouserRouter as Router,
  Route
 Link
} from "react-router-dom";
import Top from './components/Top';
import About from './components/About';

function App() {
 return (
  <Router>
     <ul>
       <li><Link to="/">トップ</Link></li>
       <li><Link to="/about/">アバウト</Link></li>
     </ul>
     <Route path="/" exact component={Top} />
     <Route path="/about/" exact component={About} />
   </Router>
 );
}

export default App;

Router,Routeのならびに“Link”を読み込みます。

そして、Routerタグ内にLinkタグをセットすることで、クリックすると非同期でのページ遷移、夢のSPAサイト(超簡易版)を実現することができます。

まとめ

まだまだ自分は勉強中の身ですが、SPAサイトは非常に面白い仕組みで、これからよく使われる技術のひとつだと思うので、勉強していきたいものですね。

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

【JavaScript】日付のフォーマットを変更する

はじめに

MySQLからdatetime型のデータを取得してきたときの形式は「YYYY-MM-DD HH:MM:SS」です。
これを「MM/DD」という形式にJSで変更してみます。

「split」を使う

let datetime = '2020-03-21 10:00:00';
let datetimeArray = datetime.split(' ');
let date = datetimeArray[0];
let dateArray = date.split('-');
let month = Number(dateArray[1]);
let day = dateArray[2];
let formatDate = `${String(month)}/${day}`;
console.log(formatDate);
// 3/21

まず「YYYY-MM-DD」「HH:MM:SS」に分けます。
次に「YYYY-MM-DD」「YYYY」「MM」「DD」に分けます。
「MM」だけ先頭に0がつくことがあるので、一旦intに変換してから再度結合してます。
ただ無理やり感が否めない。

「Date」を使う

let datetime = new Date('2020-03-21 10:00:00');
let month = datetime.getMonth() + 1;
let day = datetime.getDate();
let date = `${month}/${day}`;
console.log(date);
// 3/21

Dateインスタンスに放り込んでそこから必要なデータを取得する。
ピンポイントで指定できるので「split」使うよりこっちの方が良い感じ。
month取得時に0からスタートする(+1する必要がある)のは相変わらず。

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

これから発信していく内容

これから発信してく内容

-Windows関連
-Mac関連
-Javascript
-jQuery
-Underscore.js
-C++
-VMware

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

魔法JS☆あれい 第5話「joinなんて、concatなわけない」

登場人物

丹生(にゅう)あれい
魔法少女「魔法(マジカル)JS(女子小学生)☆あれい」として活動中。

イテレー太
正体不明の魔法生物。

第1部「ミューテーター・メソッド編」
* 第1話「popでpushした、ような……」
* 第2話「shiftはとってもunshiftって」
* 第3話「もうsortもreverseも怖くない」
* 第4話「fillも、spliceも、copyWithinもあるんだよ」

join()

イテレー太「えー、私が魔法世界からデータを召喚しますので、皆さんは魔法少女になって、配列魔法でそのデータを加工して敵を倒してください。はい、あれいさん早かった」
あれい「なんで大喜利チックなんだよこの駄犬」
イ「さて、今回から第2部『アクセサ・メソッド編』なので、大きな制約が加わるよ! それは『元のデータを変更してはいけない』という制約だよ!」
あ「へえ」
イ「この制約を破ると、それはそれは恐ろしいペナルティを課せられるから気をつけてね!」
あ「例えばどんなペナルティだよ」
イ「自動販売機のお釣りが必ず100円玉とか10円玉ばっかりで返ってくるよ!」
あ「どうでもいいなそりゃ」
イ「さて、気を取り直して、今回僕が魔法世界から召喚したデータは……これだ!」

items = ['', '', 'ポ○ョ'];

イ「これを……」
あ「……まさかとは思うが、これを『の』で繋げて『崖の上のポ○ョ』にしろ、とか言わないだろうな」
イ「すごい! 鋭いね、あれい!」
あ「誰でもわかるわ。この行為に何の意味があるんだ」
イ「じゃあ['風', '谷', 'ナウ○カ']にする?」
あ「どっちでもいいわ」

return items.join('');

イ「やった! 敵が苦しんでるよ!」
あ「これでダメージ受ける敵もどうかと思うぞ」

解説

join() メソッドは、配列 (または配列風オブジェクト) の全要素を順に連結した文字列を新たに作成して返します。区切り文字はコンマ、または指定された文字列です。配列にアイテムが一つしかない場合は、区切り文字を使用せずにアイテムが返されます。

MDNより)

昔からよく使われるお馴染みのメソッドですね。
私はCSVを作る時に多用してます。

concat()

イ「さて、続いてのお題にまいりましょう」
あ「だから大喜利じゃねえよ」
イ「今回僕が魔法世界から召喚したデータは……あれ?」

items = ['とっても'];

あ「おい、要素がたった1個かよ」
イ「いや、月末だから通信速度制限がかかっちゃって……」
あ「スマホかよ」
イ「あ、来た来た、次のデータだ」

items2 = ['すごい'];

あ「おい、早くしろよポンコツ」
イ「ちょっと待って……最後のデータが届くから」

items3 = ['攻撃'];

あ「やっと揃ったか」
イ「ごめんごめん。さて、今召喚した3つの配列を、1つに連結してからreturnして敵を倒してよ!」
あ「面倒くせえなあ……」

return items.concat(items2, items3);

イ「良かった良かった。早く翌月にならないかな」
あ「魔法生物が料金プランをケチってんじゃねえよ」

解説

concat() メソッドは、配列に他の配列や値をつないでできた新しい配列を返します。

MDNより)

「concat」は「concatenate」の略で、「鎖状につなぐ」「連結する」という意味です。複数の配列を連携して、1つの配列として返します。使用されたそれぞれの配列には変更を加えません。


さて、魔法JS☆あれいは、魔法(JavaScript)の力でこの世界を守ることが出来るのか!? 次回に続く!

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

数十分で始める、Svelte+Firebaseのアプリ生活。

数日前に、ES2015以降をお勉強するのに、Svelte REPLが楽しかったので、Svelte+Firebaseでwebアプリを作ってみたいと思った(最近、オウンドメディア的なものを作りたいなと思っているもので)。

Svelteとは?

JSを知っていてSvelteって何という人は、Svelteで始める頑張らないフロントエンド生活を参考にすると良い。作ってくれているtodoアプリをどうfirebase化するかは、良い例題となりそう。

SvelteFireを使って、1時間以内でアプリを作り始める。

Firebaseのエキスパートの方が作ってくださっているSvelteFireを元にすると、(朝食のカレーを作ったり食べたりしながら)ほぼ1時間でCloud firestoreに書き込めるところまで行けた。

フロントエンド:
あぷり.PNG

Firestore側:
firestore.PNG

以下、Windowsマシンでの作業前提。
https://github.com/codediodeio/sveltefire

1) nodejs+gitforwindowsのインストール

nodejsは、windowsの普通のインストーラーを使った。結構時間がかかった(20分くらい)
途中から、gitforwindowsも入れると、普通のbashコマンドで、node/npmを扱えるようになる。

2) 開発用firebaseアプリ(サーバ側)」の作成

1)と並行してfirebaseにログインし、アプリを設定。firebase初めての人は20分くらいかかるだろうが、firebase使ったことがあれば3分で終わる。SvelteFireを開発者モード使い始めるためには、アプリ名を決めた後、『匿名ログイン』を許可し、『テストデータベース』にしておく。

普通にfirebaseアプリを作成するのみなので、委細は略。

3) 開発用sveltefireアプリ(クライアント側)の導入

おすすめに従い、作業フォルダにsveltefire-template経由でインストールした(npx使用)

npx degit codediodeio/sveltefire-template fireapp
cd fireapp
npm install

node/npm環境が正常にインストールされていれば、fireappのインストールまで1分もかからないはず。

フォルダ構成:
ふぃ.PNG

fibebaseと接続するための設定はただ一点。App.svelte以下のvar firebaseConfig = {...}のところに、firebaseアプリの設定を貼り付けるのみ。
コンフィグ.PNG
※ちなみに、↑はvscodeにsvelte拡張を入れた後の画面。

4) 実行

npm run devすると、以下から、svelteアプリにアクセスできるようになる。
http://localhost:5000/

App.svelte等を書き換えると自動でリロードしてくれるので、さっそくにはかどる。

ここから後は、適宜sveltefireを見ながら、クライアント側をsvelteで書いていくことになる。
https://github.com/codediodeio/sveltefire

あと、firebase-toolsは別途入れておいたほうがいいね。

npm install -g firebase-tools

..おや、gitforwindowsではなく、コマンドプロンプトでしかfirebaseのCLI認証ができないのか。。
おしまい。

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

【JS】querySelectorAll()メゾッド使用時にaddEventListener()メゾットが動かなかった

問題

querySelectorAll()メゾッドでセレクタを指定して、addEventListener()メゾッドでクリックイベントを仕込もうとしたときです。

main.js
  const target = document.querySelectorAll('nav a');
  const closeNav = function () {
    const close = document.getElementById('global-nav');
    close.classList.toggle('nav-open');
  };
  target.addEventListener('click', closeNav);

addEventListener()が動かない…

Consoleではこのように怒られています。
スクリーンショット 2020-03-21 2.25.32.png

Uncaught TypeError: target.addEventListener is not a function

どうやらtarget.addEventListenerが関数(function)として認識されていないようです。

querySelector()ではうまく動いたのに…

原因

まずquerySelectorAll()メゾッドのリファレンスを読むと

Document の querySelectorAll() メソッドは、与えられた CSS セレクターに一致する文書中の要素のリストを示す静的な (生きていない) NodeList を返します。

とあります。
文書中の要素のリストを示す静的な (生きていない) NodeList を返す…

さらに、querySelectorAll()メゾッドの返値というNodeListを調べると

NodeList オブジェクトはノードの集合であり、 Node.childNodes などのプロパティや document.querySelectorAll() などのメソッドの返値として用いられます。

NodeList は Array とは異なりますが、 forEach() メソッドで処理を反復適用することは可能です。 Array.from() を使うことで Array に変換することができます。

とありました。
このことから、querySelectorAll()の返値は配列ではないがリスト状になっており、forEach()メゾッドを使えば処理することができるという解釈にいたりました。

解決

main.js
  const targetList = document.querySelectorAll('nav a');
  const closeNav = function () {
    const close = document.getElementById('global-nav');
    close.classList.toggle('nav-open');
  };
  targetList.forEach(function (target) {
    target.addEventListener('click', closeNav);
  });

まずquerySelectorAll()メゾッドの返値はリストということで、targetListという変数名にしました。
そして、targetList内のひとつひとつの要素にaddEventListener()メゾッドが実行されるように、forEach()メゾッドでtargetListを回しました。

無事、エラーが解消され、望んだ動きをしてくれるようになりました。

まとめ

  • querySelectorAll()メゾッドは配列ではないがリスト状のNodeListを返す。
  • forEach()メゾッドでリストの中身ひとつひとつに処理をあてることができる。

参照

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

フロントエンドエンジニア(主にVue.js)に転職して学んだこと

Qiita初投稿です。

はじめに

転職して1年が経ったので、個人的な振り返りです。

未経験から主にVue.jsを学び、モダンなフロントエンド開発が学べて個人的には大きな収穫となりました。

私について

  • WEB制作会社出身
  • 前職はコーダー
  • 2019年に転職、フロントエンドエンジニアになる

古典的なWEB制作会社にいましたので、モダンなフロントエンドを学びたいと思い、転職活動をしました。

もちろん未経験では採用してくれる企業はなかなかいないので苦戦しましたが、縁あって今の会社に転職することができました。

入社前のVue.jsの勉強方法

1ヶ月ぐらい休める期間があったので、Udemyを見ながらコードを書きました。
それを2~3回繰り返しました。

移動時間などでは本を読みました。

Udemyと本では、片方しか載ってないこともあるので、双方向から学べて良かったです。

学んだこと

主に学んだことの一覧です。

  • Vue.js
  • Git
  • XD
  • ES6
  • Ajax(非同期通信)
  • VueValidator
  • webpack
  • babel
  • eslint

Vue.js

Vue.jsでデータバインディングが手軽にできるのが良いです。

Vue.jsで学んだことについては別途書きます。

Git

GitLabを使用しています。

前職はバージョン管理はしてませんでしたし、前前職はSubversionでしたので、
gitを実務で使ったことがない状態でした。

高度な使い方はしていませんが、
基本的にはコミット&プル、ブランチを切る、マージリクエストでコードレビュー&マージをするなどです。

ES6

プロジェクト内のコードでは古い書き方も残っていますが、アロー関数やforEach系(map,filterなど)極力新しい書き方にするようにしています。

Ajax(非同期通信)

プロジェクトではaxiosを使ったAjaxでGETやPOSTを多数使ってます。

GETやPOSTでパラメーターを投げ、返ってきたJSONを受け取り、表示させます。

VueValidator

VueValidatorでフォームの入力時にリアルタイムでバリデーションができます。

日付や期間などカスタマイズしています。

Webpack

以前はGulpは使ってましたが、Webpackは初めてでした。

Babel

プロジェクトはIE11を対応しなくてならないので助かります。

ESlint

プロジェクトではESLintを使ってます。
問題があればブラウザに出るので助かりますが、WebStormであれば事前に警告が出ます。

XD

デザインをするわけではないですが、デザイナーが作ったデザインを再現するために基本操作を知る必要があります。

その他、やってること

コードを書くこと以外にやっていることです。

  • サーバーサイドとの連動部分の設計やドキュメント作成
  • 新規機能や追加機能の要件定義

サーバーサイドから受け渡すJSONや、コンポーネントのProps、の設計やドキュメント作成をしてます。

また、プロジェクトには企画職やディレクターはいないので、エンジニア達で作る機能を決めています。

学びたいこと

今後、学びたいことです。

  • Vuex
  • nuxt.js
  • ユニットテスト
  • StoryBook
  • AtomicDesign
  • TypeScript
  • GitHub

以下、それぞれの理由です。

Vuex

プロジェクトではSPAではなくMPA(Multi page application)のためVuexは必要ないというのがあります。
Vuexもやらないとな思うところです。

nuxt.js

やっぱVue.jsといえばnuxt.jsなので。
ローカルにインストールして動かしてしたり、Udemyの動画を観てますが、実践の場がありません。
何かアイデアあれば公開しようと思います。

ユニットテスト

「テストとは?」という状態なので。
nuxt.jsをインストールするときに聞かれるので試してみたいと思います。

StoryBook

StoryBookを使っている会社が多いと聞くので。

AtomicDesign

プロジェクトを構築する初期にはAtomicDesignを検討してたようですが、私がジョインしたころは崩壊してました。

TypeScript

私がプロジェクトにジョインしてからはJSDocを書くようにしてますが、やっぱ型を見たほうが良いと思うので。

GitHub

プロジェクトではGitLabを使ってますが、世間はGitHubが多数派なので。

所感

学ぶことが多くて大変満足した1年でした。

ですが、課題もあるのでこの先に学んでいきたいです。

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

チーム開発 3/20

最終課題3日目
トップページ担当

これはメモです

contentとは
contentはCSSで指定できるプロパティです。
要素の直前または直後に、文字列や画像などのコンテンツを挿入する際に使用します。contentプロパティを適用することができるのは、:before擬似要素および:after擬似要素のみです。
擬似要素とは、要素の一部にスタイルを適用するために、擬似的に設定される「要素のようなもの」のことです。
:before擬似要素および:after擬似要素は、要素の直前および直後に、文字列や画像などのコンテンツ(内容)を挿入するために擬似的に設定されます。
https://web-camp.io/magazine/archives/10930
参考サイト

link_toにh3などタグをネストして書く時は

=link_to “リンク” do
=“名前”
=%h3
と記述

ブラウザを小さくしても
大きさを変えたくないブロックに使用
Margin部分は真ん中に設定
max-width: 700px;
margin: 0 auto;

ボックスに影を作る時使用
box-shadow
http://www.htmq.com/css3/box-shadow.shtml
参考サイト
https://ferret-plus.com/8961
↑ブロックの形によって変わるプロパティ

リンクする所をブロック全体にしたい
ブロックには
position: relative;
インライン要素(link_to)
display: block
height: 100%;
これでブロック全体がリンクできるようになる

商品の写真登録は最大4枚に設定


復習

JavaScript
window.alert()
ブラウザでアラートを表示させるメソッドです。引数としてアラートに表示させる情報を渡します。

console.log()
ブラウザのコンソールにテキストを表示させるメソッドです。引数としてコンソールに表示させる情報を渡します。

letは、後で書き換えることができる変数宣言です。
constは、後で書き換えることができない変数宣言です。

if文
if (条件式1) {
// 条件式1がtrueのときの処理
} else if (条件式2) {
// 条件式1がfalseで条件式2がtrueのときの処理
} else {
// 条件式1も条件式2もfalseのときの処理
}

配列
let list = ['Ruby', 'Ruby on Rails', 'JavaScript', 'HTML', 'CSS'];
1. 配列の要素を取得する
console.log(list[2]);
=Ruby on Rails
2. 配列の要素数を取得する
console.log(list.length);
=5
3. 配列の要素を追加する
list.push('GitHub');
6番目にgithubが入る
4. 配列の要素を削除する
list.pop();
index.htmlを開いているブラウザのコンソールを確認して、CSSという文字列が消えていれば成功です。
popメソッドは配列の最後の要素を取り除きます。Rubyの場合は
popメソッドの引数に数字を渡すことで「何個分要素を取り除くか」という数を指定できますが、
JavaScriptのpopメソッドではそれができません。

list.shift();
index.htmlを開いているブラウザのコンソールを確認して、Rubyという文字列が消えていれば成功です
。こちらもpopメソッド同様、Rubyでは取り除く要素数を指定できますが、JavaScriptではできません。

オブジェクト
オブジェクトを作成するには波カッコ{}を用います。
オブジェクトの定義1
1 let obj = {};
オブジェクトはデータを名前と値をセットで管理します。このセットをプロパティと言います。
プロパティを持った状態でオブジェクトを定義することもできます。

オブジェクトの定義2
1 let obj = { name: 'yamada' };
変数objはnameプロパティを持っています。プロパティは必ず属性名である"プロパティ名"とデータである"値"をセットで持ちます。
したがって、nameはプロパティ名、'yamada'は対応する値です。

 for文
for文を用いた、繰り返しの基本

for (let i = 0; i < 繰り返す回数; i = i + 1) {
// 繰り返す処理の内容
}

num = 1;
for (let i = 0; i < 10; i += 1) {
console.log(num + '回目の出力')
num += 1
}

関数を定義
Rubyでは def ~ end で囲むことで関数を定義しましたが、
JavaScriptで関数を定義するにはfunction文を使います。

function 関数名(引数) {
// 処理の内容
}

return
Rubyでは関数における最後の戻り値が関数の戻り値として処理されましたが、
JavaScriptではreturnを用いて明示する必要があります。

function calc(num1,num2){
num1*num2;
}

let num1 = 3;
let num2 = 4;
console.log(calc(num1,num2));

function calc(num1,num2){
return num1*num2;
}

let num1 = 3;
let num2 = 4;
console.log(calc(num1,num2));

無名関数を定義

// 関数宣言
function hello(){
console.log('hello');
}

// 関数式(無名関数)
let hello = function(){
console.log('hello');
}
関数式の方は、関数自体に名前が無いため無名関数と呼ばれます。
関数宣言と関数式では読み込まれるタイミングが異なります。

JavaScriptにおいては関数宣言は先に読み込まれる。
一方で、関数式であれば先に読み込まれることはありません。

DOM
DOMとはDocument Object Model(ドキュメントオブジェクトモデル)の略です。HTMLを解析し、
データを作成する仕組みです。
HTMLで書かれたファイルは結局ただの文字情報なので、そのまま表示しても意味がありません。HTMLを解析し、
CSSやJavascriptによる装飾を行ってから画面に映すという工程が必要なはずです。これを行っているのが、
Google ChromeやSafariといったブラウザです。ブラウザがHTMLやCSS、
JavaScriptを受け取ってユーザーに届けるまでには、以下のような手順を踏んでいます。

HTMLは階層構造になっていることが特徴です。
DOMによって解析されたHTMLは、階層構造のあるデータとなります。これを、DOMツリーやドキュメントツリーと呼びます。
DOMツリーはデータとして階層構造になっていることで、階層構造を扱うためのアルゴリズムを使い操作できます。

ノード
HTML1つ1つのタグが、DOMツリーの中ではノードと呼ばれます。

ノードの取得
JavaScriptを使ってHTML要素のid名やクラス名を指定することで、マッチするノードを取得できます
document.getElementById("id名");
documentは、開いているWebページのDOMツリーが入っているオブジェクトです。
documentに対していくつかのメソッドを利用することで、DOMツリーに含まれる要素を抽出して取得することができます。
.getElementById("id名")はDOMツリーから特定の要素を取得するためのメソッドの1つです。引数に渡したidを持つ要素を取得します。
document.getElementsByClassName("class名");
classを指定して取得する際はこちらを利用します。ここで気をつけたいのは、getElementsと複数形になっていることです。
id名はhtml上に必ず一つしか存在しないのに対して、class名を指定するgetElementsByClassName("class名")の場合は、
同じclassを持つ要素を全て取得することが可能です
document.querySelector("セレクタ名");
セレクタ名とは、CSSでスタイルを適用するために指定している要素です。
セレクタ名を指定してDOMを取得する場合、querySelectorメソッドを使用します。
HTML上から、引数で指定したセレクタに合致するもののうち一番最初に見つかった要素1つを取得します。

イベント
JavaScriptにおけるイベントとは、HTMLの要素に対して行われた処理要求のことをいいます。例えば「ユーザーがブラウザ上のボタンをクリックした」
「テキストフィールドでキー入力をした」「要素の上にマウスカーソルを乗せた」などがいずれもイベントです。
また、イベントを起こすのはユーザーだけでなく、「ドキュメントの読み込みが終わった」などブラウザが発生させるものもあります。

イベント駆動
JavaScriptはイベント駆動という概念に基づいて設計されています。
これは、「イベント」が発生したら、それをきっかけにコードが実行される仕組みです。
JavaScriptはもともとブラウザに搭載するための言語として開発されており、
ユーザーが行う様々な操作に対応できるイベント駆動の仕組みが適しているためです。
イベントを取得するためには、先に取得したノードに対して処理を書きます。
addEventListener
addEventListenerメソッドはノードオブジェクトのメソッドで、以下のようにして実行します

(ノードオブジェクト).addEventListener("イベント名", 関数);

上記のような記述により、この記述の読み込み以降で「ノードオブジェクト」に「イベント」が起きた時、「関数」を実行するようになります。
一つのイベントと一つの関数を紐付ける仕組みのことをイベントリスナと呼びます。
一つのイベントに複数の関数を紐付ける場合は、関数の数だけイベントリスナが存在します。
addEventListenerは、あるノードオブジェクトに対して、イベントリスナを追加するメソッドです。

ページを読み込まないとイベントが発生しない時があるので
(コードの上から読み込んでいくので)

ページの読み込みをするwindow.onload
「ページの読み込みが終わる」イベントは、windowオブジェクトのloadイベントに対応します。
そこで、windowオブジェクトのloadイベントに対応する関数として上記の一連の処理を定義すれば良いと考えられます。
なお、windowオブジェクトは、元から用意されている、ブラウザの情報を持つオブジェクトです。
記述は2つある
window.onload = function() { 処理 };

window.addEventListener('load', function() { 処理 });

コードを見直す

function func() {}
// 何もしないfunc関数
btn.addEventListener("click", func);

btn.addEventListener("click", function func() {});

省略した書き方ができる

イベント発火の流れ
①DOMツリーからノードを取得する
②JavaScriptでやりたい処理を書く
③イベント発火でHTML側で動かす

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