Slide 1

Slide 1 text

Никита Соболев github.com/sobolevn 1

Slide 2

Slide 2 text

Бизнес логика и Vue.js !2

Slide 3

Slide 3 text

Композиция и абстракция !3

Slide 4

Slide 4 text

Но во Vue.js - компоненты! !4

Slide 5

Slide 5 text

отображение = f(состояния) !5

Slide 6

Slide 6 text

отображение = f(состояния) !6

Slide 7

Slide 7 text

export default { data () { return { username: '' } }, template: ` Hello {{ username }} `, loadUser () { this.username = this.$axios.get('...').data }, } !7

Slide 8

Slide 8 text

отображение = f(состояния) !8

Slide 9

Slide 9 text

export default { data () { return { username: '' } }, template: ` Hello {{ username }} `, loadUser () { this.username = this.$axios.get('...').data }, } !9

Slide 10

Slide 10 text

export default { data () { return { username: '' } }, template: ` Hello {{ username }} `, loadUser () { this.username = this.$axios.get('...').data }, } !10

Slide 11

Slide 11 text

f = check( transform( fetch() ) ) !11

Slide 12

Slide 12 text

f = fetch() |> transform() |> check() !12

Slide 13

Slide 13 text

Кто видит проблему? !13

Slide 14

Slide 14 text

Изменение логики !14

Slide 15

Slide 15 text

export default { data () { return { username: '' } }, template: ` Hello {{ username }} `, loadUser () { this.username = localStorage.getItem('user') }, } !15

Slide 16

Slide 16 text

Плохие решения • Сделать два компонента • Передавать callback в props !16

Slide 17

Slide 17 text

export default { // ... props: ['callback'], loadUser () { this.username = this.callback(...) }, } !17

Slide 18

Slide 18 text

Вопросики • А если у нас нет возможности передать callback? • А если очень глубокая вложенность? !18

Slide 19

Slide 19 text

Плохие решения • Сделать два компонента • Передавать callback в props !19

Slide 20

Slide 20 text

Плохие решения • Сделать два компонента • Передавать callback в props • Передавать флаг для поведения !20

Slide 21

Slide 21 text

export default { // ... props: ['http'], loadUser () { if (this.http) { this.username = this.$axios.get(...) } else { this.username = localStorage(...) } }, } !21

Slide 22

Slide 22 text

Вопросики • А если поведений много? • А если у нас нет возможности выставить флаг? • А если очень глубокая вложенность? • А как его тестировать? !22

Slide 23

Slide 23 text

Тестирование !23

Slide 24

Slide 24 text

import { mount, createLocalVue } from '@vue/test-utils' import MockAdapter from 'axios-mock-adapter' import MyComponent from '~/components/MyComponent' const localVue = createLocalVue() describe('unit tests for MyComponent', () => { test('should have correct logic', () => { const wrapper = mount(MyComponent, { localVue }) new MockAdapter(wrapper.axios) .onGet('...') .reply(200, ['sobolevn']) wrapper.loadUser() expect(wrapper.username).toBe('sobolevn') }) }) !24

Slide 25

Slide 25 text

Плохие решения • Терпеть !25

Slide 26

Slide 26 text

Хорошие решения: композиция и абстракция !26

Slide 27

Slide 27 text

provide / inject !27

Slide 28

Slide 28 text

import { mount, createLocalVue } from '@vue/test-utils' import MyComponent from '~/components/MyComponent' const localVue = createLocalVue() describe('unit tests for MyComponent', () => { test('should have correct logic', () => { const wrapper = mount(MyComponent, { localVue, provide: { buttonCallback: () => 'sobolevn' }, }) wrapper.loadUser() expect(wrapper.username).toBe('sobolevn') }) }) !28

Slide 29

Slide 29 text

Рефакторинг компонента !29

Slide 30

Slide 30 text

export default { data () { return { username: '' } }, template: ` Hello {{ username }} `, inject: ['buttonCallback'], loadUser () { this.username = this.buttonCallback() }, } !30

Slide 31

Slide 31 text

Создание контекста * https://mmhaskell.com/monads-4 !31

Slide 32

Slide 32 text

export default { provide: { buttonCallback: () => this.$axios.get('...'), }, components: { MyComponent }, template: ``, } !32

Slide 33

Slide 33 text

Плохие решения • Пихать логику в компоненты • Делать вид, что ты не пихаешь логику в компоненты !33

Slide 34

Slide 34 text

Рефакторинг компонента !34

Slide 35

Slide 35 text

import buttonCallbackFactory from 'logic/http' export default { provide: { buttonCallback: buttonCallbackFactory(...), }, components: { MyComponent }, template: ``, } !35

Slide 36

Slide 36 text

И не только для компонентов !36

Slide 37

Slide 37 text

C Vue понятно, а Vuex? !37

Slide 38

Slide 38 text

Плохие решения • Пихать логику во Vuex !38

Slide 39

Slide 39 text

отображение = f(состояния) !39

Slide 40

Slide 40 text

Vuex !40

Slide 41

Slide 41 text

import { Inject, Injectable } from 'vue-typedi' import { Action } from 'vuex-simple' @Injectable() export default class UsernameModule { @Inject(tokens.USERNAME_SERVICE) public service // type: UsernameServiceProtocol @Action() public async fetchUsername () { const username = await this.service.fetchUsername() // ... } } !41

Slide 42

Slide 42 text

// type: UsernameServiceProtocol

Slide 43

Slide 43 text

Вставляй любую абстракцию! !43

Slide 44

Slide 44 text

provide / inject vs TypedDI !44

Slide 45

Slide 45 text

И не только DI • Higher Order Components • Slots, Named Slots • https://github.com/vuejs/vue-function-api • Event Bus • Vue.observable, https://github.com/vuejs/vue-rx !45

Slide 46

Slide 46 text

Сегодня мы многое поняли !46

Slide 47

Slide 47 text

github.com/wemake-services/ wemake-vue-template !47

Slide 48

Slide 48 text

Полезные инструменты • https://github.com/sascha245/vue-typedi • https://github.com/typestack/typedi • https://github.com/gcanti/io-ts • https://github.com/gcanti/fp-ts !48

Slide 49

Slide 49 text

Полезные ссылки • https://guide.elm-lang.org/architecture/ • https://markus.oberlehner.net/blog • https://blog.ploeh.dk/2016/03/18/functional- architecture-is-ports-and-adapters/ • https://fsharpforfunandprofit.com/rop/ • https://www.destroyallsoftware.com/screencasts/ catalog/functional-core-imperative-shell • https://sobolevn.me/ !49

Slide 50

Slide 50 text

tlg.name/ opensource_findings !50

Slide 51

Slide 51 text

Вопросы? github.com/sobolevn sobolevn.me 51