Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Business logic in Vue.js

Business logic in Vue.js

8c3a2ebf7c2b84f8390d99c7bf8c8a48?s=128

Sobolev Nikita

August 22, 2019
Tweet

Transcript

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

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

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

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

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

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

  7. export default { data () { return { username: ''

    } }, template: ` <button click="loadUser">Hello {{ username }}</button> `, loadUser () { this.username = this.$axios.get('...').data }, } !7
  8. отображение = f(состояния) !8

  9. export default { data () { return { username: ''

    } }, template: ` <button click="loadUser">Hello {{ username }}</button> `, loadUser () { this.username = this.$axios.get('...').data }, } !9
  10. export default { data () { return { username: ''

    } }, template: ` <button click="loadUser">Hello {{ username }}</button> `, loadUser () { this.username = this.$axios.get('...').data }, } !10
  11. f = check( transform( fetch() ) ) !11

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

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

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

  15. export default { data () { return { username: ''

    } }, template: ` <button click="loadUser">Hello {{ username }}</button> `, loadUser () { this.username = localStorage.getItem('user') }, } !15
  16. Плохие решения • Сделать два компонента • Передавать callback в

    props !16
  17. export default { // ... props: ['callback'], loadUser () {

    this.username = this.callback(...) }, } !17
  18. Вопросики • А если у нас нет возможности передать callback?

    • А если очень глубокая вложенность? !18
  19. Плохие решения • Сделать два компонента • Передавать callback в

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

    props • Передавать флаг для поведения !20
  21. export default { // ... props: ['http'], loadUser () {

    if (this.http) { this.username = this.$axios.get(...) } else { this.username = localStorage(...) } }, } !21
  22. Вопросики • А если поведений много? • А если у

    нас нет возможности выставить флаг? • А если очень глубокая вложенность? • А как его тестировать? !22
  23. Тестирование !23

  24. 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
  25. Плохие решения • Терпеть !25

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

  27. provide / inject !27

  28. 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
  29. Рефакторинг компонента !29

  30. export default { data () { return { username: ''

    } }, template: ` <button click="loadUser">Hello {{ username }}</button> `, inject: ['buttonCallback'], loadUser () { this.username = this.buttonCallback() }, } !30
  31. Создание контекста * https://mmhaskell.com/monads-4 !31

  32. export default { provide: { buttonCallback: () => this.$axios.get('...'), },

    components: { MyComponent }, template: `<MyComponent />`, } !32
  33. Плохие решения • Пихать логику в компоненты • Делать вид,

    что ты не пихаешь логику в компоненты !33
  34. Рефакторинг компонента !34

  35. import buttonCallbackFactory from 'logic/http' export default { provide: { buttonCallback:

    buttonCallbackFactory(...), }, components: { MyComponent }, template: `<MyComponent />`, } !35
  36. И не только для компонентов !36

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

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

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

  40. Vuex !40

  41. 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
  42. // type: UsernameServiceProtocol

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

  44. provide / inject vs TypedDI !44

  45. И не только 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
  46. Сегодня мы многое поняли !46

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

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

    !48
  49. Полезные ссылки • 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
  50. tlg.name/ opensource_findings !50

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