Slide 1

Slide 1 text

͜ͷՆϞς͍ͨਓͷͨΊͷTUFQ ͰىಈͰ͖Δ/VYUKT3BJMT *% 18ೝূ෇͖ αϯϓϧΛެ։ʂ גࣜձࣾϚπϦΧ/PUJBࣄۀ੹೚ऀ"ZVNV4VNJEB ˞͜ͷࢿྉ͸5XJUUFS8&.Ͱྲྀ͍ͯ͠·͢

Slide 2

Slide 2 text

ࣗݾ঺հ

Slide 3

Slide 3 text

גࣜձࣾ ϚπϦΧ 4FOTFT 4'"$3. /PUJB &NBJM5SBDLJOH 3BJMTPS3FBDU ΤϯδχΞ ઈࢍืूதͰ͢ʂ

Slide 4

Slide 4 text

w େֶߦ͖ͳ͕Β*5ϕϯνϟʔاۀͰ໿೥ؒ༗ঈΠϯλʔϯ w 1)1 .Z42- $BLF1)1 Y 8PSEQSFTT $ "41 /&5 w ౦ࣳ৘ใγεςϜגࣜձࣾͰ೥ؒϔϧεέΞࣄۀʹैࣄ w 7#/&5 $ 0SBDMF%BUBCBTF w ̍ਓͰىۀ͠Α͏ͱࢼΈΔ͕ࣦഊ w 3VCZPO3BJMT 'VFM1)1 .Z42- "84 "OTJCMF w גࣜձࣾϚπϦΧʹΤϯδχΞೋਓ໨Ͱ+PJOͯ͠4FOTFTΛ։ൃ w 3VCZPO3BJMT "OHVMBS+4 .Z42- "84 ܦྺ

Slide 5

Slide 5 text

w ̍ਓͰ/PUJBࣄۀΛ੒௕͍ͤͯ͞·͢ ݱࡏࣾ௒ w ΤϯδχΞͷྖҬ͸ɺϑϩϯτɺόοΫΤϯυɺ
 Πϯϑϥ w #J[ྖҬ͸ɺ1.ɺ$4ɺϚʔέɺӦۀ ࠷ۙͯ͠ͳ͍ w ෭ۀͰ΋ͭͷ৽͍͠ࣄۀΛ্ཱͪ͛த ݱࡏ

Slide 6

Slide 6 text

ͳͥ͜ͷλΠτϧʹ ͳͬͯ͠·ͬͨͷ͔

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

/VYUKTͱ͸

Slide 10

Slide 10 text

/VYUKTͱ͸

Slide 11

Slide 11 text

αϯϓϧͷઆ໌

Slide 12

Slide 12 text

ͳͥ͜ͷ044Λ࢝ΊΑ͏ͱ ࢥͬͨͷ͔

Slide 13

Slide 13 text

৽͍͍͍͠αʔϏεΛࢥ͍ͭ ͍ͨʂ

Slide 14

Slide 14 text

ͱ͍͏ͱ͖ʹɺ

Slide 15

Slide 15 text

͙͢ʹ࣮૷Ͱ͖Δ؀ڥ͕ ͳ͍ʜ

Slide 16

Slide 16 text

ͦ͜Ͱࢥߟ͕ࢭ·ͬͨΓɺ໘౗ ʹͳͬͯ΍ΊͨΓ͠ͳ͍Α͏ʹ

Slide 17

Slide 17 text

͜ͷ044Λ΍Ζ͏ͱࢥ͍ͬͯ ·͢ʂ

Slide 18

Slide 18 text

w αϯϓϧ͸શͯެ։͍ͯ͠·͢ w /VYUKTIUUQTHJUIVCDPN XBMLFSTVNJEBOVYUKTTBNQMF w 3BJMTIUUQTHJUIVCDPN XBMLFSTVNJEBSBJMTBQJGPS OVYUKT w DMPOFͨ͠Βͭͱ΋ANBLF EPDLFS@VQAىಈͰ͖·͢ w OVYUKTͷىಈ͸গ͔͔࣌ؒ͠Γ· ͢ αϯϓϧ ˒ελʔ˒௖͚Δͱخ͍͠Ͱ͢ʂ ΍Δؾग़·͢ʂ

Slide 19

Slide 19 text

# Nuxtjsͷ࡞੒ $ yarn create nuxt-app # TypeScript༻ͷؔ࿈ϥΠϒϥϦ $ yarn add -D @nuxt/typescript $ yarn add ts-node $ yarn add vue-property-decorator /VYUKTͷ࡞੒

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

ҎԼͷ3ͭͷϑΝΠϧΛ௥Ճ pages/posts/index.vue components/PostPreview.vue models/Post.ts ػೳ1PTUҰཡ ˡϦϑΝΫλϦϯάͯ͠ݱࡏ͸ 1PTU4IPXʹίϯϙʔωϯτ໊ม Θ͍ͬͯ·͢

