- 投稿日:2020-03-21T23:55:38+09:00
React Native でいろんなメニューを持ったアプリを作る
最終形
- タブメニューで画面が切り替わる
- どの画面からでもアクセスできるスライドタイプのメニューを持つ
- イベントトリガーでポップアップ画面が差し込まれる
このような複数の画面パターンを持ったアプリを実装していきます。
ナビゲーターの構成
今回の実装では、 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 を上位にするかで挙動が異なります。
今回紹介したように、覆いかぶさるポップアップ画面も実装できますし、一方で選択タブ内で詳細画面などの別画面を表示することも可能です。(その場合はタブメニューが隠れない)
どのような画面構成にしたいかを設計し、どのページをどのナビゲーターに配置するかを考える必要があります。
今回は、簡単ではありますがその実装方法について紹介しました。
不明点や間違いなどがありましたらコメントお願いします。ここまで読んでいただき、ありがとうございました。
- 投稿日:2020-03-21T22:56:08+09:00
Class Component + TypeScript の Vue アプリケーションを Composition API で作り直したので思ったことを書く
はじめに
少し前に作って、更新が滞っていたアプリケーションに機能を追加したかった。
ちょうどよかったので Vue + Class Decorator + TypeScript の構成だったが、 Vue + CompositionAPI + TypeScript に作り直した。
この記事では、その過程で思ったことや考えたことを共有したい。作成しているアプリケーションは以下。
- ソース: https://github.com/sterashima78/vue-webpage-builder
- アプリケーション: https://sterashima78.github.io/vue-webpage-builder
- 使い方はソースについている README を見てください
作り直す前
- コンポーネント定義はクラススタイル
- 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/e5518dabbfccdf6a205dcomposition-api は適切に利用すれば Vue の抱えていた多くの問題をすると思うので、ぜひいろんな人に使い込まれてベストプラクティスが構築されていってほしいと思う。
- 投稿日:2020-03-21T22:20:51+09:00
【JS/Vue】VueXの浅い理解
注意
VueXについては、下記の記事を読むのが一番です。
vuexをまだ理解していない全人類に捧ぐvuexを利用したコードの図解 - QiitaVueX
VueXの構成
- Actions
- Mutations
- State
以下全体図
全体の流れ
- stateを確認
- actionを呼び出し
- mutationsへcommit
- 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 | MDNFlux
VueXは、Fluxがベースになっています。
Fluxを実装したのが、Reduxで、そこからVueXが生まれました。
flux/examples/flux-concepts at master · facebook/flux · GitHubより引用全ての状態を持つべきか
公式ドキュメントでも述べられている通り、全てを置かなくても良いみたいです。
Vuex を使うということは、全ての状態を Vuex の中に置くべき、というわけではありません
ステート | Vuexより引用参考記事
VueとVuexの間の値の連携の仕方 - Qiita
2018年Vue.jsとVuexを使ってる人には必ず知っていてほしい開発やメンテナンスの際に役立つ設計とTipsとサンプルコード - Qiita
- 投稿日:2020-03-21T21:29:40+09:00
Pay.jpを用いた購入機能の実装
概要
Pay.jpを用いた購入機能の実装を備忘録としてまとめます。
修正点ありましたらご指摘お願いいたします。
Transaction(取り引き)テーブルおよびProduct(商品)テーブルをもとに作っていきます。前提
- Pay.jpのアカウント作成済み
- Pay.jpにてクレジットカード登録機能は実装済み
- ビューはHamlで記載
- deviseにてログイン済み
手順
- Transactionテーブルを作成(購入済みの場合SOLD OUTを表示させるため)
- アソシエーションの設定
- Transactionコントローラーを作成
- 環境変数の設定
- ルーティングの設定
- マークアップ:購入内容確認画面
- マークアップ:購入完了画面
- マークアップ:購入済みの場合SOLD OUTを表示
- 購入データの確認
Transactionテーブルを作成
今回は購入済みの場合SOLD OUTを表示させるためにTransactionテーブルを作成します。
$ rails g model Transactiondb/migrate/20200000000000_create_transactions.rbclass 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.rbclass Transaction < ApplicationRecord belongs_to :user, optional: true belongs_to :product, optional: true endTransactionコントローラーを作成
$ rails g controller transactionstransactions_controller.rbclass 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.rbRails.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マークアップ:購入内容確認画面
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 購入するマークアップ:購入完了画面
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を表示
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以上です
- 投稿日:2020-03-21T21:23:18+09:00
【JS/Vue】ライフサイクル
主に下記のUdemyのコースをベースに書きました。
Vue.js + Firebaseで作るシングルページアプリケーション | UdemyVueライフサイクル
ドキュメントに書かれているライフサイクルの図は下記の通りです。
大まかに定義すると,下記の流れのようになっており、それぞれに作成前、後が存在しています。
- Vueインタンス作成
- マウント
- DOM アップデート
- Vueインタンス削除
またこのライフサイクルでは、ライフサイクルフックで処理を追加できるようになっています。
ライフサイクルフックとは、上図で書かれてるbeforeCreate
、created
などの赤枠で囲われているものをさします。
この中で色々実装することで、細かい挙動の変更を行うことができます。Vueインタンス作成
beforeCreate
、created
がインスタンスの作成に該当します。
下記のような実装でインスタンスを作成することができます。new Vue({ router, store, render: h => h(App) }).$mount('#app')マウント
beforeMount
、mounted
が該当します。
定義については下記がわかりやすかったです。既存のDOM要素をVue.jsが生成するDOM要素で置き換えること。
Vue.js入門 2章 Vue.jsの入門(コンストラクタ、マウント、data) - Qiitaより引用仮想DOM
beforeUpdate
、updated
が該当します。
つまり変更差分のところだけ、アップデートしようという考え方です。なぜ仮想DOMという概念が俺達の魂を震えさせるのか - Qiita
仮想DOMは本当に“速い”のか? DOM操作の新しい考え方を、フレームワークを実装して理解しよう - エンジニアHub|若手Webエンジニアのキャリアを考える!実験
下記のように html, js を定義して、 console上でメッセージを書き換えると、
beforeUpdate
、updated
が動いていることがわかります。<div id="app"> {{ message }} </div>const vm = new Vue({ el: '#app', data() { return { message: 'こんにちは' } }, beforeUpdate() { console.log('再描画前') }, updated() { console.log('再描画後') } }) window.vm = vm便利なサイト
下記のサイトで色々 js の実装とかを試せます。
めちゃ便利です。
JSFiddle - Code Playgroundその他参考にした記事
- 投稿日:2020-03-21T21:15:15+09:00
JavaScriptでカジュアルに日付操作する
JSでちょっとしたDate操作をしたのでメモ。
YYYYMMDD文字列を取得
toISOString() メソッドは、簡潔な拡張表記の ISO 形式 (ISO 8601) の文字列を返します。これは、常に 24 文字または 27 文字の長さになります (それぞれ、YYYY-MM-DDTHHss.sssZ または ±YYYYYY-MM-DDTHHss.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() // 1584792437135const 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
- 投稿日:2020-03-21T20:30:30+09:00
JavaScriptにおけるthisの挙動について
対象読者
- JavaScriptのオブジェクトについて理解している初学者
- thisの挙動がいまいちわからないJavaScript初学者
そもそもJavaScriptにおけるthisって何?
JavaScriptにおけるthisとは、自分自身を表すオブジェクトのことを挿します。重要なポイントは呼び出し元によって変わる点です。どういうことか以下に記載していきます。
グローバルオブジェクトを指すthis
sayFoo.jsconst sayFoo = function () { console.log(this["foo"]); } foo = "foo";// 予約後であるconst,letをつけない場合はグローバルオブジェクトとして定義される sayFoo();
sayFoo.js
では、sayFoo()を呼び出した場合、グローバルオブジェクト(window
オブジェクト)として変数fooを宣言しています。よって、thisの対象はグローバルオブジェクトをさす挙動になっています。
※this["foo"]
の[]で書く方法はブラケット表記法と呼ばれる書き方です。
ブラケット表記法インスタンス変数を指すthis①
sayFooInstance.jsconst 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.jsfunction 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と表示されるオブジェクトのプロトタイプに関数を定義して、実行しても、2が表示されず、5が表示されます。
なぜなら、MyObj関数をnewを使ってインスタンス化する際にthis.id = id
で変数idをMyObj関数に代入しているため、printId
が実行されても、this.id
の値に代入されないので、実行結果が5と表示されます。ちなみに以下の様にClassを使っても同じ形になります。
インスタンス変数を指すthis③
myClass.jsclass MyClass { constructor(id) { this.id = id; } print(id) { console.log(this.id); } } const myClass = new MyClass(5); myClass.print(3);コンストラクターを使ってインスタンス生成時にthis.idの値を代入します。
インスタンス変数を指すthis②でご紹介した通り、インスタンス生成時にthis.id
にid値を代入しているため、実行結果は5と表示されます。クラスについてはMDN Web docに記載があります。
クラス MDN Web docちなみに、オブジェクト内部で関数を入れ子にした場合はどうなるでしょうか
インスタンス変数の内部で関数が入れ子になっている場合のthis
sayFooInstanceNest.jsconst nestFunction = { nestFunc1: function () { console.log(this);// nestFunctionを参照 const nestFunc2 = function () { console.log(this);// グローバルオブジェクトを参照 const nestFunc3 = function () { console.log(this);// グローバルオブジェクトを参照 }(); }(); } } nestFunction.nestFunc1();// 実行入れ子にした場合のthisは、グローバルオブジェクトを参照します。
ちなみに以下の形
{}();
と書かれているコードをみて「ん?」と思った人もいると思います。
以下の書き方は即時実行関数式と呼ばれ、定義されるとすぐに実行されるJavaScriptの関数となります。sokuji.jsconst nestFunc3 = function () { ・・・中略・・・ }();// <= 波括弧の隣に丸括弧をつけることで、定義されるとすぐに実行される即時実行関数となります。MDN Web Docs 用語集 IIFE (即時実行関数式)
thisは理解してしまえば、あとは応用が効く
内容に不備があったり、誤字脱字などあれば、コメントをいただければと思います。
- 投稿日:2020-03-21T19:41:00+09:00
[Javascript] 要素の外側をクリックした時にremoveする
概要
例えばモーダルウインドウには×で閉じるボタンがレイアウトされている事が一般的ですが、
ユーザビリティを考えると、ボタン以外でも閉じられるようにしている事が望ましいですよね。
だからといって、どこでもクリックすれば閉じられるような処理では、
そもそも閉じるボタンの意味がなくなってしまいます。今回はクリックイベントで付与したクラスを、
反応させたくない要素の外側かを判定して取り除く処理になります。
あくまで私が調べた結果ですので、もっとスマートな書き方があるかもしれません。制御部分のコード
Javascriptconst 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されます。
- 投稿日:2020-03-21T19:09:55+09:00
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の公式ドキュメントにも記載されています。
以上です?
- 投稿日:2020-03-21T19:09:55+09:00
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の公式ドキュメントにも記載されています。
以上です?
- 投稿日:2020-03-21T18:26:27+09:00
[Rails]非同期のいいね機能実装
ユーザー(user)が出品した商品(item)にいいねできる機能を実装してます。
Likeモデル、テーブル作成
rails g model Like
like.rbclass Like < ApplicationRecord endXXXXXXXXXXX_create_likes.rbclass 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.rbhas_many :likes, dependent: :destroy has_many :like_items, through: :likes, source: :itemitem.rbhas_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.rbclass 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.rbclass AddLikesCountToItems < ActiveRecord::Migration[5.2] def change add_column :items, :likes_count, :integer end end
rails db:migrate
マイグレーションファイル実行ルーティング設定
routes.rbresources :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.rbclass 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.haml
とunlike.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の説明は省略します。
完成イメージ
ユーザーがいいねした商品一覧を表示させたい方はこちらをご覧ください
間違えている部分があったらぜひコメントよろしくお願いします!!
- 投稿日:2020-03-21T18:11:00+09:00
Vue.js 自分用
テンプレート構文
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
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>
- 投稿日:2020-03-21T18:08:54+09:00
知識ゼロから始めるTypeScript 〜応用編〜
はじめに
クラス編の続きです。
知識ゼロから始めるTypeScriptシリーズのラストパート応用編です。予習はこちらから
知識ゼロから始める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型
で定義された変数name
をlengthメソッド
を使用して、変数length
に代入する場合、本来であれば変数length
はnumber型
である必要があるのに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 = 175Nullable 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' }まとめ
応用の使い所がまだわからないけど、完璧より前進
- 投稿日:2020-03-21T18:01:37+09:00
魔法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 = [ 'ウォ一タ一', 'ワォ一リ一', 'ウォ一リ一', 'ウォ一ソ一', 'ウォ一リ一', 'ウ才一リ一', 'ウォ一リ一', 'チャ一リ一', 'チャ一ハン' ];あ「前回と一緒じゃねえか。どれだけ引っ張るんだこのネタ」
イ「……果たして、本当にそうかな?」
あ「何だと」
イ「じゃあ、この配列の中に、『ウォーリー』という文字列があるかどうかを判定して、true
かfalse
のいずれかの値を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)の力でこの世界を守ることが出来るのか!? 次回に続く!
- 投稿日:2020-03-21T16:58:09+09:00
【paizaスキルチェック用】配列内の数値を少ない順に並べ替えるJavaScript
- 投稿日:2020-03-21T16:58:02+09:00
Javascript での、時計の作り方。
Javascriptのコードで時計を作る。
html5での記述は、
この、p id="watch" 以下に、時計の値を入れる。
その式は、
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桁表示に変える。
- 投稿日:2020-03-21T16:01:33+09:00
複数画像投稿で盛大に自爆した時の確認事項[備忘録]
はじめに
某フリマアプリの模倣アプリを開発中、出品機能実装で複数画像の登録に死ぬほど手を焼いたので、備忘録として掲載します。
誤った記述などあればご指摘いただけると幸いです。
開発環境・前提
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.rbbelongs_to :item, optional: true validates_presence_of :item validates :content, presence: true mount_uploader :content, ImageUploaderitem.rbbelongs_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: trueitems_controller.rbdef 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]) endnew.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.rbmount_uploaders :content, ImageUploaderafter
image.rbmount_uploader :content, ImageUploadermount_uploaderとするかmount_uploadersか。
error.messageNoMethodError (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についての知識がかなり深まった。欲張りな性格なので、いろんな記事のいいとこ取りをしようとした結果、こんなにもの何重もの罠を自分で仕掛けて自分でハマるということになってしまった。次からは是非とも一つ一つの用法や性質を理解した上で、実装していきたい。
でも、何かしらの初学者ってこういう風に泥臭く成長していくのかなぁとも思った。
諦めたら、そこで試合終了だよ。
- 投稿日:2020-03-21T15:06:53+09:00
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.jswebpack
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を動かしてみます。
- 投稿日:2020-03-21T14:36:29+09:00
魔法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)の力でこの世界を守ることが出来るのか!? 次回に続く!
- 投稿日:2020-03-21T13:56:50+09:00
Chrome DevTools の便利な機能を何もわかっていなかった(Debugger編)
はじめに
以前の記事でDevToolsのドキュメントを読んで学びがあったため続編。
日々フロントエンドで戦っているエンジニアには当然の知識かもしれませんが
バックエンドからたまに出てくるくらいの方には発見があるかもしれません。「Javascript」の項目あたりからいくつかピックアップします。
https://developers.google.com/web/tools/chrome-devtools/javascript/※上記リファレンスのデモページが使いやすいためサンプルとして利用します
活用できていなかった機能
ブレークポイントの使い分け
DevtoolsのSourcesタブ上にソースを表示して
該当行をクリックしてやると青色の矢印が表示され、ブレークポイントを置けます。
実行時にはここで処理が一時停止し、関連する変数や処理の確認ができます。
debuggerステートメントをソースに書いたときにも同じ動作になります。
この状態からステップ実行してデバッグができます。ここまでは流石に知っていましたが、画面の右下を見るとさらにメニューがあります。
例えば今回のデモにはボタンのクリックイベントによって発火する処理があるため
ここをデバッグするためにはイベントリスナー用のブレークポイントを設定します。
この状態でデモ上のボタンをクリックすると、ソースの15行目で処理が一時停止しました。
他にもDOMの変更やXHRの内容に応じて動作するブレークポイントなどがあるようです。条件付きブレークポイント
ブレークポイントを置きたい行をクリックではなく右クリックすると
「条件付きブレークポイント(Conditional breakpoint)」を選択できます。
ここではaddend1
の値が1を超える場合のみ一時停止させる設定をしています。
設定が完了すると該当行に黄色の矢印が表示されます。
同じ処理が何度も呼ばれるが特定の条件下でのみ確認したい というときに使えそうです。
(粘り強くステップ実行で進めても良いですが、非常に手間…。ソースのブラックボックス化
ブレークポイントからステップインなどで細かく処理を進めていると
サードパーティのライブラリのようなデバッグの必要のないコードに入り込むときがあります。
ソースコード上で右クリックするとメニュー上でブラックボックス化が可能です。
これによって、デバッガがこのスクリプトを無視するようになります。
ブラックボックス化されたスクリプトは↓このように設定されていきます。
ログの出力
条件付きブレークポイントと同様に行を右クリックするとログポイントの設定メニューがあります。
試しに3つの変数を記載して確定すると、この行にも黄色い矢印が設定されます。
該当行が実行されるときにはconsoleタブに変数の中身が出力されています。
変数の監視
ログポイントでは該当行実行時の変数の中身を表示することができましたが
特定の変数をしばらく追い続けたい状況もあると思います。
Sourcesタブにある「Watch」には監視したい変数を設定します。変数名を設定しておくと、処理が一時停止されるたびに内容が更新されます。
「Scope」にも同様の表示がありますが、実行箇所ごとに表示が大きく変化するため
継続的に変化を追うためには「Watch」で監視をした方が良いと思います。もっと頻繁に変化する値をデバッグしたい場合は
Consoleタブにある目玉ボタン(Live Expression)を利用します。
静止画なのでわかりませんが、記載した処理が250msごとに実行され表示されます。
ロジックを左右するローカル変数の変化などの監視はできませんが
タイマー処理や座標の調整などのデバッグに活用できそうです。ブラウザ上でのコード編集
ブレークポイントで一時停止している状態においては
consoleタブ上で実行時のローカル変数にも触れることができます。
例として、32行目でコードを一時停止しておきます。
この状態でconsoleタブに移動すると、ローカル変数の内部を表示できました。
直後に「こう変えたら正しい挙動になるのでは…?」という検証もできます。
ちなみにSourcesタブ上で直接ソースを書き換えてしまうことも可能です。
大元のソースとブラウザを何度も行き来する必要がなくなりました。まとめ
console.log()
やalert()
を埋めていた日々は何だったんでしょう。
まだまだ効率の良い開発を追求できそうです。
- 投稿日:2020-03-21T13:54:31+09:00
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でクラスを定義する方法でした。
- 投稿日:2020-03-21T13:24:09+09:00
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 ] ]
- 投稿日:2020-03-21T13:11:36+09:00
【REACT】カンタンなSPAサイトの仕組み
JavaScript学習をはじめてから2ヶ月目でSPAに触れてみた所感
基本的なweb制作のスキルはすでに獲得していて、次のステップにJavaScriptを選択してみました。
理由をカンタンに説明すると、web制作をより深く学習していく中で、SPA(シングルページアプリケーション)という技術への興味が高ぶったからです。
そこで、実際にJavaScriptを本格的に学びはじめてからSPAに触れてみた所感を共有できればと思い記事を投稿しようと思います。
これからSPAを学ばれる方に少しでも参考になれば幸いです。今回使用する技術
・React Router
→Routeコンポーネント、Linkコンポーネント1.ディレクトリの作成
※既に環境構築済みの想定
→まだの方はこちらから
sample(好きなディレクトリ名)> src> App.jsとindex.js のみのシンプルな構造App.jsimport React from 'react'; function App() { return ( <h1>hello</h1> ); } export default App;index.jsimport 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.jsximport React from 'react'; function Top() { return( <h1>TOP</h1> ); } export default Top;About.jsximport React from 'react'; function About() { return( <h1>ABOUT</h1> ); } export default About;4.ルーティングの設定
実際の表示ページとなるApp.jsに以下の要領でルーティング設定を行います。
App.jsimport 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.jsimport 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サイトは非常に面白い仕組みで、これからよく使われる技術のひとつだと思うので、勉強していきたいものですね。
- 投稿日:2020-03-21T12:13:44+09:00
【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/21Dateインスタンスに放り込んでそこから必要なデータを取得する。
ピンポイントで指定できるので「split」使うよりこっちの方が良い感じ。
month取得時に0からスタートする(+1する必要がある)のは相変わらず。
- 投稿日:2020-03-21T10:19:00+09:00
これから発信していく内容
- 投稿日:2020-03-21T10:01:24+09:00
魔法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)の力でこの世界を守ることが出来るのか!? 次回に続く!
- 投稿日:2020-03-21T09:38:13+09:00
数十分で始める、Svelte+Firebaseのアプリ生活。
序
数日前に、ES2015以降をお勉強するのに、Svelte REPLが楽しかったので、Svelte+Firebaseでwebアプリを作ってみたいと思った(最近、オウンドメディア的なものを作りたいなと思っているもので)。
Svelteとは?
JSを知っていてSvelteって何という人は、Svelteで始める頑張らないフロントエンド生活を参考にすると良い。作ってくれているtodoアプリをどうfirebase化するかは、良い例題となりそう。
SvelteFireを使って、1時間以内でアプリを作り始める。
Firebaseのエキスパートの方が作ってくださっているSvelteFireを元にすると、(朝食のカレーを作ったり食べたりしながら)ほぼ1時間でCloud firestoreに書き込めるところまで行けた。
以下、Windowsマシンでの作業前提。
https://github.com/codediodeio/sveltefire1) 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 installnode/npm環境が正常にインストールされていれば、fireappのインストールまで1分もかからないはず。
fibebaseと接続するための設定はただ一点。App.svelte以下の
var firebaseConfig = {...}
のところに、firebaseアプリの設定を貼り付けるのみ。
※ちなみに、↑は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認証ができないのか。。
おしまい。
- 投稿日:2020-03-21T02:52:58+09:00
【JS】querySelectorAll()メゾッド使用時にaddEventListener()メゾットが動かなかった
問題
querySelectorAll()
メゾッドでセレクタを指定して、addEventListener()
メゾッドでクリックイベントを仕込もうとしたときです。main.jsconst target = document.querySelectorAll('nav a'); const closeNav = function () { const close = document.getElementById('global-nav'); close.classList.toggle('nav-open'); }; target.addEventListener('click', closeNav);
addEventListener()
が動かない…
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.jsconst 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()
メゾッドでリストの中身ひとつひとつに処理をあてることができる。参照
- 投稿日:2020-03-21T02:39:29+09:00
フロントエンドエンジニア(主にVue.js)に転職して学んだこと
Qiita初投稿です。
はじめに
転職して1年が経ったので、個人的な振り返りです。
未経験から主にVue.jsを学び、モダンなフロントエンド開発が学べて個人的には大きな収穫となりました。
私について
- WEB制作会社出身
- 前職はコーダー
- 2019年に転職、フロントエンドエンジニアになる
古典的なWEB制作会社にいましたので、モダンなフロントエンドを学びたいと思い、転職活動をしました。
もちろん未経験では採用してくれる企業はなかなかいないので苦戦しましたが、縁あって今の会社に転職することができました。
入社前のVue.jsの勉強方法
- 入社前、前職の有給消化期間でUdemyと本で学習
- Udemy 「Vue JS 入門決定版」https://www.udemy.com/course/learn-vuejs/
- 本「基礎から学ぶ Vue.js」https://www.amazon.co.jp/gp/product/4863542453/
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年でした。
ですが、課題もあるのでこの先に学んでいきたいです。
- 投稿日:2020-03-21T02:05:37+09:00
チーム開発 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側で動かす