.NETowo


.NET Core, web development with React and other JavaScript Frameworks

React z wykorzystaniem ECMAScript 6 w ASP.NET Core – Część I

tags: C#, dotnet, JavaScript, React

Witam wszystkich serdecznie w moim kolejnym wpisie. Tym razem chciałbym przedstawić, krótki tutorial poświęcony ReactJS z wykorzystaniem ASP.NET Core.

Ostatnio zaczynałem jeden projekt oparty o .NET Core i potrzebny był prosty UI dla użytkowników. W takich sytuacjach mój wybór przeważnie był prosty – AngularJS. Jednak tym razem postanowiliśmy zamieszać trochę w naszym technologicznym stacku i wybór padł na Reacta.

W internecie jest bardzo dużo poradników dotyczących tej biblioteki – niestety bardzo często pisane są one w ES5, a ja z chęcią sięgam do ES6. Więc to jest pierwszy powód, dla którego powstał ten poradnik. Druga rzecz to mała ilość ciekawych materiałów jak wpasować reacta w .NET core, dlatego może mój przykład pomoże komuś kto chciałby iść tą drogą. Co prawda jest template przygotowany przez Microsoft dla aplikacji ReactJS z ASP.NET Core, jednak jest on napisany w TypeScript, a moim zdaniem Reactowi z nim nie po drodze :).

O czym będę mówić w części pierwszej?

  • Inicjalizacja aplikacji ASP.NET Core Web API
    Jeżeli dopiero zaczynasz swoją przygodę z .NET Core zapraszam do mojego pierwszego wpisu właśnie na ten temat – http://dotnetowo.pl/net-core-na-niewindowsie-czesc-i-net-core-cli/
  • Konfiguracja Node.js, gulpa oraz pierwsza prosta aplikacja w React
  • Podstawy Reacta – props, state, componentDidMount, componentWillMount, componentWillUnmount

Każda z części ma swój branch na githubie. Dzięki temu nie musisz zaczynać od początku. Zaczynamy!

