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

380 views

Published on

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

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
380
On SlideShare
0
From Embeds
0
Number of Embeds
19
Actions
Shares
0
Downloads
1
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

【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

×