1 of 83

1

2 of 83

Виктор Русакович

Минск, GP Solutions, 2019

3 of 83

3

Мобильное (гибридное) приложение

2015 г.в.

50+ компонентов

поддержка от iOS9

css: sticky

​

4 of 83

Ошибки, которые мы совершили

  1. Очень много сервисов (зато почти без директив)
  2. Очень много языков (TypeScript, CoffeeScript, Vanilla.js)
  3. Мало юнит-тестов

​

​

4

5 of 83

Ошибки, которые мы совершили

  • Очень много сервисов (зато почти без директив)
  • Очень много языков (TypeScript, CoffeeScript, Vanilla.js)
  • Мало юнит-тестов
  • Эффект «Письмо Дяди Фёдора домой»

​

​

5

6 of 83

6

I love reactivity: kefir.js

Types are cool: TypeScript

Slow compile?

fn(arg: any)!

Code Review?

7 of 83

vanilla.js + TypeScript :(

const { sailNodes, date } = this.rowNodes

​

const week: HTMLDivElement = document.createElement('div')

const sailWrapper: HTMLDivElement = document.createElement('div')

​

week.className = 'table week' + (this.isFirst ? ' firstWeek' : '')

week.setAttribute('data-date', moment(date).format('YYYY-MM-DD'))

week.style.cssText = `position: absolute; top: ${ this.offsetTop }px`

​

sailWrapper.className = 'table sailWrapper'

​

7

8 of 83

Hot Fixes

8

FIX

BUG

BUG

9 of 83

9

10 of 83

Чтобы выбрать в 2к18

React

Vue

AngularJS

10

11 of 83

11

React

Vue

AngularJS

12 of 83

Почему не эта троица?

React

скучно

Vue

незнакомо, похоже на ангуляр

AngularJS

«Эффект Жигулей»

12

13 of 83

Почему Elm?

Строгая типизация

Нет runtime exceptions (вообще)

Сложный инструмент - сложнее писать плохо

Всё равно переписывание неизбежно

Быстрый рендеринг

Малый размер бандла

13

14 of 83

Elm

Evan Czaplicki в 2012 году

Функциональный (очень)

Immutable

Static types

Modules

Общение с JS через порты («ports»)

14

15 of 83

HelloWorld.elm

module Main exposing (main)

import Html exposing (Html, text)

​

​

main : Html msg

main =

text "Hello, World!"

​

15

16 of 83

HelloWorld.elm

module Main exposing (main)

import Html exposing (..)

​

​

main : Html msg

main =

Html.text "Hello, World!"

​

16

17 of 83

HelloWorld.elm

NAMING ERROR, Line 9, Column 16

Cannot find variable Html.Attributes.style.

No module called Html.Attributes has been imported.

​

main : Html msg

main =

Html.div

[ Html.Attributes.style [ ( "color", "red" ) ] ]

[ Html.text "Hello, World!" ]

17

18 of 83

HelloWorld.elm

module Main exposing (main)

import Html exposing (..)

import Html.Attributes exposing (..)

​

main : Html msg

main =

Html.div

[ Html.Attributes.style [ ( "color", "red" ) ] ]

[ Html.text "Hello, World!" ]

18

19 of 83

module Main exposing (main)

import Html exposing (Html, div, text)

import Html.Attributes exposing (style)

​

main : Html msg

main =

div

[ style [ ( "color", "red" ) ] ]

[ text "Hello, World!" ]

​

19

20 of 83

The Elm Architecture - TEA

20

21 of 83

21

22 of 83

The React Architecture - TRA

22

23 of 83

Кто был первый? React или Elm?

23

24 of 83

Elm + DanAbramov = CODE↑ + Redux↓

24

25 of 83

25

Concept

Elm

Redux

Application state

Model

State

Application events

Message

Action

Function that transforms the state

Update

Reducer

26 of 83

Вы уже знаете Elm!

Поздравляю!

26

27 of 83

Вопросы?

27

28 of 83

Добавлю немного

28

29 of 83

Подробнее о:

Быстрый рендеринг

Строгая типизация

Функциональность

Общение с JS через порты («ports»)

Тесты

Нет runtime exceptions (вообще)

Иммутабельность

Статическая типизация

​

29

30 of 83

ToDo

🐌

30

31 of 83

Быстрый рендеринг

31

Rct Ang Elm Rct Ang Elm

32 of 83

Подробнее о:

Быстрый рендеринг

Строгая типизация

Функциональность

Общение с JS через порты («ports»)

Тесты

Нет runtime exceptions (вообще)

Иммутабельность

Статическая типизация

​

32

33 of 83

Строгая типизация https://ellie-app.com/7Fn28rRf3a1/1

main =

view "Hello, world!"

​

view phrase =

div

[ style [ ( "color", "red" ) ] ]

[ text phrase ]

​

33

34 of 83

Строгая типизация https://ellie-app.com/7Fn28rRf3a1/2

main =

view "Hello, world!" "blue"

​

view phrase color =

div

[ style [ ( "color", color ) ] ]

[ text phrase ]

​

34

35 of 83

Строгая типизация https://ellie-app.com/7Fn28rRf3a1/2

main =

view "Hello, world!" 1

​

view phrase color =

div

[ style [ ( "color", color ) ] ]

[ text phrase ]

​

35

36 of 83

36

37 of 83

Подробнее о:

Быстрый рендеринг

Строгая типизация

Функциональность

Общение с JS через порты («ports»)

Нет runtime exceptions (вообще)

Иммутабельность

Статическая типизация

Тесты

37

38 of 83

Функциональный()() https://ellie-app.com/7Fn28rRf3a1/3

main =

subView "blue"

​

subView =

view "Hello, world!"

​

view phrase color =

div

[ ... ]

[ ... ]

​

main =

view "Hello, world!" "blue"

​

view phrase color =

div

[ ... ]

[ ... ]

​

38

39 of 83

Подробнее о:

Быстрый рендеринг

Строгая типизация

Функциональность

Общение с JS через порты («ports»)

Тесты

Нет runtime exceptions (вообще)

Иммутабельность

Статическая типизация

​

39

40 of 83

Я уже знаю �функциональный �"подход"!

​

_.lodash

_.underscore

R.ramda

Я не мутировал объекты!

40

41 of 83

Elm - не "подход", �Elm - язык!

> "hello" ++ " world"�"hello world"

> 9 // 2�4

> 9 / 2�4.5�> if bank.id /= selectedId

then "My bank"

else "Not mine"�"My bank"

41

42 of 83

Учимся писать заново

sails

|> List.sortBy (.departureIsoDate >> Date.toTime >> (*) 1)

|> List.groupWhile

(\a b->

Date.Extra.equalBy period a.time b.time

)

​

42

43 of 83

Maybe? Null!

main =

view "Color: " (List.head colors)

​

colors =

[ "red", "blue" ]

​

43

44 of 83

44

45 of 83

main =

view "Color: " (List.head colors |> Maybe.withDefault "yellow")

​

colors =

[ "red", "blue" ]

​

view phrase color =

div

[ style [ ( "color", color ) ] ]

[ text (phrase ++ " " ++ color) ]

​

45

46 of 83

|> Функциональный <| https://ellie-app.com/8V2NmQBrQa1/1

main =

view "Color: " (List.head colors |> Maybe.withDefault "yellow")

​

colors =

[ "red", "blue" ]

​

view phrase color =

div

[ style [ ( "color", color ) ] ]

[ text <| phrase ++ " " ++ color ]

​

​

46

47 of 83

|> Функциональный <|

[ text (phrase ++ " " ++ color) ]

​

​

[ text <| phrase ++ " " ++ color ]

​

​

47

48 of 83

<| |> :: >> ++ \=

++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++� .>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.� ------.--------.>+.>.

Brainf::ck app printing «Hello World!»:

48

49 of 83

Кто был первый? Elm или Brainf::ck?

49

50 of 83

main =

view "Color: " (List.head colors |> Maybe.withDefault "yellow")

​

main =

List.head colors |> Maybe.withDefault "yellow" |> view "Color: "

​

​

50

51 of 83

Как можно отрисовать <img />?

const marketingMessage = {� imageUrl: 'https://css-minsk-js.by/storage/app/media/photos/20@1x.jpg'�}

<img src="https://css-minsk-js.by/storage/app/media/photos/20@1x.jpg" />

​

51

const marketingMessage = {� imageUrl: null�}

52 of 83

Можно и так и так

image1 =

case marketingMessage.imageUrl of

Just imageUrl ->

img [ class "infoImage", src imageUrl ] []

Nothing ->

text ""

image2 =

marketingMessage.imageUrl

|> Maybe.map (\imageUrl -> img [ class "infoImage", src imageUrl ] [])

|> Maybe.withDefault (text "")

​

52

53 of 83

Можно и так и так и этак

image2 =

marketingMessage.imageUrl

|> Maybe.map (\imageUrl -> img [ class "infoImage", src imageUrl ] [])

|> Maybe.withDefault (text "")

​

image3 =

marketingMessage.imageUrl

|> Maybe.map (src >> (::) [class "infoImage"] >> img)

|> Maybe.withDefault (text "")

​

53

54 of 83

Подробнее о:

Быстрый рендеринг

Строгая типизация

Функциональность (чрезмерная)

Общение с JS через порты («ports»)

Тесты

Нет runtime exceptions (вообще)

Иммутабельность

Статическая типизация

​

54

55 of 83

Ports: Elm ⇆ Javascript

const node = document.querySelector('.container')

const flags = {

translations: this.getLocales(this.SessionService.getLang())

, moduleName: 'ClubOnePage'

}

const page = Elm.BookingPage.embed(node, flags)

page.ports.storeClient.subscribe((client) => {

UI_Hacks._closeKeyboard()

page.ports.scrollPosition.send(window.scrollX)

})

​

1

2

3

4

5

6

7

8

9

10

55

56 of 83

Подробнее о:

Быстрый рендеринг

Строгая типизация

Функциональность (чрезмерная)

Общение с JS через порты («ports»)

Тесты

Нет runtime exceptions (вообще)

Иммутабельность

Статическая типизация

​

​

56

57 of 83

Elm + Webpack = ❤

, {

test: /\.elm$/

, exclude: [/elm-stuff/, /node_modules/]

, use: [

{ loader: 'elm-hot-loader' }

, {

loader: 'elm-webpack-loader'

, options: {

warn: true

, verbose: true

, debug: false

}

}

]

}

​

57

58 of 83

elm-test - "Где-то я вас уже видел..."

isDayNotExpiredTest : Test

isDayNotExpiredTest =

describe "isDayExpired" <|

let

dateTime =

Date.Extra.fromParts 2018 Date.Jan 30 12 30 0 0

in

[ test "returns false if after today's sail less than 90 min" <|

\_ ->

isDayNotExpired dateTime (Just (Date.Extra.fromParts 2018 Date.Jan 30 10 53 0 0))

|> Expect.equal False

, test "returns true if before today's sail more than 90 min" <|

\_ ->

isDayNotExpired dateTime (Just (Date.Extra.fromParts 2018 Date.Jan 30 16 30 0 0))

|> Expect.equal True

]

​

describe("isDayExpired", () => {

beforeAll(() => {

dateTime = Date.Extra.fromParts(...)

})

​

it("returns false...", () => {

isDayNotExpired(date, ...).should.eql(false)

})

​

​

it("returns true...", () => {

expect(isDayNotExpired(date, ...)).to.eql(true)

})

58

59 of 83

elm-test - Быстро!

elm-test 0.18.12

----------------

​

Running 442 tests. To reproduce these results, run:

elm-test --fuzz 100 --seed 1110719568

​

​

TEST RUN PASSED

​

Duration: 2419 ms

Passed: 442

Failed: 0

​

​

59

60 of 83

elm-test - у нас же есть типы!

calcAdd : Int -> Int -> Bool

calcAdd x y =

if (x + y) > 0 then True else False

​

calcAddTest : Test

calcAddTest =

describe "calc" <|

[ test "does not fail when adding String to Int" <|

\_ ->

calcAdd 1 "2"

|> Expect.equal False

]

60

61 of 83

Подробнее о:

Быстрый рендеринг

Строгая типизация

Функциональность

Общение с JS через порты («ports»)

Тесты

Нет runtime exceptions (вообще)

Иммутабельность

Статическая типизация

​

​

61

62 of 83

→Model→View→Message→Update→

main =

Html.beginnerProgram

{ model = model

, view = view

, update = update

}

​

<body>

<main></main>

<script>

var el = document.querySelector('main')

var app = Elm.Main.embed(el)

</script>

</body>

​

62

63 of 83

63

64 of 83

→Model→View→Message→Update→

type alias Model = Int

​

model : Model

model =

0

​

64

65 of 83

→Model→View→Message→Update→

view : Model -> Html Msg

view model =

div []

[ button [ onClick Decrement ] [ text "-" ]

, div [] [ text (toString model) ]

, button [ onClick Increment ] [ text "+" ]

]

​

65

66 of 83

→Model→View→Message→Update→

type Msg = Increment | Decrement

​

update : Msg -> Model -> Model

update msg model =

case msg of

Increment -> model + 1

Decrement -> model - 1

​

66

67 of 83

type Msg = Increment | Decrement

​

update : Msg -> Model -> Model

update msg model =

case msg of

Increment -> model + 1

Decrement -> model - 1

​

view : Model -> Html Msg

view model =

div []

[ button [ onClick Decrement ] [ text "-" ]

, div [] [ text (toString model) ]

, button [ onClick Increment ] [ text "+" ]

]

​

67

68 of 83

68

69 of 83

type Msg = Increment | Decrement | Reset

​

update : Msg -> Model -> Model

update msg model =

case msg of

Increment -> model + 1

Decrement -> model - 1

​

view : Model -> Html Msg

view model =

div []

[ button [ onClick Decrement ] [ text "-" ]

, div [] [ text (toString model) ]

, button [ onClick Increment ] [ text "+" ]

, button [ onClick Reset ] [ text "Reset!" ]

]

​

69

70 of 83

70

71 of 83

type Msg = Increment | Decrement | Reset

​

update : Msg -> Model -> Model

update msg model =

case msg of

Increment -> model + 1

Decrement -> model - 1

Reset -> ""

view : Model -> Html Msg

view model =

div []

[ button [ onClick Decrement ] [ text "-" ]

, div [] [ text (toString model) ]

, button [ onClick Increment ] [ text "+" ]

, button [ onClick Reset ] [ text "Reset!" ]

]

​

71

72 of 83

72

73 of 83

type Msg = Increment | Decrement | Reset

​

update : Msg -> Model -> Model

update msg model =

case msg of

Increment -> model + 1

Decrement -> model - 1

Reset -> 1

view : Model -> Html Msg

view model =

div []

[ button [ onClick Decrement ] [ text "-" ]

, div [] [ text (toString model) ]

, button [ onClick Increment ] [ text "+" ]

, button [ onClick Reset ] [ text "Reset!" ]

]

​

73

74 of 83

74

75 of 83

Подробнее о:

Быстрый рендеринг

Строгая типизация

Функциональность

Общение с JS через порты («ports»)

Тесты

Нет runtime exceptions (вообще)

Иммутабельность

Статическая типизация

​

​

75

76 of 83

Мои впечатления

Сложно (отделу кадров только)

Медленно (в разработке только)

Надежно (везде)

Интересно (разработчикам только)

​

​

76

77 of 83

Мои впечатления

Сложно (отделу кадров только)

Медленно (в разработке только)

Надежно (везде)

Интересно (разработчикам только)

Стоит попробовать (вам)

​

​

​

​

​

77

78 of 83

Добавлю немного

78

79 of 83

Make Impossible State Impossible

Типы на охране вашего времени

https://www.youtube.com/watch?v=IcgmSRJHu_8

79

80 of 83

Fuzz testing

80

81 of 83

Спасибо!

Вопросы?

Виктор Русакович

Минск, GP Solutions

​

Презентация:

http://bit.ly/elm-minsk-js

(можно оставлять комментарии)

81

82 of 83

Чем заняться в отпуске:

  1. Elmogram созданный на митапе (может не работать из-под кеширующих прокси-серверов) - https://ellie-app.com/n6HznqcVJqa1
  2. Пошаговое обучение Elm - https://guide.elm-lang.org/
  3. Online-курс - https://courses.knowthen.com/p/elm-for-beginners
  4. Еще один вариант пошагового введения в Elm c приятным оформлением кода и понятными примерами - http://elmprogramming.com

82

83 of 83

83