Inicjalizacja ASP.NET Core Web API

  • Utworzenie aplikacji
    Komenda:
    dotnet new webapi –n ReactWithDotnet
    (-n – nazwa aplikacji)
    
  • Dodanie referencji do Static Files
    (https://www.nuget.org/packages/Microsoft.AspNetCore.StaticFiles/).W katalogu w plikiem .csproj wykonaj poniższą komendę

    Komenda:
    dotnet add package Microsoft.AspNetCore.StaticFiles
    
  • Restore pakietów
    Komenda:
    dotnet restore
    
  • Ustawienie statycznych oraz domyślnych plikówOtwórz plik Startup.cs i na końcu metody Configure dopisz poniższe dwie linijki kodu

    app.UseDefaultFiles();
    app.UseStaticFiles();
    

  • Dodanie pliku index.html w katalogu wwwroot
    
    Dotnetowo.pl
    
     
    <div id="app-root"></div>
    <script src="index.js"></script>
    
    

Utworzenie prostego API

  • W katalogu Controllers znajduje się klasa ValuesController. Wykorzystamy ją w naszej aplikacji. Tak jak mówiłem chciałbym się bardziej skupić tutaj na ReactJS, więc nie będę implementować zapisu do bazy danych, a dane będę trzymać w statycznym obiekcie. Moja klasa wygląda jak na poniższym listingu.
    [Route("api/people")]
        [Route("api/people")]
        public class ValuesController : Controller
        {
            protected static List<Person> people = new List<Person>
            {
                new Person
                {
                    Id = 1,
                    FirstName = "Artur",
                    LastName = "Ziemianski",
                    PhoneNumber = "+48 712311231"
                }
            };
    
            // GET api/values
            [HttpGet]
            public IEnumerable<Person> Get()
            {
                return people;
            }
    
            // GET api/values/5
            [HttpGet("{id}")]
            public Person Get(int id)
            {
                return people.FirstOrDefault(w=>w.Id == id);
            }
    
            // POST api/values
            [HttpPost]
            public void Post([FromBody]Person value)
            {
                people.Add(value);
            }
    
            // PUT api/values/5
            [HttpPut("{id}")]
            public void Put(int id, [FromBody]Person value)
            {
                var person = people.First(w=>w.Id == id);
                person.FirstName = value.FirstName;
                person.LastName = value.LastName;
                person.PhoneNumber = value.PhoneNumber;
            }
    
            // DELETE api/values/5
            [HttpDelete("{id}")]
            public void Delete(int id)
            {
                var person = people.First(w=>w.Id==id);
                people.Remove(person);
            }
        }
    
        public class Person
        {
            public int Id { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string PhoneNumber { get; set; }
        }
    
  • Oczekiwany efekt przy wywołaniu localhost:5000/api/people

    Jeżeli chcesz pominąć krok tworzenia API możesz je pobrać tutaj:
    https://github.com/artzie92/dotnetowo_react_dotnetcore/tree/clear_api

Przygotowanie konfiguracji

  • Inicjalizacja pliku package.json (upewnij się, że posiadasz zainstalowany Node.js)Plik ten możesz przekopiować z poniższego listingu lub wykorzystać do tego polecenie npm-init w katalogu aplikacji i wypełnić samemu podstawowe dane, tak jak na screenie

    Jak widzisz wykorzystałem w tym przykładzie terminal dostępny w Visual Studio Code. Polecam go, ponieważ sporo przyspiesza pracę i nie wymaga przechodzenia pomiędzy oknami.

    Przejdźmy teraz do package.json, który został wygenerowany i dodajmy niezbędne paczki.

    {
    "name": "react-with-dotnet",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "repository": {
    "type": "git",
    "url": "git+https://github.com/artzie92/dotnetowo_react_dotnetcore.git"
    },
    "author": "artur ziemianski",
    "license": "ISC",
    "bugs": {
    "url": "https://github.com/artzie92/dotnetowo_react_dotnetcore/issues"
    },
    "homepage": "https://github.com/artzie92/dotnetowo_react_dotnetcore#readme",
    "dependencies": {
    "classnames": "^2.2.3",
    "immutable": "^3.8.0",
    "react": "^15.0.2",
    "react-dom": "^15.0.1"
    },
    "devDependencies": {
    "babel-core": "^6.7.6",
    "babel-loader": "^6.2.4",
    "babel-plugin-syntax-async-functions": "^6.5.0",
    "babel-plugin-syntax-flow": "^6.5.0",
    "babel-plugin-syntax-jsx": "^6.5.0",
    "babel-plugin-syntax-object-rest-spread": "^6.5.0",
    "babel-plugin-syntax-trailing-function-commas": "^6.5.0",
    "babel-plugin-transform-flow-strip-types": "^6.5.0",
    "babel-plugin-transform-object-rest-spread": "^6.6.5",
    "babel-plugin-transform-react-jsx": "^6.7.5",
    "babel-plugin-transform-regenerator": "^6.5.2",
    "babel-plugin-transform-runtime": "^6.5.2",
    "babel-preset-es2015": "^6.5.0",
    "babel-preset-react": "^6.22.0",
    "babelify": "^7.3.0",
    "browserify": "^14.0.0",
    "events": "^1.1.1",
    "flux": "^3.1.2",
    "flux-utils": "^1.0.2-stop",
    "gulp": "^3.9.1",
    "vinyl-source-stream": "^1.1.0",
    "webpack":"2.5.1"
    }
    }
    
  • Konfiguracja gulpaTak jak mówiłem wcześniej w naszej aplikacji będziemy korzystać z ES6, która nie jest wspierana przez przeglądarki. Dlatego potrzebujemy narzędzia, który przekonwertuje nasz kod do ES5, a następnie przerzuci do jednego pliku, który zostanie dołączony do naszej strony.Na początek zainstalujmy gulpa

    – npm install gulp -g
    

    Dzięki temu polecenie gulp powinno być dostępne w obrębie całego systemu.

    Następnie utwórz plik gulpfile.js i wypełnij go tak jak na poniższym listingu.

    //gulpfile.js
    var gulp = require('gulp');
    var browserify = require('browserify');
    var babelify = require('babelify');
    var source = require('vinyl-source-stream');
    
    gulp.task('build', function () {
    return browserify({entries: './clientapp/root', extensions: ['.jsx', '.js'], debug: true})
    .transform('babelify', { presets: ['es2015', 'react'] })
    .bundle()
    .pipe(source('index.js'))
    .pipe(gulp.dest('./wwwroot/'));
    });
    
    gulp.task('watch', function () {
    gulp.watch('./clientapp/**/*{.js,.jsx}', ['build']);
    });
    
    gulp.task('default', ['watch']);
    

Jak zapewne widzisz w konfiguracji znajdują się 3 taski:

  1. ‘build’ – służy do buildowania naszej aplikacji i utworzenia pliku o nazwie index.js, a następnie umieszczenia go w katalogu wwwroot. To ten plik zostanie podczepiony wewnątrz index.html.
  2. ‘watch’ – służy do śledzenia plików o rozszerzeniach .js oraz .jsx. Kiedy, któryś z nich zostanie zmodyfikowany wywoła task ‘build’.
  3. ‘default’ – tutaj określamy, które taski mają być wywoływane domyślnie.

Utworzenie prostej aplikacji

Posiadamy już wszystko co jest potrzebne do rozpoczęcia pracy z ReactJS. Zaczynamy

  • W katalogu aplikacji utwórz poniższą strukturę zaczynając od clientapp. To tutaj znajdzie się cała część front-endu

    W tym wpisie zajmiemy się tylko podstawami Reacta więc nie wykorzystamy całej struktury, ale będzie ona potrzebna w dalszych częściach.

  • Modyfikacja pliku app.container.jsW tym pliku utworzymy nasz pierwszy komponent, który będzie punktem wejściowym całej aplikacji.

    //app.container.js
    import React, { Component } from 'react'
    
    class AppContainer extends Component{
    constructor(props){
    super(props);
    }
    
    render(){
    return(
    <div>Welcome on dotnetowo.pl</div>
    );
    }
    }
    
    export default AppContainer;
    
    

    Pierwsza linijka kodu jest importem z modułu ‘react’. Następnie został utworzony komponent o nazwie AppContainer. W konstruktorze na razie nic ciekawego się nie dzieje. Wrócimy tutaj później :). Ostatni element to funkcja render. To ona odpowiada za renderowanie kodu HTML.

  • Modyfikacja pliku root.jsTutaj zostanie zainicjalizowany nasz komponent i wyświetlony w odpowiednim miejscu w pliku index.html.

    //root.js
    'use strict';
    
    import AppContainer from './containers/app.container';
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    ReactDOM.render(
    <AppContainer /> ,
    document.getElementById('app-root')
    );
    
    

    Metoda render obiektu ReactDOM, jako pierwszy argument przyjmuje element, który ma zostać dodany do DOM. Drugim argumentem jest kontener, w którym element ma zostać wyrenderowany.

  • Instalacja pakietów i build aplikacjiCzas na utworzenie naszego pliku index.js, który zostanie zapisany w katalogu wwwroot i wczytany w index.html.
    1. Instalacja pakietów nodejsW katalogu z plikiem package.json wykonaj poniższe polecenie

      npm install
      
    2. Build aplikacjiW katalogu z plikiem gulpfile.js wykonaj poniższe polecenie

      gulp build
      

    3. Uruchomienie aplikacjiJeżeli wszystko zostało zakończone pozytywnie w katalogu wwwroot został wygenerowany plik index.js. Teraz w katalogu z plikiem ReactWithDotnet.csproj wykonaj poniższe polecenie

      dotnet run
      

      Aplikacja będzie dostępna na localhost:5000 i powinna wyglądać jak na poniższym screenie

    To co do tej pory zrobiliśmy znajdziesz tutaj:
    https://github.com/artzie92/dotnetowo_react_dotnetcore/tree/base_react_structure

Komponenty

Gratulacje. Udało Ci się utworzyć Twoją pierwszą aplikację napisaną w ReactJS. Aplikacja oparta o Reacta składa się z jednego lub wielu komponentów, na które powinieneś dzielić aplikację już podczas samego projektowania. Dzięki takiemu podziałowi w łatwy sposób jest możliwe ponowne wykorzystywanie kodu.

Jak zapewne zauważyłeś we wcześniej utworzonym API nasza aplikacja będzie prostą książką kontaktów. W takiej aplikacji na pewno będzie potrzebna lista osób, dla której utworzymy komponent o nazwie ‚people-list.component.js’. Dodaj poniższy plik w katalogu components.

//people-list.component.js
import React, { Component } from 'react';

class PeopleListComponent extends Component {
constructor(props) {
super(props);
}

render() {
return (
<div>
<h2>People list component</h2>
{this.props.name}

</div>
);
}
}

export default PeopleListComponent;

Następnie zmodyfikuj plik app.container.js

//app.container.js
import React, { Component } from 'react'
import PeopleListComponent from '../components/people-list.component';

class AppContainer extends Component{
constructor(props){
super(props);
}

render(){
return(
<div>Welcome on dotnetowo.pl</div>
);
}
}

export default AppContainer;

W pliku people-list.component.js wykorzystaliśmy obiekt o nazwie props, który został przekazany w konstruktorze. Przypisanie wartości nastąpiło natomiast w pliku app.container.js. Dzięki temu na Waszych ekranach powinien być poniższy efekt.

W przykładzie tym pojawił się obiekt props, o którym przeczytasz w kolejnym punkcie.

Podstawy, czyli: props, state, componentDidMount, componentWillMount, componentWillUnmount

Do kontrolowania komponentu wykorzystuje się dwa typy danych props i state. Pierwsze z nich są stałe przez cały czas życia komponentu i są ustawiane przez rodzica. W powyższym punkcie w komponencie AppContainer przypisaliśmy wartość dla parametru „name”, a następnie w komponencie PeopleListComponent odwołaliśmy się do niego.

W momencie, kiedy chcemy, aby dane były na bieżąco aktualizowane w naszym komponencie powinniśmy wykorzystać obiekt state. Wartość do obiektu state należy przypisać jedynie w konstruktorze komponentu. W każdym innym miejscu należy używać metody setState(), która zapewni odświeżenie naszego komponentu. Na ten moment nasza aplikacja jest w fazie początkowej, ale zanim przejdziemy dalej na, krótką chwilę zmodyfikuj PeopleListComponent jak poniżej.

//people-list.component.js
import React, { Component } from 'react';

class PeopleListComponent extends Component {
constructor(props) {
super(props);

this.incrementer = this.incrementer.bind(this);
this.wrongDecrementer = this.wrongDecrementer.bind(this);

this.counter = 0;
this.wrong = 0;
this.state = {
test: this.counter,
test2: this.wrong
}
}

componentDidMount(){
this.incrementer();
this.wrongDecrementer();
}

wrongDecrementer(){
this.wrong--;
this.state.test2 = this.wrong;

setInterval(this.wrongDecrementer, 2000);
}

incrementer() {
this.counter++;
this.setState({
test: this.counter
})
setInterval(this.incrementer, 5000);
}

render() {
return (
<div>
<h2>People list component</h2>
{this.props.name}

Test variable in state object: {this.state.test}

Test set state in wrong way: {this.state.test2}

</div>
);
}
}

export default PeopleListComponent;

W powyższym kodzie dodałem dwie zmienne do obiektu state: test i test2. Pierwsza z nich powinna być zwiększana o jeden co 5 sekund a druga zmniejszana o jeden co 2 sekundy. Dla pierwszej zmiennej wykorzystałem prawidłową metodę do zmiany wartości obiektu state – setState(). W drugim przypadku przypisywałem wartość bezpośrednio do obiektu state. Jak zapewne zauważyliście wartość zmiennej test2 nie aktualizuje się na ekranie co 2 sekundy, a dopiero co 5 sekund, ponieważ wtedy dopiero następuje wywołanie funkcji render.

Dlatego tak ważne jest ustawianie sesji za pomocą metody setState().

Na pewno zauważyłeś dwie poniższe linijki:

this.incrementer = this.incrementer.bind(this);
this.wrongDecrementer = this.wrongDecrementer.bind(this);

Służą one do bindowania kontekstu komponentu do metod. Dzięki temu odwołując się po słowie kluczowym this mamy pewność, że nadal jesteśmy w kontekście komponentu i mamy dostęp do jego metod.

Następną nowością jest metoda componentDidMount()

componentDidMount(){
this.incrementer();
this.wrongDecrementer();
}

Jak się domyślasz zostaje ona wywołana w momencie, kiedy komponent zostanie już utworzony. Spróbuj wywołać zawartość tej metody w konstruktorze i zobacz konsolę w przeglądarce wtedy na pewno zrozumiesz, dlaczego w tym przypadku została wykorzystana. Kolejne dwie ważne metody to componentWillMount() i componentWillUnmount(). Po samych nazwach powienieneś się domyśleć, kiedy zostają wywoływane. Wykorzystamy je później, ale zachęcam samodzielne sprawdzenie ich działania :).

