- 投稿日:2019-07-30T21:14:53+09:00
Vuexで状態管理しながらメモアプリを作ってみた
メモアプリを Vuex で管理する
index.vue ファイルとコンポーネントだけで作れる簡単なアプリですが、 Vuex による状態管理まで理解したかったので今回 Vuex を取り入れることにしました。
小さいアプリケーションだとデータの流れが分かりやすいので、これから Vuex を学びたい人におすすめです。前提条件
Nuxt.js をインストール済 バージョン 6.4.1 (
npm -v
で確認)Nuxt.js 公式ドキュメントのチュートリアルを参考に、新しいプロジェクト名で 「create-nuxt-app」 の設定をします。作業はターミナル上で、ルートで行います(新しいプロジェクト名のディレクトリがルートに出来上がります)。これで簡単に開発の準備が整います。
何もない状態(スクラッチ)から始める場合も上記ドキュメントの中に、続けて記載がありますのでそちら参考にしてみてください。
ディレクトリができ、エディタを開くと↓写真のようなフォルダ一覧があります。http://llocalhost:3000で確認すると、 pages フォルダの中の index.vue の内容になっています。 h1 タグのプロジェクト名の下に自動で description の文字が入っている場合があるので、変更するときは package.json を編集します。(写真の下から3番目)
Flux の考え方 → Vuex を利用する場面
Flux についての説明は省きますが、 Vuex とは、ドキュメントによると
Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。
Vuex は一箇所にアプリケーションの状態(ステート)がまとまっています。つまり、"Single source of truth"を保てます。情報源を信頼できるものにしようという事で、下記のように流れは一方向です。
State を変更できるのは Mutations のみであり、 Actions は「コミット」しかできません。必要なファイルを揃える
さっそく作っていきますが、必要なファイルを3つ用意します。ファイルは template タグでくるみます。私のエディタは Emmet が入っており
scaffold
と入力するとテンプレートが用意されます。
- pages/index.vue (最初に既にあるはずです)
- components/MemoList.vue (文字はパスカルケースで作成/ 再利用可能な部品のようなもので Model 。)
- store/memo.js ( state, mutations, actionsを今後記述します)
フォームを作る
input タグを作るときは、
v-model
で Model, View の双方向バインディングを可能にします。片方変更したら、( input の中身について DOM やデータに変更があれば)もう片方も同じように変更されます。
@click
とはv-on
のイベントハンドラの省略。
props でコンポーネントに値を渡す際は、ケバブケースmemo-contents
で渡します。コンポーネント側で受け取る時の属性はキャメルケース。index.vue<div class="input_container"> <p> <input v-model="inputText" type="text" > </p> <button @click="addMemo()">Save</button> </div> <!-- コンポーネントに分けた部分について記述--> <!-- memo-contents が属性で、" memoContents "はここでは変数です--> <!-- @delete は、ただのきっかけで memo.js アクションの deleteMemo メソッドに伝えているだけ--> <memo-list :memo-contents="memoContents" @delete="deleteMemo()" />コンポーネントを作る
input 入力したものは一つ一つがメモリストです。何度も使うのでコンポーネントとして MemoList.vue ファイルに記述します。
ここで
ul
タグの中でv-for
を用いてリストを複数生成しています。
先にscript
タグのprops:
を見てください。memoContents
があり、ここで配列を作っています。
先程のリストの複数生成時にはここのことを指しており、 memoContents に入っている分だけインデックスを付けてアイテム( リスト )を生成します。Memolist.vue<template> <div> <ul v-for="(item, index) in memoContents" :key="index" > <li> <span>{{ item.text }}</span> <!-- ここについては後ほど --> <span>{{ item.date | dateConvert}}</span> <!-- ここについては後ほど --> <button @click="handleDelete(index)">x</button> </li> </ul> </div> </template> <script> export default { //DOMに描き出すためにこのように書きます props: { //親である index.vue から受け取るときはこの中に入ります memoContents: { // memoContents として受け取り、値を一つずつ配列[]に入れます type: Array, default: () => [] } },index.vue に追記
コンポーネントを組み込む際には import する必要があります。
まだ Vuex に関する memo.js は記述していませんが、ここで呼び出します。methods
の中addMemo()
については連想配列の階層がコンポーネントで書いたものと一致する必要があります。
computed
では後述の memo.js の state の memoContents を参照。若干ややこしいですが、後述の memo.js と照らし合わせてみてください。index.vue<script> // MemoList.vue を import したら、 exportdefault の中に compornent として記述 import MemoList from '~/components/MemoList' import { mapState, mapActions } from 'vuex' // このように記述する export default { components: { //ここでも描き出すためにMemoListを書きます MemoList }, data: () => ({ inputText: "" }), // 算出プロパティといいますが、 DOM へ表示されるものが computed です computed: { ...mapState({ // どう index.vue で呼ぶか宣言して、 state の中の memo.js の中の memoContents を参照 memoContents: state => state.memo.memoContents }), // ...mapState('memo', ['memoContents'])とも書けます }, methods: { ...mapActions('memo', ['addMemoContents','deleteMemoContents']), addMemo() { // methods 内の関数として呼び出し this.addMemoContents({ //ここがコンポーネント側で{{ item.text }}で書いたところと階層が一致するはず text: this.inputText, date: new Date() }) this.inputText = "" }, // memoContens を消すものなので deleteMemo メソッドは index.vue にあるべき // 子( MemoList.vue からの)の index を受け取っている deleteMemo(index) { this.deleteMemoContents(index) } } } </script>MemoList.vue に追記
ここで重要なものがもうひとつ、子から親へ値を渡す
$emit
です。MemoList.vue<script> export default { // component 内で完結するもの filters: { dateConvert: function (value) { const date = new Date(value) return `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}` } }, // 親のメソッドを発火させるためだけのもの methods: { //上記のクリックイベントの index を受け取って $emit で親に受け渡す handleDelete(index) { this.$emit('delete', index) } }memo.js に記述
ようやくここまで来ました。今回重要なポイントは、「 mutations 以外は直接 state とやり取りしない」ことです。mutations が唯一 state を更新できるという事を踏まえて見ていきます。
memo.js// state memoContentsが入っている状態 export const state = () => ({ memoContents: [] }) //mutations データを更新できる export const mutations = { //引数に state と書く。 memo は actions から渡される updateMemoContents(state, memo) { state.memoContents.push(memo) }, deleteMemoItem(state, index) { state.memoContents.splice(index,1) } } // actions ・・ mutations を発動する export const actions = { // { commit }と書くことで mutations に渡せる // index.vue からの memoDate を受け取ってから mutations に commit addMemoContents({ commit }, memoData) { commit('updateMemoContents', memoData) }, deleteMemoContents({ commit }, index) { commit('deleteMemoItem', index) } }まとめ
今回初めて Vuex を勉強しましたが、まだまだ理解が足りない部分が多く、冗長になってしまいました。 Vuex を使うべきアプリではありませんが、小さいアプリで全体像が捉えられたのは良かったです。
- 投稿日:2019-07-30T19:45:22+09:00
Vueプロジェクトにおいて、クラスにプロパティを持たせようとした時のエラー
vue-cliのプロジェクトにおいて、
class AuthService extends EventEmitter { idToken = null // ←これがエラーになる profile = null tokenExpiry = null // Starts the user login flow login (customState) { webAuth.authorize({ appState: customState }) } ... }こういう書き方をすると、
Syntax Error: Unexpected token (9:10) 7 | 8 | class AuthService extends EventEmitter { > 9 | idToken = null; | ^ 10 | profile = null; 11 | tokenExpiry = null; 12 |こんな文法エラーぽいのがでた。
package.json
に... "devDependencies": { "@babel/polyfill": "^7.4.4", "@vue/test-utils": "^1.0.0-beta.29", "autoprefixer": "^7.1.2", "babel-core": "^6.22.1", "babel-eslint": "^8.2.1", "babel-helper-vue-jsx-merge-props": "^2.0.3", "babel-jest": "^24.8.0", "babel-loader": "^7.1.1", "babel-plugin-istanbul": "^4.1.1", "babel-plugin-syntax-jsx": "^6.18.0", + "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-runtime": "^6.22.0", "babel-plugin-transform-vue-jsx": "^3.5.0",
.babelrc
+ "plugins": ["transform-runtime", "transform-class-properties"],でエラーが解消された。
- 投稿日:2019-07-30T19:24:34+09:00
Vueインスタンスとコンポーネントの関係
はじめに
オブジェクトリテラルの観点でVueインスタンス、コンポーネントを整理してみる。
これによって、
- ルートVueインスタンス
- グローバルコンポーネント
- ローカルコンポーネント
3つのVueインスタンスがそれぞれ何故そういう書き方になるのかがクリアになる。また、
単一ファイルコンポーネント
等のより発展的な内容についても理解の助けになる。本記事では、「ルートVueインスタンスとは?」「コンポーネントとは?」といった、そもそものところについては触れないけれども、それらについてなんかしっくりこない感じがあるひとに対しては何かしら役に立つかもしれない。
- 本記事では、Vueインスタンス、コンポーネントの生成時に記述されるオブジェクトリテラルを便宜上Vueオブジェクトと呼ぶことにしています。
- 記事内のサンプルコードはCDNでVueを読み込めば動く内容です。
- サンプルコードの結果はルートVueインスタンス、グローバルコンポーネント、ローカルコンポーネントで、ほぼ同じです。
Vueオブジェクトによる整理
まずは、ルートVueインスタンス、グローバルコンポーネント、ローカルコンポーネントについて、それぞれ導入的な書き方からVueオブジェクトの観点で整理した書き方に変更する。
ルートVueインスタンス
よくある導入的な書き方。
<div id="container"> <p>{{ comments }}.</p> </div>// ルートVueインスタンス new Vue({ el: '#container', data: { comments: "hello in root" } });# ブラウザ出力 hello in root.ルートVueインスタンスを生成する
new Vue()
の引数はオブジェクトリテラル。これをVueオブジェクトとして外に出す。// ルートVueオブジェクト // new Vue()の引数を外に出した let rootVueObj = { el: '#container', data: { comments: "hello in root" } }; // ルートVueインスタンス // rootVueObjを登録 new Vue(rootVueObj);グローバルコンポーネント
よくある導入的な書き方。
<div id="container"> <g-component></g-component> </div>// グローバルコンポーネント Vue.component('g-component', { data: function () { return { comments: "hello in global component" } } , template: '<p>{{ comments }}.</p>' }); // ルートVueインスタンス new Vue({ el: '#container' });# ブラウザ出力 hello in global component.ルートVueオブジェクトについては前章同様。
グローバルコンポーネントを生成するVue.component()
の第二引数はオブジェクトリテラル。これもコンポーネントVueオブジェクトとして外に出す。// コンポーネントVueオブジェクト // Vue.component()の第二引数の内容を外に出した let componentVueObj = { data: function () { return model } , template: '<p>{{ comments }}.</p>' }; // ルートVueオブジェクト // new Vue()の引数を外に出した let rootVueObj = { el: '#container' }; // グローバルコンポーネント // componentVueObjを登録 Vue.component('g-component', componentVueObj); // ルートVueインスタンス // rootVueObjを登録 new Vue({rootVueObj);ローカルコンポーネント
よくある導入的な書き方。
<div id="container"> <l-component></l-component> </div>// コンポーネントVueオブジェクト let componentVueObj = { data: function () { return { comments: "hello in local component" } } , template: '<p>{{ comments }}.</p>' }; // ルートVueインスタンス new Vue( { el: '#container', components: { 'l-component': componentVueObj } });# ブラウザ出力 hello in local component.ルートVueオブジェクトについてこれまで同様外に出す。
let componentVueObj = { data: function () { return { comments: "hello in local component" } } , template: '<p>{{ comments }}.</p>' }; // ルートVueオブジェクト // new Vue()の引数を外に出した let rootVueObj = { el: '#container', components: { 'l-component': componentVueObj } }; // ルートVueインスタンス // rootVueObjを登録 new Vue(rootVueObj);Vueオブジェクトの定義と登録
ここまでの整理で、Vueオブジェクトが定義と登録によって成立していることがはっきりしたので、今度はルートVueインスタンス、グローバルコンポーネント、ローカルコンポーネント、それぞれの定義と登録を比較してみる。
定義
// ルートVueインスタンスでのVueオブジェクト記述形式 let vueObj = { el: 'DOMセレクタ', data: {} };// グローバルコンポーネント/ローカルコンポーネントでのVueオブジェクト記述形式 let vueObj = { data: function () { return {} } // , template: 'テンプレート'まず、グローバルコンポーネントとローカルコンポーネントのVueオブジェクトは全く同じである。ルートVueインスタンスは全く同じでは無いが、かなり似ている(templateオプションについては、後で触れます)。
登録
// ルートVueインスタンスでのVueオブジェクトの登録 new Vue(rootVueObj);// グローバルコンポーネントでのVueオブジェクトの登録 Vue.component('g-component', componentVueObj); // ルートVueインスタンス new Vue({rootVueObj);// ローカルコンポーネントでのVueオブジェクトの登録 let rootVueObj = { el: '#container', components: { 'l-component': componentVueObj } }; // ルートVueインスタンス new Vue(rootVueObj);Vueオブジェクトという観点から整理すると、
- 定義の書き方は2種類
- 登録方法はルートVueインスタンス、グローバルコンポーネント、ローカルコンポーネントによってそれぞれ異なるこれだけでもかなり見通しがよくなりませんか?
考察
ルートVueインスタンスとコンポーネントの違い
elオプション
ルートVueインスタンスとコンポーネントの大きな違いはHTML要素へ紐付けする際の対象の違い。ルートVueインスタンスは
'#container'
という既存のHTML要素にid属性
で紐付けしている。それに対して、グローバルコンポーネントの'g-component'
もローカルコンポーネントの'l-component'
もカスタムタグへの紐付けである。Vue独自ルールであるカスタムタグは素のHTMLのままでは使用することはできない。そのため既存のHTML要素の接点としてルートVueインスタンスが存在している。そう考えると、ルートVueインスタンスにだけ'elオプション'
がある理由が理解できる(唯一の例外は el のようなルート固有のオプションです)。dataオプション
ルートVueインスタンスでは直接文字列や数値が入るのに対し、コンポーネントでは関数経由でそれらを指定する。その理由はインスタンスごとの値を保持するためということだが(data は関数でなければなりません)、関数にするとなぜそれが可能なのかはわからない(何となくはわかる気もするが、、)。誰か教えていただけると嬉しい。
templateオプション
templateオプション
はルートVueインスタンスに登録するVueオブジェクトにも記述できる(あまりそういう書き方をは目にしないが、、)。なので、実はこの点に関しては両者に違いはない。
逆に、本記事のようような書き方をする場合、HTML側ににテンプレートの内容を書くことはできない。X-テンプレート
を使うとできるようなので、気になる人はそちらを試してみると良いかもしれない(X- テンプレート)。
X-テンプレート
の他には、単一ファイルコンポーネント
という形式でVueオブジェクトからテンプレートの記述を分割できる。プロジェクトがある程度大きくなれば単一ファイルコンポーネント
を使うことが必須になるはず。本記事では触れないけれども、本記事の内容が理解の助けになると思う(そもそも、それが本記事の目的の一つ)。グローバルコンポーネントとローカルコンポーネントの違い
両者の違いは名前通りグローバルとローカルの違いだが、それは記述にも表れている。グローバルコンポーネントの登録は直接
カスタムタグ
の名前が紐付けられている。それに対し、ローカルコンポーネントの登録はルートVueインスタンス(親)のcomponentsオブション
に対してである(親VueオブジェクトはルートVueインスタンスである必要はない)。これによってローカル化を実現している。
違いではなく共通点に目を向けると、どちらもルートVueインスタンスで紐付けされたHTML要素
(<div id='container'>
)のなかで、コンポーネントのカスタムDOM
が記述されている。繰り返しになるが、ルートVueインスタンスは既存のHTML要素の接点なので、グローバルコンポーネントであってもその外には出られない。
- 投稿日:2019-07-30T18:27:05+09:00
日本史上初! 速読ウェブアプリ 時間を生み出す最強スキル
日本史上初! 速読ウェブアプリ 時間を生み出す最強スキル
LINE、SNS、ネット小説、実用書
ちまたには文字があふれている
読むべきものと読まないものを見極める知性と
速く読む力
この二つが現代社会でうまく文字とつきあうマストスキルだだれかの知恵は真似できなくても
自分の速読は鍛えられる速く読めれば、時間が生まれる
時間が生まれれば、多くを読める
多くを学び、自由な時間をもっていれば、
私たちは全然違う人間になれる目次
- 1 結論
- 2 細事
- 2-1 開発の動機
- 2-2 日本史上初とは
- 2-3 ロジック
- 3 おわりに
1 結論
Sokdokは、速読訓練用のウェブアプリです。
- 好きなテキストで練習可能です(ニュース、ブログ、小説を読もう、好きなサイトから文章をコピー&ペーストするだけ)
- 好きなスピードで速読の訓練をすることができます
- 一度に表示する語数を自由に調整できます
2 細事
2-1 開発の動機
海外に目を向ければ、Spreederという速読用ウェブアプリがあります。
好きなテキストを好きなスピードで速読できるサービスです。
Spreederを使っているうちに、日本語でも練習したいと思うようになりました。
ところが日本の速読用サイトで、Spreederのようなアプリは見つけれず。
自分で作りました。2-2 日本史上初とは
従来の日本語用速読アプリは、事前に用意されたテキストでしか練習できなかったようです。
Sokdokは、ユーザが自由にテキストを用意できる、日本で最初の無料のウェブサービスです。2-3 ロジック
英語の文章を分割するのは容易です。
English sentence has blank space between each words, so it's easy to divide a sentence into a list of words.
(英語の文は単語の間にスペースがあるから、単語のリストに分割するのは簡単だよ)日本語の文章を速読用に分割することが難しいから、従来のアプリは、事前に用意されたテキストでしか練習できなかったのかと思われます。
例えば、「宝石商リチャード氏の謎鑑定」という文言を文節に区切るとすると
「宝石商,リチャード氏の,謎鑑定」ですが、プログラミングで区切ると難しい。なぜなら、辞書データ「石、鑑定、氏、商、宝、謎、の、宝石、宝石商、リチャード」に対して、「宝、石、商、リチャード、氏の、謎、鑑定」と区切ってしまうかもしれないです。
それに、実際は辞書はもっと重いので、長文の全文字を辞書検索していると、時間がかかって仕方ありません。そこで人工知能で文の形を解析されている方もいます。
でも私は人工知能の知識がありません。
そして幸いにも、速読という山を登るのに人工知能という飛行機はいりません。日本語には、主に漢字と平仮名とカタカナしかないからです。
「漢字とカタカナと平仮名、3種類も文字があって、日本語はとても難しいと言うが、漢字がなければどれだけ読みづらいか気づかないわけじゃないでしょう。すくなくとも あいだに すぺーすが ないと ごらんこんなによみにくいじゃあないか」
漢字:〇 平仮名:× カタカナ:△ として変換すると…
「〇×△×〇、3〇×〇×、〇×〇×〇×、〇×〇×〇×。× × × × ×」
だったら、漢字+平仮名、カタカナ+平仮名で速読用の文章は作れます。
つまり「宝石商、リチャード氏の、謎鑑定」です。analyzeJapanese.jsfunction analyze(targetText) { var reg = /「?[ァ-ヶー]+[\u4E00-\u9FFF]+[ぁ-んー]+[、。;:!?!?.,;:: …...・]?[!?]?[?!]?[!?]?[?!]?」?|「?[A-Za-z0-9/-:.,;]+[\u4E00-\u9FFF]+[a-zA-Z0-9]?[ぁ-んー]+[、。;:!?!?.,;:: …...・]?[!?]?[?!]?[!?]?[?!]?」?|「?[\u4E00-\u9FFF]+[a-zA-Z0-9]?[ぁ-んー]+[、。;:!?!?.,;:: …...・]?[!?]?[?!]?[!?]?[?!]?」?|「?[ァ-ヶー]+[0-9]?[ぁ-んー]+[、。;:!?!?.,;:: …...・]?[!?]?[?!]?[!?]?[?!]?」?|「?[0-9a-zA-Z]+[ぁ-んー]+[、。;:!?!?.,;:: …...・]?[!?]?[?!]?[!?]?[?!]?」?|「?[a-zA-Z0-9]+[ァ-ヶー]+[、。;:!?!?.,;:: …...・]?[!?]?[?!]?[!?]?[?!]?」?|「?[ぁ-んー]+[、。;:!?!?.,;:: …...・]?[!?]?[?!]?[!?]?[?!]?」?|[\u4E00-\u9FFF]+[、。;:!?!?.,;:: …...・]?[!?]?[?!]?[!?]?[?!]?」?/g; return targetText.match(reg) }鬼のように長い馬鹿な正規表現になっておりますが、以下の組み合わせを |(または) でつないで、
〇×、△〇などのかたまりを見つけているだけです。
分類 正規表現 (文字コードはutf-8) 漢字 [\u4E00-\u9FFF] 平仮名 [ぁ-んー] カタカナ [ァ-ヶー] おわりに
お読みいただきありがとうございます
速読は筋トレみたいなもので、才能とは関係なしに伸ばせるらしいです
私も、英文で400wpmから始めて、2週間で480wpmまで伸びました
Sokdokは日本語で練習できるので、チェックしてみてください!みなさんの速さや速読に関するコメントお待ちしております↓
日本人の人生の自由な時間が増えますよう
かしこ
- 投稿日:2019-07-30T16:27:47+09:00
Javascriptの関数の引数でよく見る({ id })ってどういう意味?
VueやReactを書いていると、こんな感じの関数をよく見ます。
sample.vuemethods: { userData({ id }) { // 処理 } }Vuexのactionでも({ commit })という記述をよく見ますよね。
正直、初めて見たときは意味不明でした。引数に波括弧ってどういうこと?みたいな。
同じように思っている人も多い(はず)なので、以下でこの独特な記法について解説していきます。
上記の書き方はES6の分割代入の一種っぽい
分割代入とは、以下のようなものです。
const data = { id: 1, name: 'taro' } const { id, name } = data; console.log(id); // 1 console.log(name); // tarodataオブジェクトはidとnameというプロパティを持っています。代入したい変数名とプロパティ名が一致している場合、上記のように書くことで1行にまとめることが可能です。
関数の引数に出てくる波括弧もよく似たもので、上の考え方を少し応用すれば理解できるかと思います。
sample.vue<template> <div> {{ displayUserData(userData) }} // taroと表示される </div> </template> <script> export default { data() { return { userData: { id: 1, name: 'taro' } } }, methods: { displayUserData({ name }) { return name } } } </script>displayUserDataメソッドの引数は({ name })となっています。これは、引数として渡されたオブジェクトのプロパティにnameがあれば、それを利用するという意味です。
このメソッドを使う場合、上記のようにuserDataオフジェクトをそのまま放り込みます。userDataはnameプロパティを持っているので、この場合は'taro'が表示されます。
これは以下の書き方と同義です。
sample.vue<template> <div> {{ displayUserData(userData.name) }} // taroと表示される </div> </template> <script> export default { data() { return { userData: { id: 1, name: 'taro' } } }, methods: { displayUserData(name) { return name } } } </script>displayUserDataの引数から波括弧がなくなり、加えて呼び出し時にuserData.nameと、プロパティを指定しています。
ということは、({ commit })はどういう意味?
Vuexのactionsを定義する際、下記のような書き方をすることが多いかと思います。
sample.jsactions = { somethingAction({ commit }) { commit('somethingMutation') } }実は、上記についてはこのように書くこともできます。
sample.jsactions = { somethingAction(context) { context.commit('somethingMutation') } }Vuexのactionsはdispatchされると、contextオブジェクトを受け取ります。そしてcontextオブジェクトは、以下のようなプロパティを持っています。
{ state, rootState, commit, dispatch, getters, rootGetters }ただ、commitしか使わないケースが大半なので、({ commit })と書くことで簡略化することが多いかと思います。
上記の通り、contextオブジェクトはdispatchプロパティも持っているので、以下のような感じにすればactionから別のactionを呼び出すことも可能です。
actions = { somethingActions({ commit, dispatch }) { commit('somethingMutation') dispatch('otherAction') } }公式ドキュメントにより詳しく書かれているので、もっと詳しく知りたい人はそちらを見てください。。
まとめ
jsフレームワークのconfigファイルとかでもよく出てくる記法なので、理解しておくと便利です。
これらについては、「呼びされるときに呼び出し元からオブジェクトを渡される」ということを知っておくと、より理解しやすいかなと思います。
- 投稿日:2019-07-30T15:02:32+09:00
javascript vue.js object 配列を検索
vue.jsで配列を検索したい。
しかし、なぜか find を使うと googlebot が エラーを吐く。
これはSEO的にまずい。ということで、 filter を使って要素を取得したり、存在チェックしたりします。
まずは元になるデータ
var arr = [ { id:100, name: '一郎', age: 25 }, { id:102, name: '二郎', age: 21 }, { id:153, name: '三郎', age: 18 } ];ID153のデータはある?
var tmp = arr.filter(e => e.id == 153); if(tmp.length > 0){ console.log("ID153のデータはあります"); } else { console.log("ユーザーが見つかりません"); }データを絞り込む
//指定した条件のデータを抽出 var tmp = arr.filter(e => e.age >= 20); // 結果 // 0: {id: 100, name: "一郎", age: 25} // 1: {id: 102, name: "二郎", age: 21}
- 投稿日:2019-07-30T13:44:44+09:00
【Vue.js】v-forの中で外部リンクと内部リンクを混ぜて表示する方法
本記事の内容
v-forを使って要素を繰り返して表示するのはわかったけど、その中身に外部リンクと内部リンクが混ざっている時の実装で少し悩んだので、同じような悩みを抱えている人の助けになる記事です。
前提
・Vue.jsとNuxt.jsを使ったアプリケーションでの実装
・Vuetifyを利用です。
実装
大元のvueファイルの内容(一部抜粋)
(色々省略) <v-btn v-for="link in links" :key="link" color="white" flat round :to="link.url" > {{ link.name }} </v-btn> (色々省略) <script> export default { data: () => ({ links: [ { url: 'hoge', name: '外部リンクへ' }, { url: 'privacy-policy', name: 'プライバシーポリシー'}, { url: 'service-policy', name: '利用規約'}, { url: 'contact', name: 'お問い合わせ'}, { url: 'for_user', name: '出品したい方'}, ] }) } </script>上記の内容では。:to="それぞれのurl"が入ってくる形になっていて、下の4つについてはそれで問題なかったのですが、hogeの部分を https://google.com のような外部リンクに飛ばしたかったので、ここの部分がちゃんと動作するように変更することにしました。
手順としては下記になります。
・もともとあった:to="link.url"を削除
・クリックした時にurlを判定して、特定のものについては外部リンクに遷移するように変更この手順で実装したものが下記になります。
変更後のvueファイルの内容(一部抜粋)
(色々省略) <v-btn v-for="link in links" :key="link" color="white" flat round @click="getCreateUrl(link.url)" > (色々省略) <script> export default { data: () => ({ links: [ { url: 'hoge', name: '外部リンクへ' }, { url: 'privacy-policy', name: 'プライバシーポリシー'}, { url: 'service-policy', name: '利用規約'}, { url: 'contact', name: 'お問い合わせ'}, { url: 'for_user', name: '出品したい方'}, ] }), methods: { getCreateUrl(url) { if(url==='hoge') { location.href="https://google.com/" } else { location.href=`${url}` } } } } </script>これで、無事に動いてくれました。
※他にいいやり方があったらご指摘ください。
- 投稿日:2019-07-30T12:36:53+09:00
MEMO[Vue.js]
Vue.jsのメモ
データの反映
作成されたVueインスタンスは、プロパティ値が変更されると、関連付いているメソッドの値も変更される。
example// 定義 var data = { a: 1 } var vm = new Vue({ data: data }) // プロパティ操作 vm.a = 2 data.a // 2プロパティ値を変更されたくない場合
以下を適用する。
exampleObject.freeze(obj)プロパティへのアクセス
接頭辞「$」を使用して呼び出す事が可能。
examplevar data = {'data' : 1} var vm = ner Vue ({ el = $example, data = data }) // 呼び出す vm.$data vm.$elライフサイクルフック
インスタンス生成処理に、特定の処理を挟むことが出来る機能が、備わっている。
以下は、インスタンス生成後への処理追加方法。examplenew ({ data : {a:1}, created : function (){ console.log('インスタンス完了') }, })その他にも関数が用意されている。
★テンプレート
HTMLパースを適切に行うために。
初歩
基本的には、Mustache構文を利用する。
特にプロパティを指定しない場合には、要素の操作で、データバインディング箇所は、可変式になっている。example//表示データが変わってもいい場合 <span> Message: {{ msg }} </span> // 画面上 -> Message: example! vm.msg = 'change!' // 画面上 -> Message: change! // 表示データを変えられたく無い場合 <span v-once> Message: {{ msg }} </span> // 画面上 -> Message: example! vm.msg = 'change!' // 画面上 -> Message: example!属性
Mustacheは属性値では使用できない。
代わりに、v-bindディレクティブを使用する。example<div v-bind:id="example"></div>desable設定時は挙動が変わる。
以下は、値が存在する時には、ボタンが非活性にする記述。examplevar vm = new Vue ({ el :'#example', data: { example: 'desable', } })example<button id="example" v-bind:disabled="example">Button {{ example }}</button>javascriptの処理を活用
バインディング内で、javascriptを利用できる。
example{{ number + 1 }} // 定義した値に1を足す。文字列の場合には連結。 {{ ok ? 'YES' : 'NO' }} // 条件式 {{ message.split('').reverse().join('') }} // scriptを適用適用されない記述方式もあるので、仕様を把握するのがよい。
三項演算子でしか適用されな等。ディレクティブ
接頭辞「v-」で定義した箇所へ、動的な処理を施せる。
引数
引数を既述することにより、要素を動的に扱うことが出来る。
examplevar vm = new Vue({ el: '#example', data: { bool: false, url: 'http://', }, methods: { go:function(e){ alert('Hello ' + this.name + '!') }, }, })表示例。
example<div id="example"> 以下要素は表示されない <p v-if="bool">Now you see me</p> href属性にurl変数展開 <a v-bind:href="url"> ... </a> go関数実行 <button v-on:click="go"> DODODO </button> </div> <p class="example" v-if="bool">Now you see me</p> href属性にurl変数展開 <a class="example" v-bind:href="url"> ... </a>動的引数
鉤括弧にて、動的引数を利用して、値の展開が可能。
examplevar vm = new Vue({ el: '#example', data: { el: 'class', clsnm : 'example', }, })example<div id="example"> el変数展開、clsnm変数展開 <p v-bind:[el]="clsnm"></p> イベントハンドラも動的記述可能 <a v-on:[eventName]="doSomething"> ... </a> </div>引数は、stringが扱われることを想定されている。
nullはバインディングを削除する場合に活用できる。スペースや、引用符を既述すると、正しく動作しないので注意。
また、eventNameのようなキャメルケースは、ブラウザが全て小文字にするので注意。省略記法
冗長さを避けたい人へ。
こちら算出プロパティ
テンプレート内に、ロジックを埋め込めるのは便利だけど、保守、運用の観点から、あまり良く無いため、算出プロパティを利用する。
基本的な例
examplevar vm = new Vue ({ el:'#example', data:{msg : 'Hello'}, computed: { joinStr: function(e){ return this.msg + 'World' }, }, })example<div id="example"> 出力 -> HelloWorld <p>{{ joinStr }}</p> </div>プロパティ操作も適用される。
examplevm.msg = 'small' // 出力 -> smallWorld算出プロパティとメソッドの違い
メソッド記法により、同様の結果を得られることは可能。
しかし、処理のパフォーマンスに違いが生まれる。算出プロパティは、依存関係が変化しなければ、キャッシュされ続ける特質を持っている。
すなわち、プロパティの変更が行われなければ、再度計算を行われず、保持された値を返すのみなので、アプリケーション負荷が低くなる利点がある。必要で無い限りは、算出プロパティの使用をこころがけよう。
要素のclassを動的に操作
利用者の操作によって、見た目を変えたい時の処理。
v-bind:classを使用する。
有効化したい場合は、examplevar vm = new Vue ({ el:'#example', data: { is_active: true, is_a : false, }, })example<div id="example" v-bind:class="{'active':is_active, 'cls_a':is_a}"></div> ↓ <div id="example" class="active"></div>算出プロパティの利用も可能。
examplevar vm = new Vue ({ el :'#example', data: { 'el_activ': true, 'error': true, }, computed: { ExampleObj: function(){ return { 'active': this.el_activ, 'error_cls' : this.error, } } } })example<div id="example" v-bind:class="ExampleObj"></div> ↓ <div id="example" class="active error_cls"></div>条件付きレンダリング
v-if
真偽値、条件結果を利用して、画面描写結果を変化させる。
要素自体を削除してくれるので、アクセス制限、表示制限等に利用すると良さそう。入力項目の制御
input等に関しては、条件操作によって、要素の表示操作を行ったとしても、入力項目が切り替わらない場合がある。
その際には、一意の属性をもたせると良い。example<div v-if="login_type == 'usr_id'"> <input placeholder="username" key="username-input"> </div> <div v-if="login_type == 'email'"> <input placeholder="mail addless" key="email-input"> </div>v-show
display要素により、表示、非常時を切り替える。
template要素、v-else要素に対応していないので、注意する。v-ifとv-else
特性を理解して、適切に扱う。
v-ifは、描画時に要素の操作を、スクリプトによってシステム的に処理するため、比較的、動作が遅くなる。
v-elseは、要素を非表示にするだけ。
入力項目等の、不正操作による、不具合、アプリケーションの脆弱性を緩和することができそう。v-ifとv-for
同時に利用することは推奨されていない。
こちらリストレンダリング
類似した形式の情報を一覧で表示する場合には、v-forが利用できる。
pythonのforと似ているの。
inの代わりにofを使用できる。状態の維持
リストを並び替える処理を行った際に、情報が正しく保持されない場合がある。
例えば、インプットの値等。
適切に保持させるためには、key属性を適用するとよい。example<div v-for="item in items" v-bind:key="item.id"></div>keyが必要のない場合には、付与しなくてもいいが、基本的には適用しておけば、不具合を回避できる。
リストの状態を操作する時
変更メソッドが用意されているので、適宜活用しよう。
注意点
Vueが検知できない場合がある。
- インデックスを利用した、直接な値操作
- vm.items[key] =
- 配列の長さの操作
- vm.items.length =
追記中
- 投稿日:2019-07-30T09:45:36+09:00
nuxt generate + S3 + CloudFront + Lambda Edge で静的サイト構築&ハマりポイントと解決法
Repsona LLCの代表兼エンジニア(ひとり)の、ガッシーです。ひとりで、「理想のタスク管理ツール」Repsona(レプソナ)を作っています。
前回の記事(Nuxt + Sails + TypeScript + Fargateでタスク管理ツールを作ったら快適だった話)でRepsona本体側の全体像をざっくりと書きました。今回はウェブサイトのについてです。
Nuxt は静的ウェブサイト制作にも断然おすすめ
https://repsona.com
Repsona では本体のアプリケーションに Nuxt を採用していますが、ウェブサイトにも Nuxt を使っています。小規模なサイトをサクッと作りたい人にとってもおすすめです。
- 公式ドキュメントのままやれば簡単に始められる
- SPC のおかげで HTML を書いている感覚でコンポーネント化できる
- おかげで パーツの使い回しがきく
- おかげで CSS を多少ムチャしても破綻しにくい
- 「JavaScriptでやりたーい」的な見せ方はライブラリでだいたいできる
- jQuery も組み込んじゃえば使える
- webpack とかJavaScript界の色々を考えなくていい(組み込まれてる)
- ビルドは
nuxt generate
だけでいいそして快適に作ったサイトを、S3 + CloudFront でデプロイしたい!と思う方もいるでしょう。今回はその手順と、ハマったところと解決法を共有したいと思います。
前提
- ドメイン取得済み、Route 53 設定済み、ACM設定済み
- Nuxt環境構築済み、サイト作成済み、
nuxt generate
できるアーキテクチャ
nuxt generate
で静的ファイル生成- gulpでS3にデプロイ & CloudFront invalidate
- Lambda Edge でオリジンパスをハンドリング
構築手順
公式ドキュメント通りやればよし!なんですが、公式が「シークレットキーを記載したdeploy.shを作って.gitignoreする」というなんだか微妙なかんじなので、ちょっとアレンジした手元の手順を紹介します。
- S3 バケットを作成する
- CloudFront distribution を作成する
- Route 53 を設定する
- Lambda Edge でオリジンパスのハンドリングを設定する
- セキュリティアクセスを設定する
- ビルドスクリプトを作成する
- CloudFront invalidate のスクリプトを修正する
- デプロイして確認する
S3 バケットを作成する
バケットを作ります。全部デフォルト設定のままでよかったはず。CloudFront経由のアクセスのみ許可するので、
静的ウェブホスティング
も無効
でOKです。
CloudFront distribution を作成する
- Create Distribution > Web - Get Started
- Origin Domain Name > さっき作ったバケットを選択 (プルダウンにでてくる)
- Origin Path > (空白)
- Origin ID > (勝手に入る値)
- Restrict Bucket Access > Yes
- Origin Access Identity > 初めてなら Create a New Identity
- Grant Read Permissions on Bucket > Yes, Update Bucket Policy
- Alternate Domain Names > ドメイン名
Repsonaの場合repsona.com www.repsona.com
- SSL Certificate > Custom SSL Certificate (example.com): (プルダウンにでてくる)
- まだ作ってなければ Learn more about using ACM.
- 他項目は各自の都合に合わせてください(デフォルトでもよかったはず)。
Route 53 を設定する
管理下にあるドメインに「レコードセットの作成」から下記の設定を入れて、CloudFront distribution にまわしてやります。
Repsonaの場合repsona.com. タイプ: A エイリアス先: さっき作った CloudFront distribution (プルダウンにでてくる)Repsonaの場合www.repsona.com. タイプ: A エイリアス先: さっき作った CloudFront distribution (プルダウンにでてくる)アクセスできるか確認する
ここまでで、青で囲んだ部分が通っているはずです。
S3バケット直下に
index.html
を置いて、https://ドメイン名/index.html
でアクセスできるか確認しておきましょう。Lambda Edge でオリジンパスのハンドリングを設定する
CloudFront には S3 でいうところのインデックスドキュメントにあたるものがありません。
Default Root Object
を設定すれば、以下のindex.html
ナシはいけますが◯: https://ドメイン名/index.html ◯: https://ドメイン名/ ◯: https://ドメイン名以下の
index.html
ナシはいけません。◯: https://ドメイン名/foo/index.html ×: https://ドメイン名/foo/ ×: https://ドメイン名/fooそこで、Lambda Edge を、
イベントタイプ: origin-request
に仕掛けます。index-handler/index.js// arranged: https://github.com/CloudUnder/lambda-edge-nice-urls const config = { suffix: '/index.html', appendToDirs: 'index.html', } const regexSuffixless = /\/[^/.]+$/ // e.g. "/some/page" but not "/", "/some/" or "/some.jpg" const regexTrailingSlash = /.+\/$/ // e.g. "/some/" or "/some/page/" but not root "/" exports.handler = function handler (event, context, callback) { const {request} = event.Records[0].cf const {uri} = request const {suffix, appendToDirs} = config if (suffix && uri.match(regexSuffixless)) { request.uri = uri + suffix callback(null, request) return } if (appendToDirs && uri.match(regexTrailingSlash)) { request.uri = uri + appendToDirs callback(null, request) return } callback(null, request) }Lambda Function のARN(
arn:aws:lambda:us-east-1:000000000000:function:index-handller:1
みないなの)をコピっておいて、CloudFront > Behaviors の下の方にある、 Lambda Function Associations の Lambda Function ARN にセットします。$LATEST
は使えません。アクセスできるか確認する
ここまでで、青枠全部いけました。
S3バケットにfoo/index.html
を置いて、スラありスラなしなど含めて、アクセス確認をしてみます。セキュリティアクセスを設定する
デプロイ用のユーザーに「バケットへのファイル配置」と「CloudFrontのキャッシュ削除」の権限を与えます。以下のポリシーを作成し、デプロイを実行するユーザーにアタッチしてください。ユーザーがない場合はここで作成し、アクセスキーとシークレットキーを取得してください。
※ 公式ママだとうまくいかず、すこし変更しています。{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObjectAcl", "s3:GetObject", "s3:AbortMultipartUpload", "s3:ListBucket", "s3:DeleteObject", "s3:PutObjectAcl", "s3:ListMultipartUploadParts" ], "Resource": [ "arn:aws:s3:::さっき作ったS3バケット名/*", "arn:aws:s3:::さっき作ったS3バケット名" ] }, { "Effect": "Allow", "Action": [ "cloudfront:ListInvalidations", "cloudfront:GetInvalidation", "cloudfront:CreateInvalidation" ], "Resource": "*" } ] }ビルドスクリプトを作成する
Gulp をインストールする
npmの場合npm install --save-dev gulp gulp-awspublish gulp-cloudfront-invalidate-aws-publish concurrent-transform npm install -g gulpyarnの場合yarn add -D gulp gulp-awspublish gulp-cloudfront-invalidate-aws-publish concurrent-transform yarn global add gulp
gulpfile.js
を作成する※ 公式ママだとうまくいかず、すこし変更しています。
const gulp = require('gulp') const awspublish = require('gulp-awspublish') const cloudfront = require('./modules/gulp-cloudfront-invalidate-aws-publish') const parallelize = require('concurrent-transform') // https://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html const config = { // 必須 params: {Bucket: process.env.AWS_BUCKET_NAME}, accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, // 任意 deleteOldVersions: false, // PRODUCTION で使用しない distribution: process.env.AWS_CLOUDFRONT, // CloudFront distribution ID region: process.env.AWS_DEFAULT_REGION, headers: {'x-amz-acl': 'private' /*'Cache-Control': 'max-age=315360000, no-transform, public',*/}, // 適切なデフォルト値 - これらのファイル及びディレクトリは gitignore されている distDir: 'dist', indexRootPath: true, cacheFileName: '.awspublish.' + environment, concurrentUploads: 10, wait: true, // CloudFront のキャッシュ削除が完了するまでの時間(約30〜60秒) } gulp.task('deploy', function () { // S3 オプションを使用して新しい publisher を作成する // http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property const publisher = awspublish.create(config, config) // console.log(publisher) let g = gulp.src('./' + config.distDir + '/**') // publisher は、上記で指定した Content-Length、Content-Type、および他のヘッダーを追加する // 指定しない場合、はデフォルトで x-amz-acl が public-read に設定される g = g.pipe(parallelize(publisher.publish(config.headers), config.concurrentUploads)) // CDN のキャッシュを削除する if (config.distribution) { console.log('Configured with CloudFront distribution') g = g.pipe(cloudfront(config)) } else { console.log('No CloudFront distribution configured - skipping CDN invalidation') } // 削除したファイルを同期する if (config.deleteOldVersions) { g = g.pipe(publisher.sync()) } // 連続したアップロードを高速化するためにキャッシュファイルを作成する g = g.pipe(publisher.cache()) // アップロードの更新をコンソールに出力する g = g.pipe(awspublish.reporter()) return g })
package.json
に追記する
yarn run deploy
でproductionデプロイできるようにscriptsを追加する。{ "scripts": { "deploy": "rm -r ./dist; cross-env NODE_ENV=production nuxt generate; cross-env NODE_ENV=production gulp deploy", },環境変数を設定する
.env等AWS_BUCKET_NAME = バケット名 AWS_CLOUDFRONT = 14文字の大文字のID AWS_ACCESS_KEY_ID = アクセスキー AWS_SECRET_ACCESS_KEY = シークレットキー AWS_DEFAULT_REGION = リージョン(us-east-1みたいなの)CloudFront invalidate のスクリプトを修正する
Lambda Edge でオリジンパスのハンドリングをするおかげで、スラありスラなしでアクセス可能ですが、全て別々のリソースとしてCloudFrontにキャッシュされてしまいます。デプロイ時に同時にinvalidateしたいところですが、ここで使っているライブラリ
gulp-cloudfront-invalidate-aws-publish
のindexRootPaths: true
は、スラなしinvalidateをリクエストしてくれないので、本家に手を加えて利用しています(プルリク)。modules/gulp-cloudfront-invalidate-aws-publish/index.js// https://github.com/lpender/gulp-cloudfront-invalidate-aws-publish/blob/master/index.js var PluginError = require('plugin-error') , log = require('fancy-log') , through = require('through2') , aws = require('aws-sdk') module.exports = function (options) { options.wait = !!options.wait options.indexRootPath = !!options.indexRootPath var cloudfront = new aws.CloudFront() if ('credentials' in options) { cloudfront.config.update({ credentials: options.credentials }) } else { cloudfront.config.update({ accessKeyId: options.accessKeyId || process.env.AWS_ACCESS_KEY_ID, secretAccessKey: options.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY, sessionToken: options.sessionToken || process.env.AWS_SESSION_TOKEN }) } var files = [] var complain = function (err, msg, callback) { callback(false) throw new PluginError('gulp-cloudfront-invalidate', msg + ': ' + err) } var check = function (id, callback) { cloudfront.getInvalidation({ DistributionId: options.distribution, Id: id }, function (err, res) { if (err) { return complain(err, 'Could not check on invalidation', callback) } if (res.Invalidation.Status === 'Completed') { return callback() } else { setTimeout(function () { check(id, callback) }, 1000) } }) } var processFile = function (file, encoding, callback) { // https://github.com/pgherveou/gulp-awspublish/blob/master/lib/log-reporter.js // var state if (!file.s3) { return callback(null, file) } if (!file.s3.state) { return callback(null, file) } if (options.states && options.states.indexOf(file.s3.state) === -1) { return callback(null, file) } switch (file.s3.state) { case 'update': case 'create': case 'delete': { let path = file.s3.path if (options.originPath) { const originRegex = new RegExp(options.originPath.replace(/^\//, '') + '/?') path = path.replace(originRegex, '') } files.push(path) if (options.indexRootPath && /index\.html$/.test(path)) { files.push(path.replace(/index\.html$/, '')) files.push(path.replace(/\/index\.html$/, '')) // スラなしも invalidate してほしい } break } case 'cache': case 'skip': break default: log('Unknown state: ' + file.s3.state) break } return callback(null, file) } var invalidate = function (callback) { if (files.length == 0) { return callback() } files = files.map(function (file) { return '/' + file }) cloudfront.createInvalidation({ DistributionId: options.distribution, InvalidationBatch: { CallerReference: Date.now().toString(), Paths: { Quantity: files.length, Items: files } } }, function (err, res) { if (err) { return complain(err, 'Could not invalidate cloudfront', callback) } log('Cloudfront invalidation created: ' + res.Invalidation.Id) if (!options.wait) { return callback() } check(res.Invalidation.Id, callback) }) } return through.obj(processFile, invalidate) }デプロイして確認する
それではいってみます!
yarn run deployおめでとう!デプロイがうまくいけば、指定したドメインで、成果物にアクセスできるようになっているはずです。うまく動作したかどうかは、公式によると、下記らしいです。
NOTE: CloudFront invalidation created:XXXX は CloudFront invalidation を行う npm パッケージからの唯一の出力です。それが表示されない場合は、動作していません。
ハマりポイントと解決法
ポリシーがうまく適用されない
公式通りのポリシーをあてているはずなのにうまく行かず、VisualEditorでいろいろといじりながらなんとかうまくいく設定にたどり着きました。
"cloudfront:UnknownOperation"
がダメだったのかな。現状動いていますが、s3:ListBucket
はほんとはリソースわけなきゃいかんかも。デプロイ実行したらでたエラー
[12:32:01] Using gulpfile ~/xxxxxx/repsona-website/gulpfile.js [12:32:01] Starting 'deploy'... Configured with CloudFront distribution [12:32:02] 'deploy' errored after 1.51 s [12:32:02] AccessDenied: Access Denied at Request.extractError (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/services/s3.js:585:35) at Request.callListeners (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/sequential_executor.js:106:20) at Request.emit (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/sequential_executor.js:78:10) at Request.emit (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/request.js:683:14) at Request.transition (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/request.js:22:10) at AcceptorStateMachine.runTo (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/state_machine.js:14:12) at /Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/state_machine.js:26:10 at Request.<anonymous> (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/request.js:38:9) at Request.<anonymous> (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/request.js:685:12) at Request.callListeners (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/sequential_executor.js:116:18) error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.Access が Denied であること以外なんもわからん!ポリシーも無事適用できて
put object
もできていただけにかなりハマりました。どうやらS3のアクセス権限設定で、いつからか、ブロックパブリックアクセスがすべてブロックがデフォルトになったようで、そうすると、publicなファイルは配置できません。それで、gulpfile.js
にheaders: {'x-amz-acl': 'private'}
の記述を追加して、privateとしてputするようにしました。https://ホスト名/index.html じゃないと AccessDenied
上述の通り、Lambda Edgeで回避しました。
まとめ
- どこでコケてるかわかりにくいので、確認できるポイント毎に確認すべし
- 構築は結構手間だけど、一度通ってしまえばすごく楽
nuxt generate
で静的ウェブサイトにもコンポーネントの概念を・・すごくイイ- 静的サイト生成なので当たり前だけど、ものすごくはやい
という感じで快適に開発しています。ぜひお試しください。
そして、Repsonaもぜひお試しください。ベータ期間中、制限なく無料で使えます。
チームのための理想のタスク管理ツール | Repsona
- 投稿日:2019-07-30T08:05:56+09:00
自分の勉強記録を分析しよう!~新米エンジニアの個人開発~
自分の勉強記録を分析しよう!~新米エンジニアの個人開発~
プログラマー歴1年目のじょーだいです。
プログラマーになって一年、そろそろ新しいことを学んでみたかったので、使ったことのない技術を使用して簡単なwebアプリケーションを開発してみました。
どんなアプリ?
僕は勉強したことをTwitterでつぶやいています。
他にも多くの方々が自分の勉強したことをつぶやいているのをよく見かけます。
せっかく勉強しているならその記録をしっかり残したい。。。
そこで今回は自分の勉強を記録しておくことのできる「Stacker」というアプリを開発しました。
使用している技術は?
- Laravel
- Vue.js
- Chart.js
- MySQL
- Docker
- XSERVER
- Git
各画面の説明
ダッシュボード
この画面の作成が一番時間をとられました。
上部に1週間のグラフ、下部に日毎の入力したデータが表示されます。
また右上のボタンによって、1週間のグラフを総合計のグラフに切り替えることでカテゴリーごとの合計時間を知ることができます。
勉強している日、してない日がすぐわかるので結構気に入ってます。
カテゴリ―毎の詳細
ダッシュボードの左側のカテゴリー一覧からカテゴリ―を選択すると画像のような画面が開きます。
今まで入力したコメントが一覧表示されるので、どんなことを勉強してきたのか一目瞭然です。
カテゴリ―詳細ページは他のユーザーが覗くこともできます。
知らない語句や勉強の順序、気になるエンジニアがどんなことを学んでいるのかを知ることができます。
ホーム
各ユーザーの投稿が一覧表示されています。
とても簡易的なSNSのような感じです。
フォロー機能等は実装していませんが、他のユーザーの勉強量を知ることで、モチベーションを上げることができます。
現在会社の後輩とこのアプリを使用しているのですが、とても会話が盛り上がります。
特に相手が何を勉強しているのかがわかるので、気になったことをすぐに聞けるし、話のきっかけづくりにもなります。
感想
一応完成はしたものの、まだまだ実装したい機能が盛りだくさんです。
例えば
フォロー機能
、円グラフでの分析
、カテゴリ―毎の表示順序、色の切り替え
など、他にも細かいところでまだまだたくさんあります。他にもリファクタリングやテストコードの作成などすることは盛りだくさん。
これからも勉強を記録していくためにこのアプリを使用し、改善を続けていきます。
自分自身が毎日使用していくので、改善、メンテナンスを無理なく続けていくことができると思います。
個人開発でも作りっぱなしではなく、しっかりと運用していくので、よければ皆様も使ってみてください。
一言
コードが汚すぎる。リーダブルコード読んだはずなんだけどな~
- 投稿日:2019-07-30T00:41:42+09:00
Nuxt + TypeScript で 2つ以上の値をコンポーネントに渡して変更を受け取る方法
nuxt-property-decorator
(vue-property-decorator
) を使用しているとき、
親コンポーネントから、子コンポーネントに2つ以上の値を渡し、その変更を受け取る方法。意外と解決するのに手間取ったので、備忘録として。
Nuxt (Vue) + TypeScript
- 親コンポーネント
Parent.vue<template> <div> <p>Value A: {{ valueA }}</p> <p>Value B: {{ valueB }}</p> <Child :value-a.sync="valueA" :value-b.sync="valueB" /> </div> </template> <script lang="ts"> import { Vue, Component } from 'nuxt-property-decorator' import Child from '~/components/Child.vue' @Component({ components: { Child, }, }) export default class Parent extends Vue { valueA: string = '' valueB: string = '' } </script>
- 子コンポーネント
Child.vue<template> <div> <input v-model="computedValueA"> - <input v-model="computedValueB"> </div> </template> <script lang="ts"> import { Vue, Component, Prop } from 'nuxt-property-decorator' @Component export default class Parent extends Vue { @Prop({ required: true }) valueA: string @Prop({ required: true }) valueB: string get computedValueA() { return this.valueA } set computedValueA(value) { this.$emit('update:valueA', value) } get computedValueB() { return this.valueB } set computedValueB(value) { this.$emit('update:valueB', value) } } </script>対応するJavaScriptでの書き方(Child.vue)
export default { props: { valueA: { type: String, required: true, }, valueB: { type: String, required: true, }, }, computed: { computedValueA: { get() { return this.valueA }, set(value) { this.$emit('update:valueA', value) }, }, computedValueB: { get() { return this.valueB }, set(value) { this.$emit('update:valueB', value) }, }, }, }構成
package.json"dependencies": { ... "@nuxt/config": "^2.8.1", "@nuxt/typescript": "^2.8.1", "nuxt": "^2.8.1", "nuxt-property-decorator": "^2.3.0", "ts-node": "^8.2.0" }, "devDependencies": { ... "@babel/runtime-corejs3": "^7.4.5", "@types/node": "^12.0.7", "@types/webpack-env": "^1.13.9", "babel-core": "7.0.0-bridge.0", "babel-preset-vue": "^2.0.2", "webpack-merge": "^4.2.1", }Nuxt関係ないね。Vueだね。
- 投稿日:2019-07-30T00:36:57+09:00
Node.js、Vue.js インストール(Windows)
Node.js インストール ~ 起動(node:v10.16.0, npm:v6.9.0)
- インストーラー取得 https://nodejs.org/en/download/
- インストーラー実行
画面に従い進める、許可などは確認して同意、はいを選択- インストール確認
コマンドプロンプトで以下を実行してバージョンが表示されればOK
node --version
npm --version
- プロジェクトフォルダを作成
コマンドプロンプトで以下を実行して${フォルダパス}に node_modules, package.json, package-lock.json あればOK
cd ${フォルダパス}
npm install express --save
※WARNでるが気にしない
npm init -y
※ -y を指定しない場合、対話形式で進む- 実行用のapp.js, index.html を作成
${フォルダパス}の直下に以下を作成、両方とも utf8
app.jsvar express = require('express'); var app = express(); app.use(express.static('./')); var port = 8080; app.listen(port,function(){ console.log("express server port %d", port) });index.html<!DOCTYPE html> <html> <head> <title>タイトル</title> </head> <body> こんにちは Nodejs </body> </html>
- server 実行
コマンドプロンプトで以下を実行して起動ログでればOK
node app.js
- server アクセス http://localhost:8080/
Vue.js (vue:v3.9.3)
- Node.js のインストーラー実行までやっておく
- Vue CLI をインストール
コマンドプロンプトで以下を実行
npm install -g @vue/cli
※-g を指定することでどこでも使えるようになる
npm install -g @vue/cli-service-global
※-g は上記同様
- インストール確認
コマンドプロンプトで以下を実行してバージョンが表示されればOK
vue --version
- プロジェクトフォルダを作成
コマンドプロンプトで以下を実行してプロジェクトフォルダができればOK
cd ${プロジェクトルートパス}
vue create -d ${プロジェクトフォルダ名}
※-d を指定しない場合、対話形式で設定- プロジェクトフォルダに移動して server 起動
コマンドプロンプトで以下を実行して npm のプロンプトあがればOK
cd ${プロジェクトルートパス}\${プロジェクトフォルダ名}
npm run serve
- server アクセス http://localhost:8080/