Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
ES2015 の class で
アプリケーションを
書いてみた話
Hiroyuki Kusu ( @hkusu_ )
YAPC::Asia Hachioji 2016 mid in Shinagawa
7/3 LT
■ 〜2015年12月
・Android アプリの開発
■ 2016年1月〜
・JavaScript アプリケーションの開発
Javaに
慣れ親む
わりと Java っぽいクラスベースの
オブジェクト志向でいけた!
(あくまで「ぽい」)
ES2015
■ ES2015 で書く
・Babel (トランスパイラ)
・Browserify (ブラウザ用の場合)
■ エディタ
・WebStorm (JetBrains 社製 IDE)
・like Android Studio
前提となる環境
class
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
getName() {
return this.name
}
getAge() {
return...
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
getName() {
return this.name
}
getAge() {
return...
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
getName() {
return this.name
}
getAge() {
return...
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
getName() {
return this.name
}
getAge() {
return...
class SomeUtil {
static isObject(arg) {
return typeof arg === 'object' && arg !== null && !Array.isArray(arg);
}
}
export ...
import Person from './Person'
class Men extends Person {
hello() {
return `おす! ${this.name} さん`
}
}
export default Men
Men...
1ファイル、1クラス(原則)
import Person from './Person'
import SomeUtil from './SomeUtil'
// …
const person = new Person('山田', 45)
クラスをインポートして利用
列挙型
(ぽいもの)
const Week = {
SUN: Symbol(),
MON: Symbol(),
TUE: Symbol(),
WED: Symbol(),
THU: Symbol(),
FRI: Symbol(),
SAT: Symbol(),
}
...
const Action = {
SEARCH: Symbol(),
REGISTER: Symbol(),
UPDATE: Symbol(),
DELETE: Symbol(),
}
export default Action
Action....
Singleton
import axios from 'axios'
import { config } from './../config/Config'
class QiitaApiService {
constructor(config) {
this.b...
import { qiitaApiService } from './service/QiitaApiService'
// …
qiitaApiService.search('JavaScript', 10)
.then(() => {
//...
アクセス修飾子
(private / protected)
with WebStorm
& JSDoc
import axios from 'axios'
import { config } from './../config/Config'
class QiitaApiService {
constructor(config) {
/** @p...
型チェック
with WebStorm
& JSDoc
// …
class QiitaApiService {
/**
* @constructor
* @param {Config|SpecConfig} config
*/
constructor(config) {
/** @private ...
/** @type {QiitaApiService} */
export const qiitaApiService = new QiitaApiService(config)
型が迷子になったら @type で指定
Test
Repository層
Service層
Dependency injection
Config
利用 利用 利用
クラス内で出来るだけ別クラスを new() しない
static な状態の保持は可能な限り避ける
import { config } from './../config/Config'
import ItemRepository from './../repository/ItemRepository'
import QiitaApiSer...
// ...
describe('ItemRepository', () => {
let itemRepository
let qiitaApiService
before(() => {
qiitaApiService = new Qiit...
// ...
describe('ItemRepository', () => {
let itemRepository
let qiitaApiService
before(() => {
qiitaApiService = new Qiit...
// ...
describe('ItemRepository', () => {
let itemRepository
let qiitaApiService
before(() => {
qiitaApiService = new Qiit...
// ...
describe('ItemRepository', () => {
let itemRepository
let qiitaApiService
before(() => {
qiitaApiService = new Qiit...
ほか
■ 静的解析
・ESLint を利用
・Airbnb の規約がおすすめ
・WebStorm と連携しておく
・JSDoc 漏れを検査させるとよい
■ ドキュメント生成
・ESDoc を利用
・テストコードとも連動できる
■ HTTP通信ライブラ...
まとめ
■ ES2015で普通にクラスベースのオブジェ
クト志向でアプリケーションが書ける
ようになった
■ IDE(WebStorm)でクラス含む型のサポート
もある程度うけられる
⇒ 機能はできるだけ class で表現する
.. ちゃんとやるなら...
Sample code
hkusu/react-app-example
※React 周りのコードも含んじゃってます
END
【YAPC::Asia Hachioji 2016】ES2015のclassでアプリケーションを書いてみた話
【YAPC::Asia Hachioji 2016】ES2015のclassでアプリケーションを書いてみた話
【YAPC::Asia Hachioji 2016】ES2015のclassでアプリケーションを書いてみた話
Upcoming SlideShare
Loading in …5
×

【YAPC::Asia Hachioji 2016】ES2015のclassでアプリケーションを書いてみた話

673 views

Published on

2016/07/02-03に開催された YAPC::Asia Hachioji 2016 mid in Shinagawa (http://yapcasia8oji-2016mid.hachiojipm.org) での発表資料です。

Published in: Technology
  • Be the first to comment

  • Be the first to like this

【YAPC::Asia Hachioji 2016】ES2015のclassでアプリケーションを書いてみた話

  1. 1. ES2015 の class で アプリケーションを 書いてみた話 Hiroyuki Kusu ( @hkusu_ ) YAPC::Asia Hachioji 2016 mid in Shinagawa 7/3 LT
  2. 2. ■ 〜2015年12月 ・Android アプリの開発 ■ 2016年1月〜 ・JavaScript アプリケーションの開発 Javaに 慣れ親む
  3. 3. わりと Java っぽいクラスベースの オブジェクト志向でいけた! (あくまで「ぽい」) ES2015
  4. 4. ■ ES2015 で書く ・Babel (トランスパイラ) ・Browserify (ブラウザ用の場合) ■ エディタ ・WebStorm (JetBrains 社製 IDE) ・like Android Studio 前提となる環境
  5. 5. class
  6. 6. class Person { constructor(name, age) { this.name = name this.age = age } getName() { return this.name } getAge() { return this.age } hello() { return `こんにちは! ${this.name} さん` } } export default Person Person.js
  7. 7. class Person { constructor(name, age) { this.name = name this.age = age } getName() { return this.name } getAge() { return this.age } hello() { return `こんにちは! ${this.name} さん` } } export default Person Person.js コンストラクタ
  8. 8. class Person { constructor(name, age) { this.name = name this.age = age } getName() { return this.name } getAge() { return this.age } hello() { return `こんにちは! ${this.name} さん` } } export default Person Person.js インスタンス変数
  9. 9. class Person { constructor(name, age) { this.name = name this.age = age } getName() { return this.name } getAge() { return this.age } hello() { return `こんにちは! ${this.name} さん` } } export default Person Person.js インスタンスメソッド
  10. 10. class SomeUtil { static isObject(arg) { return typeof arg === 'object' && arg !== null && !Array.isArray(arg); } } export default SomeUtil SomeUtil.js クラスメソッド
  11. 11. import Person from './Person' class Men extends Person { hello() { return `おす! ${this.name} さん` } } export default Men Men.js 継承
  12. 12. 1ファイル、1クラス(原則)
  13. 13. import Person from './Person' import SomeUtil from './SomeUtil' // … const person = new Person('山田', 45) クラスをインポートして利用
  14. 14. 列挙型 (ぽいもの)
  15. 15. const Week = { SUN: Symbol(), MON: Symbol(), TUE: Symbol(), WED: Symbol(), THU: Symbol(), FRI: Symbol(), SAT: Symbol(), } export default Week import Week from './Week' // … const myWeek = Week.SUN Week.js 利用例
  16. 16. const Action = { SEARCH: Symbol(), REGISTER: Symbol(), UPDATE: Symbol(), DELETE: Symbol(), } export default Action Action.js キーとして利用 (例えば Redux などで)
  17. 17. Singleton
  18. 18. import axios from 'axios' import { config } from './../config/Config' class QiitaApiService { constructor(config) { this.baseUrl = config.QIITA_BASE_URL } search(searchWord, perPage = 10) { return this.httpGet(`search?q=${searchWord}&per_page=${perPage}`) } httpGet(query) { return axios.get(`${this.baseUrl}/${query}`) } } export default QiitaApiService export const qiitaApiService = new QiitaApiService(config) QiitaApiService.js インスタンス化したものを export
  19. 19. import { qiitaApiService } from './service/QiitaApiService' // … qiitaApiService.search('JavaScript', 10) .then(() => { // ... }) アプリケーション内で インスタンスが共有される
  20. 20. アクセス修飾子 (private / protected) with WebStorm & JSDoc
  21. 21. import axios from 'axios' import { config } from './../config/Config' class QiitaApiService { constructor(config) { /** @private */ this.baseUrl = config.QIITA_BASE_URL } search(searchWord, perPage = 10) { return this.httpGet(`search?q=${searchWord}&per_page=${perPage}`) } /** * @private */ httpGet(query) { return axios.get(`${this.baseUrl}/${query}`) } } // … QiitaApiService.js private変数 privateメソッド
  22. 22. 型チェック with WebStorm & JSDoc
  23. 23. // … class QiitaApiService { /** * @constructor * @param {Config|SpecConfig} config */ constructor(config) { /** @private */ this.baseUrl = config.QIITA_BASE_URL } /** * @param {string} searchWord * @param {number} [perPage=10] * @returns {promise} */ search(searchWord, perPage = 10) { return this.httpGet(`search?q=${searchWord}&per_page=${perPage}`) } /** * @param {string} query * @returns {promise} * @private */ httpGet(query) { return axios.get(`${this.baseUrl}/${query}`) } } // …
  24. 24. /** @type {QiitaApiService} */ export const qiitaApiService = new QiitaApiService(config) 型が迷子になったら @type で指定
  25. 25. Test
  26. 26. Repository層 Service層 Dependency injection Config 利用 利用 利用 クラス内で出来るだけ別クラスを new() しない static な状態の保持は可能な限り避ける
  27. 27. import { config } from './../config/Config' import ItemRepository from './../repository/ItemRepository' import QiitaApiService from './../service/QiitaApiService' const itemRepository = new ItemRepository(new QiitaApiService(config)) Dependency injection
  28. 28. // ... describe('ItemRepository', () => { let itemRepository let qiitaApiService before(() => { qiitaApiService = new QiitaApiService(config) itemRepository = new ItemRepository(qiitaApiService) }); describe('#getItemByWord', () => { let qiitaApiServiceSearchStub before(() => { qiitaApiServiceSearchStub = sinon.stub(qiitaApiService, 'search') qiitaApiServiceSearchStub.resolves({ result: 'success' }) }); after(() => { qiitaApiServiceSearchStub.restore() }); it('be fulfilled', (done) => { expect(itemRepository.getItemByWord('abc', 99)).to.be.fulfilled .then((result) => { expect(qiitaApiServiceSearchStub).to.have.been.calledWith('abc', 99) expect(result).to.eql({ result: 'success' }) }) .then(done, done) }) }) }) ※Mocha、Chai、Sinon.JS および Promise系のライブラリを利用 class 単位 でテスト
  29. 29. // ... describe('ItemRepository', () => { let itemRepository let qiitaApiService before(() => { qiitaApiService = new QiitaApiService(config) itemRepository = new ItemRepository(qiitaApiService) }); describe('#getItemByWord', () => { let qiitaApiServiceSearchStub before(() => { qiitaApiServiceSearchStub = sinon.stub(qiitaApiService, 'search') qiitaApiServiceSearchStub.resolves({ result: 'success' }) }); after(() => { qiitaApiServiceSearchStub.restore() }); it('be fulfilled', (done) => { expect(itemRepository.getItemByWord('abc', 99)).to.be.fulfilled .then((result) => { expect(qiitaApiServiceSearchStub).to.have.been.calledWith('abc', 99) expect(result).to.eql({ result: 'success' }) }) .then(done, done) }) }) }) メソッドの テスト
  30. 30. // ... describe('ItemRepository', () => { let itemRepository let qiitaApiService before(() => { qiitaApiService = new QiitaApiService(config) itemRepository = new ItemRepository(qiitaApiService) }); describe('#getItemByWord', () => { let qiitaApiServiceSearchStub before(() => { qiitaApiServiceSearchStub = sinon.stub(qiitaApiService, 'search') qiitaApiServiceSearchStub.resolves({ result: 'success' }) }); after(() => { qiitaApiServiceSearchStub.restore() }); it('be fulfilled', (done) => { expect(itemRepository.getItemByWord('abc', 99)).to.be.fulfilled .then((result) => { expect(qiitaApiServiceSearchStub).to.have.been.calledWith('abc', 99) expect(result).to.eql({ result: 'success' }) }) .then(done, done) }) }) }) テスト対象のインスタンス の組み立て (必要に応じてテスト用の ものと差し替え)
  31. 31. // ... describe('ItemRepository', () => { let itemRepository let qiitaApiService before(() => { qiitaApiService = new QiitaApiService(config) itemRepository = new ItemRepository(qiitaApiService) }); describe('#getItemByWord', () => { let qiitaApiServiceSearchStub before(() => { qiitaApiServiceSearchStub = sinon.stub(qiitaApiService, 'search') qiitaApiServiceSearchStub.resolves({ result: 'success' }) }); after(() => { qiitaApiServiceSearchStub.restore() }); it('be fulfilled', (done) => { expect(itemRepository.getItemByWord('abc', 99)).to.be.fulfilled .then((result) => { expect(qiitaApiServiceSearchStub).to.have.been.calledWith('abc', 99) expect(result).to.eql({ result: 'success' }) }) .then(done, done) }) }) }) 必要に応じてスタブを用意
  32. 32. ほか
  33. 33. ■ 静的解析 ・ESLint を利用 ・Airbnb の規約がおすすめ ・WebStorm と連携しておく ・JSDoc 漏れを検査させるとよい ■ ドキュメント生成 ・ESDoc を利用 ・テストコードとも連動できる ■ HTTP通信ライブラリ ・axios .. Promise に対応
  34. 34. まとめ
  35. 35. ■ ES2015で普通にクラスベースのオブジェ クト志向でアプリケーションが書ける ようになった ■ IDE(WebStorm)でクラス含む型のサポート もある程度うけられる ⇒ 機能はできるだけ class で表現する .. ちゃんとやるなら TypeScript がいいと思う
  36. 36. Sample code hkusu/react-app-example ※React 周りのコードも含んじゃってます
  37. 37. END

×