- 投稿日:2019-12-18T23:35:32+09:00
Javascript復習①
Javascriptの復習もかねて記事を書いていきます。
今日は関数と配列について。関数の書き方
①関数宣言
function wanko(){ }wankoという関数名をつけて関数宣言しています。
おなじみですね。②関数式
const wanko = function(){ } const wanko = () => { }無名関数というやつです。
関数はデータの方として存在しているのでこのような書き方が可能になります。Functionオブジェクトのオンストラクタを使った書き方もあるのですが、ほぼ間違いなく使わないので省略します。
書き方
const p = document.createElement('p'); p.textContent = 'wanko'; documen.body.appendChild(p); これを関数にすると function toypoo(){ const p = document.createElement('p'); p.textContent = 'wanko'; documen.body.appendChild(p); }コード自体はdocument.createElment(p)でp要素を作成
p要素のtextContentをwankoに
bodyにp要素を付与しています。toypooという関数が出来ましたね、toypoo();で呼び出せます。
ボタン
このように書くとボタンを押したらtoypoo関数が呼び出されます。HTMLファイルが読み込まれたらwankoと表示します。 window.onload = function(){ console.log('wanko'); } window.addEventListner('load', function(){ console.log('wanko'); })引数
Javascriptでは引数を複数指定できます。
function wanko(pome,toypoo){ } 引数にpomeとtoypooを指定することで関数内でpomeとtoypooが使えるようになります。 wanko(1,2); それぞれpomeとtoypooに対応しています。引数にコールバック関数
function wanko(callback){ setTimeout(function(){ console.log('wanko'); callback(); }, 2000) }引数にcallback関数を指定しているので2秒経過後に引数に指定した関数が実行されます。
returnで返り値
function wanko(toypoo, pome){ return toypoo+pome; } if(wanko(7,5) > 3){ console.log('wan'); } else { console.log(wanwan); }returnで返り値を戻すことで普通の値として関数を使えるようになりました。
続く
- 投稿日:2019-12-18T23:22:53+09:00
JavaScriptで配列内の文字列から最も文字数の小さい要素を取得する方法。
はじめに
短く書くにはどうすればいいか調べたのでまとめたよ。
ゴール
index.jsconst string = "Lorem Ipsum is simply dummy text of the printing and typesetting industry."; function findShortWord(s){ return Math.min(...s.split(" ").map (s => s.length)); } console.log(findShortWord(string));概要
Math.min()
- 引数に渡された一番小さい値を返す。
- MDN Web Docs - Math.min()
スプレッド構文(...)
- 配列・関数の引数・オブジェクトを個々に展開できる。
- MDN Web Docs - スプレッド構文
String.prototype.split()
- 文字列を、引数で指定した区切り文字で分割して、配列で返す。
- MDN Web Docs - String.prototype.split()
Array.prototype.map()
- 配列の各要素に関して、コールバック関数を実行して配列で返す。
- MDN Web Docs - Array.prototype.map()
さいごに
- スプレッド構文(...)をちゃんと勉強したけど、こんなに便利だと思わなかった。
- 投稿日:2019-12-18T23:18:57+09:00
Node.jsのバージョンを上げた際のnode-sassのビルドエラー
ほんとにどうってこと無いメモです。
久々に開発しようとしたNuxtJSプロジェクトで、node-sassがコケました。
node-sassはv4.13.0です。$ yarn dev ・ ・ ・ ● Client █████████████████████████ building (61%) 431/466 modules 35 active node_modules/markdown-it/lib/rules_core/state_core.js ✖ Server Compiled with some errors in 9.34s ✖ Client Compiled with some errors in 13.93s ✖ Server Compiled with some errors in 9.34s ERROR Failed to compile with 1 errors friendly-errors 23:04:34 ERROR in ./layouts/blog.vue?vue&type=style&index=0&lang=scss& friendly-errors 23:04:34 Module build failed (from ./node_modules/sass-loader/dist/cjs.js): friendly-errors 23:04:34 Error: Missing binding /Users/n0bisuke/dotstudio/1_protooutstudio/node_modules/node-sass/vendor/darwin-x64-79/binding.node Node Sass could not find a binding for your current environment: OS X 64-bit with Node.js 13.x Found bindings for the following environments: - OS X 64-bit with Node.js 12.x ・ ・ ・みたいなコケかたをしました。
Node Sass could not find a binding for your current environment: OS X 64-bit with Node.js 13.xこの
64-bit
をみると一瞬カタリナにmacOSをアップデートしたから怒られるやつかと思いましたが、Found bindings for the following environments: - OS X 64-bit with Node.js 12.xこちらを見ると既に64bit Node.js v12でnode-sassが紐付けされてたみたいですね。
それにしてもエラー表示がすごい。
これで解決
yarn add node-sassnpmの人も入れ直せばOK。
- 投稿日:2019-12-18T23:12:28+09:00
expoのAppAuthでOAuthを実装してみる
What
expoのAppAuthの登場で、イジェクトなしでOAuth実装が非常に簡単になりました。
マイナーなSNSとの連携が必要な場合に非常に重宝しますね。実装手順は以下
グーグルログインの場合import * as AppAuth from "expo-app-auth"; const config = { issuer: "https://accounts.google.com", clientId: "クライアント識別子" scopes: ["profile"] }; const tokenResponse = async () => { const authState = await AppAuth.authAsync(config); console.log(authState); return authState; };これだけの手順でOauth認証が実装できます。非常に簡単ですね。
※AppAuth.authAsync()
はtoken情報のみを返します。
- 投稿日:2019-12-18T23:12:28+09:00
[ReactNative] expoのAppAuthでOAuthを実装してみる
What
expoのAppAuthの登場で、イジェクトなしでOAuth実装が非常に簡単になりました。
マイナーなSNSとの連携が必要な場合に非常に重宝しますね。実装手順は以下
グーグルログインの場合import * as AppAuth from "expo-app-auth"; const config = { issuer: "https://accounts.google.com", clientId: "クライアント識別子" scopes: ["profile"] }; const tokenResponse = async () => { try { const authState = await AppAuth.authAsync(config); console.log(authState); } catch ({ message }) { alert(`${message}`); } };これだけの手順でOAuthが実装できます。非常に簡単ですね。
※AppAuth.authAsync()
はtoken情報のみを返します。
- 投稿日:2019-12-18T22:26:10+09:00
Vue初級者がPropsとv-Slotをどう使い分けるかを考えた話
はじめに
Vue.jsをある程度やり、propsで子コンポーネントにデータの受け渡しをできるようになった…。
ところでSlotてあるけど、どういうシチュエーションで使っていくのだろうか、ってのを考えていきましょう。
ついでにPropsとどう使い分けていくのかも。おさらい
まずはPropsとSlotは何なのかのおさらいをしていきましょう。
Propsとは
Vue.jsでコンポーネント間のデータの受け渡しをする上で最も基本的な機能ですね。
親側でv-bindや子側のProps名でデータを紐付けて、子側はProps内にプロパティで型とか状態を指定してって使い方。parent.vue<div> <child title="Qiita!"></child> <child :title="Vue"></child> <div> //*省略 data() { return { Vue: "Vue.js!" }; },child.vueprops:{ title:{ type:String, required: true } }上の
title="Qiita!"
は文字列を受け渡し、下の:title="Vue"
はVue
というインスタンス変数などをv-bindで受け渡ししています。Slotとは
コンポーネントの開始タグと終了タグの間に、何かしらの要素があった時に、それを子側で
<slot></slot>
と置換する機能ですね。
子側に<slot></slot>
がないと、親側でタグの間に要素があってもそれは表示されないので、表示させたい場合は子側に<slot></slot>
を指定してあげる必要があります。parent.vue<template> <child> Qiita! </child> </template>child.vue<template> <slot></slot> </template>
<child>
に囲まれたQiita!の文字列が、子の<slot></slot>
と置換され、Qiita!が表示されます。
なお、これは文字列だけではなく他のタグ要素に囲まれた物も置換されます。parent.vue<template> <child> <h1>Vue.js!</h1> <span>YES!Vue!</span> </child> </template>child.vue<template> <slot></slot> </template>
<h1>
タグと<span>
タグも、子の<slot></slot>
と置換されます。
主に親コンポーネントの要素を子要素に差し込みたいってときに使うと思います。使い分けを考える
値を渡すだけならProps
文字列なり数値なり、単にデータを受け渡すのならslotでやるよりもPropsでやるべきです。
状態で中の要素が変わるならslot
v-ifなどで中の要素の構成が動的に変わる事があり、それを子側で挟み込んでやりたい。
という場合にはslotを使ったほうが、子側にpropsを使い状態を渡してやるよりも依存関係が薄くなり、
子側のコンポーネントの再利用性が上がるので、このような場合はslotを使いましょう。parent.vue<template> <child> <template v-if="flag"> <h1>Vue.js!</h1> <span>YES!Vue!</span> </template> <template v-else> <p>Error!!!</p> </template> </child> </template>child.vue<template> <slot></slot> </template>flagの値がtrueの時は上の要素が、falseの時は下の
Error
とかかれた要素が表示されます。
また、置換したい親側の要素をVueコンポーネントにしても、それを子側のslotで置換することができます。AtomicDesignで使ってみよう
AtomicDesignを使ってコンポーネント設計を行っていた場合にでも、AtomやMoleculesなど、コンポーネントの粒度が細かい箇所でよく使う機能になります。
特にAtomレベルのコンポーネントは、それ以上機能として分けれない、親との疎結合性を保つ必要がある、
などの必要があるのと、なによりもAtomコンポーネントの中に他の要素を入れる事はAtomicDesignの考えから離れてしまいます。
なので、AtomicDesignでも要素の子に別の要素を入れたい…!という時には、slotを使って置換させる、などの手法が特に有効と思います。おわり
作成中のプロジェクトでslotを使う機会が出てきたので、今回は記事にしてみました。
propsもslotもどちらも子に何かしらの値や要素を受け渡す便利な機能ですが、
どちらも使い方によってはアンチパターンとなるので、しっかりとコンポーネントの設計を考えていく必要がありますね。
- 投稿日:2019-12-18T21:23:41+09:00
ReactNative AndroidのBrige解説
機能
Native Modules
出来ること
・関数の作成
・イベントの通知
・コールバックNative UI Components
出来ること
・Viewの作成
・Viewのイベントの通知Native Modules
ダイアログを表示するModuleを例にします。
[機能]
1, Androidのダイアログを表示する。(関数の作成)
2, ダイアログの"OK"ボタンがクリックされたらコンソールに"true"が表示される。(イベントの通知)
3, "DATE"ボタンがクリックされたら現在時刻が表示される。(コールバック)DialogModule.javapublic class DialogModule extends ReactContextBaseJavaModule { // コンストラクター public DialogModule(@NonNull ReactApplicationContext reactContext) { super(reactContext); } // このモジュールを呼び出すためのタグのようなもの @Override public String getName() { return "DialogModule"; } // RNで使用出来るクラス定数を定義出来る public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SINGLE_BUTTON", "SINGLE"); constants.put("DOUBLE_BUTTON", "DOUBLE"); return constants; } // ダイアログを表示する関数 @ReactMethod public void showDialog(String message, String type) { if (type.equals("SINGLE")) { new AlertDialog.Builder(getCurrentActivity()) .setTitle("DialogModule") .setMessage(message) .setPositiveButton("CLOSE", (dialog, which) -> { dialog.dismiss(); }) .show(); } else { new AlertDialog.Builder(getCurrentActivity()) .setTitle("DialogModule") .setMessage(message) .setPositiveButton("OK", (dialog, which) -> { sendEvent(); }) .setNegativeButton("CLOSE", (dialog, which) -> { dialog.dismiss(); }) .show(); } } // 現在時刻を表示してコールバックする関数 @ReactMethod private void getCurrentTime(Callback callback) { Calendar calendar = Calendar.getInstance(); callback.invoke(calendar.getTime().toString()); } // ボタンがクリックされたことを通知するイベント private void sendEvent() { WritableMap params = Arguments.createMap(); params.putBoolean("click", true); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("onClick", params); } }解説
getName
RNから呼び出す際の文字列を"DialogModule"のように記述する必要があります。
@Override public String getName() { return "DialogModule"; }
getConstants
RNから使用できるMolueのクラス定数を設定できます。
※実装は必須ではありません。
constants.put(定数名, 値);
public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SINGLE_BUTTON", "SINGLE"); constants.put("DOUBLE_BUTTON", "DOUBLE"); return constants; }
@ReactMethod
RNから使用できるメソッドを設定できます。
コールバックを実行する場合は.invoke()
で実行します。@ReactMethod private void getCurrentTime(Callback callback) { Calendar calendar = Calendar.getInstance(); callback.invoke(calendar.getTime().toString()); }
イベント(リスナー)
RNに登録できるイベントを設定できます。// コールバックの値を設定 WritableMap params = Arguments.createMap(); params.putBoolean("click", true); // イベント名とコールバックの値を設定 getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("onClick", params);Native UI Components
動画playerを表示するViewを例にします。
[機能]
1, 動画playerを表示する。(Viewの作成)
2, urlをRN側からpropsで受け取り動画を再生する。
3, 再生終了時にログを表示する。(Viewのイベントの通知)VideoViewManager.javapublic class VideoViewManager extends SimpleViewManager<VideoView> { private Context context; // このモジュールを呼び出すためのタグのようなもの @Override public String getName() { return "VideoView"; } // 使用するViewのインスタンを返すコンストラクターのようなもの @Override protected VideoView createViewInstance(ThemedReactContext reactContext) { this.context = reactContext; return new VideoView(reactContext); } // Propsで受け取った値で処理をする関数 @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) @ReactProp(name="url") public void setVideoPath(VideoView videoView, String urlPath) { Uri uri = Uri.parse(urlPath); videoView.setMediaController(new MediaController(context)); videoView.setVideoURI(uri); // 再生準備出来次第再生する videoView.setOnPreparedListener(mp -> { videoView.start(); }); // 再生終了時にpropsの『onFinish』にコールバックする。(通知) videoView.setOnCompletionListener(mp -> { ReactContext reactContext = (ReactContext)context; WritableMap event = Arguments.createMap(); event.putString("message", "onDirectEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event); WritableMap event2 = Arguments.createMap(); event2.putString("message", "onBubblingEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2); }); videoView.getDuration(); } @Override public Map<String, Object> getExportedCustomDirectEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent")) .build(); } @Override public Map<String, Object> getExportedCustomBubblingEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames", MapBuilder.of( "bubbled", "onBubble", "captured", "onCapture"))) .build(); } }解説
getName
RNから呼び出す際の文字列を"VideoView"のように記述する必要があります。
@Override public String getName() { return "VideoView"; }
@ReactProp
ViewのPropsを受け取るセッターメソッド
メソッド名自体はなんでもいいです
@ReactProp(name="props名")
public void setProp(Viewの型 view, 型 Propsの値)
@ReactProp(name="url") public void setVideoPath(VideoView videoView, String urlPath) { // ... }
createViewInstance
Viewのインスタンスを返す関数
※必須です@Override protected VideoView createViewInstance(ThemedReactContext reactContext) { return new VideoView(reactContext); }
イベントの通知
Propsのコールバックを設定できます。イベントの登録
イベントを登録するメソッドは2つあります。
・getExportedCustomDirectEventTypeConstants
・getExportedCustomBubblingEventTypeConstants
getExportedCustomDirectEventTypeConstants
1つのイベントで1つのpropsに通知する。
登録方法
MapBuilder.builder().put("イベント名", MapBuilder.of("registrationName", "プロップス名")).build();
getExportedCustomBubblingEventTypeConstants
1つのイベントで2つのpropsに通知することができる。
登録方法
MapBuilder.builder().put("イベント名", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "プロップス名1","captured", "プロップス名2"))).build();
// イベントを登録する関数1 @Override public Map<String, Object> getExportedCustomDirectEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent")) .build(); } // イベントを登録する関数2 @Override public Map<String, Object> getExportedCustomBubblingEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames", MapBuilder.of( "bubbled", "onBubble", "captured", "onCapture"))) .build(); }通知したい箇所で
receiveEvent
を呼び出す
receiveEvent("viewのID", "イベント名", コールバックの値)
// ... ReactContext reactContext = (ReactContext)context; // イベントを通知する処理1 "onDirectEvent"イベント WritableMap event = Arguments.createMap(); event.putString("message", "onDirectEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event); // イベントを通知する処理2 "onBubblingEvent"イベント WritableMap event2 = Arguments.createMap(); event2.putString("message", "onBubblingEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2); // ...RN側では以下のようにイベントが通知される
<VideoView onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} // onDirectEvent onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent />ModuleとUI Componentの登録
作成したModuleとUI Componentを以下のように登録します
ExamplePackage.javapublic class ExamplePackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.<ViewManager>singletonList( // UI Componentが増えるたびに追加していく new VideoViewManager() ); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext return Collections.<ViewManager>singletonList( // Moduleが増えるたびに追加していく new DialogModule(reactContext) ); } }作成したpackageをMainApplication.java内の
getPackages
に登録しますMainApplication.java@Override protected List<ReactPackage> getPackages() { List<ReactPackage> packages = new PackageList(this).getPackages(); // packageが増えるたびに追加していく packages.add(new ExamplePackage()); return packages; }RN側
Native Modules
Dialog.jsximport React from 'react'; import { NativeModules } from 'react-native'; // getName関数の返り値("DialogModule")を指定 DialogModule = NativeModules.DialogModule; const Dialog = () => { const [date, setDate] = React.useState(""); React.useEffect(() => { const eventEmitter = new NativeEventEmitter(DialogModule); eventEmitter.addListener('onClick', (event) => { console.log(event.change) // "true" }); }, []) return ( <> <TouchableOpacity onPress={() => DialogModule.showDialog('SINGLE BUTTON', DialogModule.SINGLE_BUTTON)}> <Text>SINGLE BUTTON Dialog</Text> </TouchableOpacity> <TouchableOpacity onPress={() => DialogModule.showDialog('DOUBLE BUTTON', DialogModule.DOUBLE_BUTTON)}> <Text>DOUBLE BUTTON Dialog</Text> </TouchableOpacity> <TouchableOpacity onPress={() => DialogModule.getCurrentTime(time => setDate(time))}> <Text>DATE</Text> </TouchableOpacity> <Text>CURRENT DATE:</Text> <Text>[ {date} ]</Text> </> ) } export default DialogNative UI Components
VideoView.jsximport React from 'react'; import { requireNativeComponent } from 'react-native'; VideoView = requireNativeComponent('VideoView'); const VideoView = () => { return ( <> <VideoView style={{ width: '100%', height: '100%' }} url="https://www.radiantmediaplayer.com/media/bbb-360p.mp4" onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} /> </> ) } export default VideoViewおまけ
UI Componentに実装してある関数をModuleから呼びたい場合
ExamplePackage.javapublic class ExamplePackage implements ReactPackage { private ExampleViewManager instance; @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.<ViewManager>singletonList( instance ); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext instance = new ExampleViewManager(); return Collections.<ViewManager>singletonList( // モジュールにViewManagerのインスタンスを渡す // あとはモジュール内ので定義した関数からインスタンスのメソッドを呼べば良い new ExampleModule(reactContext, instance) ); } }最後に
Androidのブリッジ部分については英語の記事ばかりかつ動かないサンプルコードが多く苦労しました。
記事書く時間が少なかったので、抜けてるところや間違った箇所があるかもしれないので記事の内容は参考程度にしてください。続きまして、React Native Advent Calendar 20日目の記事は @duka さんの「AB test とかの話」です
- 投稿日:2019-12-18T21:23:41+09:00
ReactNative AndroidのBridge解説
機能
Native Modules
出来ること
・関数の作成
・イベントの通知
・コールバックNative UI Components
出来ること
・Viewの作成
・Viewのイベントの通知Native Modules
ダイアログを表示するModuleを例にします。
[機能]
1, Androidのダイアログを表示する。(関数の作成)
2, ダイアログの"OK"ボタンがクリックされたらコンソールに"true"が表示される。(イベントの通知)
3, "DATE"ボタンがクリックされたら現在時刻が表示される。(コールバック)DialogModule.javapublic class DialogModule extends ReactContextBaseJavaModule { // コンストラクター public DialogModule(@NonNull ReactApplicationContext reactContext) { super(reactContext); } // このモジュールを呼び出すためのタグのようなもの @Override public String getName() { return "DialogModule"; } // RNで使用出来るクラス定数を定義出来る public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SINGLE_BUTTON", "SINGLE"); constants.put("DOUBLE_BUTTON", "DOUBLE"); return constants; } // ダイアログを表示する関数 @ReactMethod public void showDialog(String message, String type) { if (type.equals("SINGLE")) { new AlertDialog.Builder(getCurrentActivity()) .setTitle("DialogModule") .setMessage(message) .setPositiveButton("CLOSE", (dialog, which) -> { dialog.dismiss(); }) .show(); } else { new AlertDialog.Builder(getCurrentActivity()) .setTitle("DialogModule") .setMessage(message) .setPositiveButton("OK", (dialog, which) -> { sendEvent(); }) .setNegativeButton("CLOSE", (dialog, which) -> { dialog.dismiss(); }) .show(); } } // 現在時刻を表示してコールバックする関数 @ReactMethod private void getCurrentTime(Callback callback) { Calendar calendar = Calendar.getInstance(); callback.invoke(calendar.getTime().toString()); } // ボタンがクリックされたことを通知するイベント private void sendEvent() { WritableMap params = Arguments.createMap(); params.putBoolean("click", true); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("onClick", params); } }解説
getName
RNから呼び出す際の文字列を"DialogModule"のように記述する必要があります。
@Override public String getName() { return "DialogModule"; }
getConstants
RNから使用できるMolueのクラス定数を設定できます。
※実装は必須ではありません。
constants.put(定数名, 値);
public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SINGLE_BUTTON", "SINGLE"); constants.put("DOUBLE_BUTTON", "DOUBLE"); return constants; }
@ReactMethod
RNから使用できるメソッドを設定できます。
コールバックを実行する場合は.invoke()
で実行します。@ReactMethod private void getCurrentTime(Callback callback) { Calendar calendar = Calendar.getInstance(); callback.invoke(calendar.getTime().toString()); }
イベント(リスナー)
RNに登録できるイベントを設定できます。// コールバックの値を設定 WritableMap params = Arguments.createMap(); params.putBoolean("click", true); // イベント名とコールバックの値を設定 getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("onClick", params);Native UI Components
動画playerを表示するViewを例にします。
[機能]
1, 動画playerを表示する。(Viewの作成)
2, urlをRN側からpropsで受け取り動画を再生する。
3, 再生終了時にログを表示する。(Viewのイベントの通知)VideoViewManager.javapublic class VideoViewManager extends SimpleViewManager<VideoView> { private Context context; // このモジュールを呼び出すためのタグのようなもの @Override public String getName() { return "VideoView"; } // 使用するViewのインスタンを返すコンストラクターのようなもの @Override protected VideoView createViewInstance(ThemedReactContext reactContext) { this.context = reactContext; return new VideoView(reactContext); } // Propsで受け取った値で処理をする関数 @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) @ReactProp(name="url") public void setVideoPath(VideoView videoView, String urlPath) { Uri uri = Uri.parse(urlPath); videoView.setMediaController(new MediaController(context)); videoView.setVideoURI(uri); // 再生準備出来次第再生する videoView.setOnPreparedListener(mp -> { videoView.start(); }); // 再生終了時にpropsの『onFinish』にコールバックする。(通知) videoView.setOnCompletionListener(mp -> { ReactContext reactContext = (ReactContext)context; WritableMap event = Arguments.createMap(); event.putString("message", "onDirectEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event); WritableMap event2 = Arguments.createMap(); event2.putString("message", "onBubblingEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2); }); videoView.getDuration(); } @Override public Map<String, Object> getExportedCustomDirectEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent")) .build(); } @Override public Map<String, Object> getExportedCustomBubblingEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames", MapBuilder.of( "bubbled", "onBubble", "captured", "onCapture"))) .build(); } }解説
getName
RNから呼び出す際の文字列を"VideoView"のように記述する必要があります。
@Override public String getName() { return "VideoView"; }
@ReactProp
ViewのPropsを受け取るセッターメソッド
メソッド名自体はなんでもいいです
@ReactProp(name="props名")
public void setProp(Viewの型 view, 型 Propsの値)
@ReactProp(name="url") public void setVideoPath(VideoView videoView, String urlPath) { // ... }
createViewInstance
Viewのインスタンスを返す関数
※必須です@Override protected VideoView createViewInstance(ThemedReactContext reactContext) { return new VideoView(reactContext); }
イベントの通知
Propsのコールバックを設定できます。イベントの登録
イベントを登録するメソッドは2つあります。
・getExportedCustomDirectEventTypeConstants
・getExportedCustomBubblingEventTypeConstants
getExportedCustomDirectEventTypeConstants
1つのイベントで1つのpropsに通知する。
登録方法
MapBuilder.builder().put("イベント名", MapBuilder.of("registrationName", "プロップス名")).build();
getExportedCustomBubblingEventTypeConstants
1つのイベントで2つのpropsに通知することができる。
登録方法
MapBuilder.builder().put("イベント名", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "プロップス名1","captured", "プロップス名2"))).build();
// イベントを登録する関数1 @Override public Map<String, Object> getExportedCustomDirectEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent")) .build(); } // イベントを登録する関数2 @Override public Map<String, Object> getExportedCustomBubblingEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames", MapBuilder.of( "bubbled", "onBubble", "captured", "onCapture"))) .build(); }通知したい箇所で
receiveEvent
を呼び出す
receiveEvent("viewのID", "イベント名", コールバックの値)
// ... ReactContext reactContext = (ReactContext)context; // イベントを通知する処理1 "onDirectEvent"イベント WritableMap event = Arguments.createMap(); event.putString("message", "onDirectEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event); // イベントを通知する処理2 "onBubblingEvent"イベント WritableMap event2 = Arguments.createMap(); event2.putString("message", "onBubblingEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2); // ...RN側では以下のようにイベントが通知される
<VideoView onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} // onDirectEvent onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent />ModuleとUI Componentの登録
作成したModuleとUI Componentを以下のように登録します
ExamplePackage.javapublic class ExamplePackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.<ViewManager>singletonList( // UI Componentが増えるたびに追加していく new VideoViewManager() ); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext return Collections.<ViewManager>singletonList( // Moduleが増えるたびに追加していく new DialogModule(reactContext) ); } }作成したpackageをMainApplication.java内の
getPackages
に登録しますMainApplication.java@Override protected List<ReactPackage> getPackages() { List<ReactPackage> packages = new PackageList(this).getPackages(); // packageが増えるたびに追加していく packages.add(new ExamplePackage()); return packages; }RN側
Native Modules
Dialog.jsximport React from 'react'; import { NativeModules } from 'react-native'; // getName関数の返り値("DialogModule")を指定 DialogModule = NativeModules.DialogModule; const Dialog = () => { const [date, setDate] = React.useState(""); React.useEffect(() => { const eventEmitter = new NativeEventEmitter(DialogModule); eventEmitter.addListener('onClick', (event) => { console.log(event.change) // "true" }); }, []) return ( <> <TouchableOpacity onPress={() => DialogModule.showDialog('SINGLE BUTTON', DialogModule.SINGLE_BUTTON)}> <Text>SINGLE BUTTON Dialog</Text> </TouchableOpacity> <TouchableOpacity onPress={() => DialogModule.showDialog('DOUBLE BUTTON', DialogModule.DOUBLE_BUTTON)}> <Text>DOUBLE BUTTON Dialog</Text> </TouchableOpacity> <TouchableOpacity onPress={() => DialogModule.getCurrentTime(time => setDate(time))}> <Text>DATE</Text> </TouchableOpacity> <Text>CURRENT DATE:</Text> <Text>[ {date} ]</Text> </> ) } export default DialogNative UI Components
VideoView.jsximport React from 'react'; import { requireNativeComponent } from 'react-native'; VideoView = requireNativeComponent('VideoView'); const VideoView = () => { return ( <> <VideoView style={{ width: '100%', height: '100%' }} url="https://www.radiantmediaplayer.com/media/bbb-360p.mp4" onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} /> </> ) } export default VideoViewおまけ
UI Componentに実装してある関数をModuleから呼びたい場合
ExamplePackage.javapublic class ExamplePackage implements ReactPackage { private ExampleViewManager instance; @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.<ViewManager>singletonList( instance ); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext instance = new ExampleViewManager(); return Collections.<ViewManager>singletonList( // モジュールにViewManagerのインスタンスを渡す // あとはモジュール内ので定義した関数からインスタンスのメソッドを呼べば良い new ExampleModule(reactContext, instance) ); } }最後に
Androidのブリッジ部分については英語の記事ばかりかつ動かないサンプルコードが多く苦労しました。
記事書く時間が少なかったので、抜けてるところや間違った箇所があるかもしれないので記事の内容は参考程度にしてください。続きまして、React Native Advent Calendar 20日目の記事は @duka さんの「AB test とかの話」です
- 投稿日:2019-12-18T21:19:50+09:00
Amazonの注文履歴ページから商品のASINコードを取得する雑JavaScript
これを参考にした
- Amazonで一年間に使った金額と、注文履歴のTSVを出力するブックマークレット【2015年版】
https://qiita.com/koyopro/items/d8b259f1eb75a01d3a0b- koyopro/amazon-calc.js
https://gist.github.com/koyopro/a480a45712ccf1bf239c下記スクリプトを、注文履歴ページ上で対象期間を絞った上で実行すると、商品名・ASINコード・商品ページURLが記載されたcsvが吐き出される。
スクリプト
// メッセージ用オーバーレイ初期化 var overlayId = 'overlay'; function initMsgOverlay() { var overlay = document.createElement('div'); overlay.id = overlayId; overlay.style.position = 'fixed'; overlay.style.left = 0; overlay.style.top = 0; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.zIndex = 100; overlay.style.backgroundColor = 'rgba(0,0,0,0.7)'; overlay.style.color = '#fff'; overlay.style.textAlign = 'center'; overlay.style.paddingTop = '15em'; document.body.appendChild(overlay); } function displayMsg(msg) { document.getElementById(overlayId).innerHTML = '<h1>' + msg + '</h1>'; } function appendMsg(msg) { document.getElementById(overlayId).innerHTML += '<h1>' + msg + '</h1>'; } function appendMsgSmall(msg) { document.getElementById(overlayId).innerHTML += '<br><h3>' + msg + '</h3>'; } function getTargetPeriod() { try { var period = document.querySelector('#timePeriodForm .a-dropdown-prompt').textContent.trim(); return period } catch(error) { displayMsg('対象期間を取得できません') throw Error } } function generateCsv(itemDetails) { var header = '"商品名","ASIN","商品ページURL"\n'; var body = ''; for (var i=0; i<itemDetails.length; i++) { body += '"' + itemDetails[i]['name'].replace(/"/g, '""') + '",'; body += '"' + itemDetails[i]['asin'].replace(/"/g, '""') + '",'; body += '"' + itemDetails[i]['url'].replace(/"/g, '""') + '"\n' } return header + body; } function sleep(milliSec) { return new Promise(resolve => { setTimeout(resolve, milliSec); }); } async function getItemDetails(orderHistoryHtml, itemDetails) { return new Promise(async (resolve, reject) => { var itemNameSelector = '.a-box-group.a-spacing-base.order .a-fixed-left-grid-col.a-col-right div.a-row > a.a-link-normal'; var itemNameElements = Array.from(orderHistoryHtml.querySelectorAll(itemNameSelector)); var itemNames = itemNameElements.map(elem => elem.textContent.trim()); var itemUrls = itemNameElements.map(elem => elem.href); var asinPattern = /gp\/product\/(.*)\/ref=ppx_yo_dt/; var itemAsins = itemUrls.map(url => url.match(asinPattern)[1]); for (var i=0; i<itemNames.length; i++) { var itemDetail = {}; itemDetail['name'] = itemNames[i]; itemDetail['asin'] = itemAsins[i]; itemDetail['url'] = itemUrls[i]; itemDetails.push(itemDetail); } var nextPageLinkSelector = '.a-last a'; var nextPageLinkElement = orderHistoryHtml.querySelector(nextPageLinkSelector); var nextPageUrl; if (nextPageLinkElement === null) { displayMsg('全ページ取得完了'); console.log('全ページ取得完了'); resolve(); return; } else { nextPageUrl = nextPageLinkElement.href; displayMsg('以下のページの情報を取得'); appendMsg(nextPageUrl); console.log('以下のページの情報を取得'); console.log(nextPageUrl); console.log(new Date()); } var request = new XMLHttpRequest(); request.open('GET', nextPageUrl, false); request.send(null); if (request.status === 200) { var nextPageHtmlText = request.responseText; nextPageHtml = document.createElement('div') nextPageHtml.innerHTML = nextPageHtmlText; } await sleep(1500); await getItemDetails(nextPageHtml, itemDetails); resolve(); }); } async function main(itemDetails) { initMsgOverlay(); var period = getTargetPeriod(); displayMsg(period+'注文のASINコードを取得します'); await sleep(2000); displayMsg('以下のページの情報を取得'); appendMsg(location.href); await getItemDetails(document, itemDetails); var csv = generateCsv(itemDetails); // csvの内容に"#"があるとdata urlにcsvの内容を設定した際に途中で途切れてしまう csv = csv.replace(/#/, ''); var csvWindow = window.open('', 'csv', 'width=640,height=480'); var downloadLink = csvWindow.document.createElement('a'); downloadLink.setAttribute('download', 'asin.csv'); downloadLink.setAttribute('href', 'data:text/html;charset=utf-8,'+csv); downloadLink.innerHTML = 'CSVダウンロード'; csvWindow.document.write('<title>取得結果CSV</title>'); csvWindow.document.write('<p>'+downloadLink.outerHTML+'</p>'); csvWindow.document.write('<code>'); csvWindow.document.write(csv); csvWindow.document.write('</code>'); } var itemDetails = []; main(itemDetails);
- 投稿日:2019-12-18T21:02:14+09:00
JavaScriptのArrayとLuaのtableの比較
はじめに
Qiitaに下書きが多すぎて新しい記事作れませんと言われたので途中まで書いて放っておいた記事をとりあえず投稿して下書きを減らします、すみません。
JavaScript の Array は「0以上の整数のキーを特別扱いするコレクションオブジェクト」と捉えると
Lua の テーブルに結構似ています。(テーブルは1以上ですが)
(実際にはArrayオブジェクトはObjectを継承しているものの、独自にプロパティを
設定することは推奨されていない気がします)
- JavaScriptのArrayとLua(5.3)のテーブルの比較
- Arrayにあってテーブルにないものの中で便利そうなのはLuaでも実装してみる
をやっていこうと思います。
- JavaScriptは下記参照先の表記優先(その他、ブラウザやVScodeのデバッグ)
- Luaは lua.exe (lua-5.3.5_Win64_bin)にて確認
- Luaはやっとメタテーブルをなんとなく理解してきたレベル
- JavaScriptは調べながらじゃないとFizzBuzzも怪しいレベル
Array - Javascript|MDN
Lua 5.3 リファレンスマニュアル作成と(簡易)文字列出力
JavaScriptvar fruits = ["りんご","バナナ"]; console.log(fruits); // ["りんご","バナナ"] <- 実際には先頭に Array(2) とか [Object Array](2) とかが入るLua5.3fruits = {"りんご","バナナ"} print(fruits) --> table: 0000000000d3a390Luaのテーブル型は参照なので識別子を型名とともに出力しています。(環境によっては4byte表記)
これはまあそうなのですが、ちょっと中身を確認したい時がありますので、
文字列変換を実装(とりえあず多次元テーブルはスルー)
後、テーブル用標準ライブラリ(table テーブルに格納されている)をメソッドっぽく
使用できるようにメタテーブルを利用します。Lua5.3table.of=function(...) return setmetatable({...},{ __index=table, __tostring=function(t) return ("{%s}"):format(t:concat(", ")) end }) end fruits=table.of("りんご","バナナ") print(fruits) --> {りんご, バナナ}デフォルトの参照先を設定する、prototype と メタテーブルは似ているけど、
長さ(要素数)を取得する
JavaScriptconsole.log(fruits.length); // 2Lua5.3print(#fruits) --> 2プロパティで持っているJavaScript、長さ演算子
#
があるLua。どちらもめずらしい部類でしょうか?
添え字に抜けがある場合に返す値に関しては異なりますが、とりあえずスルーで。
個人的にarray.lenght=0
とするとArray部分をクリアできるのはうらやましいのでclear
として追加。Luatable.clear=function(t) for n=1,#t do t[n]=nil end end要素を取得する
JavaScriptvar first = fruits[0] // りんご var last = fruits[fruits.length - 1]; // バナナLua5.3first = fruits[1] --> りんご last = fruits[#fruits] --> バナナ自動で割り振ったり、要素数算出のために利用する順番号(添え字)が
javascriptでは0から開始なのに対し、Luaでは1から開始。番号割り振り(numbering)を数え上げ(counting)に寄せた Lua(1オリジン)と、
基準とそこからの相対値(offset)に寄せた javascript(0オリジン) の違い。プログラミング初心者にとっては1オリジンの方が自然に感じますが、
他のプログラミング言語では0オリジンの方が多数派のため混乱の元となっているよう。
(C言語の配列がアドレス算出に都合がよいために 0 オリジンを採用し、
それに倣った言語が多かったという説が有力のようです)ループ処理
JavaScriptfruits.forEach(function(item, index, array) { console.log(item, index); }); // りんご 0 // バナナ 1Lua5.3for index,item in ipairs(fruits) do print(item,index) end --> りんご 1 --> バナナ 2末尾に要素を追加する
JavaScriptvar newLength = fruits.push("みかん"); // ["りんご", "バナナ", "みかん"]Lua5.3fruits:insert("みかん") --> {りんご, バナナ, みかん}pushメソッドは末尾に要素を追加し、追加後の要素数を返す
table.insert 関数は何も返さない
末尾の要素を削除する
JavaScriptvar last = fruits.pop(); // 配列の末尾の要素 "みかん" を削除 // ["りんご", "バナナ"]; console.log(last); // みかんLua5.3last = fruits:remove() --> {りんご, バナナ} print(last) --> みかん先頭の要素を削除する
JavaScriptvar first = fruits.shift(); // 配列の末尾の要素 "みかん" を削除 // ["バナナ"]; console.log(first); // りんごLua5.3first = fruits:remove(1) --> {バナナ} print(first) --> りんご先頭に要素を追加する
JavaScriptvar newLength = fruits.unshift("いちご") // 配列の先頭に追加 // ["いちご", "バナナ"];Lua5.3fruits:insert(1,"いちご") print(fruits) --> {いちご, バナナ}
fruits:insert("いちご",1)
ではないことに注意要素のインデックスを取得する
JavaScriptfruits.push("マンゴー"); // ["いちご", "バナナ", "マンゴー"] var pos = fruits.indexOf("バナナ"); // 1Lua5.3table.indexOf=function(tbl,val) for n,v in ipairs(tbl)do if v==val then return n end end return false end fruits:insert("マンゴー") --> {いちご, バナナ, マンゴー} pos = fruits:indexOf("バナナ") --> 2事前に
{ [val1]={key1,key2,..},[val2]={key}..}
みたいなテーブルを作成しておくのも有り(?)インデックスから複数の要素を削除する
JavaScriptvar vegetables = ['Cabbage', 'Turnip', 'Radish', 'Carrot']; console.log(vegetables); // ["Cabbage", "Turnip", "Radish", "Carrot"] var pos = 1, n = 2; var removedItems = vegetables.splice(pos, n); // 複数の要素を削除するには、n で削除する要素数を定義します // 指定位置(pos)以降から n 個分の要素が削除されます console.log(vegetables); // ["Cabbage", "Carrot"] (元の配列が変化) console.log(removedItems); // ["Turnip", "Radish"]Lua5.3table.splice=function(t,pos,n) local rmvd=table.of() for nums=1,n do rmvd:insert(t:remove(pos)) end return rmvd end vegetables = table.of('Cabbage', 'Turnip', 'Radish', 'Carrot') print(vegetables) --> {Cabbage, Turnip, Radish, Carrot} pos = 2; n = 2 -- posは 2番目(Turnip)を指定 removedItems = vegetables:splice(pos, n) print(vegetables) --> {Cabbage, Carrot} print(removedItems); --> {Turnip, Radish}こんなメソッドがあるんだなあ。
何個取り出すというより、同じ場所から指定回数取り出す、という実装にしたけど遅そうですね。いつか続きを書くかも知れません。
- 投稿日:2019-12-18T20:02:36+09:00
ゲームにおけるFirebase 活用例
PONOS Advent Calendar 2019の24日目の記事です。
?メリークリスマス!!クリスマス・イブにFirebaseの記事をお届けします!!!?
???????????????????????????はじめに
![]()
現在運用中のゲームでFirebaseを導入しました。
この記事では活用事例とノウハウを紹介していきます。
Firebase利用の一例になれば幸いです。組み込み方法は別記事をご参照ください
公式ドキュメント
iOS
Android
Unity
C++
Webこの記事の対象者
![]()
・Firebase初心者、中級者
・Firebaseを検討中の方
・Firebaseの実装イメージを掴みたい方
・サーバーレスを検討中の方
・Googleのサービスが好きな方、興味のある方Firebaseの各機能をざっくり紹介
機能がたくさんあります
名称 機能 活用 Firebase Authentication 認証機能 ◎ Firebase Realtime Database NoSQL クラウド データベースでデータの保管と同期を行うことができる ◯ Cloud Firestore Realtime Database からさらにパワーアップした NoSQL データベース。 △ Cloud Storage for Firebase 写真や動画などのコンテンツを保管、取得することができる。 ◎ Firebase Hosting webサイトを構築 ◎ Cloud Functions for Firebase 関数ごとにサーバー処理を簡単に用意できる ◯ ML Kit for Firebase 機械学習 × Firebase Crashlytics クラッシュ レポート管理 ◯ Firebase Performance Monitoring パフォーマンスの問題を診断 ◯ Firebase Test Lab 様々なデバイスでアプリの自動テストおよびカスタマイズされたテストを実行 ◎ Firebase Cloud Messaging サーバから通知機能 ◎ Firebase In-App Messaging ターゲットを絞り込んでアプリ内で通知 × Firebase Predictions 機械学習を用いた分析ツール ◎ Firebase Remote Config Firebase consoleから、外観の変更、機能の段階的な展開などをカスタマイズ × Google Analytics for Firebase 総合的な分析ツール ◎ Firebase A/B Testing ユーザーごとに出し分けしてA/Bテストを行う ◎ Firebase Dynamic Links 複数のプラットフォームで機能するURLを生成 × Firebase App Indexing アプリを Google 検索結果に表示することができる × 活用1:ログイン時のユーザー認証
Firebase Authenticationをゲーム内での認証機能として使っています。
ログイン時に認証して、Firebase各種機能と連携して認証済みのアカウントしかセキュリティー的に許可しないようにしています。
以下の4種類の認証を使っています。
・メールアドレス
・匿名認証匿名認証で作成したデータと各種連携したデータを紐づける処理はこちらで実装する必要がありますが、
公式ドキュメントにもサンプルコードが用意されているので低コストで実装できます。匿名認証のコード
Auth.cpp#include "firebase/app.h" #include "firebase/auth.h" void Auth::signInAnonymously(){ firebase::App* app = App::GetInstance(); firebase::auth::Auth* auth = Auth::GetAuth(app); auth->SignInAnonymously().OnCompletion([this](const firebase::Future<firebase::auth::User*>& result){ if (result.status() == firebase::kFutureStatusComplete && result.error() == firebase::auth::kAuthErrorNone && result.result()) { //成功処理 }else{ //失敗処理 } }); }活用2:通信時の不正対策
通信時の不正対策として
Firebase Authentication
と
Cloud Functions
を組み合わせてサーバーサイドでも認証チェックしております。Cloud Functionsを使用するとクライアント側の通信処理もセキュアでシンプルな作りになります。
詳細はCloud Functionsのドキュメントを一読するのもありです。クライアント側の通信処理
Cloud Functionsで定義したsampleにリクエストを投げます。Request.cppvoid Request::callSample(){ firebase::Variant data = firebase::Variant::EmptyMap(); firebase::functions::HttpsCallableReference doSomething = functions->GetHttpsCallable("sample"); doSomething.Call(data).OnCompletion([this] (firebase::Future<firebase::functions::HttpsCallableResult> result){ if(result.status() == firebase::kFutureStatusComplete && result.error() == firebase::functions::kErrorNone && result.result()){ //成功処理 }else{ //失敗処理 } }); }サーバー側の認証チェック
exports.sampleの部分がレスポンスを返します。Server.jsconst functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); exports.sample = functions.https.onCall((data, context) => { if(!checkAuth (data, context) ){ throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +'while authenticated.'); } //以降でサーバー処理を記入していく return { text:"success", }; }); //認証チェック async function checkAuth (data, context) { if( !(context.auth && context.auth.uid && context.auth.token)){ console.log('checkAuth error'); return false; } const user = await admin.auth().getUser(context.auth.uid); if (user.customClaims && user.customClaims.admin !== true) { console.log('checkAuth error'); return false; } return true; }活用3:サーバーからのPush通知
Cloud Messagingを活用して特定条件を満たしたユーザーにPush通知を送るようにしています。
このサンプルソースではRealTimeDBにユーザーのpushTokenが保存されており、pushTokenを取り出して
使用しているパターンです。Server.jsexports.pushNotification = functions.https.onCall((data, context) => { if(!checkAuth (data, context) ){ throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' + 'while authenticated.'); } const userId = data.userId; var pass = "user_table/" + userId + "/" + "pushToken"; return admin.database().ref(pass).once('value').then(function(snapshot){ var pushToken = snapshot.val(); const payload = { notification: { title: 'プッシュ通知タイトル', body: `プッシュ通知本文` } }; return admin.messaging().sendToDevice(pushToken, payload); }).catch(error => { console.error(error); throw new functions.https.HttpsError(error); }); });シンプルに書くとこれだけで通知を送れます。
return admin.messaging().sendToDevice(pushToken, payload);活用4:バトルログの保存(アップロード)
バトル時のログや詳細情報はCloud Storageに保存しています。
DBに保存すると量と値段の面で、デメリットがあったので分析の必要がなく、他のユーザーから参照されないデータはCloud StorageにGZip化して保存しています。CloudStorage.cpp#include "firebase/app.h" #include "firebase/storage.h" void CloudStorage::storageUpLoad(){ firebase::App* app = App::GetInstance(); firebase::storage::Storage* storage = Storage::GetInstance(app); //アップロード先のパス std::string serverPath = "/"; //アップロードするファイル保存先 std::string localPath = "file://" + "ローカルパス"; storage->GetReference().Child(serverPath).PutFile(localPath.c_str()).OnCompletion([](const firebase::FutureBase& result) { if (result.error() == firebase::storage::kErrorNone){ //成功処理 } else { //失敗処理 } }); }活用5:リソースダウンロード
これもCloud Storageを活用しています。
リソース数、容量などによって状況は変わると思いますが、問題ない速度でダウンロードできています。
何よりもCloud Storageはコストパフォーマンスがいいです。
現在、ほぼ無料枠で済んでいます。CloudStorage.cpp#include "firebase/app.h" #include "firebase/storage.h" void CloudStorage::storageDownload(){ firebase::App* app = App::GetInstance(); firebase::storage::Storage* storage = Storage::GetInstance(app); //対象リソースのパス std::string serverPath = "/"; //ダウンロード後の保存先 std::string localPath = "file://" + "ローカルパス"; storage->GetReference().Child(serverPath).GetFile(localPath.c_str()).OnCompletion([](const firebase::FutureBase& result) { if (result.error() == firebase::storage::kErrorNone) { //成功処理 } else { //失敗処理 } }); }活用6:ゲーム内お知らせ
お知らせ機能はFirebase Hostingを活用しています。
環境構築などの導入は非常に簡単です。
5分くらいでできると思います。
環境構築環境構築が終わったら、HTML,CSS,画像などのWEB素材を用意して
firebase deployで、デプロイして完了です。
固定のお知らせやヘルプページを見せるだけでしたらFirebase Hostingだけで十分なのですが
ユーザーごとに見せるページを変えたい、期間で出し分けをしたい
などの仕様によってはサーバー側でHTMLを生成する必要があるので
うちのゲームではCloud Function、RealTimeDatabaseなども絡めて使っている箇所があります。注意点としては
過去にデプロイしたWEBページがCDNに溜まっていき、容量が無駄になるので
Firebaseコンソール上からときどき消去しないといけません。
WEBページに問題があったときに、とっさにロールバックできるのでそれとトレードオフという感じですね活用7:KPI 分析
ゲーム運営にKPI 分析は必須ですよね
Google Analytics for Firebaseを使っています。
for Firebaseと書いていますが、バックエンドの仕組みはGoogle AnalyticsでKPI分析するうえでは違いはありません。違いは組み込み方法が若干違うくらいです。こちらのFirebase デモプロジェクトを触ってみるのが理解が早いです。
1.デモプロジェクトログイン
デモプロジェクトが見れるようになったらAnalyticsページへ
2.デモプロジェクト Analyticsページ
※リンクから飛べない場合は、左側のメニューにてアナリティクス配下のDashboardをクリックAnalyticsは組み込むだけでセッション数、DAU、課金額、ARPPUなど
デフォルトでいくつかの値を集計してくれます。
ゲーム内独自の集計をしたい場合は、イベントを実装すればできます。イベント実装例
FirebaseAnalytics.cpp#include "firebase/app.h" #include "firebase/analytics.h" void FirebaseAnalytics::init(){ firebase::App* app = App::GetInstance(); firebase::analytics::Initialize(*app); } //レベルアップ時、イベント登録 void FirebaseAnalytics::levelUp(){ firebase::analytics::LogEvent(firebase::analytics::kEventLevelUp); }活用8:予測データの運営活用
Firebase PredictionsではGoogleの機械学習の予測結果を簡単に知ることができます。
以下項目の一週間後の予測結果を知ることができます。
churn:離脱ユーザーの数not_churn:継続ユーザーの数
spend:課金するユーザーの数
not_spend:課金しないユーザーの数
Firebase Predictionsの優れている点は
これらの予測結果に対して
・A/Bテストを行なって動向を分析できる
・Remote ConfigによってUIやパラメーターなどを瞬時に変えることができる
・Cloud Messagingを使ってお得な情報などを送ることができる
ことにあると思います。また、Google Analyticsと連携してコンバージョンに設定されたイベントを予測項目として追加することもできます。
ゲームごとに有効なイベントを設定すれば有効活用できると思います。活用9:バナー広告、動画広告
Google AdMobを使って、バナー広告、動画広告に対応することができます。
Firebaseの機能としてあまり注目されてないですが、実はFirebase SDKではAdMobの機能もサポートされてます。バナー広告表示
FirebaseAdmob.cpp#include "firebase/app.h" #include "firebase/admob.h" #include "firebase/admob/banner_view.h" void FirebaseAdmob::createBanner(){ //firebase::admob::BannerView* banner_view banner_view = new firebase::admob::BannerView(); firebase::admob::AdSize ad_size; ad_size.ad_size_type = firebase::admob::kAdSizeStandard; ad_size.width = 320; ad_size.height = 50; // my_ad_parent is a reference to an iOS UIView or an Android Activity. // This is the parent UIView or Activity of the banner view. // kBannerAdUnitはAdMobのサイトで発行されるId banner_view->Initialize(getAdParent(), kBannerAdUnit, ad_size); } void FirebaseAdmob::showBanner(){ banner_view->MoveTo(banner_view->kPositionBottomLeft); banner_view->Show(); firebase::admob::AdRequest my_ad_request = {}; banner_view->LoadAd(my_ad_request); }活用10:多端末テスト
スマホゲーム開発において多端末検証って大変ですよね
特にAndroidとかAndroidとかAndroidとか・・・
そこで活躍するのが
Firebase Test Lab
Google データセンターでホストされているデバイス上でアプリをテストします。
1 回のオペレーションで、さまざまなデバイス、
さまざまなデバイス構成で Android アプリや iOS アプリをテストし、
Firebase consoleで結果(ログ、動画、スクリーンショットなど)
を確認できます。・Android端末が足りない場合
・テスターが足りない場合
・QA開始前のシステム的なテストをしたい場合
など使いどころはたくさんあります。1.デモプロジェクトログイン
デモプロジェクトが見れるようになったらTest Labページへ
2.デモプロジェクト Test Labページ
※リンクから飛べない場合は、左側のメニューにて品質、配下のTest Labをクリックおわりに
Firebaseを導入してみての感想としては・・・
非常に簡単、便利で素晴らしいです!
ゲーム仕様や料金面での検討をする必要はありますが
まずは無料プランで使ってみてから他のサービスと比較検討する方法がオススメです!料金面にて実装で創意工夫した箇所もありますが、結果として今は超・低コストで運営できています。
以下の機能は無料で使い続けることができるので、組み込んでおいて損はないです
・Google Analytics for Firebase
・Firebase Authentication
・Firebase Crashlytics
・Firebase Performance Monitoring
・Firebase Cloud Messaging
・Firebase Predictions
・Firebase Dynamic Links
・Firebase App Indexing
・Firebase A/B Testing本プロジェクトではGCPも併用して活用しています。
次回はGCPのゲーム活用事例を書きたい思います。明日は@honeniqさんの記事です
最終日の記事も楽しみですね!
- 投稿日:2019-12-18T19:46:06+09:00
[javascript]2つのarrayで完全一致と部分一致
たまに使いたくなるけど、書き方忘れるので備忘録。
変数const baseArray = [1,2,3,4,5,6,7,8,9,10] const target1 = [10,9,8,7,6,5,4,3,2,1] const target2 = [1,2,3] const target3 = [11]完全一致(every)baseArray.every((num) => target1.includes(num)) > true baseArray.every((num) => target2.includes(num)) > false baseArray.every((num) => target3.includes(num)) > false部分一致(some)baseArray.some((num) => target1.includes(num)) > true baseArray.some((num) => target2.includes(num)) > true baseArray.some((num) => target3.includes(num)) > false参考
- 投稿日:2019-12-18T19:33:02+09:00
PlayCanvasで作成したゲームをPWAにしてAndroid端末へインストールする
PlayCanvasで作成したプログラムをPWAにしてリソースをキャッシュする
はがです。
PlayCanvasアドベントカレンダーの記事となります
今回のデモはこちらになります。
コインドーザー
https://yushimatenjin.github.io/PlayCanvasからダウンロードします
まずPlayCanvasで作成したプロジェクトをダウンロードします。
ダウンロードされたファイルについて
PlayCanvasからダウンロードされたファイルは、
index.html
を始めとする静的なファイルが入っているものになります。PWA対応のウェブサイトを作る
PlayCanvasで作ったゲームをPWAに対応させるために2つのファイルを追加します。
- serviceWorker.js
- manifest.json
manifest.jsonはゲームの基本的な情報を記載します
manifest.json{ "name": "コインドーザー", "short_name": "コインドーザー", "icons": [ { "src": "https://yushimatenjin.github.io/logo192.png", "sizes": "192x192", "type": "image/png" } ], "start_url": "https://yushimatenjin.github.io/", "display": "standalone", "background_color": "#f16625", "theme_color": "#f16625" }https://yushimatenjin.github.io/manifest.json
serviceWorker.jsにはキャッシュについてを記述します。
https://yushimatenjin.github.io/serviceWorker.js
serviceWorker.jsvar CACHE_NAME = 'coin-dozer-version-1'; var urlsToCache = [ キャッシュするファイル.... ]; self.addEventListener("install", function(event) { event.waitUntil( caches.open(CACHE_NAME).then(function(cache) { return cache.addAll(urlsToCache); }) ); }); self.addEventListener("fetch", function(event) { event.respondWith( caches.match(event.request, { ignoreSearch: true }).then(function(response) { if (response) { return response; } return fetch(event.request); }) ); });serviceWorker.jsを一瞬で作る
playcanvas-tools
というnpmのパッケージを作りましたのでこちらを使用すると、1つのコマンドでserviceWorker.jsを作ることができます。ソース
https://github.com/yushimatenjin/playcanvas-clinpm
https://www.npmjs.com/package/playcanvas-tools
npx
cd PlayCanvasのプロジェクトを解凍したフォルダ npx playcanvas-tools sw --name キャッシュ名PlayCanvas特有の問題
PlayCanvasはロードするたびにクエリーストリングを追加します。そのURLについてもキャッシュを適用させるために
caches.match
無視するオプションを追加します。https://developer.mozilla.org/en-US/docs/Web/API/Cache/match
serviceWorker.jscaches.match(event.request, { ignoreSearch: true })serviceWorker.jsself.addEventListener("fetch", function(event) { event.respondWith( caches.match(event.request, { ignoreSearch: true }).then(function(response) { if (response) { return response; } return fetch(event.request); }) ); });このオプションを追加することでキャッシュから呼び出されるようになります。
index.htmlからServiceWorkerを読み込むコードを追記する
index.htmlのHead内にこちらのコードを追加します。
index.html<script> if ("serviceWorker" in navigator) { navigator.serviceWorker .register("./serviceWorker.js") .then(function(registration) { if (typeof registration.update == "function") { registration.update(); } }) .catch(function(error) { console.log("Error Log: " + error); }); } </script>https://github.com/yushimatenjin/yushimatenjin.github.io
キャッシュ無し
キャッシュ有り
キャッシュをしないと28.1MBのダウンロードをしていたファイルをキャッシュすることで、2回目以降の読み込みを24.2KBまでにすることにできます。
インストールが可能になる
さらに下記の要件を満たすことでAndroidの端末へインストールすることができます。
- manifest.jsonに必要な要件を入れる
- short_nameかname
- icons 192pxか512px
- start_url
- display,fullscreen,standalone,minimal-uiのうちどれか
- HTTPSの設定
- fetchイベントハンドラをserviceWokerに追加する
Androidの表示
iOSの表示
Storeにも出せる
- 投稿日:2019-12-18T19:30:20+09:00
jsで配列の特定keyのだけ取り出してカンマ区切りの文字列にする
jsで配列の特定keyのだけ取り出してカンマ区切りの文字列にする
言葉だけだと何言ってるの??ってなるので
表示だけ考えて文字列でカンマ区切りで最後は必要ないってやつ
// これを const array = [ {id: 1, category: 'ねこ', name: 'ポチ'}, {id: 1, category: 'ねこ', name: 'ねこねこ'}, {id: 1, category: 'いぬ', name: 'にゃん'}, ]; // ↓↓↓ こうしたい ↓↓↓ const string = 'ポチ, ねこねこ, にゃん';結論
function(array) { // 配列からnameだけ取り出して「,」区切りの文字列にする return array.map( (row) => {return [row['name']]} ).join(',') },分解
function(array) { // 配列からkeyのnameだけ取り出した配列を作る const namesArray = array.map((row) => { return [row['name']] }); // 配列を「,」区切りの文字列にする const namesString = nameArray.join(','); },Vue.jsで実際に使った時の用途
<template> <div> <span> <!-- 「ねこ, にゃん, ぽち」 のように表示する --!> {{ array | convertArrayToString }} </span> </div> </template> <script> data: { array: array }, filters: { convertArrayToString(array) { // 配列からnameだけ取り出して「,」区切りの文字列にする return array.map( (row) => {return [row['name']]} ).join(',') }, } </script>
- 投稿日:2019-12-18T19:30:20+09:00
jsで配列の特定keyだけ取り出してカンマ区切りの文字列にする
jsで配列の特定keyだけ取り出してカンマ区切りの文字列にする
言葉だけだと何言ってるの??ってなるので
表示だけ考えて文字列でカンマ区切りで最後は必要ないってやつ
// これを const array = [ {id: 1, category: 'ねこ', name: 'ポチ'}, {id: 1, category: 'ねこ', name: 'ねこねこ'}, {id: 1, category: 'いぬ', name: 'にゃん'}, ]; // ↓↓↓ こうしたい ↓↓↓ const string = 'ポチ, ねこねこ, にゃん';結論
function(array) { // 配列からnameだけ取り出して「,」区切りの文字列にする return array.map( (row) => {return [row['name']]} ).join(',') },分解
function(array) { // 配列からkeyのnameだけ取り出した配列を作る const namesArray = array.map((row) => { return [row['name']] }); // 配列を「,」区切りの文字列にする const namesString = nameArray.join(','); },Vue.jsで実際に使った時の用途
<template> <div> <span> <!-- 「ねこ, にゃん, ぽち」 のように表示する --!> {{ array | convertArrayToString }} </span> </div> </template> <script> data: { array: array }, filters: { convertArrayToString(array) { // 配列からnameだけ取り出して「,」区切りの文字列にする return array.map( (row) => {return [row['name']]} ).join(',') }, } </script>
- 投稿日:2019-12-18T19:07:32+09:00
Node.js boilerplate / Authentication from scratch - (express, graphql, mongodb)
A boilerplate for Node.js apps / API server / Authentication from scratch - express, graphql - (graphql compose), mongodb - (mongoose).
https://github.com/watscho/express-graphql-mongodb-boilerplate
- 投稿日:2019-12-18T19:03:22+09:00
asyncを使用したコードをjestでテストすると「regeneratorRuntime is not defined」エラーが発生
確認環境
- Node.js: 10.16.0
- Jest: 24.9.0
- @babel/core: 7.7.5
エラー内容
jestでテスト実行すると、asyncの記述の部分で、以下のようなエラーが発生しました。
ReferenceError: regeneratorRuntime is not defined 9 | } 10 | > 11 | const main = async () => { | ^対応
.babelrcにて、babelのターゲットを指定することで解決しました。
変更前:.babelrc{ "presets": [ "@babel/preset-env" ] }変更後:
.babelrc{ "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ] ] }以上。
- 投稿日:2019-12-18T18:54:19+09:00
JavaScriptで日本語DSLを作る
概要
普段のプログラムを逆ポーランド記法(後置記法)で書きたいとは思いませんが…
プログラミングに明るくない方に簡単なスクリプティングのようなことをしてもらう必要がある場合、日本語の識別子、日本語の語順(≒逆ポーランド記法)のDSLを用意できると便利なこともあるかもしれません。
というわけで何個目の車輪か見当も付きませんが作ってみました。
See the Pen
rpn by shinji (@shinji709)
on CodePen.
逆ポーランド記法(後置記法)について少し
2つの引数をとり加算結果を返す加算演算子
+
を考えたとき、呼び出しの際+ 1 2
などと関数を引数より前に置く記法を前置記法、1 + 2
などと引数と引数の間に置く記法を中置記法、1 2 +
などと引数の後ろに置く記法を後置記法といいます。
後置記法では1と2を足す
と自然と読み下すことができ、日本語と相性がいいです。中置記法と違い計算順序が一意に定まりカッコが要らないなど他の利点もあるようですが、本トピックで逆ポーランド記法を採用するのはこの理由だけです。コード主要部の説明
主要な部分は以下7行だけです。(続けて四則演算の単純な使用例も付記しています)
const createRpn = dictionary => words => words.reduce((acc, word) => { if (word in dictionary) { return acc.concat(dictionary[word](...acc.splice(-dictionary[word].length || Infinity))); } else { return [...acc, word]; } }, []); // 以下使用例 // 辞書の定義 const dictionary = { 足す: (a, b) => Number(a) + Number(b), 引く: (a, b) => Number(a) - Number(b), 掛ける: (a, b) => Number(a) * Number(b), 割る: (a, b) => Number(a) / Number(b), }; // DSL処理関数の生成 const rpn = createRpn(dictionary); // サンプルDSLコード const code = ` 3 4 足す 8 5 引く 割る `; const codeArr = code.trim().split(/\s+/); console.log(codeArr); //=> ["3", "4", "足す", "8", "5", "引く", "割る"] // 中置記法で書くと(3 + 4) / (8 - 5)ということ // 処理 const result = rpn(codeArr); console.log(result); //=> [2.3333333333333335]DSL内の識別子をキー、その関数実装を値として持つオブジェクト、dictionaryを取り、コードを配列として取って結果を返す関数を返します。
処理時は、受け取ったコード(文字列の配列)をreduceにより先頭から順に処理していき処理結果としての新しい配列を作ります。
文字列が辞書に定義されていなければ、文字列をそのまま結果配列に入れていきます。
文字列が辞書に定義されている単語であれば、その実装関数の仮引数の個数(Function.prototype.lengthにより取得)分の要素を結果配列からpopし(popは使ってませんが…)、それを引数として実装関数を呼び出し、戻り値を結果配列に入れます。実装にあたってはプログラミング言語Forthをほんの少し参考にしています。
「ほんの少し」というのは、「ほとんどオレが考えたんだぜ」というのではもちろんなく、せっかく参考にさせていただいたが本家の足元にも及ばないという意味です。念の為。(ちなみにwordとか、dictionaryというのはforthの用語です)UIコード部の説明
主要コード部以外はほぼUIコードです。
コード入力エリアのonchangeで実行しようと思ってましたが、サンプルコードにユーザー入力を入れた関係でいちいち実行ボタンを押す形式に変更しました。
コード入力エリアの右にはdictionaryオブジェクトに定義されている単語を列挙し、ボタンを押すことでカーソル位置に単語を挿入できるようにしています。もちろん直接入力でもいいです。またPCであればボタンホバーで各単語の実装がツールチップで表示されます。(横着してtitle属性を使いました)まとめ・感想
思っていたより簡単にできました。JavaScriptのコードで言う、
foo();bar();baz()
とか、foo(bar(baz()))
などの単純な呼び出しのみの処理だからですね。
制御構造を入れだすととたんに難しくなりそうな気がしますが、追々挑戦していきたいです。
JavaScriptはライブラリが充実していてけっこう何でもあるので今回のコード利用して色々日本語DSL作ってみたいですね。まあ、自分では使わないんですけどね。
(今回のサンプルでは、"D"SLっていうけど一体どんなドメインなんだ?というようなしょうもないdictionaryですみません。思いつかなかったんです…)
以上です。
質問、追記・修正要請あればコメント欄にお願いします。
- 投稿日:2019-12-18T18:46:23+09:00
【Nuxt.js】slot基礎編:コンポーネントに自由にデータを渡そう
前置き
Nuxt.jsと言えばコンポーネント。
componentの内容を、ページによって少しで良いから変えたい…
そんな時、slotに随分と助けられたので記録に残しました。
Vue.jsでも同様の使い方が可能です。
※サンプルテキストはHello Nuxt.js!ではなくHello Vue.js!になっていますが、ご了承ください。間違い等がございましたら、優しくご指摘いただけると嬉しいです!
基礎編:超簡単な解説から実践編まで
応用編:全体のまとめ、propsとの違い
を書いていきます。slotって?メリットは?
ウェブサイトを作ってて、
「同じコードをコピペしまくってめんどいなぁ〜
あ!コンポーネント使えばいいじゃん!!
あれ?でもページによって○○だけ変えたいんだよなぁ」
って時、ありますよね?コンポーネントを使いまわしたいけど、
ここの文字だけ変えたい!って時に使います。
(文字以外も変更は利きますが、今回はわかりやすく文字で例えます)コンポーネントが使えないから、
基本のコードコピペして中身だけ編集する…
なんて面倒なことをしなくていいんです!
コンポーネントにしちゃって、一部だけ変えればいいんです!
楽ちん!!slotを使わない場合どうなるの?
じゃあコンポーネントを使って…親コンポーネントで中身を変えよう!
slotなくてもの中身に書けばよくない?
と思ったそこのアナタ!
こちらをご覧ください。Component.vue // 子コンポーネント <div class="ItemTitle"> <h1>ここは親ページによってタイトルを変えたい</h1> </div>index.vue // 親ページ <Component> Hello Vue.js! </Component>表示結果 <div class="ItemTitle"> </div>そうなんです。。。
子のコンポーネントの中身は無視されちゃいます。
それも完全なシカトです。ツライ。。。<Component>ここは無視される</Component>slotの使い方(超簡単ver.)
そこでslot君の登場です!!
使い方は超〜〜〜簡単。
子コンポーネント内に変えたい部分をslotにするだけ。
上の説明に使ったコードでやってみます。<div class="itemTitle"> <h1>Hello Vue.js!</h1> </div>【コード】
子コンポーネントだけslotを加えます。
Component.vue // 子コンポーネント <div class="itemTitle"> <h1> <slot /> // ここは親ページによってタイトルを変えたい </h1> </div> <style scoped> .itemTitle { margin: 10px; } h1 { color: rgb(65, 193, 222); } </style>親は一緒。
index.vue // 親ページ <Component> Hello Vue.js! </Component>これだけで表示結果が変わる!!
slotくんに呼びかけて、ちゃんとデータを表示してくれます。
偉いぞslotくん!!!slotの使い方(複数ver.)
便利なslotくん
「じゃあいっぱいslot使おう!
h1とh2にも使おう〜」
って場合は
各slotに名前をつけてあげる必要があります。「これ表示して?」って言われても、
名前で呼んでくれないと、
どのslotくんが表示すればいいのか分からなくなっちゃいますよね。そういう先輩いません?
「これやっといて〜」って、
今誰に言ったの?私やるの???え、誰???
みたいな。なので、
もし名前をつけないでslotを2つ用意すると
親に入れたものが2回表示されます。
「はいはい、私やりますね」って2人反応しちゃいます。名前で呼ばなかった先輩はきっと
同じ仕事を無駄に2個やらせてしまったことを後悔するでしょう。
こんなにいらんわ。。。って。<div class="ItemTitle"> <h1> Hellot Vue.js! about slot </h1> <h2> Hellot Vue.js! about slot </h2> </div>【コード】
Component.vue // 子コンポーネント <div class="ItemTitle"> <h1> <slot /> // ここは親ページによってタイトルを変えたい </h1> <h2> <slot /> // ここは親ページによってサブタイトルを変えたい </h2> </div> <style scoped> .itemTitle { margin: 10px; } h1 { color: rgb(65, 193, 222); } h2 { color: gray; } </style>index.vue // 親ページ <Component> Hellot Vue.js! about slot </Component> 表示結果 <div class="ItemTitle"> <h1> Hellot Vue.js! about slot </h1> <h2> Hellot Vue.js! about slot </h2> </div>slotの使い方(実践ver.)
ということで各slotくんに名前をつけてあげましょう。
「◯◯くん、これ表示しといてよ」って言ってくれたら、
はい、私ですね!表示しますね!ってなりますよね。ここで1つ注意。
名前をつける場合は、
親でタグで囲う必要があります。
これさえできればもう完璧。<div class="itemTitle"> <h1 class="title">Hello Vue.js!</h1> <p>slotを学ぼう</p> <div class="catchCopy"># slotって?メリットは?</div> </div>【コード】
ItemTitle.vue // 子コンポーネント <div class="itemTitle"> <h1> <slot name="title" /> // 置き換えたい部分のslot nameを決める </h1> <p>slotを学ぼう</p> <h2> <slot name="catchCopy" /> // 置き換えたい部分のslot nameを決める </h2> </div> <style scoped> .itemTitle { margin: 10px; } h1 { color: rgb(65, 193, 222); } p { color: gray; h2 { color: rgb(76, 212, 227); } </style>index.vue // 親ページ <ItemTitle> <template v-slot:title> Hello Vue.js! // slot name="title"を置き換える </template> <template v-slot:catchCopy> # slotって?メリットは? // slot name="catchCopy"を置き換える </template> </ItemTitle>おめでとうございます!
これでVue.js、Nuxt.jsのslot、今日から使えますね!わーい!
次は更なる応用も記載していきます★
- 投稿日:2019-12-18T18:26:25+09:00
jsで配列の特定keyを指定してgroupByをしたい(lodashすげ〜)
jsで配列の特定keyを指定してgroupByをしたい(lodashすげ〜)
結論
lodashすげ〜!!
const array = [ {id: 1, category: 'ねこ', name: 'ポチ'}, {id: 1, category: 'ねこ', name: 'ねこねこ'}, {id: 1, category: 'いぬ', name: 'にゃん'}, ]; const key = 'category'; const res = _.groupBy(array, key);まとめ
lodashはすごいからとりあえずドキュメント読んでおこう!!
Lodash → https://lodash.com/docs/4.17.15
LodashのgroupBy → https://lodash.com/docs/4.17.15#groupBy
- 投稿日:2019-12-18T17:52:01+09:00
ラーメンで理解するasync/await
JavaScript 2 Advent Calendar 2019 の19日目の記事です。
対象
- async/awaitがなんだかはある程度知ってる人
- async/awaitをなんとなくで使ってる人
そもそもasync/awaitって?
async/awaitは、Promiseによる非同期処理をより簡潔に効率よく記述できる手法。
普通にPromiseを使うとネストが深くて辛くなるのを救ってくれる。
「async/await Promise」で検索すれば比較についてはたくさん出るので今回は書かない。便利だから全部async/awaitにしちゃおう!
って思うんですけど、実は罠があって。
ちゃんと気をつけないと非効率な感じになっちゃうよっていうのが今回のお話。
ただ、コードを並べて説明してもよくわからない気がしたので、みんな大好きラーメンで例えてみようと思います。ラーメンを作ろう!
突然ですが、あなたはラーメン屋店主です。
あなたのラーメン屋には腕のいい三人の店員がいます。それぞれ、
- 麺担当
- スープ担当
- 具担当
とします。
(なんかどっかで見た)ラーメンを作るためには、
- 麺担当に麺をゆでてもらう
- スープ担当にスープを作ってもらう
- 具担当にチャーシューを切ってもらう
- 1~3までが出来たらそれらを組み合わせてあなたがラーメンを完成させる
という工程が必要になります。
APIを準備しよう!
こんなAPIが用意されているとしましょう。
エンドポイント メソッド 概要 /noodle GET 麺を作成し取得する /soup GET スープを作成し取得する /pork GET 具を作成し取得する /ramen POST ラーメンを作る 実装してみよう!
なんのこっちゃない、かんたんかんたん
async () => { // 麺担当に麺をゆでてもらう const noodles = await axios.get('/noodle'); // スープ担当にスープを作ってもらう const soup = await axios.get('/soup'); // 具担当にチャーシューを切ってもらう const pork = await axios.get('/pork'); // ラーメンを完成させる。 const ramen = await axios.post('/ramen', { noodles, soup, pork }); // 提供 return ramen; }ちょちょいのちょい!
すっきりしてて良さそうですが、これはNGです。awaitの罠
awaitは、指定した関数のPromiseの結果が返されるまで、async function内の処理を一時停止します。
つまりどういうことかというと、await axios.get('/noodle');の結果が帰ってくるまで処理を待ってしまいます。
その間/soup
/pork
へのリクエストはされません。つまり、
麺を茹で上がるのを待って、スープを作り始め
スープが出来上がるのを待って、チャーシューを切り始める
などといったものすごく非効率なことになってしまうのです!そんなことしてたら出来上がる頃には麺がのびのびになってしまう...
一人ずつ作業が終わるのを待つのではなく、三人店員がいるんだから同時に指示をしたいですよね?
どうすればいいの?
awaitをするタイミングをずらします。
async () => { // 麺担当に麺をゆでてもらう const noodlesPromise = axios.get('/noodle'); // スープ担当にスープを作ってもらう const soupPromise = axios.get('/soup'); // 具担当にチャーシューを切ってもらう const porkPromise = axios.get('/pork'); // 全員が出来上がるのを待つ const noodles = await noodlesPromise; const soup = await soupPromise; const pork = await porkPromise; // ラーメンを完成させる。 const ramen = await axios.post('/ramen', { noodles, soup, pork }); // 提供 return ramen; }awaitはPromiseを返す処理とセットにしがちですが、別にバラバラに書いても良いのです。
これを行うことで、
麺担当・スープ担当・具担当に同時に調理指示をする。
その後全員が調理終了し、完成したらそれらを使ってラーメンを完成させる。
ということが実現できるわけです。完成!やったね!
まとめ
ラーメンでふわふわっとした感じになっちゃいましたが、伝えたかったことは
待つ必要がある場合は待つ、待つ必要がない場合は待たない
ということをちゃんと意識して書くと良さそうっていうあれです。最後に
書いててラーメン食べたくなってしまったのでラーメン食べに行ってきます。
- 投稿日:2019-12-18T17:51:06+09:00
Nuxt.jsとmysqlを連携してデータを表示してみた
はじめに
前から気になっていたNuxt.jsを触ってみました。これ使って何かやってみようと考えた結果、
mysqlと連携させてみることにしました。Nuxt.jsに関して入門的な情報は既にいろいろと存在するので、
ここでは、そのつなぎの部分にフォーカスして記載したいと思います。内容
下記の項目に沿って記載します。
- Nuxt.jsについて
- Nuxt.jsのインストール
- データベース準備
- 必要なモジュール
- APIの実装
- フロントエンドの実装
- 結果
- おわりに
Nuxt.jsについて
ご存知の方には恐縮ですが、Nuxt.jsはナクストと読むようであり、
Webアプリケーションを開発するのに必要なものが既にある程度整った状態で
スタートできるVue.jsベースのJavaScriptフレームワークです。
UIではVue.jsを使いますが、UI以外の機能についてもいろいろ組み込まれているようです。
フレームワークなので効率的にアプリケーションの開発ができるようですね。Nuxt.jsのインストール
早速、自分のPCにインストールしてみます。
事前にnpm と Node.jsがインストールされていることが前提です。npx create-nuxt-app プロジェクト名この形式でインストールするようなので、
npx create-nuxt-app prjプロジェクト名をprjとして実行してみました。
いくつか聞かれるので、
- server framework を Express
- Nuxt.js module をAxios
- rendering mode をUniversal (SSR)
として設定しました。
何度も繰り返し試した結果、上記の設定に落ち着きました。
Nuxt v2.10.2 がインストールされたようです。次に、アプリを起動する必要があるので、
npm run devnpm run スクリプト名ここで、上記の形式で入力すると、package.json(下記)で記述されているscripts内のスクリプト名
(この場合は"dev")で定義されたコマンドが実行されます。{ "name": "prj", "version": "1.0.0", "description": "My swell Nuxt.js project", "author": "**** ****", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server", "build": "nuxt build", "start": "cross-env NODE_ENV=production node server/index.js", "generate": "nuxt generate" }, "dependencies": { "@nuxtjs/axios": "^5.3.6", "cross-env": "^5.2.0", "express": "^4.16.4", "fs": "0.0.1-security", "mysql": "^2.17.1", "net": "^1.0.2", "nuxt": "^2.0.0", "tls": "0.0.1" }, "devDependencies": { "nodemon": "^1.18.9" } }下記URLをブラウザで表示すると、
http://localhost:3000こんな感じに初期表示されました。
Nuxt.jsのデフォルトポートは3000のようです。
インストールされたあとのディレクトリ構造は下記のようになってました。prj----assets |--components |--layouts |--middleware |--node_modules |--pages |--plugins |--server |--static |--store |nuxt.config.js |package.json調べたところ、最初に表示されたページは、
prj-- |--pages |index.vuepagesディレクトリ内にあるindex.vueに記載された内容でした。
<template> <div class="container"> <div> <logo /> <h1 class="title"> prj </h1> <h2 class="subtitle"> My laudable Nuxt.js project </h2> <div class="links"> <a href="https://nuxtjs.org/" target="_blank" class="button--green" > Documentation </a> <a href="https://github.com/nuxt/nuxt.js" target="_blank" class="button--grey" > GitHub </a> </div> </div> </div> </template> <script> import Logo from '~/components/Logo.vue' export default { components: { Logo } } </script> <style> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; } .title { font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; display: block; font-weight: 300; font-size: 100px; color: #35495e; letter-spacing: 1px; } .subtitle { font-weight: 300; font-size: 42px; color: #526488; word-spacing: 5px; padding-bottom: 15px; } .links { padding-top: 15px; } </style>ごちゃごちゃ書かれているので、余計な記述をバッサリ削除してコードを見やすくします。
<template> <div class="container"> あいうえお </div> </template> <script> </script> <style> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; } </style>表示は、下記のようになりました。
ここで気づいたのが、このindex.vueファイルを更新した場合、
ブラウザで再描画しなくても(F5押さなくても)自動でブラウザに反映されることです。
index.vueファイルを更新するたびに、自動でコンパイルが走るようで、
それが終わり次第ブラウザの表示が変更されるようです。データベース準備
mysqlと連携させて何かしたいな?と考えたところ、
一番簡単なお試し内容として、DBに登録されているデータを取り出して、
それをブラウザに表示させてみるのがよさそうかなと思い、それを実装
してみることにしました。なので、まずデータベースの準備をします。ローカル環境のmysqlにテスト用のデータベースを作成し
その中に都道府県情報のテーブルを作成しました。
データベース名:testdb
テーブル名:prefectures
この情報を取り出してブラウザに表示させてみようと思います。必要なモジュール
mysqlを使うには、どうやらmysql用のモジュールをインストールする必要が
あることがわかりましたので下記を実行します。npm install --save mysqlAPIの実装
index.vueの中のscritpt要素の中に、データベースアクセスの為の記述をしても
うまくいかずエラーを解消することができなかったので、
別途APIを作成し、そこにアクセスすることでDBの内容をとってくる、
という方法にしました。prj-- |--server |api.jsserverディレクトの直下にapi.jsというファイルを作成し、
dbに接続してデータを取ってくるコードを記述します。const express = require('express') const router = express.Router() router.get('/prefectures', (req, res, next) => { const mysql = require('mysql'); const connection = mysql.createConnection({ host : 'localhost', user : 'testuser', database: 'testdb', password: 'testuser' }); var ret=[]; connection.connect(); connection.query('SELECT * from prefectures;', function(error, row, fields){ if (error) { console.log(error); } var dat = []; for (var i = 0;i < row.length; i++) { dat.push({id: row[i].id, name: row[i].name}); } ret = JSON.stringify(dat); res.header('Content-Type', 'application/json; charset=utf-8') res.send(ret) }); connection.end(); }) module.exports = router実はこれだけではだめなようで、同じディレクトリに存在するindex.jsというファイルを
一部編集しないといけないようです(★印の箇所)const express = require('express') const consola = require('consola') const { Nuxt, Builder } = require('nuxt') const app = express() // Import and Set Nuxt.js options const config = require('../nuxt.config.js') config.dev = process.env.NODE_ENV !== 'production' //↓↓↓★この行の追加 const apiRouter = require('./api') async function start () { //↓↓↓★この行の追加 app.use('/api', apiRouter) // Init Nuxt.js const nuxt = new Nuxt(config) const { host, port } = nuxt.options.server // Build only in dev mode if (config.dev) { const builder = new Builder(nuxt) await builder.build() } else { await nuxt.ready() } // Give nuxt middleware to express app.use(nuxt.render) // Listen the server app.listen(port, host) consola.ready({ message: `Server listening on http://${host}:${port}`, badge: true }) } start()下記でアクセスするとDBからのデータをJSON形式でとってこれるようになりました。
http://localhost:3000/api/prefectures参考サイト
https://dev.classmethod.jp/etc/node-js-module-mysq/
(他にもいくつか参考サイトがあったのですが忘れてしまった。。)フロントエンドの実装
axiosというライブラリを使ってhttp通信を行います。
Node.jsで動作するhttpクライアントであり、通信するためのAPIが提供されているので
これを利用します(Ajaxのようなものですね。多分)上記でばっさり削除したindex.vueの中を下記のように変更します。
先ほどのAPIにアクセスして取ってきたデータをitemsの中に格納すると、
Vue.jsによってフロントエンドに反映されます。<template> <div class="container"> <table border="1"> <tbody> <tr> <th>ID</th> <th>都道府県名</th> </tr> <tr v-for="item in items" :key="item.id"> <td>{{ item.id }}</td> <td>{{ item.name }}</td> </tr> </tbody> </table> </div> </template> <script> export default { data() { return { items: [] } }, mounted: function() { this.$axios .$get('/api/prefectures') .then(response => { this.items = response }) .catch(error => { console.log(error) }) } } </script> <style> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; } </style>結果
表示は、下記のようになりました。
おわりに
今回は、mysqlとの連携に注目しましたが、Nuxt.jsに関する知識が浅いこともあり、
もっといろんなこともできると思われますので、今後も引き続きいろいろ試してみたいと思います。
私はPHPを使っての作業が多いので、JavaScriptだけで当たり前のようにWebアプリケーションを
作れるようなったことに関心を寄せています。
試した内容は大したものではないですが、それでも途中いくつもの壁にぶち当たりました。
が、なんとか乗り越えました。
今後はよりスムーズに実装できるよう一層進化していくことと思います。
- 投稿日:2019-12-18T17:20:44+09:00
kintone Spreadsheetプラグインを作り直している話
Spreadsheetプラグインについて
kintoneの一覧画面をExcelのようなスプレッドシートの見た目で閲覧、編集をするためのプラグインを作っています。
https://github.com/mura-/kintone-spreadsheet正確には、もともと下記リポジトリで作っていたのを書き直している状況です。
https://github.com/mura-/kintone-spreadsheet-no-longer-maintained
なぜ書き換えたか、変えたこと
最初書き始めたのが4年前で古く、コードの品質もよくない、タスクランナーなどの周辺ツールも古いという点と、そのためにここからグレードアップしようにもやる気がしないという点が大きいです。
今回の新開発からは、下記のようにHandsontable使っている以外は、色々変えて作り直しています。
今回 前回 言語 TypeScript JavaScript ライブラリ React, kintone UI Component Vue グリッド表示ライブラリ Handsontable Handsontable バンドラ Webpack + plugin-uploader Gulp + browserify 今回vueからreactに変えたのは、kintone UI Componentとも親和性が高いのもありますし、Vueよりは記述量が増えてしまいますがプリミティブに書きやすいという僕の好みによります。
kintone plugin-uploaderも今回利用しており、Build後コマンドラインでアップロードできるようにもしています。Watchモードにおいては、再Build後自動でアップロードするよう、WebpackのPlugin設定を変更して開発・デバッグしやすくしています。現在の進捗
現時点では設定画面から任意のフィールドを表示する、というところまではできています。
no longer maintainedの方は編集までできますが、まだ追いつけていません。今後どうするか
ひとまずno longer maintainedで実装できていた, データ編集までは実装を進めます。
その後はまだ検討中ですが、少しずつグレードアップしていく予定です。ちなみに、krewSheet など、すでに展開されているサービスと違うのは、機能はだいぶ少ないですが、無料、あるいは低価格で使えることを目指しています。(半分趣味)
もしくはIssueに要望をいれていただければそこから対応するかもしれません!要望あればぜひご意見ください。
- 投稿日:2019-12-18T17:05:17+09:00
intra-martにコード整形ツールを実装してみた
JSONとSQL整形問題
業務中結構使われているJSONとSQL文が
毎回毎回変換を掛けてから、ようやく中身を確認することができる。
それが面倒だ!!intra-mart環境でも使えるツール
FormaDesignerが簡単に画面が作れるし、
それに、JSもすぐ試せるので、作ってみた。
JSONの整形コードは以下の通り
js
var jsonBefore = JSON.parse(text);
var json = JSON.stringify(jsonBefore, null, " ")
二行で今までの悩みを紹介した。SQLの整形はこのリンクを参照した。
- 投稿日:2019-12-18T16:20:41+09:00
認可Cookie持ってなかったらリダイレクトさせる
認可Cookie持ってなかったらリダイレクトさせる
目的
index.htmlに,「18歳以上ですか」というお決まりの案内を出しているが,URLベタが記されると目的のエロページに辿り着いてしまう.
そこで18歳ボタンを押した人だけが,エロページに行けるようにしたかった.
(という建前で,本音は会社での成果発表で,意図せずエロページ出さないようにするため)やってみる
1.index.htmlの「18歳以上ですか」に対する「はい」ボタンクリック時に,認可Cookie(ていうほどのもんじゃないけど)をセット.
index.html<!DOCTYPE html> <html> <head> </head> <body> <!-- onClickに認可Cookie払い出し関数を紐付ける --> <a href="eropage.html"><button type="button" onClick="yesBtn();">はい</button></a> <script src="index.js"></script> </body> </html>index.jsfunction yesBtn() { //認可Cookieのセット document.cookie = "auth=over18; max-age=3600";//有効期限は3600秒=1時間 }2.認可Cookieを持ってエロページに遷移.ここで認可Cookieがなければ,index.htmlにリダイレクト
eropage.js// 認可Cookieを持っていなければ,リダイレクト var cookies = document.cookie; if (!cookies) { location.href="index.html"; }このドメインでは,Cookieはこの1つのみ扱うため,cookieを持っているか否かで判定を行なっている.
持っていないければ,index.htmlにリダイレクトさせる.終わりに
これで,直打ちされても表示は回避でき,意図しないエロサイト表示は避けられた.
- 投稿日:2019-12-18T16:08:42+09:00
漢文風プログラミング言語「文言」の基本文法を読み解いてみた
導入
以下のツイートを見かけまして。
「漢文風プログラミング言語」とかいう古代文明SFモノみたいな激ヤバなブツが流れてきてて滅茶苦茶興奮してしまったhttps://t.co/8yV8uRWFlX pic.twitter.com/UwrT6MdGHI
— 避雷 (@lucknknock) 2019年12月17日
すごい。そしてGitHubで公開されているみたいなので早速触ってみました。概要
- 言語名:文言
- Wenyan-lang
- 拡張子:
.wy
- 実行方法:JavaScriptかPythonに変換
- オンラインIDE
- リポジトリの
./build/wenyan.js
- ライセンス:MIT License
- その他:
src/render.js
でソースコードを原稿用紙にレンダリングできる基礎文法
READMEに掲載されている例に基づき、取り急ぎ変数・関数・条件分岐・繰り返しのみ読解し、意味を読み解いてみました。これである程度のことは書けるはず。
原文READMEの文言例とJavaScript例は変数名や文字列を適宜翻訳して対応付けていますが、今回はわかりやすいように両者ともアルファベットにしました。アルファベットでも動きます。文法を覚えたらぜひ漢字で変数名や関数名などを付けてみてください。変数宣言
例
文言 JavaScript 吾有一數。曰三。名之曰「a」。 var a = 3; 吾有一言。曰「「text_text_text」」。名之曰「str」。 var str = "text_text_text"; 吾有一爻。曰陰。名之曰「flag」。 var flag = false; 吾有一列。名之曰「array」。 var array = []; 有數五十。名之曰「b」。 var b = 50; 対応表
文言 意味 漢数字(一二三……) 英数字(123...) 零 0 數 数値型 文 文字列型 爻 真偽値型 列 配列 「」で囲まれた部分 変数名 「「」」で囲まれた部分 文字列 。 半角スペース 解説
吾有一數。曰num。名之曰「var_name」。
有數num。名之曰「var_name」。
で変数
var_name
に数字num
を代入して変数宣言できます。変数名は「」
で囲む。初期値なしの変数宣言はできない。
前者の書き方の場合は、冒頭の吾有一數
の一
で宣言する変数の数を表しているため、ここの数字の分だけ後ろに曰num。名之曰「var_name」。
を書き連ねる必要があります。
面倒なので有【型】~名之曰「【変数名】」
だけ使えば良いと思う。補足
実は
數
、文
、爻
、列
は全部var
になるので間違っても動くには動きます。変数代入
例
文言 JavaScript 昔之「a」者。今「b」是也。 a = b; 対応表
文言 意味 昔之~者。今~是也。 =(代入) 条件分岐
例
文言 JavaScript 若三大於二者。乃得「「true_text」」也。 if (3>2){ return "true_text"; } 若三不大於五者。乃得「「true_text」」。若非。乃得「「false_text」」也。 if(3<=5){return "true_text"}else{return "false_text"} 対応表(条件分岐)
文言 意味 若 if 乃得 then 若非 else 也 end(条件分岐用) 対応表(真偽値)
文言 意味 a等於b a == b a不等於b a != b a大於b者 a > b a不大於b者 a <= b a小於b者 a < b a不小於b者 a >= b 中無陰乎 and 中有陽乎 or 變 not 解説
スペースはいくら入れてもよいのでインデントも可能です。インデント例は下記の通り。
若三不大於五者。乃得 「「true_text」」。 若非。乃得 「「false_text」」 也。繰り返し
文法的な近さの関係上この項目のみJavaScriptではなくRubyと対応付けます。(注意:2019年12月18日現在、文言をRubyにコンパイルすることはできない。)
例
文言 Ruby 為是n遍。⋯⋯ 云云。 n.times do ... end 恆為是。⋯⋯ 云云。 loop do ... end 凡「array」中之「elem」。⋯⋯ 云云。 for elem in array do ... end 乃止。 break; 対応表
文言 意味 為是n遍 云云(end)までの処理をn回繰り返す 恆為是 云云(end)までの処理を無限に繰り返す 云云 end(繰り返し用) 乃止 break 補足
実は
也
と云云
はどちらもend
に変換されるためどちらでもよい。おわりに
文言の基礎文法だけ読み解いてみました。さらに発展的な書き方については、公式のREADMEまたはソースコードをお読みください。
とにかくsrc/render.js
でソースコードを原稿用紙に出力できるのが楽しみすぎるので何か書きたい。皆さんも漢字文化圏の特典を享受してプログラミング言語「文言」を楽しんでください!参考文献
- 投稿日:2019-12-18T15:23:16+09:00
【vue】Vue Styleguidistの使い方を説明① 〜Laravel + vue環境でVue Styleguidistを動かす〜
※元々ブログに書いていたのですが、qiitaに転載しました
https://www.moyashidaisuke.com/entry/vue-styleguidist-install概要
vueのstyleguild「Vue Styleguidist」をLaravel + vue環境で使い始めてみました。
思ったより手間取ってしまったので、設定ファイルや、私の環境で発生したエラーの対応等を残しておきます。↑のカメレオンはVue Styleguidistのロゴ。きもかわいい。
※Laravel環境じゃなくても多分参考になると思います。
Vue Styleguidistとは
とりあえず動作サンプル見た方がわかりやすいのでいきなりですがリンクを。
https://vue-styleguidist.github.io/basic/
こういうコンポーネントの仕様と、サンプルのドキュメントを生成してくれるツールです。 こういうの。
GitHubのStar数はこの記事を書いてる時点で1419なので、デファクトになってる感はまだ無いですが、競合のvueseよりはstar数多いのでこちらを採用しました。
https://github.com/vue-contrib/vuese導入手順(理想形
特にはまらないですんなりいくパターン。
https://vue-styleguidist.github.io/docs/GettingStarted.html#_1-install
前提
- Laravel + vue環境導入済み
- npmじゃなくてyarn
- Vue CLIは使ってない
公式手順だとnpmですが、私の環境ではyarnを使っているのでyarnの手順を紹介します。
インストール
$ yarn add -D vue-styleguidist 〜色々インストールされる。省略〜 Done in 296.61s.style guildの設定
公式手順だとリンクが2つ貼ってあります。
https://vue-styleguidist.github.io/docs/Components.html#finding-components
https://vue-styleguidist.github.io/docs/Webpack.htmlいきなりぶん投げられてわかんないですが、プロジェクトルートに
styleguide.config.js
というファイルを作成してください。中身は
const VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { webpackConfig: { module: { rules: [ // Vue loader { test: /\.vue$/, exclude: /node_modules/, loader: 'vue-loader' }, ] }, plugins: [ new VueLoaderPlugin() ] }, // vueファイルへのpathを指定 components: 'resources/js/components/**/[A-Z]*.vue', }で私の環境だといけました。
ポイントとしては、
webpackConfig
はlaravel mix
の設定を流用しても全く動かないです。(あれはmixで動的にconfigを生成したりしてるので)また、
vue-loader
の設定もちゃんと書いてあげないと動かないです。デフォルトで呼んでくれたりはしないようです。あと、vueのファイルはLaravelだと普通
resources/js
以下で作成してる事が多いと思いますが、適時調整してください。package.jsonにコマンド追加
これは公式そのままで大丈夫です。
{ "scripts": { + "styleguide": "vue-styleguidist server", + "styleguide:build": "vue-styleguidist build" } }実行
hot reload版
yarn run styleguideサーバが立ち上がってlocalhost:6060でつなげるようになります。vagrantやDocker等の仮想環境を使っている方はポートの設定をしてください。
私はdocker-composeを使っていたので
ports: - 6060:6060 # styleguideを追加しました。
htmlとjs吐き出す版
yarn run styleguide:buildstyleguideというディレクトリにhtmlとjsが吐き出されますので、htmlを開けばOKです。
エラー色々
styleguide.config.jsの設定系
componentsへのpathがおかしい
画面を開くとこれが表示されるパターン。
Welcome to Vue Styleguidist! We couldn’t find any components using these patterns: src/{components,Components}/**/*.vue Create styleguide.config.js file in your project root directory like this: module.exports = { components: 'src/components/**/*.vue' }; Read more in the locating components guide.componentsの設定を自分の環境に合わせればOK。
Failed to compile
Failed to compile ./resources/js/components/XXXXX.vue 1:0 Module parse failed: Unexpected token (1:0) You may need an appropriate loader to handle this file type.
vue-loader
の設定をいれてあげればOK。vueとvue-template-compilerのバージョン違い
Failed to compile ./resources/js/components/XXXXXX.vue (./node_modules/vue-styleguidist/loaders/vuedoc-loader.js!./resources/js/components/XXXXXX.vue) Error: Vue packages version mismatch: - vue@2.6.8 (/var/www/node_modules/vue/dist/vue.runtime.common.js) - vue-template-compiler@2.6.10 (/var/www/node_modules/vue-docgen-api/node_modules/vue-template-compiler/package.json) This may cause things to work incorrectly. Make sure to use the same version for both. If you are using vue-loader@>=10.0, simply update vue-template-compiler. If you are using vue-loader@<10.0 or vueify, re-installing vue-loader/vueify should bump vue-template-compiler to the latest.バージョン合わせないとダメらしい。
yarn.lockを確認するとvue-template-compiler@^2.0.0: version "2.6.10" resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc" integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg== dependencies: de-indent "^1.0.2" he "^1.1.0"となっており、
^2.0.0
2以上を使ってね、の指定で2.6.10
をinstallしちゃってる。というわけで、無理やり
2.6.8
を入れてみる。$ yarn add vue-template-compiler@2.6.8 yarn add v1.13.0 info Direct dependencies └─ vue-template-compiler@2.6.8 info All dependencies └─ vue-template-compiler@2.6.8 Done in 223.61s.これで
2.6.8
入ったと思いきや、2.6.10
も入ったままなのでダメ。yarn.lockはこんな状態
vue-template-compiler@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.8.tgz#750802604595134775b9c53141b9850b35255e1c" integrity sha512-SwWKANE5ee+oJg+dEJmsdxsxWYICPsNwk68+1AFjOS8l0O/Yz2845afuJtFqf3UjS/vXG7ECsPeHHEAD65Cjng== dependencies: de-indent "^1.0.2" he "^1.1.0" vue-template-compiler@^2.0.0: version "2.6.10" resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc" integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg== dependencies: de-indent "^1.0.2" he "^1.1.0"installしたりremoveしてもダメだったので、最終手段でyarn.lockを直接書き換える。
vue-template-compiler@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.8.tgz#750802604595134775b9c53141b9850b35255e1c" integrity sha512-SwWKANE5ee+oJg+dEJmsdxsxWYICPsNwk68+1AFjOS8l0O/Yz2845afuJtFqf3UjS/vXG7ECsPeHHEAD65Cjng== dependencies: de-indent "^1.0.2" he "^1.1.0" vue-template-compiler@^2.0.0: version "2.6.8" resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.8.tgz#750802604595134775b9c53141b9850b35255e1c" integrity sha512-SwWKANE5ee+oJg+dEJmsdxsxWYICPsNwk68+1AFjOS8l0O/Yz2845afuJtFqf3UjS/vXG7ECsPeHHEAD65Cjng== dependencies: de-indent "^1.0.2" he "^1.1.0"$ yarn installこれで動きました。
issueはちょいちょい見かけるのだけど、ちゃんとした対応方法は不明。誰か知ってたら教えて下さい。
https://github.com/vuejs/vue/issues/3941
次
実際のドキュメントを生成したり、パラメータの解説したりの予定。
第2段
https://www.moyashidaisuke.com/entry/vue-styleguild-sections
- 投稿日:2019-12-18T14:47:29+09:00
フルスタックエンジニアへの道(CakePHP/React)
はじめに
こんにちは、 @IZUMIRU0313 です。
ランサーズ Advent Calendar 2019 23日目の記事です。法人向けの社外人材活用サービス「Lancers Enterprise」のフルスタックエンジニアです。
まだよわよわなので恐縮ですが、api blueprintでAPI仕様書、CakePHPでAPI、ReactでUIを実装しています?想定する読者は、サーバーサイドエンジニアでフロントエンド(React)も学習していこうとしている方です。
エンジニア経歴
学生時代は、主にRails、Swift、AWS(EC2、S3)、Heroku、WordPressを利用して、サービス開発やインターンに取り組んでいました。特に以下2つのサービスは、すべての設計および開発をやっていたため、努力は報われると今日でも思える貴重な経験になっています。
フロントは、SassとjQueryが多少書けるレベルでした?ランサーズには、SREとしてジョインしました。当時、ターミナルはgitと多少のコマンドを知っているレベルであり、@yakitori009さんに、何から何まで教えていただきながら取り組んでいました?♂️
LPICでインプットしながら取り組んでいたため、座学と実務の両輪が上手く回せていました。
- 踏み台サーバーの移行
- Let's Encryptワイルドカード証明書の導入
- AutoScaling
- AutoScaling中ではデプロイ不可
- docker-compose対応
- MySQLコンテナ、WordPressコンテナの構築
- MySQLのバージョンアップ5.6->5.7
- LambdaでGitHubとChatworkの連携
- LambdaでAthenaのload partitionを自動実行
その後、サーバーサイドエンジニアとしてCakePHPでプラットフォームの開発をすることにしました。まともにチーム開発とCakePHPを書くのは初めてだったため、@waldo0515さんや@numanomanuさん、井上さんにシステム設計からプロジェクトマネジメント、コーディングに渡るまで大変お世話になりました?♂️
インプットは、オブジェクト指向やドメイン駆動設計、クリーンアーキテクチャ、リーダブルコード等に努めました。
- ランサーランク制度
- アナリティクス
- 顔写真判定
- ヤフースコア
- Linkedinログイン
- 提案見積書
- 業種
- 提案追加オプション
- Freelance Basics移行
- Lancers Pro
- BigQuery/Redashで全社データ出し・モニタリング構築
JavaScriptの習得
正直まだまだ未熟であり器用貧乏になる可能性も大いにあるのですが、自分が目指したいエンジニア像のために本格的にJavaScriptに力を入れることにしました?
まずは、半年後業務でReactを書けるレベルになることを目標に、GASでの個人開発から始めました。SREの際にLambdaでnode.jsを書いていたこともあり、特に詰まることなく開発できました。
インプットは、改訂新版JavaScript本格入門を読んでいました。
- [GAS/Twilio]広瀬すずさんがSlackのリマインダー機能でモーニングコールしてくれるサービス
- [GAS/GoogleCloudNaturalLanguageAPI]宇垣美里さんが時には厳しく、時には優しくしてくれるアメムチbot
- IZUMIRU/kenkahadamewan
- [Nuxt/WebSpeechAPI]騒がしい居酒屋でもワンタップで店員さんを呼ぶサービス「親指ですみません」
Reactの習得
ES6のお作法や非同期通信の変遷等も一通り理解することができたので、本格的にReactの学習を始めました。
元々副業や個人開発でVueやNuxtを触る機会があったのですが、個人的にはReactの方が学習ハードルが高かった印象です。
特にJSX(TSX)、TypeScript、Redux、redux-sagaは業務で開発するまで理解できませんでした。半年ほど、@intrudercl14さんと@takepo0928さんにキャリアやJavaScript、Reactのアドバイスをいただき、なんとか「Lancers Enterprise」の開発にジョインすることができました?♂️
- チュートリアル
- React.Component
- 一人React.js Advent Calendar 2014
- りあクト!
- React開発 現場の教科書
- WEB+DB PRESS Vol.112
- Atomic Design
- Atomic Design by Brad Frost
- Redux. From twitter hype to production
特にりあクト!は、対話形式で先輩エンジニアが後輩エンジニアに教えるというストーリーなので、非常に読みやすくオススメです。
またVue、React、React(Redux)で同じアプリケーションを実装することは、共通点と相違点を把握でき学習促進に繋がったのでオススメです。
Reactの学習と合わせて、APIの学習にも努めました。APIは学生時代のサービスでRailsでAPIを生やし、Swiftでキャッチするという経験等はありましたが、なんちゃってAPIレベルだったので1から学習しました。
- Web API: The Good Parts
- IZUMIRU/youtube-manager-nuxt
- IZUMIRU/youtube-manager-go
- GraphQLはRESTの置き換えではない
- 「GraphQL」徹底入門 ─ RESTとの比較、API・フロント双方の実装から学ぶ
- vvakame/graphql-with-go-book
- IZUMIRU/laravel-vue-with-graphql
- IZUMIRU/amplify-react
- よくわかるgRPC
- スターティングgRPC
- IZUMIRU/hello-grpc
展望
ReactやTypeScriptの学習は継続していますが、ReactNativeやFlutterの学習もし始めたため、@sayanetさんと@terukuraさんとともに「Lancers Enterprise」をより良くした後は、アプリの改善にコミットできたらと考えています。
またモチベーション高く学習するには、自分の性格を理解することが大事だなと非常に思いました。家だと怠惰なので仕事終わり必ずカフェに行く、まずは簡単なアプリケーションを開発した後に体系だった書籍で質を上げていく等。長くなりそうなので、個人開発のすゝめ的な記事は別途書けたら良いなと思います。
QiitaいいねやTwitterフォローは励みになります?
- 投稿日:2019-12-18T14:42:23+09:00
webpackを使ってobjectFitをIEに対応させる
webpackでobjectFitImagesを使えるようにするのははじめてだったので手順を残す。
最低限必要なのはライブラリだけ。
https://github.com/fregante/object-fit-imagesobject-fit-imagesをインストールする
yarn add object-fit-images読み込ませるためのjsを書く
objectFitImages.js/* * This file is use object-fit-images * IEをobjectFitに対応させるためのライブラリ * https://www.npmjs.com/package/object-fit-images * */ import objectFitImages from 'object-fit-images' objectFitImages()後はapp.tsなりにimportすればおk。
IEにも対応する
全てのCSSにfont-familiyを書いていくのは手間なので、postCssを使う。
https://github.com/ronik-design/postcss-object-fit-imagesyarn add -D postcss-object-fit-images
postcss.configに追記
const objectFitImages = require('postcss-object-fit-images') const autoprefixer = require('autoprefixer') module.exports = { plugins: [ autoprefixer, objectFitImages, ], }終わり。
- 投稿日:2019-12-18T14:23:03+09:00
v-date-pickerをカスタマイズしてbirthday-pickerを作る
TL;DR
- VuejsのUIフレームワークであるVuetifyを使っていい感じの誕生日入力フォームを作成する
- 以下を満たすものとする
- 日本語対応
- 日付の「日」は取る
- 年から選択
- 最初に年を選択するとき、1995年からになるようにする
- 日付範囲制限(未来や古すぎる日付を誕生日に設定できない)
- 日付を選択と同時にpickerを閉じる
動作環境
Vue.js v2.x
Vuetify v2.1.14できたもの
https://codepen.io/kmr_0811/pen/oNgBzxR
解説
- 日本語対応
- v-date-pickerのlocaleプロパティを設定
- 日付の「日」は取る
- v-date-pickerのday-formatプロパティを設定
- 日付範囲制限(未来や古すぎる日付を誕生日に設定できない)
- max, minプロパティを設定
- 最初に年を選択するとき、1995年からになるようにする
- picker-dateプロパティを制御する必要がある
- picker-dateではpickerの値をコントロールできる
- そのため、v-modelでbindされたdateオブジェクトを触らずpickerについてのみ触ることができる
<v-date-picker ref="picker" locale="jp-ja" v-model="date" :day-format="date => new Date(date).getDate()" :max="new Date().toISOString().substr(0, 10)" :picker-date="pickerDate" min="1950-01-01" @change="save" ></v-date-picker>
- 年から選択
- v-menuにバインドしたmenuをwatchし、v-date-pickerのactivePickerを制御する。
- menuを選ばれたタイミングのみでactivePickerをYEAR指定にしないと、ずっと年選択になったりする
- 選び始めたらpickerDateを初期化しないと、ユーザが入力してもpickerDateの値になってしまうので注意
watch: { menu (val) { val && setTimeout(() => ( // 年から選ぶようにする this.$refs.picker.activePicker = 'YEAR', // 選び始めたら初期化 this.pickerDate = null )) }, },
- 日付を選択と同時にpickerを閉じる
- 日付を選択したとき = v-date-pickerの
@change
が発火するタイミング- その際、pickerDateと選択したdateを同期させないと、もう一度誕生日を選びなおした時に前回の日付が初期値として機能してくれないので注意
methods: { save (date) { this.$refs.menu.save(date) // 再入力に備えて、入力が終わったら同期する this.pickerDate = date; }, },感想
v-date-pickerは抽象的な作りをしていて、開発者が用意されたプロパティやコンポーネントをよく調べる必要があります。
そのため、必然的にVueコンポーネントの学びを多く得られると思いました。ぜひ、v-date-pickerもといVuetifyを触ってみてください!
付録
今回のコード
template
<div id="app"> <v-app id="inspire"> <v-menu ref="menu" v-model="menu" :close-on-content-click="false" transition="scale-transition" offset-y full-width min-width="290px" > <template v-slot:activator="{ on }"> <v-text-field v-model="date" label="誕生日を入力" prepend-icon="event" readonly v-on="on" ></v-text-field> </template> <v-date-picker ref="picker" locale="jp-ja" v-model="date" :day-format="date => new Date(date).getDate()" :max="new Date().toISOString().substr(0, 10)" :picker-date="pickerDate" min="1950-01-01" @change="save" ></v-date-picker> </v-menu> </v-app> </div>script
new Vue({ el: '#app', vuetify: new Vuetify(), data: () => ({ date: null, menu: false, // pickerの初期値(95年から年が選べるようになる) pickerDate: '1995-1-1', }), watch: { menu (val) { val && setTimeout(() => ( // 年から選ぶようにする this.$refs.picker.activePicker = 'YEAR', // 選び始めたら初期化 this.pickerDate = null )) }, }, methods: { save (date) { this.$refs.menu.save(date) // 再入力に備えて、入力が終わったら同期する this.pickerDate = date; }, }, })