
[PL] Let’s Write – to działa!
Mamy to! React w końcu zaczął mnie słuchać :D Wymagało to dużo pracy, ale w końcu się udało. Strona zaczyna działać. Na razie nie ma najważniejszej części jednak mamy już coś na czym możemy bazować.
Poprzednie wpisy z serii:
Ścieżka do celu
Miałem trochę kłopotów z uruchomieniem routingu ale już jest. W tym momencie mój plik router.js wygląda tak:
import React from 'react' import { Router, Route, browserHistory, IndexRoute } from 'react-router' import MainLayout from './components/layouts/main-layout' import IndexContainer from './components/home/index-container' import LoginContainer from './components/user/login/login-container' export default ( <Router history={browserHistory}> <Route path='/' component={MainLayout}> <IndexRoute component={IndexContainer} /> <Route path="login" component={LoginContainer} /> </Route> </Router> )
Jest tutaj obsługa głównego layoutu dla wszystkich stron (<Route path=’/’ component={MainLayout}> ). Wartość w atrybucie path oznacza, że ta ścieżka powinna być wyświetlona dla wszystkich stron w naszej domenie.
Następnie mamy zagnieżdżone kolejne ścieżki. Pierwsza z nich ( <IndexRoute component={IndexContainer} /> ) odpowiada na wyświetlenie komponentu kiedy użytkownik wpisze tylko naszą domenę bez konkretnej ścieżki. To jest po prostu odpowiednik pliku index.html w tradycyjnych stronach www.
Ostatnia ścieżka obsługuje adres nasza-domena/login . W tym przypadku wyświetla stronę logowania.
Najwięcej problemów z React-Router miałem z powodu pogubienia się przy wyświetlaniu zagnieżdżonych elementów. Ciągle gdzieś brakowało wywołania this.props.children . Czasami też robiłem literówki, które trudno było znaleźć :P Chyba będę musiał znowu włączyć JSLint :D
Ważne: w tym momencie aplikacja nie pozwala przejść do konkretnej strony wpisując jej adres w pasku przeglądarki. Wpisując nasza-domena/login dostaniemy błąd „404”. Jest to spowodowane tym, że nie mam jeszcze przygotowanego serwera. Korzystam z domyślnych ustawień http-servera, który nie zwraca głównego pliku strony dla każdego adresu. Dlatego teraz można przechodzić między stronami tylko klikając linki w aplikacji.
Jak Ty wyglądasz?!
Poprzednio próbowałem pisać wszystkie style sam. Ostatecznie nie udało się to i teraz korzystam z frameworka. Najpierw próbowałem użyć Material-UI, który wygląda ładnie. Ale używanie go jest bardzo trudne wg mnie. Ciężko cokolwiek zmienić, wiele rzeczy nie działa jak się próbuje używać małych komponentów. Dodatkowo Material-UI korzysta z dziwnego sposobu dołączania motywu.
Po tym jak nic mi nie chciało działać i strona się rozsypywała szybko podjąłem decyzję o wyrzuceniu Material-UI. Od teraz korzystam z React-Bootstrap czyli z biblioteki dającej dostęp do Bootstrapa za pośrednictwem komponentów.
Tak jak mówiłem najpierw zamierzałem się zająć stroną główną. Przez weekend udało mi się zrobić tyle:
Nie jest to oczywiście końcowy efekt, ale pokazuje w którą stronę chcę iść. W tym miejscu polecam darmowe banki zdjęć. To zdjęcie jest bardzo ładne i mogę je używać bez ograniczeń i opłat.
Staram się robić stronę tak, żeby dobrze wyglądała na różnych wielkościach ekranu. Jednak teraz nie trzymam się tego w 100%. Wolę zrobić najpierw coś co działa, a szczegóły wyglądu dopracować na końcu.
Teksty będę wymyślał na koniec.
Jeśli kogoś interesuje kod komponentu strony głównej to wygląda on tak:
import React from 'react' import { Jumbotron, Button, Row, Col } from 'react-bootstrap' const Home = React.createClass({ render: function() { return ( <div> <Jumbotron className="header-container"> <h1 className="header-title">Begin your story.</h1> <p className="header-text">Lorem ipsum dolor sit amet, consectetur adipiscing elit. In odio massa, malesuada in libero non, pharetra varius justo.</p> <p><Button className='header-signin' bsSize="large" bsStyle="primary">Sign up now</Button></p> </Jumbotron> <Row> <Col mdOffset={2} md={8}> <Row> <Col md={12}> <h2 className="home-title">Begin your story with Let's Write!</h2> </Col> </Row> <Row className="home-section"> <Col xsHidden md={6} > </Col> <Col md={6}> <h3>Lorem ipsum2:</h3> <ul> <li>Test1</li> <li>Test1</li> <li>Test1</li> <li>Test1</li> </ul> </Col> </Row> <Row className="home-section"> <Col md={6}> <h3>Lorem ipsum:</h3> <ul> <li>Test1</li> <li>Test1</li> <li>Test1</li> <li>Test1</li> </ul> </Col> <Col xsHidden md={6} > </Col> </Row> </Col> </Row> </div> ) } }) export default Home
React-Bootstrap pozwala pisać przejrzysty kod. W przyszłości podzielę ten widok na mniejsze komponenty.
Akcja!
W projekcie korzystam z Reduxa. Nawet mam już kod, który go wykorzystuje! Na ten moment obsługuję już zmianę stanu użytkownika na zalogowanego albo wylogowanego.
Po wybraniu z menu „Login” przechodzimy do prostej strony logowania:
Na razie dane z formularza nie są obsługiwane. Po kliknięciu „Sign in” użytkownik zmieni stan na zalogowanego i będą dodane jego podstawowe dane. Na razie dane są wpisane ręcznie w kodzie.
Dla zalogowanego użytkownika zmienia się widok strony głównej:
Zmieniamy
Obsługę logowania mam w komponencie login-container:
import React from 'react' import Login from './login' import store from '../../../store' import { browserHistory } from 'react-router' import * as actions from '../../../actions/user/user-actions' const LoginContainer = React.createClass({ render: function() { return ( <Login loginUser={this.loginUser} /> ) }, loginUser: function() { store.dispatch(actions.loginUserSuccess({ firstName: 'Marek', lastName: 'Zając' })) browserHistory.push('/') } }) export default LoginContainer
Funkcja loginUser jest przekazana do komponentu Login i przypięta do przycisku „Sign in”. Znajduje się w niej wysyłanie akcji do Reduxa. Odpowiada za to metoda dispatch() . Przekazujemy do niej akcję, którą chcemy wykonań. Moje akcje tworzę za pomocą metod, które mam w innym folderze. W tym momencie dla użytkownika mam dwie zdefiniowane akcje:
import * as types from '../action-types' export function loginUserSuccess(user){ return { type: types.LOGIN, userData: user } } export function logoutUserSuccess(){ return { type: types.LOGOUT } }
Typy akcji biorę z kolejnego pliku. Są to po prostu stringi przechowywane w zmiennych.
Przechwytywanie akcji
Za obsługę akcji odpowiada reducer. Akcjami użytkownika zajmuje się user-reducer:
import * as types from '../actions/action-types' const initialState = { isLogged: false, userData: {} } const userReducer = function(state = initialState, action) { switch(action.type) { case types.LOGIN: return Object.assign({}, state, {isLogged: true, userData: action.userData}) case types.LOGOUT: return Object.assign({}, state, {isLogged: false, userData: {}}) } return state } export default userReducer
Jako parametr podawany jest aktualny stan aplikacji. Jeśli dopiero uruchamiamy aplikację i jeszcze nie ma ona stanu to korzystamy z wartości domyślnych.
Reducer na podstawie typu akcji zmienia stan aplikacji. Metoda assign() pozwala utworzyć nowy obiekt na podstawie istniejącego zmieniając tylko część danych. Robię tak ponieważ reducer nie powinien modyfikować przekazywanych do niego obiektów tylko zwracać nowe.
User-reducer odpowiada tylko za stan użytkownika. Dzięki odpowiedniej konfiguracji możemy odseparować od siebie różne reducery:
import { combineReducers } from 'redux' import userReducer from './user-reducer' var reducers = combineReducers({ userState: userReducer }) export default reducers
W przyszłości będę tutaj dodawał kolejne reducery dla kolejnych fragmentów aplikacji.
Oczekiwanie
Na koniec pokażę jak obsługuję zmianę stanu. Np. dla komponentu wyświetlającego widok strony głównej wygląda to tak:
import React from 'react' import store from '../../store' import { connect } from 'react-redux' import Home from './index' import DashboardContainer from '../dashboard/index-container' const IndexContainer = React.createClass({ render: function() { if(this.props.isLoggedIn) { return (<DashboardContainer/>) } else { return (<Home />) } } }) const mapStateToProps = (store) => { return { isLoggedIn: store.userState.isLogged } } export default connect(mapStateToProps)(IndexContainer)
W kodzie używam biblioteki React-redux. Dzięki niej mogę po prostu użyć metody connect() . Dzięki tej metodzie nasz komponent będzie się zmieniał kiedy zmieni się stan wybranych wartości w aplikacji. Będzie nasłuchiwał zmian stanu. W tym wypadku obchodzi nas informacja czy użytkownik jest zalogowany. Metoda mapStateToProps() odpowiada za „przetłumaczenie” wartości trzymanych przez Reduxa na parametry przekazywane do komponentu. Komponent działa tak jakby miał po prostu przekazywane parametry. Nie wie, że odpowiada za nie Redux.
Podsumowanie
Nareszcie zrobiłem coś co działa. Czuję się coraz pewniej w świecie Reacta.
Na pewno popełniam masę błędów dlatego chętnie wysłucham każdej uwagi od osób, które pracują z tą technologią dłużej ;)
Aktualny kod znajdziecie na GitHubie pod adresem: https://github.com/zajacmarekcom/letswrite. Nie jest on ładny, ale najpierw chcę zrobić coś co działa. Później będę sprzątał.
Niedługo będę musiał zabrać się za najważniejsze funkcje aplikacji :P Zachęcam do śledzenia bloga i zaglądania na fanpage na Facebooku ;)
Ciekawe kiedy zamienię w postach logo konkursu na logo aplikacji…
Leave a Comment