Slide 22

Slide 22 text

w ˢAQBHFTAσΟϨΫτϦ͸ಛघͳσΟϨΫτϦͰɺͦͷ഑Լ ʹ഑ஔ͞ΕͨύεͲ͓ΓͷϧʔςΟϯά͕ࣗಈతʹઃఆ͞Ε· ͢ w IUUQQPTUTJOEFY˞JOEFY͸লུ͞ΕΔ w IUUQQPTUTʹΞΫηε͢Δ͜ͱͰAQBHFT QPTUTJOEFYWVFAϑΝΠϧ͕ݺͼग़͞ΕΔ QBHFTQPTUTJOEFYWVF

Slide 23

Slide 23 text

಺෦తʹ͸ҎԼͷΑ͏ʹϧʔςΟϯά͕ు͖ग़͞Ε͍ͯΔ router: { routes: [ { path: '/posts', component: 'pages/posts.vue', children: [ { path: '', component: 'pages/posts/index.vue', name: 'posts' } ] } ] } QBHFTQPTUTJOEFYWVF

Slide 24

Slide 24 text

import { Component, Vue } from 'vue-property-decorator' import Post from '~/models/Post' @Component({ components: { PostPreview: () => import('~/components/PostPreview.vue') }, asyncData() { return { // TODO: call api server posts: [ { id: 1, title: 'aaa', description: 'AAA' } ] } } }) export default class FeedPage extends Vue { posts: Post[] = [] } QBHFTQPTUTJOEFYWVF

Slide 25

Slide 25 text

export default Post { id: number title: string description: string } NPEFMT1PTUUT

Slide 26

Slide 26 text

{{ post.title }}

{{ post.description }}

import { Component, Vue, Prop } from 'vue-property-decorator' import Post from '~/models/Post' @Component export default class PostPreview extends Vue { @Prop({ type: Object, required: true }) post: Post } DPNQPOFOUT1PTU1SFWJFXWVF

Slide 27

Slide 27 text

͜Μͳײ͡Ͱ 1PTUҰཡ͕ग़ དྷ্͕Γ·͢

Slide 28

Slide 28 text

// plugins/axios.ts import axios from 'axios' export default axios.create({ baseURL: process.env.apiUrl }) // nuxt.config.js env: { apiUrl: process.env.API_URL || 'http://0.0.0.0:3000' }, BQJTFSWFSͱͷ௨৴෦෼

Slide 29

Slide 29 text

BQJTFSWFSͱͷ௨৴෦෼

Slide 30

Slide 30 text

ೝূํ๏

Slide 31

Slide 31 text

ࠓճ࢖༻ͨ͠ͷ͸ 5PLFOCBTFೝূ

Slide 32

Slide 32 text

w *%1BTTXPSEΛ4FSWFSʹૹ৴ w *%1BTTXPSE͕ਖ਼͚͠Ε͹ηο γϣϯ*%Λൃߦ͠ɺ$MJFOUଆʹฦ ٫ w $MJFOUଆͰηογϣϯ*%Λ$PPLJF ʹอଘ w Ҏ߱ͷϦΫΤετʹൃߦ͞Εͨ ηογϣϯ*%ΛҰॹʹૹ৴͢Δ͜ ͱͰ4FSWFSଆͰϢʔβΛࣝผ Ұൠతͳೝূ ᶃϩάΠϯ৘ใ *%EFNP!YYYDPN 18EFNPEFNP ᶄϩάΠϯ৘ใΛอଘ ᶅηογϣϯ*% LEGKJBEBKGKGBʜ ᶆ$PPLJFʹηογϣϯ*% Λอଘ

Slide 33

Slide 33 text

w *%1BTTXPSEΛ4FSWFSʹૹ৴ w *%1BTTXPSE͕ਖ਼͚͠Ε͹5PLFOΛ ൃߦ͠ɺ$MJFOUଆʹฦ٫ w $MJFOUଆͰ5PLFOΛ$PPLJFʹอଘ w ࣍ͷϦΫΤετʹ5PLFOΛҰॹʹૹ ৴͢Δ͜ͱͰϢʔβΛࣝผɻͦͷϨ εϙϯεʹ৽ͨͳ5PLFO͕4FSWFSଆ ͔ΒૹΒΕͯ͘ΔͷͰɺͦͷ5PLFO Λอଘ͢Δ w 5PLFO͸࢖͍ࣺͯͳͷͰɺϦΫΤε τຖʹҧ͏ 5PLFOCBTFೝূ ˞EFWJTF@UPLFO@BVUIHFNΛ࢖༻

Slide 34

Slide 34 text

ҰൠతͳೝূΑΓ΋ ηΩϡΞ

Slide 35

Slide 35 text

࠷ۙͷҰൠతͳೝূͰ΋ϥΠϒϥϦʹΑͬͯ͸ ηογϣϯ*%Λ࢖͍ࣺͯͷ஋ʹ͍ͯ͠Δ 5PLFOCBTFͱ΄ͱΜͲมΘΒͳ͍͔΋

Slide 36

Slide 36 text

// plugins/axios.ts axiosInstance.interceptors.response.use((response) => { // See https://devise-token-auth.gitbook.io/devise-token-auth/conceptual setCookies(response.headers) return response }, (error) => { removeCookies() // FIXME: redirect without using window.location if(error.response.status === 401) { window.location.href = '/sign_in' } return Promise.reject(error) }) ೝূ෦෼ͷ࣮૷

Slide 37

Slide 37 text

// plugins/axios.ts axiosInstance.interceptors.request.use((config) => { config.headers = setHeaders(config.headers) return config }, (error) => { return Promise.reject(error) }) ೝূ෦෼ͷ࣮૷

Slide 38

Slide 38 text

ೝূ෦෼ͷ࣮૷

Slide 39

Slide 39 text

ը໘ભҠ ᶃϩάΠϯ੒ޭ ᶄ"%%ϘλϯΛΫϦοΫ ᶅ͏·͘อଘ͞Εͨ৔߹ ᶆ5PLFO͕ਖ਼͘͠ͳ͍৔߹

Slide 40

Slide 40 text

7VFUJGZͷ࢖͍ํ

Slide 41

Slide 41 text

7VFUJGZ IUUQTWVFUJGZKTDPN

Slide 42

Slide 42 text

Save

DPNQPOFOUTQPTU'PSNWVF

Slide 43

Slide 43 text

import { Component, Vue, Prop } from 'vue-property-decorator' import Post from '~/models/Post' @Component export default class PostForm extends Vue { @Prop({ type: Object }) post: Post data() { return { title: '', titleRules: [v => !!v || 'Title is required'], body: '', valid: false } } DPNQPOFOUTQPTU'PSNWVF

Slide 44

Slide 44 text

7VFUJGZ

Slide 45

Slide 45 text

+FTUͷ࢖͍ํ

Slide 46

Slide 46 text

w 'BDFCPPL͕ࣾ044ͱͯ͠։ൃΛਐ Ί͍ͯΔ+BWB4DSJQUͷϢχοτς ετͷͨΊͷπʔϧ w /PEF 3FBDU "OHVMBS 7VFͳͲ ৭ʑͳ؀ڥͰ࢖༻͢Δ͜ͱ͕Ͱ͖ Δ +FTUͱ͸

Slide 47

Slide 47 text

import { shallowMount } from "@vue/test-utils" import PostShow from "@/components/post/Show.vue" describe("PostShow component", () => { let wrapper; beforeEach(() => { wrapper = shallowMount(PostShow, { propsData: { post: { id: 1, title: "AAA", body: "aaa" } } }); }); it("has the expected html structure", () => { expect(wrapper.element).toMatchSnapshot(); }); it("has the expected text", () => { expect(wrapper.text()).toBe('AAA aaa'); }); }); 1PTU4IPXWVFͷςετ 1PTU4IPXίϯϙʔωϯτʹ1PTUͷ஋Λ౉ͯ͠ ΤϨϝϯτΛੜ੒

Slide 48

Slide 48 text

import { shallowMount } from "@vue/test-utils" import PostShow from "@/components/post/Show.vue" describe("PostShow component", () => { let wrapper; beforeEach(() => { wrapper = shallowMount(PostShow, { propsData: { post: { id: 1, title: "AAA", body: "aaa" } } }); }); it("has the expected html structure", () => { expect(wrapper.element).toMatchSnapshot(); }); it("has the expected text", () => { expect(wrapper.text()).toBe('AAA aaa'); }); }); 1PTU4IPXWVFͷςετ exports[`PostShow component has the expected html structure 1`] = `

AAA

aaa

`; ΋ͱ΋ͱ߹ͬͨεφοϓγϣοτͱ৽ͨʹੜ੒͞Εͨ εφοϓγϣοτ͕Ұக͢Δ͔Λςετ͍ͯ͠Δ

Slide 49

Slide 49 text

࠷ޙʹ

Slide 50

Slide 50 text

͋ͱ͸ɺDMPOFͯ͠σόοά͠ ͯײ֮௫ΜͰ͍ͩ͘͞

Slide 51

Slide 51 text

(JUIVCελʔ௖͚Ε͹ࢲ΋΋ͬͱࠓ ޙ΋ؤுͬͯίϛοτ͍͖ͯ͠·͢স

Slide 52

Slide 52 text

༧ఆͱͯ͠͸(PPHMFೝূͱ͔ ΋࣮૷͍͖ͯ͠·͢

Slide 53

Slide 53 text

͜ͷՆϞς͍ͨํ͸ւͳΜ͔ʹߦ͔ͣʹҾ ͖͜΋ͬͯ044׆ಈ͍͖ͯ͠·͠ΐ͏ʂ

Slide 54

Slide 54 text

5IBOLZPV