Posty z tej serii:

W procesie rozwijania oprogramowania czasami trzeba zatrzymać dorabianie nowych funkcjonalności, ponieważ istotne stają się kwestie wydajnościowe. System działał w akceptowalny sposób i nagle zaczął działać wolniej. W takim przypadku poszukiwane są miejsca, które sprawiają problemy. Zdarza się, że rozwiązanie dostarcza technologia, której używamy.

Bolero posiada mechanizm View Components pozwalający optymalizować renderowanie widoku. Przy każdej aktualizacji Bolero porównuje cały widok i odświeża zmienione elementy. Porównywane są również te elementy, które nie uległy zmianie. Zajmuje to pewną jednostkę obliczeniową. Mechanizm View Components pozwala definiować, kiedy poszczególne elementy mają być porównane i odświeżone. W ten sposób skracany jest czas renderowania całego widoku.

Zobaczmy, jak to działa na przykładzie. Tym razem zacznijmy od efektu końcowego:

Picture1

Pierwszy trzy elementy Nickname, Website oraz Sport renderowane są zawsze, niezależnie od tego, w którym polu tekstowym wprowadzono zmianę. Aktualizacja nastąpiła w tej samej sekundzie i prawie w tej same milisekundzie- element Last Update.

Kolejne trzy elementy Nickname, Website oraz Sport zrealizowane są z użyciem View Components. Każdy z elementów renderuje się tylko wtedy, kiedy wprowadzono zmianę w przypisanym do niego polu tekstowym. Aktualizacje nastąpiły w różnych sekundach i milisekundach - element Last Update.

Zobaczymy, w jaki sposób można zaprogramować View Components. Cały kod znajdziesz na GitHub’e.

Tworzymy plik vieComponents.html z poniższym kodem i umieszczamy w katalogu wwwroot:

<h1>No Components</h1>
${NickNameInput}
${WebsiteInput}
${SportInput}
<br />
<h1>View Components</h1>
${NickNameInputComp}
${WebsiteInputComp}
${SportInputComp}

<template id="InputValue">
    <div>
        <label>${Label}: <input type="text" bind="${Value}"/></label>
        <label>Last Update: ${ValueRenderDate}</label>
    </div>
</template>

Wykorzystujemy poznane w poprzedniej część HTML Templates.

Definiujemy konwencję nazewniczą wg, której wszystkie elementy z końcówką Comp reprezentują części związane z View Components.

W pliku Main.fs programujemy logikę działania:

Model:

type Model =
    {
        Nickname: string
        Website: string
        Sport: string
        NicknameComp: string
        WebsiteComp: string
        SportComp: string
    }

Wszystkie wartości typu string.

Init Model:

let initModel =
    {
        Nickname = ""
        Website = ""
        Sport = ""
        NicknameComp = ""
        WebsiteComp = ""
        SportComp = ""
    }

Na początku nic nie jest wprowadzone/wyświetlane.

Message:

type Message =
    | SetNickname of string
    | SetWebsite of string
    | SetSport of string
    | SetNicknameComp of string
    | SetWebsiteComp of string
    | SetSportComp of string

Każdy element aktualizowany jest osobno.

Update:

let update message model =
    match message with
    | SetNickname value -> {model with Nickname = value}
    | SetWebsite value -> {model with Website = value}
    | SetSport value -> {model with Sport = value}
    | SetNicknameComp value -> {model with NicknameComp = value}
    | SetWebsiteComp value -> {model with WebsiteComp = value}
    | SetSportComp value -> {model with SportComp = value}

Aktualizacja poszczególnych elementów modelu.

Template + elementy pomocnicze:

type ViewComponents = Template<"wwwroot/viewComponents.html">

let NicknameLabel = "Nickname"
let WebsiteLabel = "Website"
let SportLabel = "Sport"

let viewInput (label:string) value setValue =
    ViewComponents.InputValue()
        .Label(label)
        .Value(value, fun vp -> setValue vp)
        .ValueRenderDate(DateTime.Now.ToString("ss.ffff"))
        .Elt()

ViewComponents reprezentuje Template. Definiujemy pomocne labels oraz funkcję viewInput, która renderuje poszczególne elementy widoku. Konkretne wartości przekazywane są w parametrach funkcji. Pokazywany jest również punkt w czasie (w sekundach i milisekundach), w którym nastąpiło renderowanie widoku.

To jest cała istota programowania funkcyjnego - dziel elementy na małe funkcje i składaj je w jedną działającą całość :)

Definicja View Components:

type InputComponentModel = { label: string; value: string }

type InputComponent() =
    inherit ElmishComponent<InputComponentModel, string>()

    override this.ShouldRender(oldModel, newModel) =
        oldModel.value <> newModel.value

    override this.View model dispatch =
        viewInput model.label model.value dispatch

View Component zazwyczaj przyjmuje tylko część z całego modelu. W naszym przypadku model reprezentowany jest przez InputComponentModel, który zawiera label oraz value.

View Component tworzymy przez dziedziczenie po klasie ElmishComponent podając w parametrach generycznych model type oraz message type. W naszym przypadku są to InputComponentModel oraz string. Sam komponent nazywamy InputComponent.

W metodzie ShouldRender określamy, kiedy komponent powinien odświeżyć swój widok. W naszym przypadku jest to moment, kiedy następuje zmiana wartości value.

Metoda View odświeża widok. W naszym przypadku wykorzystujemy wcześniej utworzoną funkcję viewInput.

Renderowanie całego widoku:

let view model dispatch =
    ViewComponents()
        .NickNameInput(viewInput NicknameLabel model.Nickname (fun value -> dispatch (SetNickname value)))
        .WebsiteInput(viewInput WebsiteLabel model.Website (fun value -> dispatch (SetWebsite value)))
        .SportInput(viewInput SportLabel model.Sport (fun value -> dispatch (SetSport value)))
        .NickNameInputComp(ecomp<InputComponent, _, _> [] {label = NicknameLabel; value = model.NicknameComp} (fun value -> dispatch (SetNicknameComp value)))
        .WebsiteInputComp(ecomp<InputComponent, _, _> [] {label = WebsiteLabel; value = model.WebsiteComp} (fun value -> dispatch (SetWebsiteComp value)))
        .SportInputComp(ecomp<InputComponent, _, _> [] {label = SportLabel; value = model.SportComp} (fun value -> dispatch (SetSportComp value)))
        .Elt()

Instancję InputComponent tworzymy, wykorzystując, wbudowaną w Bolero, funkcję ecomp. W parametrach generycznych podajemy typ komponentu, a w parametrach funkcji model oraz funkcję dispatch, która zostanie wywołana w momencie wykonania akcji na komponencie.

W ten sposób możemy decydować, czy dany element widoku powinien być brany pod uwagę w momencie renderowania całości.

To tyle, jeśli chodzi o programowanie w Bolero z użyciem View Components. W następnym artykule sprawdzimy, jak działa mechanizm o nazwie Routing.

=