40
ES2015 の class ののののののののの のののののの Hiroyuki Kusu ( @hkusu_ ) YAPC::Asia Hachioji 2016 mid in Shinagawa 7/3 LT

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

Embed Size (px)

Citation preview

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

ES2015 の class でアプリケーションを書いてみた話

Hiroyuki Kusu ( @hkusu_ )

YAPC::Asia Hachioji 2016 mid in Shinagawa7/3 LT

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

■ 〜 2015年 12月・ Android アプリの開発

■ 2016年 1月〜・ JavaScript アプリケーションの開発

Javaに慣れ親む

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

わりと Java っぽいクラスベースのオブジェクト志向でいけた!

(あくまで「ぽい」)

ES2015

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

■ ES2015 で書く・ Babel (トランスパイラ )・ Browserify (ブラウザ用の場合 )

■ エディタ・WebStorm (JetBrains 社製 IDE)・ like Android Studio

前提となる環境

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

class

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

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

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

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

コンストラクタ

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

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

インスタンス変数

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

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

インスタンスメソッド

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

class SomeUtil {

static isObject(arg) { return typeof arg === 'object' && arg !== null && !Array.isArray(arg); }

}

export default SomeUtil

SomeUtil.js

クラスメソッド

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

import Person from './Person'

class Men extends Person { hello() { return ` おす! ${this.name} さん ` }}

export default Men

Men.js

継承

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

1ファイル、1クラス(原則)

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

import Person from './Person'import SomeUtil from './SomeUtil'

// …

const person = new Person('山田 ', 45)

クラスをインポートして利用

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

列挙型(ぽいもの )

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

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

利用例

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

const Action = { SEARCH: Symbol(), REGISTER: Symbol(), UPDATE: Symbol(), DELETE: Symbol(),}

export default Action

Action.js

キーとして利用(例えば Redux などで)

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

Singleton

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

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

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

import { qiitaApiService } from './service/QiitaApiService'

// …

qiitaApiService.search('JavaScript', 10) .then(() => { // ... })

アプリケーション内でインスタンスが共有される

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

アクセス修飾子(private / protected)

with WebStorm& JSDoc

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

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メソッド

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

型チェック

with WebStorm& JSDoc

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

// …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}`) }}// …

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

/** @type {QiitaApiService} */export const qiitaApiService = new QiitaApiService(config)

型が迷子になったら @type で指定

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

Test

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

Repository

Service

Dependency injection

Config利用 利用 利用

クラス内で出来るだけ別クラスを new() しない

static な状態の保持は可能な限り避ける

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

import { config } from './../config/Config'import ItemRepository from './../repository/ItemRepository'import QiitaApiService from './../service/QiitaApiService'

const itemRepository = new ItemRepository(new QiitaApiService(config))

Dependency injection

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

// ...

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 単位でテスト

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

// ...

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) }) })})

メソッドのテスト

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

// ...

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) }) })})

テスト対象のインスタンスの組み立て

(必要に応じてテスト用のものと差し替え )

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

// ...

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) }) })})

必要に応じてスタブを用意

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

ほか

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

■ 静的解析・ ESLint を利用

・ Airbnb の規約がおすすめ・WebStorm と連携しておく・ JSDoc 漏れを検査させるとよ

■ ドキュメント生成・ ESDoc を利用

・テストコードとも連動できる

■ HTTP通信ライブラリ・ axios .. Promise に対応

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

まとめ

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

■ ES2015で普通にクラスベースのオブジェ クト志向でアプリケーションが書ける ようになった

■ IDE(WebStorm)でクラス含む型のサポート もある程度うけられる ⇒機能はできるだけ class で表現する

.. ちゃんとやるなら TypeScript がいいと思う

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

Sample codehkusu/react-app-example

※React 周りのコードも含んじゃってます

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

END