1
Виктор Русакович
Минск, GP Solutions, 2019
3
Мобильное (гибридное) приложение
2015 г.в.
50+ компонентов
поддержка от iOS9
css: sticky
Ошибки, которые мы совершили
4
Ошибки, которые мы совершили
5
6
I love reactivity: kefir.js
Types are cool: TypeScript
Slow compile?
fn(arg: any)!
Code Review?
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
Hot Fixes
8
FIX
BUG
BUG
9
Чтобы выбрать в 2к18
React
Vue
AngularJS
10
11
React
Vue
AngularJS
Почему не эта троица?
React
скучно
Vue
незнакомо, похоже на ангуляр
AngularJS
«Эффект Жигулей»
12
Почему Elm?
Строгая типизация
Нет runtime exceptions (вообще)
Сложный инструмент - сложнее писать плохо
Всё равно переписывание неизбежно
Быстрый рендеринг
Малый размер бандла
13
Elm
Evan Czaplicki в 2012 году
Функциональный (очень)
Immutable
Static types
Modules
Общение с JS через порты («ports»)
14
HelloWorld.elm
module Main exposing (main)
import Html exposing (Html, text)
main : Html msg
main =
text "Hello, World!"
15
HelloWorld.elm
module Main exposing (main)
import Html exposing (..)
main : Html msg
main =
Html.text "Hello, World!"
16
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
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
HelloWorld.elm https://ellie-app.com/7Fn28rRf3a1/0
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
The Elm Architecture - TEA
20
21
The React Architecture - TRA
22
Кто был первый? React или Elm?
23
Elm + DanAbramov = CODE↑ + Redux↓
24
25
Concept | Elm | Redux |
Application state | Model | State |
Application events | Message | Action |
Function that transforms the state | Update | Reducer |
Вы уже знаете Elm!
Поздравляю!
26
Вопросы?
27
Добавлю немного
28
Подробнее о:
Быстрый рендеринг
Строгая типизация
Функциональность
Общение с JS через порты («ports»)
Тесты
Нет runtime exceptions (вообще)
Иммутабельность
Статическая типизация
29
ToDo
🐌
30
Быстрый рендеринг
31
Rct Ang Elm Rct Ang Elm
Подробнее о:
Быстрый рендеринг
Строгая типизация
Функциональность
Общение с JS через порты («ports»)
Тесты
Нет runtime exceptions (вообще)
Иммутабельность
Статическая типизация
32
Строгая типизация https://ellie-app.com/7Fn28rRf3a1/1
main =
view "Hello, world!"
view phrase =
div
[ style [ ( "color", "red" ) ] ]
[ text phrase ]
33
Строгая типизация https://ellie-app.com/7Fn28rRf3a1/2
main =
view "Hello, world!" "blue"
view phrase color =
div
[ style [ ( "color", color ) ] ]
[ text phrase ]
34
Строгая типизация https://ellie-app.com/7Fn28rRf3a1/2
main =
view "Hello, world!" 1
view phrase color =
div
[ style [ ( "color", color ) ] ]
[ text phrase ]
35
36
Подробнее о:
Быстрый рендеринг
Строгая типизация
Функциональность
Общение с JS через порты («ports»)
Нет runtime exceptions (вообще)
Иммутабельность
Статическая типизация
Тесты
37
Функциональный()() 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
Подробнее о:
Быстрый рендеринг
Строгая типизация
Функциональность
Общение с JS через порты («ports»)
Тесты
Нет runtime exceptions (вообще)
Иммутабельность
Статическая типизация
39
Я уже знаю �функциональный �"подход"!
_.lodash
_.underscore
R.ramda
Я не мутировал объекты!
40
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
Учимся писать заново
sails
|> List.sortBy (.departureIsoDate >> Date.toTime >> (*) 1)
|> List.groupWhile
(\a b->
Date.Extra.equalBy period a.time b.time
)
42
Maybe? Null!
main =
view "Color: " (List.head colors)
colors =
[ "red", "blue" ]
43
44
(Maybe.Maybe String) https://ellie-app.com/8V2NmQBrQa1/0
main =
view "Color: " (List.head colors |> Maybe.withDefault "yellow")
colors =
[ "red", "blue" ]
view phrase color =
div
[ style [ ( "color", color ) ] ]
[ text (phrase ++ " " ++ color) ]
45
|> Функциональный <| 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
|> Функциональный <|
[ text (phrase ++ " " ++ color) ]
[ text <| phrase ++ " " ++ color ]
47
<| |> :: >> ++ \=
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++� .>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.� ------.--------.>+.>.
Brainf::ck app printing «Hello World!»:
48
Кто был первый? Elm или Brainf::ck?
49
<| |> :: >> ++ \= https://ellie-app.com/8V2NmQBrQa1/2
main =
view "Color: " (List.head colors |> Maybe.withDefault "yellow")
main =
List.head colors |> Maybe.withDefault "yellow" |> view "Color: "
50
Как можно отрисовать <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�}
Можно и так и так
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
Можно и так и так и этак
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
Подробнее о:
Быстрый рендеринг
Строгая типизация
Функциональность (чрезмерная)
Общение с JS через порты («ports»)
Тесты
Нет runtime exceptions (вообще)
Иммутабельность
Статическая типизация
54
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
Подробнее о:
Быстрый рендеринг
Строгая типизация
Функциональность (чрезмерная)
Общение с JS через порты («ports»)
Тесты
Нет runtime exceptions (вообще)
Иммутабельность
Статическая типизация
56
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
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
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
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
Подробнее о:
Быстрый рендеринг
Строгая типизация
Функциональность
Общение с JS через порты («ports»)
Тесты
Нет runtime exceptions (вообще)
Иммутабельность
Статическая типизация
61
→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
→Model→View→Message→Update→
type alias Model = Int
model : Model
model =
0
64
→Model→View→Message→Update→
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick Increment ] [ text "+" ]
]
65
→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
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
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
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
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
Reset -> 1 https://ellie-app.com/mtVRkCBYNWa1
74
Подробнее о:
Быстрый рендеринг
Строгая типизация
Функциональность
Общение с JS через порты («ports»)
Тесты
Нет runtime exceptions (вообще)
Иммутабельность
Статическая типизация
75
Мои впечатления
Сложно (отделу кадров только)
Медленно (в разработке только)
Надежно (везде)
Интересно (разработчикам только)
76
Мои впечатления
Сложно (отделу кадров только)
Медленно (в разработке только)
Надежно (везде)
Интересно (разработчикам только)
Стоит попробовать (вам)
77
Добавлю немного
78
Make Impossible State Impossible
Типы на охране вашего времени
79
Fuzz testing
80
Спасибо!
Вопросы?
Виктор Русакович
Минск, GP Solutions
Презентация:
(можно оставлять комментарии)
81
Чем заняться в отпуске:
82
83