Arkkitehtuurikirjasto JavaScript-applikaatioille
Esimerkkiprojektin alkuun:
git clone git@github.com:futurice/redux-training.git
cd redux-training
git checkout tags/1
npm install
npm start
Kaikki sovelluksen sisällä oleva tieto. Määrittää esimerkiksi sen, mitä käyttäjä näkee tietyllä ajan hetkellä. Esimerkiksi valokatkaisijan tila voi olla joko päällä tai pois päältä.
Pelin tila:
{
gameRunning: true,
points: 120,
player: {
health: 20,
position: {
x: 245,
y: 0
}
}
}
Websovelluksen tila:
{
loggedIn: true,
user: {
username: 'rikurouvila'
firstname: 'Riku'
},
currentView: 'feed',
visibleTweets: [...]
}
Sovelluksessa tapahtunut tapahtuma, esimerkiksi painikkeen painallus, uusi viesti, näkymän vaihtuminen
{
type: 'USER_LOGGED_IN',
payload: {
username: 'rikurouvila',
profilePic: '...'
}
}
Reduxissa action mallinnetaan tyypillisesti objektina, jolla on kentät type
ja payload
Funktio joka saa parametreikseen edellisen tilan ja juuri tapahtuneen tapahtuman ja palauttaa päivitetyn tilan
function myReducer(previousState, action) {
if(action.type === 'USER_LOGGED_IN') {
const newState = {
user: action.payload
};
return newState;
}
}
Esimerkiksi synkroninen funktio palauttaa tuloksen heti sen jälkeen kun sitä kutsutaan
const sum = sumNumbers(1,2); // 3
Asynkroninen kutsu palauttaa tuloksen vasta määrittelemättömän ajan jälkeen. Asynkronisen funktion tunnistaa usein siitä, että sille syötetty viimeinen parametri on "callback" funktio, jota kutsutaan kun toiminto valmistuu.
getUser('rikurouvila', function(error, user) {
console.log(user);
});
Tälläiset funktiot voivat palauttaa myös promise-objektin, mutta perusidea on sama.
getUser('rikurouvila').then(function(user) {
console.log(user);
});
const App = React.createClass({
getInitialState() {
return {
clicks: 0
};
},
onClick() {
this.setState({
clicks: this.state.clicks + 1
});
},
render() {
return (
<div>
<h1>Click counter</h1>
<p>Button clicked {this.state.clicks} times</p>
<button onClick={this.onClick}>Click here</button>
</div>
)
}
});
Esimerkkejä komponentin tilasta:
- Dropdown auki/kiinni
- Tekstikenttään kirjoitettu teksti
- Sellainen tieto, jota käytetään vain tietyn komponentin sisällä
Esimerkkejä sovelluksen tilasta:
-
Onko käyttäjä kirjautunut
-
Käyttäjäprofiilin tiedot
-
Yleisesti ottaen sellainen tieto, jota hyödynnetään ympäri sovellusta
Valmis Click counter komponentti:
git checkout tags/2
Valmis Click counter komponentti, jonka tila tallennetaan sovelluksen (reduxin) tilaan:
git checkout tags/3
Pohja: git checkout tags/4
Sovelluslogiikka kannattaa pyrkiä erottamaan mahdollisimman pitkälle React-komponenteista ja Reduxin reducereista.
Esimerkiksi tässä sovelluksessa itse haun toteuttava toiminnallisuus voidaan toteuttaa omaan search.js
moduuliinsa
// services/search.js
export function searchWithTerm(term) {
// tekee haun annetulla termillä ja palauttaa tuloksen
}
Sillä oikeassa sovelluksessa reducereita ja actioneita voi olla kymmeniä, kannattaa myös yhtä sovelluksen konseptia vastaava logiikka erottaa omaan tiedostoonsa
// ducks/search.js
import { searchWithTerm } from '../services/search';
export function searchAction(searchTerm) {
return function(dispatch, getState) {
// tee haku, lähetä uusi action kun se valmistuu
}
}
export default function reducer(state, action) {
// tilanhallinta
};
Konsepteittain jaoiteltu projekti: git checkout tags/5
Mahdollistavat custom-toiminnallisuuden toteuttamisen storelle
const store = createStore(reducer, applyMiddleware(thunk));
Valmis middleware reduxille. Mahdollistaa funktioiden käytön actioneina.
{type: __, payload: __}
vs
function(dispatch, getState) {
}
redux-thunk kutsuu funktiota kahdella parametrillä
- dispatch - sama funktio kuin komponenteille tuleva
this.props.dispatch
- getState - funktio joka palauttaa storen nykyisen tilan
Näiden avulla action-funktio voi nyt käytännössä lähettää uusia actioneita ihan milloin tahansa esim. 1 sekunnin päästä tai kun kysely rajapintaan valmistuu.
const actionAfter1000ms = function(dispatch, getState) {
setTimeout(function() {
dispatch({type: 'HELLO', payload: 'WORLD'});
}, 1000)
}
this.props.dispatch(actionAfter1000ms);
Haku toteutettuna redux-thunkilla: git checkout tags/6
import { install } from 'redux-loop';
const store = createStore(reducer, install());
Redux-loop vie sivuvaikutusten luomisen
import { loop, Effects } from 'redux-loop';
export default function reducer(state = INITIAL_STATE, action = {}) {
switch(action.type) {
case SEARCH:
return loop(state, Effects.promise(searchEffect, action.payload));
Haku toteutettuna redux-loopilla: git checkout tags/7
Nimitietokannan nimet
const DATABASE = [
'Pamela',
'Marcus',
'Krystina',
'Mirella',
'Samantha',
'Nena',
'Siu',
'Elden',
'Ashely',
'Mabel',
'Librada',
'Hulda',
'Aliza',
'Shelley',
'Leroy',
'Heriberto',
'Danita',
'Jesica',
'India',
'Wen'
];
https://github.com/reactjs/react-redux
https://github.com/erikras/ducks-modular-redux
https://github.com/gaearon/redux-thunk
https://github.com/redux-loop/redux-loop/tree/0da84e946e2b262f98c836ad310fcd0d80bee94d