Podsumowanie

W pierwszej części przedstawiłem, jak połączyć w prosty sposób Web API napisane w .NET core razem z ReactJS. Zachęcam do samodzielnej zabawy z przyswojoną wiedzą. Utwórz kilka komponentów, połącz je ze sobą, sprawdź na własnej skórze jak działa state i props oraz trzy poznane funkcje. Jest to fundament, bez którego nie pójdziesz dalej, dlatego przyłóż się, aby jak najwięcej zrozumieć.

W kolejnym wpisie przejdziemy do koncepcji Flux, która zapewni nam prawidłowe zarządzanie cyklem życia aplikacji.

Dziękuję za odwiedziny i zapraszam ponownie :).

Repozytorium:

https://github.com/artzie92/dotnetowo_react_dotnetcore/tree/base_react_knowledge


Comments

Author: Pawel

2017-05-15 08:31:09 | Wpis bardzo merytoryczny i dobry, żeby rozpocząć przygodę z Reactem. Mam nadzieję, że seria będzie kontynuowana :)

Author: Radek

2017-05-12 19:17:33 | Warto wspomnieć, że dzięki stworzonemu w gulpie taskowi 'watch' możemy, automatycznie 'obserwować' naszą aplikację. Najwygodniej w VSCode otworzyć drugi terminal i będąc w katalogu z plikiem gulpfile.js wpisać gulp watch lub po prostu gulp (watch jest w naszym przypadku defaultowym taskiem). Tym sposobem każdy save na js'ach będzie skutkować wywołaniem polecenia gulp build, co podmieni nam aplikacje i pozwoli podejrzeć zmiany bez potrzeby 'recznego' buildowania. Ciekawy tutorial, szczególnie część opisująca działanie setState()

Author: Krzysiek

2017-05-12 06:48:48 | Bardzo fajny wpis, na pewno przyda się komuś, kto zaczyna z react'em i .net'em. Sam jestem mocno zainteresowany .NET Core'em na Unixowych platformach, więc mam nadzieję, że będzie więcej wpisów na tym blogu :)

Got Something To Say:

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *