- 投稿日:2019-12-21T23:55:56+09:00
1年業務で学んだ技術を使ってTODOアプリ作ってみた ~ 備忘録も兼ねて ~
はじめに
2019年4月に株式会社オプトに入社したsh1okohと申します。
なんやかんやありまして、今はReact, Redux, Railsなどを使ったプロダクトで、日々奮闘しております。
今回は、お仕事中に出会った技術スタックを使ってモダン()なTODOアプリを作ってみたので、各種ライブラリの紹介をしつつ、Todoアプリの紹介をしていきたいと思います。(備忘録的な目的もあります)対象読者
今回は、テーマ的にも内容自体は広く浅くになっております。
各種ライブラリを使ったWEBアプリの全体像を把握したい方などを対象としており、各ライブラリを深く学びたいといった方は対象外となっておりますので、よろしくお願いします。今回使用した技術
フロントエンド
- Javascript (業務ではTypeScriptを使っています)
- React
- Redux
- axious
バックエンド
- Ruby 2.5.7
- Ruby on Rails 5.2.4
- rubocop
- guard
- rspec
CI/CD
- Circle CI
フロントエンド編
React とは
A JavaScript library for building user interfaces
Facebookの作ったJavascriptライブラリです。
MVC(Model View Controller)モデルでいうとVの部分を提供しています。Reactの考え方
Component-Based
Build encapsulated components that manage their own state, then compose them to make complex UIs.Reactでは、画面をComponentの親子関係を持つツリーとして構成します。
各コンポーネントは、親から渡されたprops(プロパティ)、State(自分の状態)を元に、renderメソッドでDOM(Virtual DOM)を生成します。axios とは
Promise based HTTP client for the browser and node.js
https://github.com/axios/axios
Http通信を簡単に行うことができるJavascriptライブラリです。
主な特徴としては、
ChromeやFirefox, Safariなどのブラウザに対応
ブラウザからXMLHttpsRequestsを作成
リクエストとレスポンスデータを変換
Promise APIをサポート
と言った特徴があります。
今回は、このライブラリを使ってAPIとの通信を行なっていきます。
実装
以下がコードになります。
import React from 'react'; import axios from 'axios'; const Title = ({todoCount}) => { return ( <div> { todoCount > 0 ? <h1>{todoCount}つのタスクがあります</h1> : <h1>タスクがありません</h1> } </div> ); } const TodoForm = ({addTodo}) => { let input; return ( <form onSubmit={(e) => { e.preventDefault(); addTodo(input.value); input.value = ''; }}> <input className="form-control col-md-12" ref={node => { input = node; }} /> <br /> </form> ); }; const Todo = ({todo, remove}) => { return ( <li> <a href="#" className="list-group-item">{todo.contents}</a> <button onClick={() => {remove(todo.id)}}>削除</button> </li> ); } const TodoList = ({todos, remove}) => { const todoNode = todos.map((todo) => { return (<Todo todo={todo} key={todo.id} remove={remove} />) }); return (<div className="list-group" style={{marginTop:'30px'}}>{todoNode}</div>); } window.id = 0; class TodoApp extends React.Component{ constructor(props){ super(props); this.state = { data: [] } this.apiUrl = 'http://localhost:3000/api/todos/' } componentDidMount(){ axios.get(this.apiUrl) .then((res) => { this.setState({ data: res.data }); }); } addTodo(val){ const todo = {contents: val} axios.post(this.apiUrl, todo) .then((res) => { this.state.data.push(res.data); this.setState({data: this.state.data}); }); } handleRemove(id){ const remainder = this.state.data.filter((todo) => { if(todo.id !== id) return todo; }); axios.delete(this.apiUrl+id) .then((res) => { this.setState({data: remainder}); }) } render(){ return ( <div> <Title todoCount={this.state.data.length}/> <TodoForm addTodo={this.addTodo.bind(this)}/> <TodoList todos={this.state.data} remove={this.handleRemove.bind(this)} /> </div> ); } } export default TodoApp上記のコードを見てもらうと分かりやすいと思うのですが、
TodoApp
コンポーネントにconstructor
メソッド、componentDidMount
メソッドを定義しています。componentDidMount
メソッドは、Reactのライフサイクルメソッドの一種で、出力が DOM にレンダーされた後に実行されます。ここではライフサイクルの話は割愛しますので、気になる方は下記リンクを参考にしてみてください。
- https://ja.reactjs.org/docs/state-and-lifecycle.html
- https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9
で、各関数コンポーネントに、propsを渡し、その値に応じた描画を行なっています。
APIの疎通に関しては、
axios.getや
axios.postで行なって、APIからのレスポンスデータを取得したりしています。
で、実際に見てみると、初期の出力結果が、こうなります。(めっちゃ殺風景・・・。)
で入力欄に値を入力してEnterを押すと、タスクが追加できて、削除までできます。
本当は最低限CRUDの実装はしたかったのですが、諸々の事情により、updateの機能は後日追記いたします。
バックエンド編
Ruby on Rails とは
Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern.
https://github.com/rails/rails
大半の方がご存知かと思いますが、Model-view-controller パターンを採用したフレームワークです。
特にActiceRecordは個人的には強力な機能だと思っています。今回はView側はReactで実装していますので、RailsのAPIモードを使って実装しました。
APIモードは、下記のようにコマンドライン引数に
--api
とつけるだけで、できてしまいます(さすがRails!)rails new my_api --apiAPIモード
https://guides.rubyonrails.org/api_app.htmlActive Record とは
Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.
https://guides.rubyonrails.org/active_record_basics.html
MVCモデル言うところの、Mの部分に相当するものです。
ORM(O/Rマッピング)システムに記述されている「Active Recordパターン」を実装したもので、このパターンと同じ名前が付けられています。ORM(O/Rマッピング)とは、アプリケーションが持つオブジェクトを、RDBMSのテーブルに接続することです。
ORMを用いると、SQL文を書かずに、完結なコードだけで、テーブルからレコードのデータを取得できたり、更新できたりしてとても便利です。例えば、ターミナル上で、
rails consoleを叩き
Todo.allなどとすると、下記のような出力結果を得ることができます。
またワンライナーでも書くことができるので、
SQL文を書くより遥かに楽だと言うことが分かると思います。他にも
- モデル同士のアソシエーションを表現する
- 関連付けられているモデル間の継承階層を表現する
- データをデータベースで永続化する前にバリデーションを行う
などの特徴を持っています。
RuboCop とは
RuboCop is a Ruby static code analyzer and code formatter.
https://github.com/rubocop-hq/rubocop
rubyのコードアナライザーであり、フォーマッターです。
例えば、チーム開発をするときにコード規約を定めると思うのですが、RuboCopを用いると捗ります。
また、警告を出すのみに止まらず、いくつかの問題を自動で直してくれることもしてくれます。
例えば、ターミナル上で下記のコマンドを実行すると、一括でフォーマットをかけてくれたりしますので、とても便利です。bundle exec rubocop -a参考
https://rubocop.readthedocs.io/en/stable/実装
実装はとても簡単で、ターミナル上で、下記のコマンドをバーン!すると、
ルーティングやコントローラー、モデルからテーブル定義までの雛形を作ってくれます。bundle exec rails generate scaffold Todo contents:string実際のコード
todo_controller.rb
class TodosController < ApplicationController before_action :set_todo, only: [:show, :edit, :update, :destroy] def index @todos = Todo.all render json: @todos end def show render json: @todo end def new @todo = Todo.new end def edit end def create @todo = Todo.new(create_params) if @todo.save render json: @todo else render :new end end def update if @todo.update!(update_params) render json: @todo else render :edit end end def destroy @todo.destroy render json: @todos end private def set_todo @todo = Todo.find(params[:id]) end def create_params params.require(:todo).permit(:contents) end def update_params params.require(:todo).permit(%i[id contents]) end def destroy_params params.require(:todo).permit(:id) end end
routes.rb
Rails.application.routes.draw do scope :api, defaults: { format: :json } do resources :todos, only: %i[show index create update destroy] end end
models/todo.rb
class Todo < ApplicationRecord def as_json(options = {}) super(options.reverse_merge(except: %i[created_at updated_at])) end end今回のAPIの設計方針として、REST APIを採用しました。
REST APIについては、下記を参照してみてください。
https://restfulapi.net/CI編
後日書きます
まとめ
半年間、自分が使ってきた技術を用いて、アプリーケーションを開発するのは良い振り返りにもなるし、一年の節目にも良さそうと思いました。今後も、何か学ぶ機会があれば、やってみるといいかもと思いました。
実はまだまだ書きたいこともあるので、後日追記するか、別の記事に載せることにします。その他の参考資料
https://restful-api-guidelines-ja.netlify.com/
https://scotch.io/tutorials/create-a-simple-to-do-app-with-react
- 投稿日:2019-12-21T23:55:56+09:00
半年間業務で学んだ技術を使ってTODOアプリ作ってみた ~ 備忘録も兼ねて ~
Opt Technologies Advent Calendar 2019の21日目の記事です。
はじめに
2019年4月に株式会社オプトに入社したsh1okohと申します。
なんやかんやありまして、今はReact, Redux, Railsなどを使ったプロダクトで、日々奮闘しております。
今回は、お仕事中に出会った技術スタックを使ってモダン()なTODOアプリを作ってみたので、各種ライブラリの紹介をしつつ、Todoアプリの紹介をしていきたいと思います。(備忘録的な目的もあります)ちなみに、今回のTODOアプリのソースコードは下記リンクにありますので、
興味のある方はみてみてください。https://github.com/sh1okoh/adventcalendar
対象読者
今回は、テーマ的にも内容自体は広く浅くになっております。
各種ライブラリを使ったWEBアプリの全体像を把握したい方などを対象としており、各ライブラリを深く学びたいといった方は対象外となっておりますので、よろしくお願いします。今回使用した技術
フロントエンド
- JavaScript (業務ではTypeScriptを使っています)
- React
Redux- axios
- eslint
- prettier
バックエンド
- Ruby 2.5.7
- Ruby on Rails 5.2.4
- rubocop
- guard
- rspec
- rack-cors
CI/CD
- Circle CI
フロントエンド編
React とは
A JavaScript library for building user interfaces
Facebookの作ったJavascriptライブラリです。
MVC(Model View Controller)モデルでいうとVの部分を提供しています。Reactの考え方
Component-Based
Build encapsulated components that manage their own state, then compose them to make complex UIs.Reactでは、画面をComponentの親子関係を持つツリーとして構成します。
各コンポーネントは、親から渡されたprops(プロパティ)、State(自分の状態)を元に、renderメソッドでDOM(Virtual DOM)を生成します。axios とは
Promise based HTTP client for the browser and node.js
https://github.com/axios/axios
Http通信を簡単に行うことができるJavascriptライブラリです。
主な特徴としては、
ChromeやFirefox, Safariなどのブラウザに対応
ブラウザからXMLHttpsRequestsを作成
リクエストとレスポンスデータを変換
Promise APIをサポート
と言った特徴があります。
今回は、このライブラリを使ってAPIとの通信を行なっていきます。
実装
以下がコードになります。
import React from 'react'; import axios from 'axios'; const Title = ({todoCount}) => { return ( <div> { todoCount > 0 ? <h1>{todoCount}つのタスクがあります</h1> : <h1>タスクがありません</h1> } </div> ); } const TodoForm = ({addTodo}) => { let input; return ( <form onSubmit={(e) => { e.preventDefault(); addTodo(input.value); input.value = ''; }}> <input className="form-control col-md-12" ref={node => { input = node; }} /> <br /> </form> ); }; const Todo = ({todo, remove}) => { return ( <li> <a href="#" className="list-group-item">{todo.contents}</a> <button onClick={() => {remove(todo.id)}}>削除</button> </li> ); } const TodoList = ({todos, remove}) => { const todoNode = todos.map((todo) => { return (<Todo todo={todo} key={todo.id} remove={remove} />) }); return (<div className="list-group" style={{marginTop:'30px'}}>{todoNode}</div>); } window.id = 0; class TodoApp extends React.Component{ constructor(props){ super(props); this.state = { data: [] } this.apiUrl = 'http://localhost:3000/api/todos/' } componentDidMount(){ axios.get(this.apiUrl) .then((res) => { this.setState({ data: res.data }); }); } addTodo(val){ const todo = {contents: val} axios.post(this.apiUrl, todo) .then((res) => { this.state.data.push(res.data); this.setState({data: this.state.data}); }); } handleRemove(id){ const remainder = this.state.data.filter((todo) => { if(todo.id !== id) return todo; }); axios.delete(this.apiUrl+id) .then((res) => { this.setState({data: remainder}); }) } render(){ return ( <div> <Title todoCount={this.state.data.length}/> <TodoForm addTodo={this.addTodo.bind(this)}/> <TodoList todos={this.state.data} remove={this.handleRemove.bind(this)} /> </div> ); } } export default TodoApp上記のコードを見てもらうと分かりやすいと思うのですが、
TodoApp
コンポーネントにconstructor
メソッド、componentDidMount
メソッド,render
メソッドを定義しています。componentDidMount
メソッド,render
メソッドは、Reactのライフサイクルメソッドの一種で、出力が DOM にレンダーされた後に実行されます。ここではライフサイクルの話は割愛しますので、気になる方は下記リンクを参考にしてみてください。
- https://ja.reactjs.org/docs/state-and-lifecycle.html
- https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9
で、各関数コンポーネントに、propsを渡し、その値に応じた描画を行なうという流れになっております。
APIの疎通に関しては、
axios.getや
axios.postで行なって、APIからのレスポンスデータを取得したりしています。
で、実際に見てみると、初期の出力結果が、こうなります。(めっちゃ殺風景・・・。)
で入力欄に値を入力してEnterを押すと、タスクが追加できて、削除までできます。
本当は最低限CRUDの実装はしたかったのですが、諸々の事情により、updateの機能は後日追記いたします。
バックエンド編
Ruby on Rails とは
Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern.
https://github.com/rails/rails
大半の方がご存知かと思いますが、Model-view-controller パターンを採用したフレームワークです。
特にActiceRecordは個人的には強力な機能だと思っています。今回はView側はReactで実装していますので、RailsのAPIモードを使って実装しました。
APIモードは、下記のようにコマンドライン引数に
--api
とつけるだけで、できてしまいます(さすがRails!)rails new my_api --apiAPIモード
https://guides.rubyonrails.org/api_app.htmlActive Record とは
Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.
https://guides.rubyonrails.org/active_record_basics.html
MVCモデル言うところの、Mの部分に相当するものです。
ORM(O/Rマッピング)システムに記述されている「Active Recordパターン」を実装したもので、このパターンと同じ名前が付けられています。ORM(O/Rマッピング)とは、アプリケーションが持つオブジェクトを、RDBMSのテーブルに接続することです。
ORMを用いると、SQL文を書かずに、簡潔なコードだけで、テーブルからレコードのデータを取得できたり、更新できたりしてとても便利です。例えば、ターミナル上で、
rails consoleを叩き
Todo.allなどとすると、下記のような出力結果を得ることができます。
またワンライナーでも書くことができるので、
SQL文を書くより遥かに楽だと言うことが分かると思います。他にも
- モデル同士のアソシエーションを表現する
- 関連付けられているモデル間の継承階層を表現する
- データをデータベースで永続化する前にバリデーションを行う
などの特徴を持っています。
RuboCop とは
RuboCop is a Ruby static code analyzer and code formatter.
https://github.com/rubocop-hq/rubocop
rubyのコードアナライザーであり、フォーマッターです。
例えば、チーム開発をするときにコード規約を定めると思うのですが、RuboCopを用いると捗ります。
また、警告を出すのみに止まらず、いくつかの問題を自動で直してくれることもしてくれます。
例えば、ターミナル上で下記のコマンドを実行すると、一括でフォーマットをかけてくれたりしますので、とても便利です。bundle exec rubocop -a参考
https://rubocop.readthedocs.io/en/stable/実装
実装はとても簡単で、ターミナル上で、下記のコマンドをバーン!すると、
ルーティングやコントローラー、モデルからテーブル定義までの雛形を作ってくれます。bundle exec rails generate scaffold Todo contents:string実際のコード
todo_controller.rb
class TodosController < ApplicationController before_action :set_todo, only: [:show, :edit, :update, :destroy] def index @todos = Todo.all render json: @todos end def show render json: @todo end def new @todo = Todo.new end def edit end def create @todo = Todo.new(create_params) if @todo.save render json: @todo else render :new end end def update if @todo.update!(update_params) render json: @todo else render :edit end end def destroy @todo.destroy render json: @todos end private def set_todo @todo = Todo.find(params[:id]) end def create_params params.require(:todo).permit(:contents) end def update_params params.require(:todo).permit(%i[id contents]) end def destroy_params params.require(:todo).permit(:id) end end
routes.rb
Rails.application.routes.draw do scope :api, defaults: { format: :json } do resources :todos, only: %i[show index create update destroy] end end
models/todo.rb
class Todo < ApplicationRecord def as_json(options = {}) super(options.reverse_merge(except: %i[created_at updated_at])) end end
db/schema.rb
ActiveRecord::Schema.define(version: 2019_12_15_104719) do create_table "todos", force: :cascade do |t| t.string "contents" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end今回のAPIの設計方針として、REST APIを採用しました。
REST APIについては、下記を参照してみてください。
https://restfulapi.net/CI編
後日書きます
まとめ
半年間、自分が使ってきた技術を用いて、アプリーケーションを開発するのは良い振り返りにもなるし、一年の節目にも良さそうと思いました。今後も、何か学ぶ機会があれば、やってみるといいかもと思いました。
実はまだまだ書きたいこともあるので、後日追記するか、別の記事に載せることにします。その他の参考資料
https://restful-api-guidelines-ja.netlify.com/
https://scotch.io/tutorials/create-a-simple-to-do-app-with-react
- 投稿日:2019-12-21T23:55:56+09:00
業務で学んだ技術を使ってTODOアプリ作ってみた ~ 備忘録も兼ねて ~
Opt Technologies Advent Calendar 2019の21日目の記事です。
はじめに
2019年4月に株式会社オプトに入社したsh1okohと申します。
なんやかんやありまして、今はReact, Redux, Railsなどを使ったプロダクトで、日々奮闘しております。
今回は、お仕事中に出会った技術スタックを使ってモダン()なTODOアプリを作ってみたので、各種ライブラリの紹介をしつつ、Todoアプリの紹介をしていきたいと思います。(備忘録的な目的もあります)ちなみに、今回のTODOアプリのソースコードは下記リンクにありますので、
興味のある方はみてみてください。https://github.com/sh1okoh/adventcalendar
対象読者
今回は、テーマ的にも内容自体は広く浅くになっております。
各種ライブラリを使ったWEBアプリの全体像を把握したい方などを対象としており、各ライブラリを深く学びたいといった方は対象外となっておりますので、よろしくお願いします。今回使用した技術
フロントエンド
- JavaScript (業務ではTypeScriptを使っています)
- React
Redux- axios
- eslint
- prettier
バックエンド
- Ruby 2.5.7
- Ruby on Rails 5.2.4
- rubocop
- guard
- rspec
- rack-cors
CI/CD
- Circle CI
フロントエンド編
React とは
A JavaScript library for building user interfaces
Facebookの作ったJavascriptライブラリです。
MVC(Model View Controller)モデルでいうとVの部分を提供しています。Reactの考え方
Component-Based
Build encapsulated components that manage their own state, then compose them to make complex UIs.Reactでは、画面をComponentの親子関係を持つツリーとして構成します。
各コンポーネントは、親から渡されたprops(プロパティ)、State(自分の状態)を元に、renderメソッドでDOM(Virtual DOM)を生成します。axios とは
Promise based HTTP client for the browser and node.js
https://github.com/axios/axios
Http通信を簡単に行うことができるJavascriptライブラリです。
主な特徴としては、
- ChromeやFirefox, Safariなどのブラウザに対応
- リクエストとレスポンスデータを変換
- Promise APIをサポート
と言った特徴があります。
今回は、このライブラリを使ってAPIとの通信を行なっていきます。
実装
以下がコードになります。
import React from 'react'; import axios from 'axios'; const Title = ({todoCount}) => { return ( <div> { todoCount > 0 ? <h1>{todoCount}つのタスクがあります</h1> : <h1>タスクがありません</h1> } </div> ); } const TodoForm = ({addTodo}) => { let input; return ( <form onSubmit={(e) => { e.preventDefault(); addTodo(input.value); input.value = ''; }}> <input className="form-control col-md-12" ref={node => { input = node; }} /> <br /> </form> ); }; const Todo = ({todo, remove}) => { return ( <li> <a href="#" className="list-group-item">{todo.contents}</a> <button onClick={() => {remove(todo.id)}}>削除</button> </li> ); } const TodoList = ({todos, remove}) => { const todoNode = todos.map((todo) => { return (<Todo todo={todo} key={todo.id} remove={remove} />) }); return (<div className="list-group" style={{marginTop:'30px'}}>{todoNode}</div>); } window.id = 0; class TodoApp extends React.Component{ constructor(props){ super(props); this.state = { data: [] } this.apiUrl = 'http://localhost:3000/api/todos/' } componentDidMount(){ axios.get(this.apiUrl) .then((res) => { this.setState({ data: res.data }); }); } addTodo(val){ const todo = {contents: val} axios.post(this.apiUrl, todo) .then((res) => { this.state.data.push(res.data); this.setState({data: this.state.data}); }); } handleRemove(id){ const remainder = this.state.data.filter((todo) => { if(todo.id !== id) return todo; }); axios.delete(this.apiUrl+id) .then((res) => { this.setState({data: remainder}); }) } render(){ return ( <div> <Title todoCount={this.state.data.length}/> <TodoForm addTodo={this.addTodo.bind(this)}/> <TodoList todos={this.state.data} remove={this.handleRemove.bind(this)} /> </div> ); } } export default TodoApp上記のコードを見てもらうと分かりやすいと思うのですが、
TodoApp
コンポーネントにconstructor
メソッド、componentDidMount
メソッド,render
メソッドを定義しています。componentDidMount
メソッド,render
メソッドは、Reactのライフサイクルメソッドの一種です。componentDidMount
メソッドは、出力が DOM にレンダーされた後に実行されます。ここではライフサイクルの話は割愛しますので、気になる方は下記リンクを参考にしてみてください。
- https://ja.reactjs.org/docs/state-and-lifecycle.html
- https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9
で、各関数コンポーネントに、propsを渡し、その値に応じた描画を行なうという流れになっております。
APIの疎通に関しては、
axios.getや
axios.postで行なって、APIからのレスポンスデータを取得したりしています。
で、実際に見てみると、初期の出力結果が、こうなります。(めっちゃ殺風景・・・。)
で入力欄に値を入力してEnterを押すと、タスクが追加できて、削除までできます。
本当は最低限CRUDの実装はしたかったのですが、諸々の事情により、updateの機能は後日追記いたします。
バックエンド編
Ruby on Rails とは
Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern.
https://github.com/rails/rails
大半の方がご存知かと思いますが、Model-view-controller パターンを採用したフレームワークです。
特にActiceRecordは個人的には強力な機能だと思っています。今回はView側はReactで実装していますので、RailsのAPIモードを使って実装しました。
APIモードは、下記のようにコマンドライン引数に
--api
とつけるだけで、できてしまいます(さすがRails!)rails new my_api --apiAPIモード
https://guides.rubyonrails.org/api_app.htmlActive Record とは
Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.
https://guides.rubyonrails.org/active_record_basics.html
MVCモデル言うところの、Mの部分に相当するものです。
ORM(O/Rマッピング)システムに記述されている「Active Recordパターン」を実装したもので、このパターンと同じ名前が付けられています。ORM(O/Rマッピング)とは、アプリケーションが持つオブジェクトを、RDBMSのテーブルに接続することです。
ORMを用いると、SQL文を書かずに、簡潔なコードだけで、テーブルからレコードのデータを取得できたり、更新できたりしてとても便利です。例えば、ターミナル上で、
rails consoleを叩き
Todo.allなどとすると、下記のような出力結果を得ることができます。
またワンライナーでも書くことができるので、
SQL文を書くより遥かに楽だと言うことが分かると思います。他にも
- モデル同士のアソシエーションを表現する
- 関連付けられているモデル間の継承階層を表現する
- データをデータベースで永続化する前にバリデーションを行う
などの特徴を持っています。
RuboCop とは
RuboCop is a Ruby static code analyzer and code formatter.
https://github.com/rubocop-hq/rubocop
rubyのコードアナライザーであり、フォーマッターです。
例えば、チーム開発をするときにコード規約を定めると思うのですが、RuboCopを用いると捗ります。
また、警告を出すのみに止まらず、いくつかの問題を自動で直してくれることもしてくれます。
例えば、ターミナル上で下記のコマンドを実行すると、一括でフォーマットをかけてくれたりしますので、とても便利です。bundle exec rubocop -a参考
https://rubocop.readthedocs.io/en/stable/実装
実装はとても簡単で、ターミナル上で、下記のコマンドをバーン!すると、
ルーティングやコントローラー、モデルからテーブル定義までの雛形を作ってくれます。bundle exec rails generate scaffold Todo contents:string実際のコード
todo_controller.rb
class TodosController < ApplicationController before_action :set_todo, only: [:show, :edit, :update, :destroy] def index @todos = Todo.all render json: @todos end def show render json: @todo end def new @todo = Todo.new end def edit end def create @todo = Todo.new(create_params) if @todo.save render json: @todo else render :new end end def update if @todo.update!(update_params) render json: @todo else render :edit end end def destroy @todo.destroy render json: @todos end private def set_todo @todo = Todo.find(params[:id]) end def create_params params.require(:todo).permit(:contents) end def update_params params.require(:todo).permit(%i[id contents]) end def destroy_params params.require(:todo).permit(:id) end end
routes.rb
Rails.application.routes.draw do scope :api, defaults: { format: :json } do resources :todos, only: %i[show index create update destroy] end end
models/todo.rb
class Todo < ApplicationRecord def as_json(options = {}) super(options.reverse_merge(except: %i[created_at updated_at])) end end
db/schema.rb
ActiveRecord::Schema.define(version: 2019_12_15_104719) do create_table "todos", force: :cascade do |t| t.string "contents" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end今回のAPIの設計方針として、REST APIを採用しました。
REST APIについては、下記を参照してみてください。
https://restfulapi.net/CI編
後日書きます
まとめ
半年間、自分が使ってきた技術を用いて、アプリーケーションを開発するのは良い振り返りにもなるし、一年の節目にも良さそうと思いました。今後も、何か学ぶ機会があれば、やってみるといいかもと思いました。
実はまだまだ書きたいこともあるので、後日追記するか、別の記事に載せることにします。その他の参考資料
https://qiita.com/matzkoh/items/90baab22ad489b78384b
https://restful-api-guidelines-ja.netlify.com/
https://scotch.io/tutorials/create-a-simple-to-do-app-with-react
- 投稿日:2019-12-21T23:55:56+09:00
React+Railsでモダン()なTODOアプリを作ってみた ~ 備忘録も兼ねて ~
Opt Technologies Advent Calendar 2019の21日目の記事です。
はじめに
2019年4月に株式会社オプトに入社したsh1okohと申します。
なんやかんやありまして、今はReact, Redux, Railsなどを使ったプロダクトで、日々奮闘しております。
今回は、お仕事中に出会った技術スタックを使ってモダン()なTODOアプリを作ってみたので、各種ライブラリの紹介をしつつ、Todoアプリの紹介をしていきたいと思います。(備忘録的な目的もあります)ちなみに、今回のTODOアプリのソースコードは下記リンクにありますので、
興味のある方はみてみてください。https://github.com/sh1okoh/adventcalendar
対象読者
今回は、テーマ的にも内容自体は広く浅くになっております。
各種ライブラリを使ったWEBアプリの全体像を把握したい方などを対象としており、各ライブラリを深く学びたいといった方は対象外となっておりますので、よろしくお願いします。今回使用した技術
フロントエンド
- JavaScript (業務ではTypeScriptを使っています)
- React
Redux- axios
- eslint
- prettier
バックエンド
- Ruby 2.5.7
- Ruby on Rails 5.2.4
- rubocop
- guard
- rspec
- rack-cors
CI/CD
- Circle CI
フロントエンド編
React とは
A JavaScript library for building user interfaces
Facebookの作ったJavascriptライブラリです。
MVC(Model View Controller)モデルでいうとVの部分を提供しています。Reactの考え方
Component-Based
Build encapsulated components that manage their own state, then compose them to make complex UIs.Reactでは、画面をComponentの親子関係を持つツリーとして構成します。
各コンポーネントは、親から渡されたprops(プロパティ)、State(自分の状態)を元に、renderメソッドでDOM(Virtual DOM)を生成します。axios とは
Promise based HTTP client for the browser and node.js
https://github.com/axios/axios
Http通信を簡単に行うことができるJavascriptライブラリです。
主な特徴としては、
- ChromeやFirefox, Safariなどのブラウザに対応
- リクエストとレスポンスデータを変換
- Promise APIをサポート
と言った特徴があります。
今回は、このライブラリを使ってAPIとの通信を行なっていきます。
実装
以下がコードになります。
import React from 'react'; import axios from 'axios'; const Title = ({todoCount}) => { return ( <div> { todoCount > 0 ? <h1>{todoCount}つのタスクがあります</h1> : <h1>タスクがありません</h1> } </div> ); } const TodoForm = ({addTodo}) => { let input; return ( <form onSubmit={(e) => { e.preventDefault(); addTodo(input.value); input.value = ''; }}> <input className="form-control col-md-12" ref={node => { input = node; }} /> <br /> </form> ); }; const Todo = ({todo, remove}) => { return ( <li> <a href="#" className="list-group-item">{todo.contents}</a> <button onClick={() => {remove(todo.id)}}>削除</button> </li> ); } const TodoList = ({todos, remove}) => { const todoNode = todos.map((todo) => { return (<Todo todo={todo} key={todo.id} remove={remove} />) }); return (<div className="list-group" style={{marginTop:'30px'}}>{todoNode}</div>); } window.id = 0; class TodoApp extends React.Component{ constructor(props){ super(props); this.state = { data: [] } this.apiUrl = 'http://localhost:3000/api/todos/' } componentDidMount(){ axios.get(this.apiUrl) .then((res) => { this.setState({ data: res.data }); }); } addTodo(val){ const todo = {contents: val} axios.post(this.apiUrl, todo) .then((res) => { this.state.data.push(res.data); this.setState({data: this.state.data}); }); } handleRemove(id){ const remainder = this.state.data.filter((todo) => { if(todo.id !== id) return todo; }); axios.delete(this.apiUrl+id) .then((res) => { this.setState({data: remainder}); }) } render(){ return ( <div> <Title todoCount={this.state.data.length}/> <TodoForm addTodo={this.addTodo.bind(this)}/> <TodoList todos={this.state.data} remove={this.handleRemove.bind(this)} /> </div> ); } } export default TodoApp上記のコードを見てもらうと分かりやすいと思うのですが、
TodoApp
コンポーネントにconstructor
メソッド、componentDidMount
メソッド,render
メソッドを定義しています。componentDidMount
メソッド,render
メソッドは、Reactのライフサイクルメソッドの一種です。componentDidMount
メソッドは、出力が DOM にレンダーされた後に実行されます。ここではライフサイクルの話は割愛しますので、気になる方は下記リンクを参考にしてみてください。
- https://ja.reactjs.org/docs/state-and-lifecycle.html
- https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9
で、各関数コンポーネントに、propsを渡し、その値に応じた描画を行なうという流れになっております。
APIの疎通に関しては、
axios.getや
axios.postで行なって、APIからのレスポンスデータを取得したりしています。
で、実際に見てみると、初期の出力結果が、こうなります。(めっちゃ殺風景・・・。)
で入力欄に値を入力してEnterを押すと、タスクが追加できて、削除までできます。
本当は最低限CRUDの実装はしたかったのですが、諸々の事情により、updateの機能は後日追記いたします。
バックエンド編
Ruby on Rails とは
Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern.
https://github.com/rails/rails
Model-view-controller パターンを採用したフレームワークです。
特にActiceRecordは個人的には強力な機能だと思っています。今回はView側はReactで実装していますので、RailsのAPIモードを使って実装しました。
APIモードは、下記のようにコマンドライン引数に
--api
とつけるだけで、できてしまいます(さすがRails!)rails new my_api --apiAPIモード
https://guides.rubyonrails.org/api_app.htmlActive Record とは
Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.
https://guides.rubyonrails.org/active_record_basics.html
MVCモデル言うところの、Mの部分に相当するものです。
ORM(O/Rマッピング)システムに記述されている「Active Recordパターン」を実装したもので、このパターンと同じ名前が付けられています。ORM(O/Rマッピング)とは、アプリケーションが持つオブジェクトを、RDBMSのテーブルに接続することです。
ORMを用いると、SQL文を書かずに、簡潔なコードだけで、テーブルのレコードのデータを取得できたり、更新できたりしてとても便利です。例えば、ターミナル上で、
rails consoleを叩き
Todo.allなどとすると、下記のような出力結果を得ることができます。
またワンライナーでも書くことができるので、
SQL文を書くより遥かに楽だと言うことが分かると思います。他にも
- モデル同士のアソシエーションを表現する
- 関連付けられているモデル間の継承階層を表現する
- データをデータベースで永続化する前にバリデーションを行う
などの特徴を持っています。
RuboCop とは
RuboCop is a Ruby static code analyzer and code formatter.
https://github.com/rubocop-hq/rubocop
rubyのコードアナライザーであり、フォーマッターです。
例えば、チーム開発をするときにコード規約を定めると思うのですが、RuboCopを用いると捗ります。
また、警告を出すのみに止まらず、いくつかの問題を自動で直してくれることもしてくれます。
例えば、ターミナル上で下記のコマンドを実行すると、一括でフォーマットをかけてくれたりしますので、とても便利です。bundle exec rubocop -a参考
https://rubocop.readthedocs.io/en/stable/実装
実装はとても簡単で、ターミナル上で、下記のコマンドをバーン!すると、
ルーティングやコントローラー、モデルからテーブル定義までの雛形を作ってくれます。bundle exec rails generate scaffold Todo contents:string実際のコード
controllers/todos_controller.rb
class TodosController < ApplicationController before_action :set_todo, only: [:show, :edit, :update, :destroy] def index @todos = Todo.all render json: @todos end def show render json: @todo end def new @todo = Todo.new end def edit end def create @todo = Todo.new(create_params) if @todo.save render json: @todo else render :new end end def update if @todo.update!(update_params) render json: @todo else render :edit end end def destroy @todo.destroy render json: @todos end private def set_todo @todo = Todo.find(params[:id]) end def create_params params.require(:todo).permit(:contents) end def update_params params.require(:todo).permit(%i[id contents]) end def destroy_params params.require(:todo).permit(:id) end end
config/routes.rb
Rails.application.routes.draw do scope :api, defaults: { format: :json } do resources :todos, only: %i[show index create update destroy] end end
models/todo.rb
class Todo < ApplicationRecord def as_json(options = {}) super(options.reverse_merge(except: %i[created_at updated_at])) end end
db/schema.rb
ActiveRecord::Schema.define(version: 2019_12_15_104719) do create_table "todos", force: :cascade do |t| t.string "contents" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end今回のAPIの設計方針として、REST APIを採用しました。
REST APIについては、下記を参照してみてください。
https://restfulapi.net/CI編
後日書きます
まとめ
半年間、自分が使ってきた技術を用いて、アプリーケーションを開発するのは良い振り返りにもなるし、一年の節目にも良さそうと思いました。今後も、何か学ぶ機会があれば、やってみるといいかもと思いました。
実はまだまだ書きたいこともあるので、後日追記するか、別の記事に載せることにします。その他の参考資料
https://qiita.com/matzkoh/items/90baab22ad489b78384b
https://restful-api-guidelines-ja.netlify.com/
https://scotch.io/tutorials/create-a-simple-to-do-app-with-react
- 投稿日:2019-12-21T23:55:56+09:00
React+Railsでモダン()なTODOアプリを作ってみた 〜備忘録も兼ねて〜
Opt Technologies Advent Calendar 2019の21日目の記事です。
はじめに
2019年4月に株式会社オプトに入社したsh1okohと申します。
なんやかんやありまして、今はReact, Redux, Railsなどを使ったプロダクトで、日々奮闘しております。
今回は、お仕事中に出会った技術スタックを使ってモダン()なTODOアプリを作ってみたので、各種ライブラリの紹介をしつつ、Todoアプリの紹介をしていきたいと思います。(備忘録的な目的もあります)ちなみに、今回のTODOアプリのソースコードは下記リンクにありますので、
興味のある方はみてみてください。https://github.com/sh1okoh/adventcalendar
対象読者
今回は、テーマ的にも内容自体は広く浅くになっております。
各種ライブラリを使ったSPAの全体像を把握したい方などを対象としており、各ライブラリを深く学びたいといった方は対象外となっておりますので、よろしくお願いします。今回使用した技術
フロントエンド
- JavaScript (業務ではTypeScriptを使っています)
- React
Redux- axios
- eslint
- prettier
バックエンド
- Ruby 2.5.7
- Ruby on Rails 5.2.4
- rubocop
- guard
- rspec
- rack-cors
CI/CD
- Circle CI
フロントエンド編
React とは
A JavaScript library for building user interfaces
Facebookの作ったJavascriptライブラリです。
MVC(Model View Controller)モデルでいうとVの部分を提供しています。Reactの考え方
Component-Based
Build encapsulated components that manage their own state, then compose them to make complex UIs.Reactでは、画面をComponentの親子関係を持つツリーとして構成します。
各コンポーネントは、親から渡されたprops(プロパティ)、State(自分の状態)を元に、renderメソッドでDOM(Virtual DOM)を生成します。axios とは
Promise based HTTP client for the browser and node.js
https://github.com/axios/axios
Http通信を簡単に行うことができるJavascriptライブラリです。
主な特徴としては、
- ChromeやFirefox, Safariなどのブラウザに対応
- リクエストとレスポンスデータを変換
- Promise APIをサポート
と言った特徴があります。
今回は、このライブラリを使ってAPIとの通信を行なっていきます。
実装
以下がコードになります。
import React from 'react'; import axios from 'axios'; const Title = ({todoCount}) => { return ( <div> { todoCount > 0 ? <h1>{todoCount}つのタスクがあります</h1> : <h1>タスクがありません</h1> } </div> ); } const TodoForm = ({addTodo}) => { let input; return ( <form onSubmit={(e) => { e.preventDefault(); addTodo(input.value); input.value = ''; }}> <input className="form-control col-md-12" ref={node => { input = node; }} /> <br /> </form> ); }; const Todo = ({todo, remove}) => { return ( <li> <a href="#" className="list-group-item">{todo.contents}</a> <button onClick={() => {remove(todo.id)}}>削除</button> </li> ); } const TodoList = ({todos, remove}) => { const todoNode = todos.map((todo) => { return (<Todo todo={todo} key={todo.id} remove={remove} />) }); return (<div className="list-group" style={{marginTop:'30px'}}>{todoNode}</div>); } window.id = 0; class TodoApp extends React.Component{ constructor(props){ super(props); this.state = { data: [] } this.apiUrl = 'http://localhost:3000/api/todos/' } componentDidMount(){ axios.get(this.apiUrl) .then((res) => { this.setState({ data: res.data }); }); } addTodo(val){ const todo = {contents: val} axios.post(this.apiUrl, todo) .then((res) => { this.state.data.push(res.data); this.setState({data: this.state.data}); }); } handleRemove(id){ const remainder = this.state.data.filter((todo) => { if(todo.id !== id) return todo; }); axios.delete(this.apiUrl+id) .then((res) => { this.setState({data: remainder}); }) } render(){ return ( <div> <Title todoCount={this.state.data.length}/> <TodoForm addTodo={this.addTodo.bind(this)}/> <TodoList todos={this.state.data} remove={this.handleRemove.bind(this)} /> </div> ); } } export default TodoApp上記のコードを見てもらうと分かりやすいと思うのですが、
TodoApp
コンポーネントにconstructor
メソッド、componentDidMount
メソッド,render
メソッドを定義しています。componentDidMount
メソッド,render
メソッドは、Reactのライフサイクルメソッドの一種です。componentDidMount
メソッドは、出力が DOM にレンダーされた後に実行されます。ここではライフサイクルの話は割愛しますので、気になる方は下記リンクを参考にしてみてください。
- https://ja.reactjs.org/docs/state-and-lifecycle.html
- https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9
で、各関数コンポーネントに、propsを渡し、その値に応じた描画を行なうという流れになっております。
APIの疎通に関しては、
axios.getや
axios.postで行なって、APIからのレスポンスデータを取得したりしています。
で、実際に見てみると、初期の出力結果が、こうなります。(めっちゃ殺風景・・・。)
で入力欄に値を入力してEnterを押すと、タスクが追加できて、削除までできます。
本当は最低限CRUDの実装はしたかったのですが、諸々の事情により、updateの機能は後日追記いたします。
バックエンド編
Ruby on Rails とは
Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern.
https://github.com/rails/rails
Model-view-controller パターンを採用したフレームワークです。
特にActiceRecordは個人的には強力な機能だと思っています。今回はView側はReactで実装していますので、RailsのAPIモードを使って実装しました。
APIモードは、下記のようにコマンドライン引数に
--api
とつけるだけで、できてしまいます(さすがRails!)rails new my_api --apiAPIモード
https://guides.rubyonrails.org/api_app.htmlActive Record とは
Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.
https://guides.rubyonrails.org/active_record_basics.html
MVCモデル言うところの、Mの部分に相当するものです。
ORM(O/Rマッピング)システムに記述されている「Active Recordパターン」を実装したもので、このパターンと同じ名前が付けられています。ORM(O/Rマッピング)とは、アプリケーションが持つオブジェクトを、RDBMSのテーブルに接続することです。
ORMを用いると、SQL文を書かずに、簡潔なコードだけで、テーブルのレコードのデータを取得できたり、更新できたりしてとても便利です。例えば、ターミナル上で、
rails consoleを叩き
Todo.allなどとすると、下記のような出力結果を得ることができます。
またワンライナーでも書くことができるので、
SQL文を書くより遥かに楽だと言うことが分かると思います。他にも
- モデル同士のアソシエーションを表現する
- 関連付けられているモデル間の継承階層を表現する
- データをデータベースで永続化する前にバリデーションを行う
などの特徴を持っています。
RuboCop とは
RuboCop is a Ruby static code analyzer and code formatter.
https://github.com/rubocop-hq/rubocop
rubyのコードアナライザーであり、フォーマッターです。
例えば、チーム開発をするときにコード規約を定めると思うのですが、RuboCopを用いると捗ります。
また、警告を出すのみに止まらず、いくつかの問題を自動で直してくれることもしてくれます。
例えば、ターミナル上で下記のコマンドを実行すると、一括でフォーマットをかけてくれたりしますので、とても便利です。bundle exec rubocop -a参考
https://rubocop.readthedocs.io/en/stable/実装
実装はとても簡単で、ターミナル上で、下記のコマンドをバーン!すると、
ルーティングやコントローラー、モデルからテーブル定義までの雛形を作ってくれます。bundle exec rails generate scaffold Todo contents:string実際のコード
controllers/todos_controller.rb
class TodosController < ApplicationController before_action :set_todo, only: [:show, :edit, :update, :destroy] def index @todos = Todo.all render json: @todos end def show render json: @todo end def new @todo = Todo.new end def edit end def create @todo = Todo.new(create_params) if @todo.save render json: @todo else render :new end end def update if @todo.update!(update_params) render json: @todo else render :edit end end def destroy @todo.destroy render json: @todos end private def set_todo @todo = Todo.find(params[:id]) end def create_params params.require(:todo).permit(:contents) end def update_params params.require(:todo).permit(%i[id contents]) end def destroy_params params.require(:todo).permit(:id) end end
config/routes.rb
Rails.application.routes.draw do scope :api, defaults: { format: :json } do resources :todos, only: %i[show index create update destroy] end end
models/todo.rb
class Todo < ApplicationRecord def as_json(options = {}) super(options.reverse_merge(except: %i[created_at updated_at])) end end
db/schema.rb
ActiveRecord::Schema.define(version: 2019_12_15_104719) do create_table "todos", force: :cascade do |t| t.string "contents" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end今回のAPIの設計方針として、REST APIを採用しました。
REST APIについては、下記を参照してみてください。
https://restfulapi.net/CI編
後日書きます
まとめ
半年間、自分が使ってきた技術を用いて、アプリーケーションを開発するのは良い振り返りにもなるし、一年の節目にも良さそうと思いました。今後も、何か学ぶ機会があれば、やってみるといいかもと思いました。
実はまだまだ書きたいこともあるので、後日追記するか、別の記事に載せることにします。その他の参考資料
https://qiita.com/matzkoh/items/90baab22ad489b78384b
https://restful-api-guidelines-ja.netlify.com/
https://scotch.io/tutorials/create-a-simple-to-do-app-with-react
- 投稿日:2019-12-21T23:51:00+09:00
kintoneを早くて変化に強いシステムにするために
この記事は「kintone Advent Calendar 2019」の21日目の記事です!
株式会社AISICの久米です。
福岡でkintoneエバンジェリストしています。
年一のQiita投稿で恐縮ですが、今年も書いてみます^^テーマ的には、昨年の記事と似ていますが、より整理を進めてみました!
弊社のkintone開発の定石を、テンプレート的にまとめてみましたので、皆さんと共有して、よりイケてるkintone生活を送っていただきたく思います!
kintoneは上手に作らなければ『早い』だけで、変化に『弱い』システムになる
「kintoneは、早い!簡単!」って言いすぎですよね。
いや、確かに「早い・簡単」ではあるんですよ。
会社への導入にしても、アプリを作るにしても、それほどの知識が無くてもできちゃう辺りは、「早い・簡単」以外の何物でもないとは思うわけです。
そして、運用に合わせて、アプリを自分たちで簡単に変更できるのもスゴクいいところ。これも間違いない。
最初はアプリ単体での活用だったのが、数ヶ月経ってくると、複数アプリが連携する事の簡単さや強力さに感動しながら、バンバン盛り上がったりするわけです。で、「そろそろ、バックアップとか考えないとねー」とか「特定の顧客ごとに年度毎に集計したいよねー」とか「開発環境って別にできた方がいいよね!」とかなってきたときに、「あれ??なんか無理ゲー」みたいになるのです。
簡単に『変わる』ために『骨』を作るべし
単に「作る」だけであれば、色々と考えなくても作りたいように作ってしまえばいいのですが、「容易に変わる・変える・変え続ける」ためには、システムとしての『骨』をちゃんと使っておくことが肝要です。
あまり意識せずにkintoneでアプリを作ってしまうと、この『骨』が作れないので、結果的に「変化に弱い」アプリになってしまうのです。では、kintoneにおける変化に強いとは??
- 拡張・連携したいと思った時に、容易に拡張・連携できるようにすること
- データのバックアップ(CSV・Excel)から一括更新(リストア)ができること
アプリを作る段階では1点目が重要ですが、運用しながら変更をしていくときには2点目が重要です。
システムの『骨』をkintoneに通すという事
他アプリから利用される・連携されることを前提とした下記の様なフィールドを各アプリに設けておくとよいです。後からこれらのフィールドを追加しようとすると、当該フィールドへのデータの更新など必要以上に手間が掛かり、迅速なアプリの更新ができなくなります。
uid
ルックアップキー
レコードタイトル
No
カスタマイズテンプレートを使ってみるといいですよ!
上記の様な事を実現するには、ガッツリとjavascriptでのプログラミングが必要になります。ですが、プログラミングに不慣れな方が実現するのはかなり難しい。なので、上記の様なアプリをテンプレートとしてダウンロードいただけるようにしておりますので、そちらをお使いいただけるとよいかと思います。
このアプリをベースに、必要な項目を自分なりに追加していただければ、おのずと『骨』の通ったシステムになります。
こちらからダウンロード
カスタマイズアプリテンプレート
ダウンロードした「カスタマイズアプリテンプレート.zip」を使ってkintoneにアプリを追加する
必要に応じてアプリテンプレートとして登録してもよいかもです。
注意事項
今回ご紹介したカスタマイズアプリテンプレートは、javascriptカスタマイズをしていますので、スタンダードプランでのみ利用できます。
最後に
こういうアプリをベースにユーザー側でアプリを作っていただければ、ハイレベルなカスタマイズをしたいと思った時にでも、弊社の様なkintoneカスタマイズを請け負っている企業にスムーズに依頼・実装出来ると思います!
このテンプレートを使ってアプリを作成し、変化に強い・変化し続けるkintoneアプリを使いながら、チームワークやビジネスを素早く確実に進めていただければと思います!
追記というかお知らせ
「カスタマイズアプリテンプレート」でも利用しているkintone_toolsも作っています。kintoneのjavascriptカスタマイズをしていくときに、あったらいいなぁとライブラリです。
皆さんからのアイデアも欲しいですし、一緒に開発を進めていただける方も絶賛募集中ですので、ぜひご協力いただけると嬉しいです!
- 投稿日:2019-12-21T23:39:08+09:00
Javascriptメモ④
復習も兼ねて一日一投稿してます。
簡単なメモです。for文
繰り返しが決まっている時はfor文
繰り返しが決まってない場合はwhile文for(初期値;条件式;増減式){
繰り返し処理
}for(i=0;i<100;i++){ documen.write(i); } const list=['toypoo','pome','siba']; for(const i=0;i<list.length;i ++){ document.write(list[i]); }このように配列と繰り返し処理はよく使います。
for(const i=o;i<10;i++){ if(i===5){ break; } document.write(i); }breakを使うことで処理を終了できます。
for(const i=o;i<10;i++){ if(i===5){ continue; } document.write(i); }iが5の時は処理がスキップされます。
continueを使うと実行中の処理を中断し次の処理へ進みます。
while文
繰り返し回数が決まってない場合はwhile文です。
無限ループは避けましょう。while(条件式){ 繰り返し処理 }条件式がtrueである限りずっと繰り返しますので制限をつけましょう。
do{ 繰り返し処理 }while(条件式)こちらのdo whileはとりあえず先に処理を行います。その後条件式が真であれば処理を続けるというものです。
const count = 0; while(count<100){ document.write(count); count++; } do{ document.write(count); count++; }while(count<10);一旦処理を行いたい場合はdo whileです。
Dateオブジェクト
DateオブのジェクトはJSの組み込み関数の一つです。
他の組み込み関数には
String,Number,Booleann,Array,Date,Mathなどがあります。//年を取得します。 const day = new Date(); console.log(day.getFullYear()); //付きを取得します console.log(day.getMonth()+1); //+1することを忘れずに。 //日付です。 console.log(day.getDate()); //曜日です。 console.log()day.getDay()); //年月日曜日を一気に取得してみます。 console.log(day.getFullYear()+'/'+day.getMonth()+1+'/'+day.getDate()+'/'+day.getDay()); //ゼロパディング処理をして日時を取得します。 const day = new Date(); day.getHours()+'時'+today.getMinutes()+'分'+('0'+today.getSeconds()).slice(-2)+'秒';
- 投稿日:2019-12-21T21:56:45+09:00
2019年フレームワークのトレンドが見れるサイトの紹介
hotframeworks.com
URLはこちらになります。
http://hotframeworks.com/
- 投稿日:2019-12-21T21:48:33+09:00
Node.jsを使ったローカルHTTPSサーバーを走らせる
目的
OAuth2認可を使ってオンラインサービスと連携させることができるコマンドラインツールを作成中です。ユーザー認証後に認可コードをこのツールで受け取りたいと思っているので、
127.0.0.1
で走るHTTPSサーバーをリダイレクト先として用意したい。
その検証として、まずローカルHTTPSサーバーをNode.jsで走らせてみました。言語は実際の使う予定のTypeScriptとなっています。何を使う?
- Node.js
- httpsモジュール
- TypeScript
準備
まずはHTTPで地ならし
私はNode.jsローカル環境ではHTTPサーバーすらを立てた経験がないので、まずはHTTPから試してみる。
HTTPサーバーならばこれでOK。import http from 'http'; const PORT = 9090; const HOST = '127.0.0.1'; function startHttpServer(portNumber: number, hostName: string): Promise<http.IncomingMessage> { return new Promise((resolve, reject)=>{ http.createServer((request, response)=>{ response.writeHead(200, { 'Content-Type': 'text/plain' }); response.end(`welcome to local page!`); resolve(request); }).listen(portNumber, hostName); }); } (async (portNumber, hostName)=>{ const req = await startHttpServer(portNumber, hostName); })(PORT, HOST);単純にHTTP -> HTTPSで置き換えてやってみる
単純にhttpモジュールをhttpsモジュールに置き換えて見ました。結論から言いますと以下のコードは機能しません。
単純な置き換えでは機能しない。import https from 'https'; import { IncomingMessage } from 'http'; const PORT = 9090; const HOST = '127.0.0.1'; function startHttpServer(portNumber: number, hostName: string): Promise<IncomingMessage> { return new Promise((resolve, reject)=>{ https.createServer((request, response)=>{ response.writeHead(200, { 'Content-Type': 'text/plain' }); response.end(`welcome to local HTTPS page!`); resolve(request); }).listen(portNumber, hostName); }); } (async (portNumber, hostName)=>{ const req = await startHttpServer(portNumber, hostName); })(PORT, HOST);何が必要なのか?
公式ドキュメントを読んでみます。目につくのが
options
で指定している。keyとcertです。https.createServer([options][,requestListener])const options = { key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'), cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem') };keyとcertはそれぞれ何かと言うと、同じく公式ドキュメントのTLSモジュールをみると下のように書いてありました。PEMフォーマットのプライベートキーですね。
key <string> | <string[]> | <Buffer> | <Buffer[]> | <Object[]>
Private keys in PEM format. PEM allows the option of private keys being encrypted. Encrypted keys will be decrypted with options.passphrase. Multiple keys using different algorithms can be provided either as an array of unencrypted key strings or buffers, or an array of objects in the form {pem: [, passphrase: ]}. The object form can only occur in an array. object.passphrase is optional. Encrypted keys will be decrypted with object.passphrase if provided, or options.passphrase if it is not.certについて上記のページに下の説明があります。PEMフォーマットの証明書ですね。
cert <string> | <string[]> | <Buffer> | <Buffer[]>
Cert chains in PEM format. One cert chain should be provided per private key. Each cert chain should consist of the PEM formatted certificate for a provided private key, followed by the PEM formatted intermediate certificates (if any), in order, and not including the root CA (the root CA must be pre-known to the peer, see ca). When providing multiple cert chains, they do not have to be in the same order as their private keys in key. If the intermediate certificates are not provided, the peer will not be able to validate the certificate, and the handshake will fail.幸いMacには元々
openssl
が入っていたので、これを使ってプライベートキーと証明書を作成します。
こちらのブログ、「秘密鍵、公開鍵、証明書、CSR生成のOpenSSLコマンドまとめ」 が大変参考になりました。このブログにあった「秘密鍵と自己署名証明書を一括で作成したい」を使います。
対話形式で入力する組織情報はどれが必須で、どれが省略可能なのかまでは調べていません。全部省略したらエラーになってしまいました。プライベートキーと証明書の作成$ openssl req -x509 -sha256 -nodes -days 9999 -newkey rsa:2048 -keyout localhttps_key.pem -out localhttps_cert.pem Generating a 2048 bit RSA private key ............................................+++ ...+++ writing new private key to 'localhttps_key.pem' ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) []:JP State or Province Name (full name) []:Tokyo Locality Name (eg, city) []:Tokyo Organization Name (eg, company) []:FiftyFifty Organizational Unit Name (eg, section) []:FortyNine Common Name (eg, fully qualified host name) []:localhost Email Address []:.このコマンドが成功すると、
localhttps_key.pem
と言うプライベートキーとlocalhttps_cert.pem
と言う証明書ファイルが生成されます。機能するコード例
プライベートキーと証明書とをオプションで指定したら、HTTPSでアクセスできるようになりました。
実際にアクセすると、Chromeブラウザは強い警告を出してきますが、許可すればなんとかアクセスはできます。機能するコードimport fs from 'fs'; import https from 'https'; import { IncomingMessage } from 'http'; const PORT = 9090; const HOST = '127.0.0.1'; const options = { key: fs.readFileSync('./localhttps_key.pem'), cert: fs.readFileSync('./localhttps_cert.pem') } function startHttpServer(portNumber: number, hostName: string): Promise<IncomingMessage> { return new Promise((resolve, reject)=>{ https.createServer(options, (request, response)=>{ response.writeHead(200, { 'Content-Type': 'text/plain' }); response.end(`welcome to local HTTPS page!`); resolve(request); }).listen(portNumber, hostName); }); } (async (portNumber, hostName)=>{ const req = await startHttpServer(portNumber, hostName); console.log(`${JSON.stringify(req.headers)}`); })(PORT, HOST);
- 投稿日:2019-12-21T20:10:01+09:00
年末まで毎日webサイトを作り続ける大学生 〜64日目 入力文字をランダムな位置にランダムなサイズで表示する〜
はじめに
こんにちは!@70days_jsです。
文字を入力したら、ランダムな位置にランダムな大きさで表示されます。gif↓
64日目。(2019/12/21)
よろしくお願いします。サイトURL
https://sin2cos21.github.io/day64.html
やったこと
キーダウンをイベントリスナーで捉えて、入力文字は一つずつオブジェクトを作っています。
50文字打ち込んだら全てのオブジェクトが消える仕組みになっています。html↓
<body> <div id="countWrapper">COUNT: <span id="count"></span></div> <canvas id="canvas"></canvas> </body>css↓
body { height: 100vh; margin: 0; background-color: black; display: flex; justify-content: center; align-items: center; flex-direction: column; } #canvas { border: solid 1px rgba(31, 227, 13, 1); } #countWrapper { color: rgba(31, 227, 13, 1); padding-bottom: 20px; }JavaScript(61行)↓
let canvas = document.getElementById("canvas"), ctx = canvas.getContext("2d"), twidth = (canvas.width = 500), theight = (canvas.height = 500), chars = [], count = document.getElementById("count"), fontSize = Math.random() * 100, countNumber = 0; function Character(ctx, e) { this.ctx = ctx; this.x = Math.floor(Math.random() * twidth); this.y = Math.floor(Math.random() * theight); this.size = fontSize; this.char = String.fromCharCode(e.keyCode); } Character.prototype.render = function() { this.draw(); }; Character.prototype.draw = function() { let ctx = this.ctx; ctx.beginPath(); ctx.fillStyle = "rgba(31, 227, 13, .5)"; ctx.font = this.size + "px Arial"; ctx.fill(); ctx.fillText(this.char, this.x, this.y); ctx.closePath(); }; //_________keydown_________; document.addEventListener("keydown", logKey, false); function logKey(e) { if (countNumber >= 50) { countNumber = 0; chars.length = 0; alert("Characters are not eternal."); } else if (e.keyCode < 65 || e.keyCode > 91) { return false; } else { let char = new Character(ctx, e); chars.push(char); countNumber++; count.innerHTML = countNumber; } } function render() { fontSize = Math.random() * 100; ctx.clearRect(0, 0, twidth, theight); for (var i = 0; chars.length > i; i++) { let char = chars[i]; char.render(); } requestAnimationFrame(render); } render();function Character(ctx, e) {...}
↑これはコンストラクターです。
位置、サイズ、文字を入れています。
色々詰め込んだ後に、コンストラクターの役割ってなんだっけと悩みました。
コンストラクターでオブジェクト固有の値を入れるのは、どうなんだろう・・・?このオブジェクトはrender()メソッドを通して、draw()メソッドを実行します。
メソッドの数が増えたときのためにrender()メソッドを介しています。render関数(メソッドの方じゃないよ)はfontsizeをランダムな値に再設定しています。
fontSize = Math.random() * 100;
(あれ、もしかしてこれもコンストラクターでやればよかった・・・?)
これはキャンバスをまっさらにしています。↓
ctx.clearRect(0, 0, twidth, theight);
for文で各オブジェクトのメソッドを実行しています。↓
for (var i = 0; chars.length > i; i++) {
let char = chars[i];
char.render();
}感想
オレオレ流でやりすぎたのを反省した一日でした。
オブジェクト指向プログラミングの本をちゃんと読まなきゃな。最後まで読んでいただきありがとうございます。明日も投稿しますのでよろしくお願いします。
- 投稿日:2019-12-21T17:59:21+09:00
【jRumble】国産OSSのプリザンターを震え上がらせてみた!
震えさ~す!!?
今日はjQueryのプラグインであるjRumbleを利用してプリザンターをガクガクブルブル震えあがらせてみようと思います。
プリザンターとは
プリザンターとは株式会社インプリムが開発しているオープンソースのWebデータベースです。
データ管理やら業務アプリがマウス操作だけ(ノンプログラミング)でサクッと作れてしまう超有能なプラットフォーム(しかもOSSなので無料という!)なのですな。
オープンソースで脱エクセル!Pleasanter公式サイト
ということで、早速このプリザンターを震え上がらせていこうと思います。jRumbleのダウンロード
jRumbleは以下のサイトよりダウンロードします。
jRumbleの使い方
使い方は簡単です。
下記のサンプルは特定のセレクタをマウスホバーした際に震え上がらせる内容になっています。サンプル.js$('セレクタ').jrumble(); $('セレクタ').hover(function () { $(this).trigger('startRumble'); }, function () { $(this).trigger('stopRumble'); });プリザンターを震えさせる!!
それでは実際にプリザンターを震え上がらせます!(^^)!
まずはダウンロードしたjquery.jrumble.1.3.min.jsをプリザンターの「Scripts/Plugins」にセットします。
その後、スクリプトタグに以下のスクリプトをセットしてください。
ガクブル.jsvar script = document.createElement("script"); script.setAttribute("src", "/scripts/plugins/jquery.jrumble.1.3.min.js"); document.getElementsByTagName("Canvas")[0].appendChild(script); $(function () { function Gakuburu(id) { $('#' + id).jrumble(); $('#' + id).hover(function () { $(this).trigger('startRumble'); }, function () { $(this).trigger('stopRumble'); }); } Gakuburu('CorpLogo'); Gakuburu('Application'); });これでプリザンターはガクブルです。
いや~、世の中には面白いプラグインがたくさんあるものです。
また色々面白そうなプラグインを見つけて触っていこうと思います。
ではでは!また!
- 投稿日:2019-12-21T17:55:10+09:00
Google のクライアントライブラリで認証するときにアカウントを切り替える方法
はじめに
Google のクライアントライブラリ (https://apis.google.com/js/api.js) を使って OAuth 認証するサンプルは多くありますが、複数アカウントを切り替える方法の記事が少なかったので記載します。
実現方法
認証時にオプションを 1 つ設定するだけです。
const options = new gapi.auth2.SigninOptionsBuilder(); options.setPrompt('select_account'); gapi.auth2.getAuthInstance().signIn(options);これを指定しないと、初回のログイン時はポップアップが表示されてアカウント選択できますが、アカウントを切り替えようと思ってログアウトして再ログインしても、前回認証したアカウントで自動ログインするため、ポップアップが表示されず、アカウントの切り替えができませんでした。
ドキュメントに書いてある通りでした。
宣伝
記載内容は、https://note.aprifield.com (Markdown でメモを取るサービス) を開発中に調査した内容です。
- 投稿日:2019-12-21T17:42:57+09:00
最新のWeb技術でIoTをする
この記事の内容はJSConf JPで話した内容です.
資料全体はこちらにあります。アジェンダ
IoTのはじめかた
とりあえずIoTをやりたい人へ
IoTとはインターネットとThingsがつながることです。
ざっくりと書くとこんな構成ですね。
この右側のアイコンは皆さんよく知ってるサービスだったりシステムだったりするのではないでしょうか。
メールもslackもよく使いますよね。逆に左側のセンサ/アクチュエーターはほとんど知らないのではないでしょうか。
直線距離を図る距離センサや、人の有無がわかる人感センサ、物を動かすことができるモーターなどがあります。IoTをやろう!(というか何かしらハードウェアを作って物理世界に関わろう)とすると、ここのThingsの知識がなくてなかなか難しいことが多いと思います
でもじつはとっても身近なThingsがあります
というわけで、JavaScriptでIoTをしたいな!とおもってる人はSmartPhoneのプログラムを書いてみるとIoTをすることができます。
完
・・・というのも寂しいので、もう少しお話しします。
カスタムハードウェアを作りたくなるとき
スマートフォンも十分IoTですが、それじゃ物足りなくなってくるときがあります
- 価格が高い
ex) 1台数万円してしまう- 機能が多すぎる
ex) 画面はいらない- 機能が足りない
ex) 物理ボタンがない/温度が取れない専用ハードウェアといってもCPUやセンサを選ぶだけで、
それらをつなげて完成させることが多いです。
自作PCみたいなものですねパーツのカテゴリもだいたい決まっています。
この中からCPUはどれ、センサはどれ、通信方式はどれ・・・といった形で選んでいきます。
重要なのはCPUで、他のところは後から変更がききやすいですが、ここだけは最初に決めたものから変更するのはおお仕事になります。
JavaScriptでIoTするときの選択肢
そのCPUを選んだときにJavaScriptができるCPUなのかどうかが決まります。
基本的にはC言語しか使えないCPUが多い(メモリ容量とかの問題)ですが、
最近はJavaScriptでもかけるCPUが出てきています。
JavaScriptでかけるCPUとしてはESP32とobnizというものになります。
この2つはどちらもJavaScirptでプログラムを書くことができますが、
JavaScriptが動作する場所が違います。・・・動作する場所がちがうというと、普段でもなにか似たようなことを言ってるのがありますよね
- Server Side JavaScript
- Client Side JavaScript
この2つの違いも、Server Side / Client Side と同じ概念で、稼働場所が違います。
ESP32 はクライアントサイドJavaScript
CPU上でJavaScriptが動くのがESP32です。
クライアント側で動いている分、高速な応答(マイクロ秒単位での反応とか)ができる代わりに、
プログラムの更新や高いCPUスペックを求めるなどのデメリットがあります。このブラウザだとこの関数が動かない・・・!みたいなこともあります。
obniz はサーバーサイドJavaScript
サーバー側で動くのがobnizです。
サーバーで動くのでサーバーのJavaScriptバージョンに依存してできることが変動します。
都度通信が発生するので応答性が少々悪くなる(ミリ秒単位での反応)になりますが、自由でフレキシブルなプログラムが可能です。サーバーと言っていますが、スマートフォンをサーバーにしてHTMLで動かす、ということもできます。
Webを活用したIoTができるobnizとは
ここではサーバーサイドJavaScriptのobnizでIoTをやる方法を説明します。
手順は
1. カスタムハードウェアを作成する
2. HTML/JavaScriptプログラムを書く
です。いくつか具体例を書いていきます。
温度取得やLED点灯が1関数で終わるので、あとはセンサやアクチュエーターがどんな物があるかわかれば作れそうですよね。
センサの知識
センサ/アクチュエーターの知識は、事例を見ながら学ぶのがわかりやすいかなと思います、
トイレの人の有無を確認して空き個室管理
人感センサという人の動きを検知するセンサーを使って、空いているかどうか判定しています。
あくまで"動き"なので、ずっと止まっている人がいても、いない判定をしますマリオコイン
ゲームをやって、ゲーム内でコインが出てきたら実際に500円玉が出てくる!というものです。
コインを押し出すためにサーボモーターというモーターを使っています4足歩行ロボット
その1
その2
こちらのサーボモーターというものを使っています。
4つの足を4つのサーボモーターで作り、うまく足を動かすことで歩行しています鉄道模型
ミニ四駆でおなじみのDCモーターをつかって動かしています。ほかにもQiita内をIoTとかで検索するとたくさん事例が出てくるので、見てみてください。
まとめ
JavaScriptでIoTをやりたいならスマートフォンがおすすめ!
スマホで物足りなくなってきたらobnizかESP32。
obnizはサーバーサイドJavaScript的に、簡単にかけるからおすすめ
- 投稿日:2019-12-21T17:02:09+09:00
Object.assign({}, obj) と { ...obj } の違い
オブジェクトリテラル内のスプレッド構文は、ES2018で追加されたたいへん便利な構文です。特に、
{ ...obj }
という形のコードでオブジェクトをコピーするのはJavaScriptプログラミングでは極めて頻出です。スプレッド構文が無かった時代は
Object.assign({}, obj)
として同様のことを達成していた方も多いと思われます。Object.assign
はES2015から使用可能でした。では、この2種類の方法は同じでしょうか。タイトルにもある通り、もちろん違います。今回は、この違いに触れている日本語資料がMDN日本語版で一瞬触れているくらいしか無かったので記事にまとめました。
結論
最初に結論を述べると、
Object.prototype
が汚染されていた場合にのみ違いが発生します。特に、Object.prototype
にsetterを持つプロパティ名が存在し、そのプロパティ名でコピーしようとした場合に違いが現れます。例
Object.defineProperty(Object.prototype, "foo", { enumerable: true, get() { return 100; }, set(n) { console.log(`foo is set to ${n}`); } }); console.log({}.foo); // 100 const sourceObj = { foo: 999 }; const obj1 = Object.assign({}, sourceObj); // foo is set to 999 と表示される const obj2 = { ...sourceObj }; // 何も表示されない console.log(obj1.foo, obj2.foo); // 100 999
obj1
とobj2
を2つの方法で作ったあと、それぞれのfoo
プロパティの値を調べると異なる値となっています。解説
Object.assign
とスプレッド構文は非常に似た動作をしますが、その違いを一言でまとめるとこうです。すなわち、Object.assign
は代入によってプロパティをコピーする一方で、スプレッド構文はプロパティ自体の作成によってプロパティをコピーします。すなわち、
sourceObj
が{ foo: 999 }
であるという前提で、const obj1 = Object.assign({}, sourceObj)
の挙動は以下とおおよそ同様です。const obj1 = {}; obj1.foo = sourceObj.foo;一方、
const obj2 = { ...sourceObj };
の挙動は以下とおおよそ同様です。const obj2 = {}; Object.defineProperty(obj2, "foo", { configurable: true, enumerable: true, writable: true, value: sourceObj.foo });前者は
obj1.foo
に対する代入ですから、obj1.foo
がセッタを持っていた場合はそれが呼び出されることになります。一方、後者はObject.defineProperty
によるプロパティの作成なので、セッタが呼び出されません。今回は{}
で新規オブジェクトを作ってそれを対象としているので、その違いを引き出すためにはObject.prototype
を汚染する必要がありました。仕様書を実際に見てみると、
Object.assign
と...obj
は非常に似た処理をしていることが分かります。まず
Object.assign
の定義を仕様書から画像で引用します。次に、
...obj
の処理の本体部分を使用書から画像で引用します。なお、この場合excludedItemsは空リストになります。この2つの仕様を注意深く比べると、本質的な違いは下から2行目だけであることが見て取れます。
Object.assign
はここでSetを使っているのに対し、スプレッド構文ではCreateDataPropertyOrThrowを使っています。深入りするのはやめておきますが、前者がプロパティへの代入に相当する操作である一方、後者はObject.defineProperty
に相当する操作です。まとめ
Object.prototype
が汚染されていたときのことを考えながらコードを書きましょう!Object.prototype
を汚染するのはやめましょう。
- 投稿日:2019-12-21T15:51:37+09:00
5分で分かるVue.jsの基礎
参考
Vue Mastery
The Net Ninja Vue JS 2 TutorialVue.js とは
Vue.jsとはユーザーインターフェースを構築するための
フレームワーク
(アプリケーションの基本的な機能やルールを提供する骨組み)で、主にSPA(シングルページアプリケーション)
を構築する時に向いています。「学習コストの低さ」「スケールの柔軟性」「公式ドキュメントの充実」が魅力
SPA(シングルページアプリケーション)
とはJavaScriptでDOMを操作しページを切り替える単一ページで構成されるWebアプリケーション
DOM / 仮装DOM
とは
DOM
: Document Object Modelの略で、JavaScriptからHTMLやCSSのデータを取得、操作するための仕組み。htmlドキュメントをツリー構造として表現したもの。index.html<html> <head> <title>Vue.js</title> </head> <body> <h1>DOMとは</h1> </body> </html>上記のマークアップは下記のような「DOMツリー」として表現されます。
index.htmlhtml ├── head │ └──title │ └──Vue.js-lesson2 └── body └── h1 ├──DOMとは └──h1タグのコメント
仮装DOM
: 仮想DOMはバインディングしたデータを元に作られ、仮想DOMを元にDOMを作成する。
仮想DOMを利用する目的の1つとして、描画処理の性能を向上させる狙いがあり、実際のDOMでの描画処理では、仮想DOMの差分検出アルゴリズムで描画処理前の仮想Nodeツリーから更新対象を抽出する。
描画対象となるDOMに対して、Nodeの新規追加・更新・削除といったDOM操作を行う。Vueアプリケーションの作成
アプリケーション作成のために、コンストラクタ関数
vue
を使ってルートとなるvueインスタンス
を作成する。vue インスタンス
はアプリケーションの心臓部分。main.jsvar app = new Vue ({ // オプション })※ 慣例的にappやvmなどの変数名が使用される。
オプションの構成
main.jsvar app = new Vue ({ el: '#app', data: { message: 'Vue.js' }, computed: { }, method: { }, created: function(){ } })index.html<div id='app'></div> //ここが対象要素となる
elオプション
: マウントするDOM要素(アプリケーションを紐付ける要素のセレクタ)data
: アプリケーションで使用するデータです。オブジェクトや配列も登録できる。ここで定義されるプロパティはリアクティブなプロパティ
へと変換される。main.jsvar app = new Vue ({ el: '#app', data: { message: 'Vue.js' //変化を検知できるようになる } })
リアクティブなプロパティ / データ
とは?DOMの同期を自動で行う「データバインディング」を行うには、
テンプレートで使用するデータは「リアクティブなデータ」として定義する必要がある。
リアクティブなデータ
とはVue.jsによって取得した時(get)、または代入した時(set)のフック処理が登録された"反応のできるデータ"のこと。
データバインディング
データと描画を同期させる仕組みのことを「データバイディング」と呼ぶ。
HTMLで作られたUIを操作するのに、viewの管理(DOMの更新など) は必要不可欠。ライブラリを一切利用せずにJavaScriptでDOMを更新した時は、
app.jsvar el = document.getElementById('text') //要素を探す el.innerText = '新しいテキスト' //要素を更新する上記の処理で、UIのパターンごとにDOMを更新するのは、
UIのパターンが増えれば、増えるほど大変になっていってしまう・・
のでこういう自動的に描画が同期できるシステムはとても便利。
computed
: 算出プロパティ算出プロパティは処理に名前をつけて、テンプレート上で変数の様に使うことができる関数。
【 特徴 】
- 一度計算を行った場合、再度関数を呼び出しても依存しているデータに変更がない限りキャッシュを返す(変更箇所だけが返される)
- プロパティ呼び出しの時は
()
が不必要- リアクティブなプロパティが変更された時に呼ばれるのが
computedプロパティ
methods
: メソッドオブジェクトの操作を処理を定義したもの
【 特徴 】
・ 計算結果をキャッシュしない
・ そのため再描画されるたびにもう一度、計算が実行される
・ イベントに反応する
created
Vue.jsのライフサイクルライフサイクルとは、
Vueインスタンス
のはじまり(初期化)からおわり(破棄)されるまでの一定のサイクルのこと。あらかじめ登録された処理を、Vueインスタンスの特定のタイミングで差し込みます。
こうした処理を割り込ませる仕組みを「フック」と呼ぶ。コンポーネントのライフサイクルフックがどの時点で実行されるかは以下になります。
ライフサイクルダイアグラム
メソッド 説明 beforeCreated インスタンスが作成され、初期化される前に実行 created インスタンスが作成され、初期化された後に実行 beforeMount DOMがマウントされる直前に実行 mounted DOMがマウントされる直後に実行 beforeUpdate リアクティブプロパティが変更されて、変更されたデータがDOMに反映される前に実行 updated リアクティブプロパティが変更されて、変更されたデータがDOMに反映された後に実行 beforeDestroy Vueインスタントが破棄される前に実行 destroyed Vueインスタンスが破棄された後に呼び出される
created
: リアクティブなプロパティの初期化が完了しているので、dataやmethodsを参照することができる。APIにリクエストを送信してデータを送信し、リアクティブプロパティに追加していく処理はcreated内に記述していくと良い。Vue.jsの基本機能
テキストと属性のデータバインディング
Mustache構文
{{}}
main.jsvar app = new Vue ({ el: '#app', data: { message: 'Vue.js', list: ['ねこ', 'いぬ', 'とり'] }, computed: { }, method: { }, created: function(){ } })
dataオプション
に定義したmessage
プロパティの値を画面に表示したいときは、
{{}}
マスターシュ構文(Mustache:口髭) で囲みテンプレートに記述します。index.html<h1>{{message}}</h1> // Vue.js と表示される <h1>{{list[0]}}</h1> // ねこ と表示されるディレクティブ
「Vue.js」では、ディレクティブと呼ばれる
v-接頭辞
で始まる属性をHTML内で使用できます。
ディレクティブは主にデータバインディングを行うために使用される。
v-bind ディレクティブ
簡単なテキストのデータバインディングであればMustache構文を使えましたが、html要素の属性をデータバインディングする場合にはMustache構文はエラーとなり使えません。
index.html<div id="error"> <a href="{{ url }}">Google</a> // 属性では展開されない! </div>app.jsvar app = new Vue({ el: "#example-error", data: { url: "https://google.com" } });そこで使用するのが
v-bindディレクティブ
です。
v-bindディレクティブ を使えばDOM要素の属性を動的に切り替えることができます。index.html<タグ v-bind:属性=“プロパティ”>~</タグ>index.html<div id="example"> <a v-bind:href="url">Google</a> </div>app.jsvar app = new Vue({ el: "#example", data: { url: "https://google.com" } });
v-bind ディレクティブ
の省略v-bind は省略して、
:
のみで省略できます。index.html<div id="example"> <a :href="url">Google</a> </div>
v-on ディレクティブ
v-onメソッド
はイベント処理をするためのディレクティブです。(例) clickイベントを設定する
index.html<div id="app"> <p>{{ count }}回クリック</p> <button v-on:click="increment">このボタンでカウントを増やす</button> //increment メソッドが呼び出される </div>main.jsvar app = new Vue ({ el: '#app', data: { count: 0 }, method: { increment: function(){ this.count += 1 // thisはdata内のリアクティブデータを指す } }, })
v-on
ディレクティブの省略記法についてv-on は省略して、
@
で省略できます。index.html<div id="app"> <p>{{ count }}回クリック</p> <button @click="increment">このボタンでカウントを増やす</button> //increment メソッドが呼び出される </div>
v-show ディレクティブ
v-showディレクティブは、式が真のときのみ要素を表示するディレクティブです。
index.html<div id="app"> <div v-show="ok"> <p>Hello</p> </div> </div>main.jsvar app = new Vue({ el: '#app', data: { ok: true // 切り替える } })
v-if
とv-show
の違いと使い分け
v-if
:条件を満たさなかった場合、要素はDOmレベルで削除される。
v-show
:条件を満たさなかった場合、display;none
が付与され、非表示になる。条件分岐
vue.jsはテンプレートベースで条件分岐を記述します。
v-ifディレクティブ
は、v-showディレクティブ
同様に式の真偽を判定して要素を表示させるディレクティブindex.html<p v-if="id === 1"> 条件がtrueの時の表示 </p> <p v-else-if="id === 2"> 上記がfalseで、else-ifでの条件がTrueの表示 </p> //今回はこのpタグが表示される <p v-else> 上記のどちらの条件もfalseの時の表示 </p>main.jsvar app = new Vue ({ el: '#app', data: { message: 'Vue.js', id: 2, // ← ここ }, })
- 投稿日:2019-12-21T15:05:42+09:00
【JavaScript】fetchで映画のポスターを表示させる!!!!
はじめに
Qiita初投稿です。けつ毛アフロの防波堤と申します。よろしくお願いします。
この記事ではTMDbというサイトのAPIを使って映画のポスターを表示させます。
TMDbでは会員登録を行なうとAPI送信に必要なAPIキーを付与してくれます。会員登録
ログインして、メニュー画面から[設定]を選択。
サイドメニューから[API]を選択し、下にスクロールすると[APIキーを要求する]とありますので、APIキーを要求してください。
手順に沿ってAPIキーを要求すると、先ほど[APIキーを要求する]と表示されていたところに、[APIキー (v3 auth)]が表示されるようになります。
そちらをコピーしてください。
ちなみに、[APIを要求する]手順で個人情報とアプリの情報を聞かれますが、私は適当に書きました。適当に書いてもAPIキーはもらえます。まずfetchAPIで必要になるものを変数に格納しておきます。
main.jslet Apikey = "your API key"; //あなたのAPIキーを記入してください。APIリクエストの例にURLが書いてありますので、まるまるコピペしてみます。
JS
fetchAPIはこのような構文で書けるらしいので、これにURLを埋めてみます。
main.jsfetch(URL) .then(res => { console.log(res.status); }) .catch(error => { console.log(error); });main.jsfetch(`https://api.themoviedb.org/3/movie/76341?api_key=${Apikey}`) .then(res => { console.log(res.status); }).catch(error => { console.error(error); });ブラウザで開き、コンソール画面を開いてみると....?
成功していますね!!
では、この情報を使って、JSONにフォーマットされた情報をコンソール画面に表示させてみます。
main.jsfetch(`https://api.themoviedb.org/3/movie/76341?api_key=${Apikey}`) .then(res => { // console.log(res.status); res.json().then(movie => { console.log(movie); }); }) .catch(error => { console.error(error); });映画の情報が表示されてます!!!
マッドマックスだったんですね!!さて、つぎはこのデータをページに表示させてみます。
main.jsfetch(`https://api.themoviedb.org/3/movie/76341?api_key=${Apikey}`) .then(res => { // console.log(res.data) res.json().then(movie => { console.log(movie); const container = document.getElementById("container"); const movie_card = ` <div id="card" class="uk-card uk-card-default uk-card-body uk-width-1-2@m"> <h4>${movie.title}</h4> <img src="https://image.tmdb.org/t/p/w300${movie.poster_path}" alt=""> <p>${movie.overview}</p> </div> `; container.innerHTML = movie_card; }); }).catch(error => { console.error(error); });index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>TMDb</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.2.4/dist/css/uikit.min.css"/> <script src="https://cdn.jsdelivr.net/npm/uikit@3.2.4/dist/js/uikit-icons.min.js"></script> </head> <body> <div id="container"></div> <script src="./main.js"></script> </body> </html>これで表示されます。
まとめ
初投稿らしい薄い記事になりました。
次回は検索機能をつけたもうちょっと内容の濃いものをつくってみたいと思います。
- 投稿日:2019-12-21T14:52:53+09:00
保守性や堅牢性を高める!モダンフロントエンド開発に必要な周辺技術をまとめてみました
はじめに
前日もくもく会で一緒になったエンジニア初学者の方を見て、ポートフォリオとしてフロントエンド開発はちゃんとできてはいるものの、実務において必要な保守性や堅牢性の高いコードについての意識がどうしても不足しているなという印象を受けました。
もちろん初学者の方にそのようなことを求めるのは酷な話なので知らないからダメ、ということを言うつもりは毛頭ありません。
が、職業としてエンジニアを目指すためにフロントエンド開発をやっている場合、保守性や堅牢性を意識すると企業からの評価が段違いなのではないかと思うのです。そこで今回の記事では単にフロントエンドが開発できるというスキルだけではなく、実務における保守性や堅牢性を意識したフロントエンドを開発するために有効になるであろう技術について説明してみたいと思います。
対象者
- プログラミング初学者の方で、モダンなJavaScriptフレームワークを使ってフロントエンド開発を行っている方
- これからモダンなJavaScriptによるフロントエンド技術をプロダクトに導入しようとしている企業のエンジニア
- ReactやVueを導入はしたが、コードの管理やバグが頻出したりして悩んでいる方
紹介する技術・ライブラリの一覧
保守性や管理性を向上させるために有効なものとして、ざっと下記が挙げられます。
- eslint, prettierによるコード解析およびコードフォーマット
- TypeScriptによる静的片付け
- ReactHooks.useReducer, Vuexによる状態の一元管理
- Storybookによるコンポーネントのカタログ管理
- Atomic Design等のコンポーネント分割ポリシー
- Jestによるメソッドの単体テスト
- Testing Library(or Enzyme)によるコンポーネントの振る舞いテスト
- CypressによるE2E(End to End)テスト
- CIパイプライン上でのlintおよび単体テストの実行による自動検査、テストの実行 ではそれぞれの技術の概要と、導入したときのメリットについてみていきます。
ESLint, prettier
lintとは?何が嬉しい?
lint とは、主にC言語のソースコードに対し、コンパイラよりも詳細かつ厳密なチェックを行うプログラムである。
静的解析ツールとも呼ばれる。
lint - Wikipedia実行するのに問題はないが、不具合の原因になるようなコードだったり、不要な記述についてチェックし、あらかじめ開発者に警告ないしエラーとして通知することでそれの解消をしてくれるツールがLintです。
ESLintにおいては静的解析を行う際に確認するルールを自由に設定することができ、例えば下記のようなことができます。
- no-console: consoleを使っていないこと
- no-empty: 空のブロックを使っていないこと
- no-func-assign: functionを再定義していないこと
- no-unreachable: 到達可能なコードが記述されていないこと
- no-eval: evalを使わないこと
等々ですね。
さらにプラグインを利用すればReactやJSXの記述を確認したり、様々なことが可能です。Prettierとは?何が嬉しい?
Prettierはコードフォーマッターのためのライブラリで、様々な言語のフォーマットが可能です。
ESLintがコードの問題を解消するのに対し、prettierではコードの整形を主眼としてしています。
業務におけるプログラミングではチーム開発が基本で、他人のコードを読んだり変更したりすることが多い。
そのため開発者ごとにコードの記述が異なると、コードの可読性が下がって効率が下がってしまいます。
Prettierを導入することで、チーム開発において重要なコードのフォーマットを維持し、自動化もさせることができます。
Further Reading - 参考
TypeScript
TypeScriptとは?何が嬉しい?
TypeScriptはMicrosoftが主体となって開発している、JavaScriptのスーパーセット言語です。
その名の通り、JavaScriptに型を与えた言語で、動的型付けのJavaScriptに対して厳密な静的型付けを与えます。
動的型付け言語は変数の型が動的に変わり、その特性上スピード感のある開発が可能ですが、その代わりに意図しない型に変換されて不具合が発生したりすることもあり、安全性という意味では静的型付け言語に劣ります。
今のところフロントエンド開発においてJavaScriptは不動の地位を占めていますが、裏を返すと動的型付けのJavaScriptしか選べなかったということでもありました。
TypeScriptはそのようなフロントエンドに対して、静的型付け機能を与え、型の不整合による不具合の発生を予防することを可能にします。
また、TypeScriptは実行前にJavaScriptに変換されます。
そのためOptional Chainingなどの機能をブラウザサポートを考えることなく利用することができ、新しい機能をフル活用することも可能です。Further Reading
Rudex, Vuex, ReactHooks.useReducer等によるステート管理
ステート管理とは?何が嬉しい?
ReactとVueにはpropsとstateという値があり、stateはその名の通りコンポーネントの状態を表しています。
このstateを使うポリシーを定めていないと、stateの管理ができず不具合の温床になることがあります。
例えばあるコンポーネントの値を使って別のコンポーネントの動作が決定され、そしてそのコンポーネントの動作によってまた別のコンポーネントの動作が...というようなことが起こり、動作のロジックが複雑になり、デバッグが困難になります。
この問題を解決するためにstateを一元管理(Single source of truth)し、state管理をシンプルにするという手法が取られることがあります。
そしてそれを実現するためのツールが、ReduxやVuex(Vue)、useReducer(React)です。
例えばReduxというライブラリでは3つの原則を掲げていて、
- Single source of truch(単一の正しい情報源)
- State is read-only(Stateは読み込みのみ)
- Changes are made with pure functions(変更は純粋関数を利用して行われる)
というものになっており、この原則によって安全な状態管理が実現されます。
ステート管理を一元的に行うことで現在のアプリケーションの状態を明確にし、複雑な状態を持たせず管理しやすくなり、stateによる不具合を抑制しやすくなります。
Further Reading
Storybookによるコンポーネントのカタログ管理
Storybookとは?何が嬉しい?
Storybook is an open source tool for developing UI components in isolation for React,
Storybookは独立した状態でReact, Vue, そしてAngularのUIコンポーネントを開発するためのオープンソースツールです。Storybook 公式サイト より
上記の公式サイトの紹介にあるように、Storybookはフロントエンドフレームワークにおけるコンポーネント開発のためのツールで、コンポーネント単位での開発と管理を可能にします。これの何が嬉しいかというと、一つのコンポーネントの修正のために、ページ全体を開いて操作をする必要がなくなる、ということです。
例えばテーブル中に存在するソート順を変更するボタンを押した際に、各行がそれぞれ指定した列で並んでいるか、というのを確認したい時に、今までのやり方ではトップページを表示して、テーブルのあるページに移動して、ボタンを押して...ということを毎回やる必要がありました。
しかしStorybookを使えば、そのテーブルコンポーネントだけを表示させることができ、さらに指定したpropsをあらかじめ与えた状態にすることも可能です。
そのため、特定の状態のコンポーネントをあらかじめ用意して、それを一目で見ることも可能というわけです。
これによってコンポーネントの確認、修正のために不要な操作をする必要がなく、モダンフロントエンドにおけるコンポーネントの管理が格段に楽になります。
下記に記載しているStorybook Demoをみると、Storybookがどのようなものか一目で分かるかと思います。
Further Reading
Atomic Design等のコンポーネント管理ポリシー
Atomic Designとは?何が嬉しい?
Atomic design is methodology for creating design systems. There are five distinct levels in atomic design:
Atomic Designはデザインシステムのための方法論である。
Atomic Designにおいては5つの別個のレベルが存在する。Atomic Design - Brad Frost
Atomic Designはデザインにおける部品を化学的な概念に当てはめたもので、部品構成の単位を明瞭にしたものです。元々はデザインガイドとして作られたが、それをフロント開発に応用しているプロダクトが増えています。
Atomic Designの何がいいかというと、コンポーネントの大きさによってコンポーネントを分類することで、管理しやすくするという点です。
Prettierの説明においてはコードの記述が開発者によってバラバラになるのを防ぐメリットがある、ということを説明しましたが、Atomic Designを使えば、開発者ごとにばらけがちなコンポーネントの大きさを統一し、再利用しやすくすることができるというわけです。
Further Reading
Jestによるメソッドの単体テスト
Jestとは?何が嬉しい?
JestはJavaScriptにおける、ユニットテストのためのツールです。
ユニットとは「単位」の意味で、ユニットテストでは小さい部品のテストを実現することができます。
基本的にはJavaScriptやTypeScriptの関数、メソッドの中で記述したロジックがテスト対象となり、関数に与えた引数やAPIリクエストの返り値ごとに、それらが期待した通りの動作になっているかを確認します。
ユニットテストを導入することで、文字通り小さい単位のテストを実施することができるため、それらをつなぎ合わせて全体のアプリケーションを動作させた時の動作が保証しやすくなります。
先ほどStorybookの項目ではページ全体ではなくコンポーネント単位での確認が可能と言いましたが、Jestではそれのメソッドバージョンと言い換えるといいかもしれません。
古典的にはページを表示して、画面を動かした時に実行されるメソッドが正しく動いているかを手動で確認する必要があったりしますが、ユニットテスト済みのメソッドのロジックに関してはテストする必要はないと言っていいでしょう。
Further Reading
Testing Library(or Enzyme)によるコンポーネントの振る舞いテスト
Testing Library, Enzymeとは?何が嬉しい?
こちらもJestの話と似ていますが、こちらは関数やメソッドではなく、画面に表示するコンポーネントをテスト対象としています。
Storybookではコンポーネントの表示がどうなるかを確認することができましたが、あくまで開発者が自分の目で確認する必要がありました。
Testing LibraryやEnzymeを利用することで開発者の目視ではなく、コード上でコンポーネントがどのように動作するかを保証させることが可能になります。
そのため後述するCIパイプラインにおける自動テストなどに組み込みやすく、コードを開発してPRを出した時にテストを実行し、常に動作が問題ないことを保証することが可能になったりします。
Further Reading
CypressやSeleniumによるE2E(End to End)テスト
Cypress, Seleniumとは?何が嬉しい?
Cypress, SeleniumはE2E(End to End)テストのためのライブラリで、E2Eテストはアプリケーション全体の動作をテストするものです。
JestやTesting Libraryはユニットやコンポーネントという小さな単位でのテストでしたが、E2Eテストではアプリケーションが全体として期待通りの動作をしてくれるのか、ということをテストします。
ユニットという単位では動作しているものの、それらをつなげた時に期待する動作をやっているかを保証してくれる機構はJestとTesting Libraryだけでは実現できません。
各種操作を画面上で行った時などのシナリオ全体での動作を保証させることが可能になるというわけです。
Further Reading
CIパイプライン上でのlintおよび単体テストの実行による自動検査、テストの実行
CIパイプラインとは?何が嬉しい?
CIとはDevOpsにおける考え方で、継続的インテグレーション(Continuous Integration)の略語です。
言葉としては難しいですが、やっていることとしては要するに新しく書いたコードをマージした時にアプリケーションが正しく動作することを確認する仕組み、と考えれば良いでしょう。
GitHubなどでCIの仕組みを導入すると、PRを出したときに自動テストを実行させ、テストが失敗したときはPRをマージできない状態にしたり、slackにテストが失敗したことを通知させたりします。
ここまでで紹介したlint, jest, testing libraryなどをCIで実行できるようにすれば、PRを出した時にコード解析とテストが自動で実行され、コードの品質やメソッド・コンポーネントの動作が常にテストされた状態のリポジトリを維持することが可能になります。
CIのためのツールとしてはCircleCIやJenkins, AWSではCodeシリーズなどがあります。
Further Reading
最後に
以上、各種関連言語、ライブラリ等について紹介させていただきました。
これらを利用することで職業エンジニアを目指している方であれば、実用的な技術を使ったポートフォリオを構築して企業からの評価を高めることができるでしょうし、実務で利用すれば保守性が高くかつ堅牢なアプリケーションを継続的に開発することが可能になります。
もちろん導入自体にもコストがかかりますし、これら全てを導入する必要はありませんが、時間とリソースが許す限り導入を検討し、そして導入するのがいいでしょう。
(時間的余裕があれば、実際にここで並べた技術を用いてフロントエンドアプリケーションを開発するハンズオン記事を書きたいと思いますが、それはまた次回...)
ここ間違ってない?これも使った方がいんじゃね?というものがあれば教えてください!
それでは、ありがとうございました!
- 投稿日:2019-12-21T14:40:58+09:00
Lambda+node.jsのREST APIをDocker+Rustに置き換えて高速化したい
はじめに
- AWS Lambdaをnode.js(javascript/typescript)でよく使っている。
- コスト、またはレスポンス改善のためにLambdaをECS+fargateなどDocker環境に移植したい。
- もちろんRustに移植すれば速くなると思ってやっている。
Lambdaの問題
- リクエスト課金のため、大規模利用では課金がヤバいことになる。
- レスポンスタイムの揺らぎが大きい、コールドスタートが遅い。
- このどっちの問題にも当てはまらないならLambdaはオススメです。
(最近、Provisioned Concurrencyとか追加されたけど、それでもコールドスタートは発生する)
なぜRust?
- 速いから。速さがそのままインフラコスト改善になるから。
- C++を長くやってきたけど、最近Rustがいい気がしてきたから。
- でも、現状のLambdaではnode.jsのほうが速いらしい。(node.js、Go、Pythonは同じぐらい) https://medium.com/the-theam-journey/benchmarking-aws-lambda-runtimes-in-2019-part-i-b1ee459a293d
簡単なREST APIサーバーを書いてみる
数値を2つ含んだJSONをPOSTして、その和を返すREST APIを作る。
普段、fastifyを使っているので、node.jsはfastifyで比較する。
node.js(javascript) + fastify
main.jsconst fastify = require('fastify'); const server = fastify({}); server.post('/', (request, reply) => { reply.send({answer: request.body.a + request.body.b}); }); server.listen(3000, (err, address) => { if (err) throw err; console.log(`server listening on ${address}`); });Rust + actix_web
main.rsuse actix_web::{web, App, HttpServer, Responder, post, HttpResponse}; use serde::{Deserialize, Serialize}; #[derive(Serialize)] struct AddResult { answer: i32, } #[derive(Deserialize)] struct AddQuery{ a: i32, b: i32, } #[post("/")] fn post(query: web::Json<AddQuery>) -> impl Responder { HttpResponse::Ok().json(AddResult{answer: query.a + query.b}) } fn main() { HttpServer::new(|| { App::new().service(post) }) .bind("127.0.0.1:3000") .expect("Can not bind to port 3000") .run() .unwrap(); println!("server listening on 3000"); }測定
ローカルマシン(MacBook Pro 4コア)で、heyを使って測定する。
Rustは
cargo run --release
で実行する。% hey -n 1000000 -c 100 -m POST -d '{"a":1,"b":2}' -T 'application/json' http://localhost:3000node+fastify heyの結果
Summary: Total: 42.1554 secs Slowest: 0.0426 secs Fastest: 0.0001 secs Average: 0.0042 secs Requests/sec: 23721.7658 Total data: 12000000 bytes Size/request: 12 bytes Response time histogram: 0.000 [1] | 0.004 [723692] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.009 [271311] |■■■■■■■■■■■■■■■ 0.013 [3823] | 0.017 [789] | 0.021 [286] | 0.026 [20] | 0.030 [3] | 0.034 [22] | 0.038 [28] | 0.043 [25] | Latency distribution: 10% in 0.0036 secs 25% in 0.0036 secs 50% in 0.0039 secs 75% in 0.0044 secs 90% in 0.0054 secs 95% in 0.0058 secs 99% in 0.0076 secsRust+actix heyの結果
Summary: Total: 11.0322 secs Slowest: 0.1170 secs Fastest: 0.0001 secs Average: 0.0011 secs Requests/sec: 90643.4012 Total data: 12000000 bytes Size/request: 12 bytes Response time histogram: 0.000 [1] | 0.012 [997956] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.023 [1203] | 0.035 [280] | 0.047 [182] | 0.059 [212] | 0.070 [89] | 0.082 [61] | 0.094 [14] | 0.105 [0] | 0.117 [2] | Latency distribution: 10% in 0.0006 secs 25% in 0.0009 secs 50% in 0.0010 secs 75% in 0.0011 secs 90% in 0.0013 secs 95% in 0.0015 secs 99% in 0.0032 secs結果
Requests/sec
node + fastify Rust + actix 23721 90643 Rustが速い。
node.js vs デフォルトでコア数だけスレッド立てるactixはフェアじゃないだろ
node側のコードをclusterを使って、マルチプロセス化する。
cluster.jsconst cluster = require('cluster'); const os = require('os'); const fastify = require('fastify'); if(cluster.isMaster) { for(let i = 0; i < os.cpus().length; i++) { cluster.fork(); } } else { const server = fastify({}); server.post('/', (request, reply) => { reply.send({answer: request.body.a + request.body.b}); }); server.listen(3000, (err, address) => { if (err) throw err; console.log(`server listening on ${address}`); }); }それに対するheyの結果
Summary: Total: 16.0576 secs Slowest: 0.1326 secs Fastest: 0.0001 secs Average: 0.0016 secs Requests/sec: 62275.7432 Total data: 12000000 bytes Size/request: 12 bytes Response time histogram: 0.000 [1] | 0.013 [977295] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.027 [17411] |■ 0.040 [4146] | 0.053 [812] | 0.066 [176] | 0.080 [67] | 0.093 [30] | 0.106 [32] | 0.119 [0] | 0.133 [30] | Latency distribution: 10% in 0.0003 secs 25% in 0.0005 secs 50% in 0.0007 secs 75% in 0.0010 secs 90% in 0.0020 secs 95% in 0.0063 secs 99% in 0.0208 secsRequests/sec
node + fastify node + fastify + cluster Rust + actix 23721 62275 90643 Rustが1.5倍ほど速いけど、思ったより差がなくなった。
他のhttp framework crateではどうなのか
nickelでやってみる。
main.rs#[macro_use] extern crate nickel; use nickel::{Nickel, HttpRouter, JsonBody}; use serde::{Deserialize, Serialize}; use serde_json; #[derive(Serialize)] struct AddResult { answer: i32, } #[derive(Deserialize)] struct AddQuery { a: i32, b: i32, } fn main() { let mut server = Nickel::new(); server.post("/", middleware! { |request, response| let query = request.json_as::<AddQuery>().unwrap(); let response = AddResult{answer: query.a + query.b}; serde_json::to_string(&response).unwrap() }); server.listen("127.0.0.1:3000").unwrap(); }けど、heyの同じ負荷では
socket: too many open files
が大量に出て耐えれなかった。
仕方なく、% hey -n 100000 -c 10 -m POST -d '{"a":1,"b":2}' -T 'application/json' http://localhost:3000同時接続数を減らして比較した
Rust + actix Rust + nickel 62275 67503 nickelがやや速い。が、多同時接続が不安。
まとめ
- Rustはnode.jsの1.5倍速かった。
- もうちょっとRustは速いと思ってた。
- ここから処理を追加するから、差はついてくると思うが、REST APIのガワだけであれば大差なかった。
- マジか。
補足
- ローカル実行の雑なベンチマークなので参考程度でお願いします。
- 投稿日:2019-12-21T12:58:37+09:00
Next.js + Emotion (CSS in JS)で始めるReact超入門
以前、React初学者向けの勉強会を開催したときに作った資料を、Qiita向けに調整したものです。
- Reactの初歩的な記法
- Next.jsでのサーバーサイドレンダリングの概要
- CSSinJSの使い方
を学ぶことができます。
事前にNode.jsをインストールしている必要があります。
Reactとは?
Reactとは、Facebookが作ったJavaScriptライブラリです。ユーザーインターフェイスをコンポーネントベースで作ることができます。
公式サイト:https://ja.reactjs.org/
シンプルなReactのサンプル
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World</title> <script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> </head> <body> <div id="root"></div> <!-- babelがJSXをReact.createElement()に変換してくれる --> <script type="text/babel"> /** * 引数:propsを、JSX:<div>{props.text}</div>で受け取り、returnで返す。 * この一式をコンポーネントと呼ぶ。 */ function Hello(props) { return <div>{props.text}</div> }; /** * Helloコンポーネントを<div id="root"></div>にマウントしている。 * 関数みたいにしてみると、`Hello({ text: 'Hello, React!' });` こんな感じ。 */ ReactDOM.render( <Hello text="Hello, React!" />, document.getElementById("root") ); </script> </body> </html>Reactの特徴
Vue.jsがディレクティブを使いHTMLを拡張するような方法で開発するのに対し、ReactはガシガシJavaScriptを書いていきます。まあ、Vue.jsもガッツリ開発を始めるとガシガシJavaScriptを書くことになると思いますが(・ω・)
また、Reactは基本的にデータを受け取って適切なViewを返すことを目的としたシンプルなライブラリなので、Angularのようなルールはなく、自由度がかなり高いです。逆に言うと、しっかりとした設計ができてないと、開発途中でつらくなります(・ω・)
宣言的UI
Reactに限らず、近年のUIフレームワーク・ライブラリ、およびプログラミングにおいて主要なパラダイムである宣言的UIについて、知っておく必要があります。
そのまえに、まず、命令的と宣言的を解説します。
命令的
- 何をするかを記述する
- 前回の実行結果に依存する
- 変数の再代入が行われる
宣言的
- どういう状態になるのかを記述する
- 前回の実行結果に依存しない
- 変数に再代入しない
命令的UIと宣言的UI
命令的UI
命令的UIの例として、jQueryによるDOM操作があげられます。
<!-- この時点ではUIの最終的な状態はわからない --> <ul id="list"></ul>const animals = ["ねずみ", "うし", "とら"]; // 配列の要素分処理を繰り返し、HTML側に挿入することでUIが決定する。 animals.forEach(animal => { $('#list').append(`<li>${animal}</li>`); });宣言的UI
一方で、Vue.jsは宣言的にUIを作ることができます。
<template> <ul> <!-- この時点でUIの状態が決まっている --> <li v-for="(item, index) in list" :key="index">{{item}}</li> </ul> </template> <script> export default { data() { return { // 配列の要素によってリストの数が決定する list: ["ねずみ", "うし", "とら"] }; } }; </script>ReactやVue.jsは、jQueryの次に流行っているフレームワーク・ライブラリではなく、宣言的なUIを作るためのフレームワーク・ライブラリです。
技術選定時に宣言的なUIが必要であれば、ReactやVue.jsを使用しましょう。逆に言えば、jQueryのほうが適切な場面であれば、無理に使用する必要はありません。
Next.jsとは?
Next.jsとは、Reactでサーバーサイドレンダリングをするためのフレームワークです。Vue.jsで言うところの、Nuxt.js。簡単にルーティングできて、静的サイトの書き出しもできます。
公式サイト:https://nextjs.org/
静的サイトの書き出しならば、Gatsby.jsのほうが使いやすいかもしれませんが、Next.jsのほうがシンプルに始められるので、今回はNext.jsを使用します。
Reactとしての書き方はほぼ同じですし、メジャーなエコシステムも使用できるなので、Next.jsが使えればGatsby.jsも使えると思います。多分。
サーバーサイドレンダリングとは?
サーバーサイドレンダリングとは、PHPやRuby、Javaのように、サーバーサイドでDOMを生成してクライアントに静的なHTMLとして渡すことです。
Reactは、本来クライアントサイドで仮想DOMを生成し、それを実DOMとしてブラウザに描画します。
<!-- サーバーから返されるHTML --> <div id="app"></div>import React from 'react'; import ReactDOM from "react-dom"; // クライアントサイドでDOMを書き換える ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('root') );サーバーサイドレンダリングは、サーバー上でReactを実行し、生成したDOMをクライアントへ渡します。
たとえば、動的なコンテンツで
<title>
要素や<meta>
要素をクライアントサイドで生成すると、TwitterやFacebook等のSNSでシェアしたときには反映されません。しかし、サーバーサイド上で事前にDOMを生成すれば、クライアントからみれば静的なHTMLがレスポンスとして返ってくるので、この問題が回避できます。
Next.jsの使い方
必要なパッケージをインストール
- next
- react
- react-dom
$ mkdir nextjs-sample $ cd nextjs-sample $ npm init -y $ npm i next react react-dom
package.json
にscripts
を追記。{ "scripts": { "dev": "next" } }
./pages
ディレクトリにindex.js
を追加。$ mkdir pages $ touch pages/index.js// pages/index.js export default () => <h1>Hello, Next.js!</h1>ローカルサーバーを起動。
$ npm run dev
http://localhost:3000
にアクセスして、Hello, Next.js!
が表示されていれば、OK!ディレクトリ構成
./pages
ルーティングの対象./static
静的ファイルの置き場所
- 画像ファイルとか
ディレクトリのルールが決まっているのは、これくらい。
また、Next.js 9.1から
src
配下でも利用できるようになったので、以下でもOKです。
src/pages
ルーティングの対象src/static
静的ファイルの置き場所JSX
JSXを使用すると、JavaScript上でHTMLのような構文が使えます。
// JSX const Button = <button className="my-button">ボタン</button>これは、
React.createElement()
の糖衣構文で、JSXを使わないと下記の記述になります。const Button = React.createElement("button", { className: "my-button" }, "ボタン");極論、JSXを使わずに
React.createElement()
を使ってもなんの問題ありません。JSXを使う理由が公式のガイドにありますので、興味のある方はどうぞ。
オンライン Babel コンパイラを使うと、JSXがどのようなJavaScriptに変換されるのかを確認できます。
Next.js(React)を書いてみよう
pages/index.js
を、省略形なしの形に変更。import React from 'react' // Next.jsでは省略可能 // returnでJSXを返す関数をコンポーネントと呼ぶ function Index() { return <h1>Hello, Next.js!</h1> } // ES Modules // 本来は import されて react-dom がレンダリングするが、Next.jsでは隠蔽されている export default IndexHTMLのように、JSXでも子要素を使うことができます。
import React from 'react' function Index() { // ()で括り、;の自動挿入に対応 // returnで返すJSXは必ず1つの要素 return ( <div> <h1>Hello, Next.js!</h1> </div> ) } export default IndexJSXは
{}
でJavaScriptを使うことができます。import React from 'react' function Index() { const text = 'Next.js!' return ( <div> {/* コメントアウト */} <h1>{`Hello, ${text}`}</h1> </div> ) } export default Index
Heading
コンポーネントを作って、JSX内で使ってみましょう。import React from 'react' // 見出し用のコンポーネント function Heading(props) { // 属性の値は、オブジェクトのプロパティとして渡される return <h1>{props.text}</h1> } function Index() { const text = 'Next.js!' return ( <div> { /** * コンポーネントの属性でテキストを渡す * これをProps(プロップス)と呼ぶ */ } <Heading text={`Hello, ${text}`} /> </div> ) } export default Index
Heading
コンポーネントに、子要素を渡してみます。import React from 'react' // Propsはオブジェクトなので、分割代入が使える function Heading({ children }) { // childrenで子要素を受け取る return <h1>{children}</h1> } function Index() { const text = 'Next.js!' return ( <div> {/* コンポーネントの子要素でspan要素を渡す */} <Heading> <span>{`Hello, ${text}`}</span> </Heading> </div> ) } export default Index
div
がいらねえときは、React.Fragment
が便利です。import React from 'react' function Heading({ children }) { return <h1>{children}</h1> } function Index() { const text = 'Next.js!' // React.Fragmentを使うとその要素はレンダリングされない return ( <React.Fragment> <Heading> <span>{`Hello, ${text}`}</span> </Heading> <p>divでラップしたないねん</p> </React.Fragment> ) } export default Index
React.Fragment
は、糖衣構文として<></>
とも使えます。記述量が遥かに少なくてすむので、とくに理由がなければ、こちらを使用しましょう。import React from 'react' function Heading({ children }) { return <h1>{children}</h1> } function Index() { const text = 'Next.js!' // <React.Fragment></React.Fragment>は<></>とも書ける return ( <> <Heading> <span>{`Hello, ${text}`}</span> </Heading> <p>divでラップしたないねん</p> </> ) } export default Indexファイルを分けてみましょう。
$ mkdir components $ touch components/Heading.js// components/Heading.js // {} と return を省略できる function Heading({ children }) { return <h1>{children}</h1> } export default Heading// pages/index.js import React from 'react' import Heading from '../components/Heading' function Index() { const text = 'Next.js!' return ( <> <Heading> <span>{`Hello, ${text}`}</span> </Heading> <p>divでラップしたないねん</p> </> ) } export default Index
map
メソッドで要素の反復処理をしてみましょう。// pages/index.js import React from 'react' import Heading from '../components/Heading' // 配列 const member = ['ネズミ', '牛', 'トラ', 'うさぎ'] function Index() { const text = 'Next.js!' return ( <> <Heading> <span>{`Hello, ${text}`}</span> </Heading> <ul> {member.map((name, index) => ( <li key={index}>{name}</li> ))} </ul> </> ) } export default Index
onClick
でイベント発火できます。// pages/index.js import React from 'react' import Heading from '../components/Heading' const member = ['ネズミ', '牛', 'トラ', 'うさぎ'] function Index() { const text = 'Next.js!' return ( <> <Heading> <span>{`Hello, ${text}`}</span> </Heading> <ul> {member.map((name, index) => ( <li key={index}>{name}</li> ))} </ul> {/* onClickに関数を書く */} <button onClick={() => console.log('onClick')}>ボタン</button> </> ) } export default IndexuseStateで関数コンポーネントに状態をもたせる
React 16.8で、hooksという新機能が追加されました。Reactで
state
などの機能を使う場合、これまではクラスで書かないといけませんでしたが、hooksの登場で関数コンポーネントでも副作用のある機能を使うことができるようになりました。今回は、関数コンポーネントに状態をもたせることができる、
useState
を使ってみましょう。// pages/index.js // `useState`をインポート import React, { useState } from 'react' import Heading from '../components/Heading' const member = ['ネズミ', '牛', 'トラ', 'うさぎ'] function Index() { const text = 'Next.js!' /** * const [変数, 変数の値を変える関数] = useState(初期値) * 以下では、`value`変数の初期値に`No, Click.`の文字列を代入しています。 * setValue('Yes, Click!!')を実行すると、 * valueの値を`No, Click.`から`Yes, Click!!`に変えることができます。 */ const [value, setValue] = useState('No, Click.'); const onClickEvent = () => setValue('Yes, Click!!'); return ( <> <Heading> <span>{`Hello, ${text}`}</span> </Heading> <ul> {member.map((name, index) => ( <li key={index}>{name}</li> ))} </ul> <button onClick={() => console.log('onClick')}>ボタン</button> {/* クリックすると、`No, Click.`が`Yes, Click!!`に変わる */} <button onClick={onClickEvent}>{value}</button> </> ) } export default IndexNext.js独自の機能
Link
コンポーネントでルーティングさせてみましょう。// pages/index.js import React, { useState } from 'react' import Link from 'next/link' // Linkコンポーネントを追加 import Heading from '../components/Heading' const member = ['ネズミ', '牛', 'トラ', 'うさぎ'] function Index() { const text = 'Next.js!' const [value, setValue] = useState('No, Click.'); const onClickEvent = () => setValue('Yes, Click!!'); return ( <> <Heading> <span>{`Hello, ${text}`}</span> </Heading> <ul> {member.map((name, index) => ( <li key={index}>{name}</li> ))} </ul> <button onClick={() => console.log('onClick')}>ボタン</button> <button onClick={onClickEvent}>{value}</button> {/* Linkコンポーネントでルーティングできる */} <Link href="/batman"><a>バットマンページへ</a></Link> </> ) } export default Index
pages/batman.js
を作成した上、バットマンページへのリンクをクリックすると、再読み込みなしでページ遷移できます。つまり、SPAです。$ touch pages/batman.js// pages/batman.js import React from 'react' function Batman() { return <div>batman</div> } export default Batman
getInitialProps
で非同期データ取得
getInitialProps
は、Next.jsのライフサイクルメソッドです。ページが読み込まれたときはサーバーサイドで実行され、以降、Link
コンポーネントによって別のpages
コンポーネントへ移動した場合にクライアントサイドで実行されます。以下の実装をして、
http://localhost:3000
からhttp://localhost:3000/batman
に遷移したときと、http://localhost:3000/batman
をリロードしたときのコンソールの表示を確認してみましょう。// pages/batman.js import React from 'react' function Batman({ text }) { return <div>{text}</div> } Batman.getInitialProps = () => { const text = 'I am Batman !!' console.log(text) return { text } // returnしたオブジェクトをコンポーネントのPropsとして受け取れます } export default Batman遷移したときはブラウザ側のコンソール、リロードしたときは開発側のコンソールに、それぞれログが出たかと思います。
Next.jsはサーバーサイドレンダリングのためのフレームワークなので、今書いているJavaScriptがサーバーサイド(Node.js)なのか?それとも、クライアントサイドなのか?を意識することが必要です。
非同期でデータ取得
バットマンAPIを叩いて、非同期に情報を取得してみましょう。ページ読み込み時になにかしらの処理をする場合は、
getInitialProps
メソッドを使います。Node.jsではfetchメソッドが使えないので、
isomorphic-unfetch
をインストールして使います。$ npm i isomorphic-unfetch// pages/batman.js import React from 'react' import fetch from 'isomorphic-unfetch' function Batman({ shows }) { return ( <div> <h1>Batman TV Shows</h1> <ul> {shows.map(show => ( <li key={show.id}> <div><img src={show.image.medium} /></div> <div>{show.name}</div> </li> ))} </ul> </div> ) } Batman.getInitialProps = async () => { const res = await fetch('https://api.tvmaze.com/search/shows?q=batman') const data = await res.json(); return { shows: data.map(entry => entry.show) } } export default Batmanサーバーサイドレンダリングの使い所
たとえば、動的なコンテンツで
<title>
要素や<meta>
要素をクライアントサイドで生成すると、TwitterやFacebook等のSNSでシェアしたときには反映されません。しかし、サーバーサイド上で事前にDOMを生成すれば、クライアントからみれば静的なHTMLがレスポンスとして返ってくるので、この問題が回避できます。
クソアプリを作ったので、これを実際に試してみましょう。
$ touch pages/nameApp.js $ touch pages/yourName.js// nameApp.js import React, { useState } from 'react' import { useRouter } from 'next/router' function NameApp() { const [name, setValue] = useState('') /** * Next.jsのルーターオブジェクト * https://nextjs.org/docs#userouter */ const router = useRouter() const onClickEvent = () => { // yourname?name=【name】に遷移する router.push({ pathname: '/yourName', query: { name }, }) } const onChangeEvent = event => setValue(event.target.value) return ( <> <div>君の名は。。。</div> <input value={name} onChange={onChangeEvent} /> <button onClick={onClickEvent}>click!!</button> </> ) } export default NameApp
nameApp.js
のやっていることは、ReactやNext.jsを使わない方法で書くとこんな感じです。<form action="yourName/" method="GET"> <div>君の名は。。。</div> <input name="name"/> <button type="submit">click!!</button> </form>続いて、遷移先の
yourName.js
を実装します。// yourName import React from 'react' import Head from 'next/head' function YourName({ query }) { const { name } = query return ( <> {/* Headコンポーネントで`title`や`meta`が設定できる */} <Head> <title>{name} | YourName</title> <meta name="description" content={`君の名は、${name}ですね。`} /> </Head> <div> 君の名は、<strong>{name}</strong>ですね。 </div> </> ) } YourName.getInitialProps = ({ query }) => { return { query } } export default YourNameフォームに名前を入力して隣のボタンをクリックすると、入力した名前を表示することができる画期的なアプリです。
command + option + u
でソースを確認してみましょう。サーバーから取得したHTMLの段階で、title
やmeta
が設定されていることがわかります。イメージを掴んでいただくために、試しにPHPで実装してみました。(PHPが全然わからないので細かいところはご勘弁を。。。(´;ω;`))
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>YourName</title> </head> <body> <form action="yourName/" method="GET"> <div>君の名は。。。</div> <input name="name"/> <button type="submit">click!!</button> </form> </body> </html>上のHTMLで
yourName/?name=ほげぼげ
みたいな感じになるので、PHPでパラメーターを受け取りHTMLとしてクライアントにレスポンスします。<!-- yourName/index.php --> <?php $name = htmlspecialchars($_GET['name']); ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title><?php echo $name; ?> | YourName</title> <meta name="description" content="君の名は、<?php echo $name; ?>ですね。" /> </head> <body> <div>君の名は、<?php echo $name; ?>ですね。</div> </body> </html>より掘り下げたい場合は、公式ドキュメントを確認してください。
また、GitHubのexampleに豊富なサンプルがあるので、とても参考になります。
EmotionでCSS in JS
Emotionとは、JavaScriptでCSSスタイルを記述するために設計されたライブラリです。後発ライブラリのため、styled-component等の良いとこ取りをしています。
必要なパッケージをインストールしましょ。
- @emotion/styled
- @emotion/core
$ npm i @emotion/styled @emotion/core
Emotionを使ってみよう
@emotion/styledを使い、styled-componentライクなコンポーネントを作ってみます。
// components/Heading.js // @emotion/styledをインポート import styled from '@emotion/styled' // styled.{要素}`{css}` の形で使用します。 // 定数に代入することで、コンポーネントとして利用できます。 // ブラウザ上ではユニークな文字列のCSSクラスが付与されるので、CSSはスコープになります。 const HeadingStyle = styled.h1` font-size: 20px; color: red; ` function Heading({ children }) { return <HeadingStyle>{children}</HeadingStyle> } export default HeadingCSS部分はテンプレートリテラルなので、
${}
内でJavaScriptが利用できます。// components/Heading.js import styled from '@emotion/styled' // フォントサイズを定数化 const fontSize = 20 // テンプレートリテラル内で定数を使用 const HeadingStyle = styled.h1` font-size: ${fontSize}px; color: red; ` function Heading({ children }) { return <HeadingStyle>{children}</HeadingStyle> } export default Headingコンポーネント側からProps経由で値を渡すことができます。別ファイルにしてデータを渡してみましょう。
$ touch components/HeadingStyle.js// components/HeadingStyle.js import styled from '@emotion/styled' // ES Modules // 関数の引数としてデータを受け取ります export const HeadingStyle = styled.h1` font-size: ${props => props.fontSize}px; color: red; `// components/Heading.js import { HeadingStyle } from './HeadingStyle' const fontSize = 20 function Heading({ children }) { return <HeadingStyle fontSize={fontSize} >{children}</HeadingStyle> } export default HeadingSCSSのようにネストが使えます。
// components/HeadingStyle.js import styled from '@emotion/styled' // SCSSのように&が使えます。 export const HeadingStyle = styled.h1` font-size: ${props => props.fontSize}px; color: red; &:hover { color: green; } `CSS in JSのメリット
たとえば、ブレークポイントをJavaScriptで管理すれば、Carouselのライブラリ等と共通の値を使うことができます。
$ mkdir const $ touch const/breakPoints.js// const/breakPoints.js const breakPoints = { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200 } export default breakPoints// components/HeadingStyle.js import styled from '@emotion/styled' import breakPoints from '../const/breakPoints' export const HeadingStyle = styled.h1` font-size: ${props => props.fontSize}px; color: red; @media (min-width: ${breakPoints.md}px) { color: green; } `;react-slickを使ってみましょ。
https://react-slick.neostack.com/
$ npm i react-slick slick-carousel raw-loader $ touch next.config.js
next.config.js
で、Next.jsが隠蔽しているwebpackの設定にアクセスできます。raw-loaderを追加して、CSSファイルを扱えるようにします。// next.config.js module.exports = { webpack: config => { config.module.rules.push({ test: /\.css$/, use: "raw-loader" }); return config } }スライダーコンポーネントを作り、トップページで使ってみましょう。
$ touch components/MySlider.js// components/MySlider.js import React from 'react' import styled from '@emotion/styled' import Slider from 'react-slick' import slickCss from 'slick-carousel/slick/slick.css' import slickThemeCss from 'slick-carousel/slick/slick-theme.css' import breakPoints from '../const/breakPoints' const settings = { infinite: false, slidesToShow: 2, slidesToScroll: 2, responsive: [ { breakpoint: breakPoints.md, // const/breakPoints.jsの値が使える settings: { infinite: true, slidesToShow: 1, slidesToScroll: 1, } } ] }; const SliderWrapperStyle = styled.div` ${slickCss} ${slickThemeCss} ` function MySlider({ member }) { return ( <SliderWrapperStyle> <Slider {...settings}> {member.map((animal, index) => ( <div key={index}>{animal}</div> ))} </Slider> </SliderWrapperStyle> ) } export default MySlider// pages/index.js import React, { useState } from 'react' import Link from 'next/link' import MySlider from "../components/MySlider"; import Heading from '../components/Heading' const member = ['ネズミ', '牛', 'トラ', 'うさぎ'] function Index() { const text = 'Next.js!' const [value, setValue] = useState('No, Click.'); const onClickEvent = () => setValue('Yes, Click!!'); return ( <> <Heading> <span>{`Hello, ${text}`}</span> </Heading> <button onClick={() => console.log("onClick")}>ボタン</button> <button onClick={onClickEvent}>{value}</button> <Link href="/batman"> <a>バットマンページへ</a> </Link> {/* member配列をPropsで渡す */} <MySlider member={member} /> </> ); } export default Index他にもEmotionでいろいろなことができるので、ぜひ掘り下げてみてください。
- 投稿日:2019-12-21T12:23:47+09:00
Firestoreへの各種データの書き込みと表示
よく使う各種データの書き込み・表示・変換のメモ。
サンプルコード
index.jsconst admin = require('firebase-admin'); const moment = require('moment'); admin.initializeApp({ credential: admin.credential.cert('/path/to/key.json'), databaseURL: 'https://xxxxxxxxxx.firebase.com', }); const db = admin.firestore(); //dataを定義 const _id = 1; const _name = "Hoge"; const _age = "33"; const _hobbies = ['Golf', 'Fishing']; const _birthday = "2011-01-11"; const _note = { pet: true, type: 'Dog' } const _agree = true; //async/awaitのために即実行形式にしてる (async () => { //add 必要な形式に変換 const res = await db.collection("members").add({ id: _id, //そのまま数字 name: _name, //そのまま文字列 age: Number(_age), //数字から文字列 hobbies: _hobbies, //そのまま配列 birthday: admin.firestore.Timestamp.fromDate(moment(_birthday).toDate()), //文字列からTimestamp形式に note: _note, //Object型 agree: _agree, //Boolean createdAt: admin.firestore.FieldValue.serverTimestamp(), //現在時 }); //登録されたid取得 const docId = res.id; //docを取得 const snapshot = db.collection("members").doc(docId).get(); const doc = (await snapshot).data(); //表示 console.log(`id:${doc.id}`); console.log(`name:${doc.name}`); console.log(`age:${doc.age}`); console.log(`hobbies:${doc.hobbies[0]}`); console.log(`birthday:${moment(doc.birthday.seconds * 1000).format('YYYY-MM-DD')}`); //形式変換 console.log(`agree:${doc.agree}`); console.log(`note:${doc.note.pet}`); console.log(`createdAt:${moment(doc.createdAt.seconds * 1000).format('YYYY-MM-DD')}`); //形式形式 })()実行
node index.js id:1 name:Hoge age:33 hobbies:Golf birthday:2011-01-11 agree:true note:true createdAt:2019-12-21
- 投稿日:2019-12-21T12:06:07+09:00
JavaScriptを勉強するべき5つの理由
はじめに
こんにちは。コードクリサリスの日本語プログラム事業責任者の新徳雅隆です。Immersiveの卒業生でもあります。
この投稿はコードクリサリスのMaryが書いてくれたこの記事の翻訳です。
Introduction
どのプログラミング言語を最初に勉強するべきなの?
という質問は頻繁にいただきます。意欲的なディベロッパーになるためには、キャリア形成を早くから、また頻繁に考えることが重要になってきます。そしてそのときに直面するのは、どのプログラミング言語を集中して勉強するべきかという問いです。
もちろんあとでいくらでも他のプログラミング言語を勉強できます。それと同時にエンジニアであればなんか一つのプログラミング言語を学んでしまえば、他の新しい言語を学ぶことは簡単なことも事実です。しかし私たちがここで言いたいのは、
最初に学ぶプログラミング言語が大事
ということです。もしコンピューターサイエンスを大学等で勉強している方は、だいたいCやJavaを学んでいるのはないでしょうか。しかしもし大学等で専門的に勉強しているのではなく、これから勉強してソフトウェアエンジニアを志すなら、他のもっと良い選択肢があります。
コードクリサリスでは、カリキュラムをJavaScriptで構成しています。今までソフトウェアエンジニアとしてのキャリアがない方には特に、またあったとしてもそれでもJavaScriptがおすすめな理由があります。
JavaScriptはどこでも使われている
もしWebエンジニアになりたいのであれば、JavaScriptは避けて通れません。すべてのWebブラウザ(Chrome, Firefox, Edgeなどなど)はビルトインといって、私たちが何もしなくてもJavaScriptを搭載しています。そしてWebアプリの90%以上がJavaScriptで開発されています。
JavaScriptを動かさない限り、Webは使えないのです。Webの構成要素の根幹と言っても過言ではなく、他の言語にこのような言語はありません。そしてこの状況はすぐに変わらないでしょう。
技術的なバックグラウンドがなくても問題なし
もしソフトウェアエンジニアリングのバックグラウンドがないところから始めると、なにか難しい気がしてしまうのではないでしょうか。大学でコンピューターサイエンスを勉強しているのでもなければそれは当然だし、ディベロッパーとしてキャリアを形成するチャンスはないとも思ってしまうでしょう。しかしそんなことは
まったくない
のです。実際、JavaScriptでプログラミングするのはとても簡単です
。JavaScriptはどこでも使われているという根拠は何だったでしょうか?実は読者のあなたはどこにでもJavaScriptのエンジンを持ち歩いているのです。それはWebブラウザです。ブラウザを開いて、ディベロッパーコンソールというのを開けば、もうプログラミングができます。次のステップに進むためには、コードエディタ(Sublime、Atom、Visual Studio Code、他多数)を使うことになります。
自分の好きなものを選びましょう。
イノベーティブ・能動的・成長し続けるコミュニティ
JavaScriptがあちこちで広まっているのは、JavaScriptのディベロッパーの人気によるところもあります。
ここ5年ほどで、JavaScriptは疑いの余地もなく、ソフトウェアエンジニアリングで一番使われている言語としての地位を確立しました。それはGitHubでもStackOverFlowでも見ることができます。SlashDataのデータによると、世界中で1,170万人のJavaScriptを使うディベロッパーがいるそうです。また、
プロフェッショナルに使われている
3つの最も有名なフレームワークもしくはライブラリである、Node.js、AngularやReactはすべてJavaScriptをベースにしています。これが意味するところは、JavaScriptを学んでソフトウェアエンジニアリング業界に飛び込めば、この業界の中で最も大きいコミュニティの一員になることができ、食いっぱぐれることもないということです。例えばこれからもう斜陽であると言われている言語を今から勉強したいと思うでしょうか?
かなり多くの勉強題材が揃っている
こうやってコミュニティが多くなると必然的にJavaScriptを勉強する題材も高品質なものがどんどん生まれます。JavaScriptはプログラミング初心者に向けて作成されている題材でも素晴らしいものがたくさんあります。たとえば無料のものでも、FreeCodeCamp、The Odin Project、Eloquent JavaScript、そしてドキュメントはMozilla Developer Network。
フリーミアムなテリトリーだと、Codeacademy ようなサイトが無料と有料の題材を提供しているところもあります。
人の学び方は人それぞれです。一番自分に向いている選択肢を選びましょう。大切なのはずっと勉強し続けること、効果的に勉強することです。
これはディベロッパーとしてキャリアを形成するなら必須です。趣味のレベルから仕事としてプログラミングするレベルに達するために、そしてそこからプロフェッショナルになるためには、自分で勉強することは必要不可欠です。事実、ソフトウェアエンジニアリングの業界の技術は凄まじく迅速に変わっていきます。キャリアを上手く形成するのは自己学習にかかっているのです。
高給料・多様・安定したキャリア形成
ソフトウェアエンジニアは世界的に不足しており、またそれと同時にJavaScriptは世界中で必要とされ、多様な用途で使われ、またその人気さもあり、JavaScriptを使えるエンジニアの需要は、実際のエンジニアの数を超えています。
2018年のデータですが、JavaScriptを使える人の世界における需要の数が、その言語を学んでいるコンピューターサイエンスの学生数を超えているそうです。
これが意味するところは、JavaScriptを学べば、高給料で、多様な働き方ができて、安定したキャリアを形成できるということです。
最後に
私たちはJavaScriptが大好きです。これを読んでくださった方も好きになってくれることを願っています。もしディベロッパーとしてキャリアを形成しようとしているとしたら、そしてもしコンピューターサイエンスのバックグラウンドがないとしたら、JavaScriptは素晴らしい最初の選択肢となるでしょう。
ぜひ最初の一歩を踏み出してみてください!
- 投稿日:2019-12-21T10:59:42+09:00
Javascript・jquery bodyの取得
body = document.body; body = document.getElementsByTagName('body')[0]; body = $('body');
- 投稿日:2019-12-21T10:10:07+09:00
JavaScriptでデータを整形するときのメモ(継続更新)
faker使えばいいが、素で生成するとき用のメモ。
乱数系
基本
まず、乱数発生の基本。
下記で、0 < n < 1で生成されるが、単独だと事実上使いみちはない。console.log(Math.random()); 0.15043077282053852よく使うのがMath.floor()と合わせて使う。
以下で0〜9までの値が生成される。console.log(Math.floor(Math.random() * 10));下記のようにすると、0~4までの値が生成される。
console.log(Math.floor(Math.random() * 5));0ありは配列操作等のときは便利(後述)だが、一般には1を足すことがお多い。
console.log(Math.floor(Math.random() * 10) + 1);これで1~10が生成される。
応用
上記を利用してよく利用するのが、
指定範囲の値を生成
例えば15歳から60歳までの値を生成したいときなどは以下のようにする。
console.log(Math.floor(Math.random() * ((60 + 1) - 15)) + 15); 34一般化すると、
Math.floor(Math.random() * ((max + 1) - min)) + minという形式となる。
配列の中から値を選ぶ
あとよくあるのが配列の中からランダムに値を選ぶときなど。
下記の場合、gender.length()は2なので0,1が生成される。const gender = ['女性', '男性']; console.log(gender[Math.floor(Math.random() * gender.length)]); 女性(前)0埋め(固定長生成)
おまけですが、生成した値を0埋めして固定長の値にする。
const id = Math.floor(Math.random() * 999) + 1; console.log(id.toString().padStart(8, "0")); console.log(id.toString().padEnd(8, "0")); 00000017 17000000splice使った書き方してましたが、上記の方がモダンというコメントいただきました。そのためのメソッドなので、こちらのほうがいいですね(前埋め、後埋めにも対応できますし)。
- 投稿日:2019-12-21T10:01:41+09:00
Overpass APIのススメ
Overpass APIのススメ
- OpenStreetMap Advent Calendar 2019向けの記事です。
- 記事中の地図は全てOpenStreetMap ( © OpenStreetMap contributors )を利用しています。
Overpass APIとは
- 世界中のみんなが自由に使える地図を描く&活用するプロジェクト「OpenStreetMap」の地理空間情報を扱うためのAPIです。
どんなデータがあるの?
- OpenStreetMapに描けるデータは全て扱えます。海岸線からベンチまでOK。
- もちろん「描ける≠描いてある」。無ければ地図に描いて世界にシェアしよう。
- 具体的に扱えるデータについては、Map Featuresを参照してみてください。
とりあえず触ってみよう
- Overpass turboというWebサイトを使えば、簡単にOverpass APIを使ってみることが出来ます。
- 簡単に説明すると「地図版のコンソール」。そう、WindowsやUnixでおなじみの黒くて文字だらけの地図版です。
![]()
- 画面の左側にOverpass APIを操作するための言語「Overpass QL」を書く欄があります。
- Overpass QLを書いて「実行」ボタンを押せばOverpass APIの実行結果が右側の地図に表示される仕組みです。
- まずは、日本の都市部へ地図を移動させてください(Google Mapsと同じ要領で地図の移動と拡大縮小が出来ます)。
トイレのデータを抜き出してみる
- そして、便利なことにOverpass QLを作る機能まであります。少しズームしてから「ウィザード」ボタンを押してください。
![]()
- クエリウィザードが表示されるので「toilets」と入力して「クエリを作成して実行」ボタンを押してください。
![]()
- しばらく待つと地図上に丸いアイコンが表示されます。このアイコンがトイレの位置を表しています。
![]()
- 表示された丸いアイコンをクリックしてみると、トイレの詳細(タグ)が表示されます。
![]()
トイレの中身(データ)を見てみる
- 画面右上の「データ」タブをクリックしてください。すると地図からデータに表示が切り替わります。
![]()
- JavaScript使いは分かるかと思いますが、Json形式のデータがOverPass APIから帰って来ます。
- 「type: node」は「点(ポイント)」を意味し、latとlonは緯度経度。tagsはMap Featuresにあるタグが入ります。
世界中のトイレ情報が手の内に!
- もちろん、建物や道路、ベンチ、海岸線、鉄道網、マンホール、湖、店舗、寺社仏閣(参道~灯籠含む)、森林(エリア)、樹木(一本)、並木。キリがないほどのデータを入手出来ます。
- そして、それが全てオープンデータとして自由に利用することが出来ます。もちろん、ビジネスでの利用もOK。
- データが無い?それなら描いてみよう。それが誰かの役に立つ。積み上がっていくと大きな価値になります。
参考サイト
- もう少し具体的(JavaScriptから使うなど)な方法は、次の機会で公開しようと思います。とりあえず、以下のサイトを参考にすると開発スキルのある方なら使いこなせるかと思います。
公式Wiki
- https://wiki.openstreetmap.org/wiki/JA:Overpass_API
- 日本語版Wikiはあまり更新されていないけど、Overpass API自身があまり更新されていないので、まぁ大丈夫かな。
まち歩きマップメーカー
- https://github.com/K-Sakanoshita/OSM_AccessMap
- Overpass APIを使ったWebサイト。Javascript使いならこれを参考にするとだいたい分かるかと思います。
- 投稿日:2019-12-21T06:14:55+09:00
Vue.jsで"ボタン"を作る
はじめに
この記事は、SLP KBIT Advent Calender 2019の22日目の記事です。
Vue.jsのコンポーネントについての理解を深めたいので、基本的な機能を持つボタンを自作してみたいと思います。
Vue.jsのインストールとプロジェクト作成はvue-cliでコマンド叩くだけなので省略します。使用環境
Node.js v12.13.1
npm 6.12.1
@vue/cli 4.0.5とりあえず作ってみる
クリックしたときに親コンポーネントにイベントを通知するようにします。
子コンポーネントから親コンポーネントにイベントを通知するには、$emitを使います。
第一引数でイベント名を登録します。第二引数以降で変数を渡すことも可能ですが、今回は使いません。
親コンポーネント側では適当なメソッドを作成してイベントが発火したことを確認します。App.vue<template> <div> <MyButton @click="click('my-button')"></MyButton> </div> </template> <script> import MyButton from './components/MyButton.vue'; export default { components: { MyButton }, methods: { click(message) { alert(message); } } } </script>MyButton.vue<template> <div @click="click">ボタン</div> </template> <script> export default { methods: { click() { this.$emit('click'); } } } </script>これでボタンが完成しました。
とはいかない
さすがにこれだけではただクリックできるだけのテキストです。
これがボタンだとユーザに知らせるため、少しデザインを変更してみましょう。
MyButton.vueにstyleを追加して、ボタンっぽく見せてみます。
ソースコードの重複する部分は省略します。MyButton.vue<template> <div class="button" @click="click">ボタン</div> </template> <script> ... </script> <style lang="scss" scoped> .button { padding-left: 2px; padding-right: 2px; display: inline-block; border: 2px solid rgb(255,194,64); background: linear-gradient(to bottom, rgb(255,244,92), rgb(240,229,86)); } </style>
陰影をつけて立体感を出したら少しだけそれっぽくなりました。
黄色なのは黄色が好きだからです。もっとボタンっぽくする
マウスホバー時とクリック時のデザインを追加して、このボタンが押せること、ボタンを押したことを知らせます。
まずborderを半透明にしておき、マウスホバー時にはっきり表示させようと思います。
カーソルも矢印から指に変えてわかりやすくします。
クリック時には陰影を反転し、ボタンが押せたことをわかりやすくします。v-bind:classにオブジェクトを渡すことでクラスを動的に切り替えることができます。
isClicked、isEnteredの真偽値によって、それぞれのクラスが適用されるかが決まります。
cssは後に読み込まれたものが優先されるので、わざわざbuttonクラスを外す必要はありません。
それぞれのイベントが発火したときに値を切り替えることで実現します。MyButton.vue<template> <div class="button" v-bind:class="{clicked: isClicked, entered: isEntered}" @click="click" @mousedown="mousedown" @mouseup="mouseup" @mouseenter="mouseenter" @mouseleave="mouseleave" >ボタン</div> </template> <script> export default { data() { return { isClicked: false, isEntered: false } }, methods: { click() {...}, mousedown() { this.isClicked = true; }, mouseup() { this.isClicked = false; }, mouseenter() { this.isEntered = true; }, mouseleave() { this.isEntered = false; } } } </script> <style lang="scss" scoped> .button { padding-left: 2px; padding-right: 2px; display: inline-block; border: 2px solid rgba(255,194,64,.5); background: linear-gradient(to bottom, rgb(255,244,92), rgb(240,229,86)); } .clicked { background: linear-gradient(to top, rgb(255,244,92), rgb(240,229,86)); } .entered { cursor: pointer; border: 2px solid rgba(255,194,64,1); } </style>ラベルを変更する
次に親コンポーネントからボタンのラベルを変更できるようにします。
slotをつかうことで、子コンポーネントの<slot></slot>が、親コンポーネントのタグの中の要素で置換されます。App.vue<template> <div> <MyButton @click="click('my-button')">スロット</MyButton> </div> </template> <script> ... </script>MyButton.vue<template> <div ... > <slot></slot> </div> </template> <script> ... </script> <style lang="scss" scoped> ... </style>状態を追加する
もう少し機能を追加していきます。
バリデーションなどで条件を満たさないと押せないボタンってありますよね。
そのために子コンポーネント側でdisableの状態を作成します。
親コンポーネントから真偽値を渡し、子コンポーネントではpropsで受け状態を操作します。
disableがtrueの場合は、ボタン全体を半透明にし、ボタンの機能を使えないようにします。
値を渡す場合はdisable="true"のように書きますが、真偽値の場合はdisableと記述するだけでtrueを渡すことができます。App.vue<template> <div> <MyButton @click="click('my-button')">ボタン</MyButton> <MyButton @click="click('my-button')" disable>disable</MyButton> </div> </template> <script> ... </script>MyButton.vue<template> <div class="button" v-bind:class="{clicked: isClicked, entered: isEntered, disabled: disable}" @click="click" @mousedown="mousedown" @mouseup="mouseup" @mouseenter="mouseenter" @mouseleave="mouseleave" > <slot></slot> </div> </template> <script> export default { data() {...}, props: { disable: { type: Boolean, default: false } }, methods: { click() { if (!this.disable) { this.$emit('click'); } }, mousedown() { if (!this.disable) { this.isClicked = true; } }, mouseup() {...}, mouseenter() { if (!this.disable) { this.isEntered = true; } }, mouseleave() {...} } } </script> <style lang="scss" scoped> .button {...} .clicked {...} .entered {...} .disabled { cursor: default; opacity: .6; } </style>
disable状態を実装できました。
これで文句なしにボタンといえるでしょう。おわりに
今回は$emitやpropsを使ってボタンを作成しました。
他にも、propsで指定の色名を受け取って色の変更などができるといいですね。
Vue.jsは面白いので全人類が一度は触ってみるべきだと思います。あとみんなどうやってきれいにgif撮ってるんですかね。
- 投稿日:2019-12-21T06:03:47+09:00
SliP のご紹介と JavaScript での実装
0.始めに
こんにちは、SliP というプログラム言語を作っているものです。
SliP がどんな言語かというと、例えば 0 から 4 までの自然数の和を求めるプログラムは例えば以下のように再帰的に書けます。(もちろん再帰的じゃなくても書けます)
'sigma = '( @ == 0 ? [ 0 ( @ + ( @ - 1 ):sigma ) ] ); 4: sigma =
@
は引数を表します。@
が0
なら0
を、そうでなければ( @ - 1 )
にsigma
を適用したものに@
を足したもの、という求め方です。
SliP という名前から連想できる方もおられるかもしれませんが、非常に Lisp に影響をうけています。というか Lisp はとにかくカッコが多くて読みにくいんで、新しい文法を考えてしまおう、と思ったのが開発のきっかけです。macOS のアプリに仕立てたものが以下のアドレスからダウンロードできます。
https://apps.apple.com/jp/app/slip/id1097457043?mt=12このアプリは Swift で書きましたが、この記事ではそのサブセットを JavaScript(node.js)で実装してみることにします。以下「JS版」といいます。
JS版の最終ソースは GitHubのここにあります。1.SliP のコンセプト
以下の型があります。
- 文字列(Literal)
- 数値(Numeric)
- 名前(Name)
- 辞書(Dict)
- リスト(List)
- 文(Sentence)
- 手続き(Procedure)
- 並行手続き(Parallel)
- 中置演算子(Infix)
- 単一引数関数(Unary)
- 前置演算子(Prefix)
- プリミティブ(Primitive)
- SliP
論理型はありません。条件判定をするときは値が
[]
(空のリスト、Nil と呼んでいます)かどうかで判断します。比較系のオペレータの値で Nil でないときは便宜的に SliP 型の唯一のオブジェクトである T が返されます。他の型は全て SliP 型を継承しています。全てのオブジェクトは評価(Eval)するとオブジェクトを返します。Eval に渡される引数はコンテキスト(辞書チェーン)です。
また全てのオブジェクトは string メソッドを持ち、それは適切な文字列を返します。JS版での実装(抜粋)
class Context { constructor( _, next ) { this.dict = _ this.next = next } } class SliP { constructor( _ ) { this._ = _ } get string() { return 'T' } Eval( c ) { return this } } class Numeric extends SliP { constructor( _ ) { super( _ ) } get string() { return String( this._ ) } } class Literal extends SliP { constructor( _ ) { super( _ ) } get string() { return `"${this._}"` } }2.定数
SliP の定数とは、評価されるとそれ自身を返すオブジェクトです。以下のものがあります。
- 文字列(Literal) 例:
"ABC"
- 数値(Numeric) 例:
123
- 辞書(Dict) 例:"{\"b\":4}":dict
- リスト(List) 例:
[ "A" 1 ]
- T なんらかの空リスト(
[]
)でないオブジェクトを表します。3.文と演算子
SliP の文は以下のような括弧にくくられた形をしてます。
( 1 + 2 )この文の値は
3
です。( 1 < 2 + 3 )この文の値は
T
です。( [ "A" "B" "C" ] : 2 )この文の値は
"C"
です。他言語での[ "A", "B", "C" ][ 2 ]
と同じ感じです。中置演算子(infix operator)
+
とか<
とか:
は中置演算子と呼ばれます。以下にその名前と優先順位を示します。左辺をl
, 右辺をr
で表します。= 90 l に r を定義する ? 80 l が Nil でなければ r[ 0 ] を、そうでなければ r[ 1 ] を評価する ¿ 80 If が Nil でなければ r を評価、そうでなければ Nil && 70 論理積 || 70 論理和 ^^ 70 排他的論理和 ∈ 60 l は r のメンバー ∋ 60 r は l メンバー == 60 l と r は等しい <> 60 l と r は等しくない < 60 l は r より小さい > 60 l は r より大きい <= 60 l は r 以下 >= 60 l は r 以上 , 50 ( "A", [ "B" "C" ] ) => [ "A" "B" "C" ](Lisp の cons) & 40 ビット単位の論理積 | 40 ビット単位の論理和 ^ 40 ビット単位の排他的論理和 + 30 加算、数値、文字列、リストに適用 - 30 減算 × 20 乗算 ÷ 20 割り算 % 20 余り : 10 適用 ( [ "A" "B" "C" ] : 2 ) => "C" ( 0 : cos ) => 1 ( "{\"b\":4}" : dict : 'b ) => 4乗算の名前は
×
です(Unicode の 0xD7)。x
(小文字のエックス) ではありません。単一引数関数(unary function)
中置演算子の右辺には単一引数関数を置くことができます。以下にその名前と優先順位を示します。左辺を
l
, 右辺をr
で表します。! 評価したものを返します。 # リストのメンバーの数を返します。 * リストの最初のメンバーを除いたものを返します。(Lisp の cdr) $ リストの最後のメンバーを返します。 · 文字列化したものを返します。 . 標準出力に表示します。引数をそのまま返します。 ¦ 標準エラー出力に改行をつけて表示します。引数をそのまま返します。アプリ版はこの他にも sin, cos のような数学関数などがインプリされています。
前置演算子(prefix operator)
前置演算子は以下のものがあります。前置演算子の優先度は中置演算子より高いです。
' 右辺をそのまま返します。クオートと呼ばれます。 ¡ 右辺を例外として投げます。 ~ 右辺をビット毎に反転したものを返します。 ¬ 右辺を論理的に反転したものを返します。 ` 右辺に2要素のリストをとり、0番目の要素である辞書をコンテクスト辞書チェーンの最初に付け加え、1番目の要素を評価したものを返します。プリミティブ(primitive)
それだけで何かを返すものです。
@ 引数(:オペレータの左辺)を返します。 @@ 引数リストを返します。 ¤ コンテクスト(辞書チェーン)の最初の辞書を返します。JS 版の実装(抜粋)
const _EvalSentence = ( c, _ ) => { switch ( _.length ) { case 0: return new List( _ ) case 1: return _[ 0 ].Eval( c ) case 2: break default: const wInfixIndices = Object.keys( _ ).filter( i => _[ i ] instanceof Infix ).map( _ => Number( _ ) ) if ( wInfixIndices.length ) { const i = wInfixIndices.reduce( ( a, c ) => _[ c ].priority >= _[ a ].priority ? c : a , wInfixIndices[ 0 ] ) if ( ( 0 < i ) && ( i < _.length - 1 ) ) { return _[ i ]._( c, _EvalSentence( c, _.slice( 0, i ) ), _EvalSentence( c, _.slice( i + 1 ) ) ) } } break } throw `Syntax error: ${new _List( _ ).string}` } class Sentence extends _List { constructor( _ ) { super( _ ) } get string() { return `( ${super.string} )` } Eval( c ) { return _EvalSentence( c, this._ ) } }4.変数と名前とコンテキスト
コンテキストは辞書のチェーンです。後述の「手続き(procudure)」が実行される場合、チェーンの最初に新しい辞書が加えられます。
コンテキストの登録、参照にはキーとして名前が使われます。名前が評価されるとコンテキストの最初の辞書から参照しにいき、値が見つかればその値を返します。見つからなければチェーンの次の辞書を見にいき、次の辞書がなければ例外を投げます。
同じ名前でも値が違う場合があるので変数と呼ばれます。名前にはほとんどの文字が使えます。使えない文字は以下のとおりです。
- 空白系 JavaScript の \s にマッチするもの
- 括弧系 [({«»})]
- オペレータに使われる文字 =,? など
- その他 ;
JS 版の実装(抜粋)
class Context { constructor( _, next ) { this.dict = _ this.next = next } } class Name extends SliP { constructor( _ ) { super( _ ) } get string() { return this._ } Eval( c ) { while ( c ) { const v = c.dict[ this._ ] if ( v ) return v c = c.next } throw `Undefined:${this._}` } }5.手続き(procudure)
手続きは複数の文(Sentence)を逐次処理するためのものです。また、これが評価されるとき、コンテキストの最初に新しい辞書が加えられます。各文の値のリストが返されます。
{ ( 'a = 1 + 2 ) ( 'b = 3 + 4 ) ( a + b ) }の値は
[ 3 7 10 ]となります。
JS 版の実装(抜粋)
class Procedure extends _List { constructor( _ ) { super( _ ) } get string() { return `{ ${super.string} }` } Eval( c ) { c = new Context( {}, c ) return new List( this._.map( _ => _.Eval( c ) ) ) } }6.並行手続き(Parallel)
並行手続きは複数の文を並行処理するものです。
« ( 'a = 4 ) ( 'a = 5 ) ( 'a = 6 ) »の値は
[ 4 5 6 ]となります。仕様ではこれの評価後の a の値は 4, 5, 6 のどれになるかは不定です。ただ、JS版での実装は上から逐次になっていて a は 6 になります。
7.糖衣構文
REPL(Read, Eval, Print Loop)のトップレベルだけ、糖衣構文を使うことができます。
最初にご紹介した以下のプログラムは糖衣構文です。
'sigma = '( @ == 0 ? [ 0 ( @ + ( @ - 1 ):sigma ) ] ); 4: sigma =元の構文は
( 'sigma = '( @ == 0 ? [ 0 ( @ + ( @ - 1 ):sigma ) ] ) ) ( 4: sigma :¦ )です。
8.実際
おなじみのハノイの塔をやってみます。
'hanoi = '{ ( 'n = @:0 ) ( 'from = @:1 ) ( 'to = @:2 ) ( 'via = @:3 ) ( n == 1 ? [ ( ( "From " + from:· + " To " + to:· ):¦ ) { ( { ( n - 1 ) from via to }:hanoi ) ( ( "From " + from:· + " To " + to:· ):¦ ) ( { ( n - 1 ) via to from }:hanoi ) } ] ) }; [ 3 "a" "b" "c" ]:hanoi;3 枚の円盤を "a" の棒から "b" の棒へ "c" を経由して移すパターンです。
結果は以下のようになります。"From a To b" "From a To c" "From b To c" "From a To b" "From c To a" "From c To b" "From a To b"9.辞書型
辞書型のオブジェクトは2通りの生成の仕方があります。
辞書型の出力時の文字表現は辞書の内容を JSON にしたものになっています。
最初の方法は、JSON 文字列を内容として持つ文字型に dict オペレータを適用する方法です。"{\"b\":\"4\"}" : dict : 'b =結果は
4
となります。次にコンテクストの最初の辞書から作る方法で、以下のように生成します。
'a = 3; 'add1 = '( @ + 1 ); ¤ =結果は
{"a":"3","add1":"( @ + 1 )"}
です。
¤
は現在のコンテクスト(辞書チェーン)の最初の辞書を内容とする辞書型のオブジェクトを返します。JSON 辞書の値の辞書は SliP のプログラムとして読み込まれ、解釈された結果が値として登録されます。
なので:
3:( ( "{\"add2\":\"( @ + 2 )\"}" : dict ) : 'add2 ) =は
5
を表示します。`は0番目の要素を評価した結果の辞書型の値をコンテクスト(辞書チェーン)の最初の辞書にして、1番目の要素を評価します。
'cd = { ( 'a = 3 ) ¤ }:$; `[ cd a ] =結果は
3
になります。JS 版の実装(抜粋)
class Dict extends SliP { constructor( _ ) { super( _ ) } get string() { const v = Object.keys( this._ ).reduce( ( dict, key ) => { const value = this._[ key ] dict[ key ] = value instanceof Literal ? value._ : value.string return dict } , {} ) return `${ JSON.stringify( v ) }` } } const c = new Context( {} , new Context( { dict : new Unary( ( c, _ ) => { const wJSON = JSON.parse( _._ ) return new Dict( Object.keys( wJSON ).reduce( ( dict, key ) => { dict[ key ] = Read( new StringReader( wJSON[ key ] ) ) return dict } , {} ) ) } ) } ) )10.最後に
サンプルプログラムを
https://github.com/Satachito/SliP/blob/master/JS/Sample.slipここにおいておきました。$ node SliP.js < Sample.slipのように実行できます。
細々と作っているマイナー言語ですが一人でも興味を持ってもらえると嬉しいです。
最後まで読んでいただきありがとうございました。
- 投稿日:2019-12-21T05:55:28+09:00
【入門】Laravel×Vue.js①〜セットアップ編〜
はじめに
PHPのフレームワークであるLaravelで作成したアプリケーションに
JavaScriptのフレームワークであるVue.jsを連携させる方法について説明します。Node.jsのインストール
Node.jsのパッケージ管理ツールnpmを使うので、
Vue.jsを利用するためにはNode.jsが必要です。まず、https://nodejs.org/ja/からインストールしましょう
推奨版、最新版どちらでも構わないので、インストールしてください。
Vue.jsをアプリケーションへインストールする
インストールするパッケージ一覧が記述されている
package.json
を変更した後、
npm
を使ってVue.jsをインストール、ビルドします。package.jsonの変更
package.jsonにはインストールするパッケージがリストとして記述されています。
Vue.js関連のパッケージはvue
、vue-template-compiler
です。package.json{ "private": true, "scripts": { "dev": "npm run development", "development": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", "watch": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", "watch-poll": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --watch-poll --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", "hot": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", "prod": "npm run production", "production": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" }, "devDependencies": { "axios": "^0.19.0", "bootstrap": "^4.0.0", "cross-env": "^5.2.0", "jquery": "^3.2", "laravel-mix": "^4.0.7", "lodash": "^4.17.5", "popper.js": "^1.12", "resolve-url-loader": "^2.3.1", "sass": "^1.15.2", "sass-loader": "^7.1.0", "tar": "^4.4.8", "vue": "^2.5.17", "vue-template-compiler": "^2.6.10" }, "dependencies": { "node-sass": "^4.12.0", "quill": "^1.3.6" } }Vue.jsのインストール
上のパッケージをインストールしましょう
ターミナル$ npm installVue.jsのビルド
インストールしただけではVue.jsは使えないので、ビルドしていきましょう
ターミナル$ npm run devコンポーネントの登録
作成されたコンポーネントをしましょう
resources/app.jsrequire('./bootstrap'); window.Vue = require('vue'); Vue.component('example-component', require('./components/ExampleComponent.vue').default); const app = new Vue({ el: '#app' });Vue.component('example-component', require('./components/ExampleComponent.vue').default);上記の記述で
resources/js/components/ExampleComponent.vue
を
example-component
という名前で登録しますビルド
Vue.js関連のファイルを変更した時はビルドしなければ、変更は反映されません。
ターミナル$ npm run devコンポーネント
サンプルのコンポーネントを確認しましょう
ない場合は手動で作成してくださいresources/js/components/ExampleComponent.vue<template> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">Example Component</div> <div class="card_body"> I'm an example component. </div> </div> </div> </div> </template> <script> export default { mouted() { console.log('Component moundted.') } } </script>内容については関係ないので、今回は説明を省きます。
単純なことなので、時間がある時に一度見てください。ビューファイルでテンプレートを参照する
コンポーネントをビューファイルで参照する方法について説明します。
適当なファイルに挿入して見てくださいresources/views/****.blade.php<head> <link href="{{ mix('/css/app.css') }}" rel="stylesheet" type="text/css"> <meta name="csrf-token" content="{{csrf_token()}}"> </head> <div id="app"> <example-component></example-component> </div> <script src="{{ mix('/js/app.js') }}"></script>スタイルシート
<link href="{{ mix('/css/app.css') }}" rel="stylesheet" type="text/css">
link
タグでスタイルシートを参照しています
しかし、スタイルシートはresources/css
、public/css
に2つあります。
そのため、mix('/css/app.css")
で2つのスタイルシートをまとめて読み込むことができます
CSRFトークン
<meta name="csrf-token" content="{{csrf_token()}}">LaravelでVue.jsを利用する時はCSRFトークンを利用することが推奨されています。
特に理由がない場合は記述しましょうコンポーネントの参照
<div id="app"> <example-component></example-component> </div>コンポーネントは
id="app"
の領域で使用できます
example-component
タグで上で作成したコンポーネントを呼び出します。スクリプトファイル
<script src="{{ mix('/js/app.js') }}"></script>スタイルシートと同様に、
mix('/css/app.css")
で2つのスクリプトファイルをまとめて読み込んでいます。
必ず<example-component>
の下に記述してください。動作確認
最後に動作確認をしましょう
忘れずにビルドしましょうターミナル$ npm run dev上記のような内容が表示されれば完了です!!
疑問、気になるところがございましたら、質問、コメントよろしくお願いします!!!
- 投稿日:2019-12-21T03:52:52+09:00
Three.jsを用いたモデルのアニメーション切り替えを簡単にした話
この記事は、WebGL Advent Calendar 2018 17日目の記事です。
AnimationMixerが扱いづらい
![]()
Three.js内でglTFなどを読み込んでアニメーションさせる場合、AnimationMixerを使用して徐々にモーションが変わるようにするには以下のようなコードを書くことになります。
const loader = new THREE.GLTFLoader(); loader.load('assets/zensuke.gltf', (gltf) { scene.add(gltf.scene) const mixer = new THREE.AnimationMixer(gltf.scene) const actionA = mixer.clipAction(gltf.animations[0]) const actionB = mixer.clipAction(gltf.animations[1]) let actionAWeight = 0 let actionBWeight = 1 mixer.play() function tick() { requestAnimationFrame(tick); // ↓適当です。AとBのWeightが入替わるような処理 actionAWeight += 0.01 actionBWeight -= 0.01 // ...省略... actionA.setEffectiveWeight(actionAWeight) actionB.setEffectiveWeight(actionBWeight) renderer.render(scene, camera); } })各Actionのweightを調整するために冗長でとてもわかりにくいコードに仕上がりました。厳密にweightのバリデーションを追加するともっと複雑になります。
もっと簡潔にアニメーションを切り替えたい
![]()
上のような面倒をしなくてもなめらかにモーションが切り替わってくれるだけでいいので、以下の3点だけ指定すればアニメーションが切り替わるようなツールが欲しい。。。
![]()
- 次のアニメーション名
- 切り替わりにかかる時間
- ループするかどうか
また作ってみました
ということで
utsuroi
というツールを作ってみました。簡単な使用例
import { Manipulator } from 'utsuroi' let manipulator; // Load asset var loader = new THREE.GLTFLoader(); loader.load('assets/model.gltf', (gltf) { scene.add(gltf.scene) // アニメーションの操作者を作成 manipulator = new Manipulator(gltf.scene, gltf.animations); // 初期状態として再生したいアニメーション名を指定 manipulator.play('Rest Pose', true); });毎フレームに
update
メソッドを実行することを忘れないでくださいfunction tick() { requestAnimationFrame(tick); manipulator.update(); // これをしないとweightの更新がされない } tick();アニメーションを切り替える
glTFに含まれているアニメーション名を指定することで切り替えられます
// manipulator.to('アニメーション名', 切り替えにかかる時間ms, ループするか否か); manipulator.to('Walk', 200, true);アニメーションを止める
manipulator.pause();ちなみにBlenderからglTF書き出した時の設定はは以下になります。NLAスタックに登録がされていないと
gltf.animations
にアニメーション情報が入ってこないので注意です。まとめ
とてもシンプルになり簡単に切り替えが可能になりました。アニメーションに特にこだわりが無い方や、とりあえずアニメーションを確認したい方におすすめです。ぜひ使ってみてください
![]()
- 投稿日:2019-12-21T03:34:10+09:00
Chrome のコンソールで動くちょっと派手目なタイマー
console で動くちょっと派手目なタイマーを作りました
スクリプト全体
{ // 各種定数 const alertMessage = "時間です" const htmlBody = document.getElementsByTagName('body'); // タイマーのセット const timerSet = () => { let sec = prompt('計測したい秒数を半角の数字で入れてください'); if(!isNaN(parseInt(sec, 10))) { countDown(sec); } else { alert('これは半角の数字ではありません。再入力してください'); timerSet(); } }; // カウントダウンタイマー const countDown = (sec) => { let nowDate = new Date(); const endDate = new Date(nowDate.getTime() + sec * 1000); let count = sec; const interval = setInterval(function(){ count--; console.log(count); nowDate = new Date(); if(nowDate.getTime() >= endDate.getTime()){ clearInterval(interval); setAlert(); } }, 1000); }; // アラート時の動作 const setAlert = () => { const htmlHead = document.getElementsByTagName("head"); let createStyle = document.createElement("style"); createStyle.setAttribute("type", "text/css"); createStyle.innerHTML = ".vibrate { animation: vibrate .1s infinite; } @keyframes vibrate { 0% {transform: translate(0px, 0px) rotateZ(0deg)} 25% {transform: translate(2px, 2px) rotateZ(1deg)} 50% {transform: translate(0px, 2px) rotateZ(0deg)} 75% {transform: translate(2px, 0px) rotateZ(-1deg)} 100% {transform: translate(0px, 0px) rotateZ(0deg)}}"; htmlHead[0].appendChild(createStyle); htmlBody[0].classList.add('vibrate'); setTimeout(() => { alert(alertMessage); htmlBody[0].classList.remove('vibrate'); }, 500); }; // タイマー関数開始 timerSet(); }解説
特に特殊なことはしてません。
ぜひ chrome の コンソールで動かしてみてください。
- 投稿日:2019-12-21T02:26:06+09:00
RiotGear入門その1
Riot.js Advent Calendar 2019 の20日目です。
元々RiotGearについて予約されていましたが、辞退されてしまったようなので僭越ながら代わりに投稿します。RiotGearとは
RiotGearとはRiot.jsのオープンソースコンポーネントライブラリです。
よく使うHTML部品をカスタムタグとして提供することで、簡単かつ汎用的に呼び出せるようになっています。
npm i -S riotgear
でインストールできます。
直接使う場合は<script src="https://cdn.jsdelivr.net/riotgear/latest/rg.min.js"></script>
v4にはまだ対応しておらず、Riot v3.13.2サポートとなっています。
また、Blaze CSSというものを使っているそうですが、
証明書が怪しいので公式ページは見ていません。後継版?のBlaze UIには対応していないようです。
https://www.blazeui.com/今回Riot v4に対応しようかと思いましたが、ちょっと量が多いのでv3で使っていきます。
使うには以下の3セットが必要です。<link rel="stylesheet" href="https://cdn.jsdelivr.net/blazecss/1.1.3/blaze.min.css"> <script src="https://cdn.jsdelivr.net/npm/riot@3.13.2/riot+compiler.js"></script> <script src="https://cdn.jsdelivr.net/npm/riotgear@3.6.0/dist/rg.min.js"></script>サンプルは公式ページにありますが、APIドキュメントは無さそうなので以下の説明ではソースから内容を判断して記載しています。
アラート
See the Pen ExaWdMV by KAJIKEN (@KAJIKEN) on CodePen.
アラートメッセージを表示します。
rg-alerts
をマウントすることで利用できます。
タグへのパラメータとしてalerts
配列を渡します。{ 'alerts': [ { 'type': 'primary', // アラートの種類 primary / secondary / error の3種類 'text': 'Look! Something you should know about.', // 表示するメッセージ 'dismissable': false, // Xボタンを表示するかどうか 省略時:true 'timeout': 10000, // 自動的に消すまでの時間(ms) 'isvisible': true // 表示するかどうか 省略時:true } ] }イベント
select
: アラートを選択時(onclick
)発行
dismiss
: アラートをXで閉じた時(onclick
)発行バブル
See the Pen gObmQNJ by KAJIKEN (@KAJIKEN) on CodePen.
マウスホバーすると指定した文字列をポップアップ表示します。
rg-bubble
をマウントすることで利用できます。
タグへのパラメータとしてbubble
オブジェクトを渡します。{ 'bubble': { 'text': 'Bubbles go pop!' // 表示する文字列 } }チャート
See the Pen OJPprPL by KAJIKEN (@KAJIKEN) on CodePen.
Chart.jsを使ってグラフを表示します。そのためChart.jsの読み込みが別途必要。
rg-chart
をマウントすることで利用できます。
タグへのパラメータとしてchart
オブジェクトを渡します。{ 'chart': { 'type': 'doughnut', // line|bar|radar|polar|pie|doughnut 'options': {}, // Look at Chart.js documentation on how to populate data and options 'data': [{ 'value': 300, 'color': "#F7464A", 'highlight': "#FF5A5E", 'label': "Red" }, { 'value': 50, 'color': "#46BFBD", 'highlight': "#5AD3D1", 'label': "Green" }, { 'value': 100, 'color': "#FDB45C", 'highlight': "#FFC870", 'label': "Yellow" }] } }イベント
loaded
: チャートを表示した後(mount
の最後)に発行コード
See the Pen KKwWbMN by KAJIKEN (@KAJIKEN) on CodePen.
公式ページには一切書いていないですが、Aceを使ってコードをエディタで表示します。そのためace.jsの読み込みが別途必要。
rg-code
をマウントすることで利用できます。
タグへのパラメータとしてeditor
オブジェクトを渡します。
オプションはAceのドキュメントを参照{ 'editor': { 'code': '<h2>Hello World</h2>', 'mode': 'html', // シンタックスハイライト 'tabsize': 2, 'softtabs': false, 'wordwrap': false, 'readonly': false } }クレジットカード
See the Pen LYEWMLM by KAJIKEN (@KAJIKEN) on CodePen.
カードのアイコンを表示するには画像を用意する必要があります。
img/amex.png
img/diners_club.png
img/discover.png
img/jcb.png
img/mastercard.png
img/visa.png有効な番号の場合、カード会社が出て緑色の枠になります。
スペースやハイフンをいれても無視されます。
rg-credit-card-number
をマウントすることで利用できます。
タグへのパラメータとしてcard
オブジェクトを渡します。
公式ページのサンプルコードはcardオブジェクトを使っていなくてバグっているのでハマった…。しかし、カード番号を入れるもので外部コンポーネントを使う気にはとてもならないので使うことは無いだろう。
{ 'card': { 'placeholder': 'Long number on front', 'cardnumber': '4000 0000 0000 0002' } }日付
See the Pen NWPjOEY by KAJIKEN (@KAJIKEN) on CodePen.
いわゆるDatepickerです。Moment.jsが必要です。
rg-date
をマウントすることで利用できます。
タグへのパラメータとしてdate
オブジェクトを渡します。
これも公式ページのサンプルコードはdateオブジェクトを使っていなくてバグっている。
さらにサンプルはマウント時のタグ名指定'rg-date'
も忘れている。{ 'date': { 'date' : moment(), // 初期日付 'min': '2019/12/01', // 選択可能最小日付 'max': '2019/12/25' // 選択可能最大日付 } }ドロワー
See the Pen GRgmwaw by KAJIKEN (@KAJIKEN) on CodePen.
ドロワーメニューです。
rg-drawer
をマウントすることで利用できます。
タグへのパラメータとしてdrawer
オブジェクトを渡します。{ drawer: { header: 'Drawer', // ヘッダー部 isvisible: true, // 初期表示 position: 'top', // 表示位置: top|right|bottom|left items: [{ id: 1, text: 'Item 1' }, { id: 2, text: 'Item 2' }] } }イベント
select
: 項目を選択(onclick
)した時に発行
close
: 枠外の黒い背景を選択(onclick
)した時に発行Google Analytics
Google Analyticsを設置します。
rg-ga
をマウントすることで利用できます。
カスタムタグのパラメータとしてproperty
属性をセットします。
見た目に変化ないのでコードだけにします。<rg-ga property="UA-12345678-1"></rg-ga>riot.mount('rg-ga');iFramify
iFramifyというライブラリを使ってレスポンシブ対応されたコンポーネントを表示する…らしい。
rg-iframify
をマウントすることで利用できます。
タグへのパラメータとしてiframify
オブジェクトを渡します。
iframifyオブジェクトの中身はiFramifyのオプションと同じとのこと。<rg-iframify> <div class="component"> This is a component. Under 200px, the border of this component will go pink. </div> </rg-iframify>riot.mount('rg-iframify', { iframify: { /* iframify options */ } });インクルード
外部ファイルを読み込んで表示する。
rg-include
をマウントすることで利用できます。
タグへのパラメータとしてinclude
オブジェクトを渡します。<rg-include></rg-include>
var tags = riot.mount('rg-include', { include: { url: 'demo_text.html', // 読み込むURL unsafe: true // trueの場合 カスタムタグごと取得結果で置き換える } }); tags[0].on('loaded', function () { console.log("loaded"); });イベント
loading
: HTTPリクエストを発行(XMLHttpRequest#send
)した後に発行
loaded
: HTTPリクエストの結果を読み込みんだ(XMLHttpRequest#onload
)時に発行Google Maps
Google Mapsを読み込んで表示する。
rg-map
をマウントすることで利用できます。
タグへのパラメータとしてmap
オブジェクトを渡します。<rg-map></rg-map>
var tags = riot.mount('rg-map', { map: { center: { lat: 37.7577, lng: -122.4376 }, zoom: 8 } }); tags[0].on('loaded', function (map) { var marker = new google.maps.Marker({ position: { lat: 37.7577, lng: -122.4376 }, map: map, title: 'Hello RiotGear!' }); });イベント
initialize
: 読み込み開始時(内部利用専用?)
loaded
: Map読み込み完了時その1 まとめ
一度に全部やりたかったですが、思いのほか時間が掛かったのでここで一旦区切ります。
RiotGearの存在自体知らなかったですが、使ってみて思うのはサードパーティのライブラリに依存しすぎていることかな。
簡単に呼び出そうという発想は良いですが、サードパーティライブラリ単体でも呼び出しは簡単なオプションで呼べるので大して変わらない。
そしてドキュメントが不十分であるため、結局サードパーティライブラリの説明は読まなきゃ使えないので意味がない。
サードパーティライブラリを使わずに、よく使うようなHTMLテンプレートがコンポーネント化されてたら便利かな。
ハンバーガーメニューとか。依存するなら1つに絞った方がいい気がする。Bootstrapとか。