Praktyczny przykład - XOR
Chcemy zasymulować na ekranie działanie bramek logicznych - na przykładzie XOR (na wyjściu daje 1 gdy tyko na jdnym wejściu mamy 1).
Przygotujmy komponenty (na bazie button) wejścia (ButtonI) i wyjścia (ButtonO):
import React, { Component } from 'react';
import './buttons.css';
class ButtonI extends Component {
zmiana = () => {
let nValue='o';
if (this.props.v==='o') nValue='i';
this.props.zmiana(this.props.i,nValue);
}
render() {
return (
<button onClick={this.zmiana}>
{this.props.v}
</button>
);
}
}
let ButtonO = (props) => (<button>{props.result}</button>);
export default ButtonI;
export {ButtonO};
Zakładamy, że funkcję "zmiana" obsługi kliknięcia otrzymamy od przodka. Wlasność v (value) = stan przycisku wejścia.
Style (button.css) ograniczmy do wielkości przycisków:
button {
width: 4em;
height: 4em;
}
W aplikacji (obiekt App) zapamiętajmy stan i1 i i2 (stan wejścia - obu przycisków).
import React, { Component } from 'react';
import ButtonI, { ButtonO } from './buttons';
class App extends Component {
constructor(props) {
super(props);
this.state = {i1 : '0', i2: '1'}
this.powrot=this.powrot.bind(this);
}
powrot(i,v) {
let i1, i2;
if (i===1) { i1=v; i2=this.state.i2}
else { i2=v;i1=this.state.i1;}
this.setState({i1 : i1, i2: i2});
}
render() {
return (
<div className="App">
[<ButtonI zmiana={this.powrot} i={1} v={this.state.i1}/>xor
<ButtonI zmiana={this.powrot} i={2} v={this.state.i2}/>] =>
<ButtonO
result={this.state.i1 !== this.state.i2 ? '1' : '0'} />
</div>
);
}
}
export default App;
Zobacz przykład: [023]
Spróbujmy teraz nasz przykład zmienić z użyciem store Redux'a.
Stwórzmy store (store.js):
import { createStore } from 'redux';
const ACTION_ZMIANA = 'ACTION_ZMIANA';
export function zmiana(index,value) {
return {
type : ACTION_ZMIANA,
index,
value
}
}
function stanPoczatkowy() {
return ({
i1 : '0',
i2: '0'})
}
const reducer = (state, action) => {
if (action.type==ACTION_ZMIANA) {
let i1, i2;
if (action.index===1) {
i1=action.value; i2=state.i2
}
else { i2=action.value;i1=state.i1;}
return({
...state,
i1 : i1, i2: i2
});
} else {
return state;
}
}
export const store = createStore(
reducer,
stanPoczatkowy()
)
W stosunku do poprzednich przykładów z Redux mamy tu jedną niewielką komplikację - funkcja kreatora akcji ma dwa parametry: index (numer przycisku) i value (aktualna wartość). Widzimy w jaki sposób są przekazywane w akcjach takie paramtry - są po prostu dopisywane do struktury opisującej zdarzenie. W samym reduktorze można zauważyć użycie operatora spread (...state).
A oto jak uprości się nasz App.js:
import React, { Component } from 'react';
import ButtonI, { ButtonO } from './buttons';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
import { zmiana } from './store';
class App extends Component {
constructor(props) {
super(props);
this.powrot=this.powrot.bind(this);
}
powrot(i,v) {
this.props.zmiana(i,v);
}
render() {
return (
<div className="App">
[<ButtonI zmiana={this.powrot} i={1}
v={this.props.store.i1}/>xor
<ButtonI zmiana={this.powrot} i={2}
v={this.props.store.i2}/>
] =>
<ButtonO result={this.props.store.i1 !==
this.props.store.i2 ? '1' : '0'} />
</div>
);
}
}
///////////// wstrzykiwanie store do props
const mapStateToProps = state => {
return {
store : state
}
}
const mapDispatchToProps = dispatch =>
bindActionCreators(
{ zmiana },
dispatch
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Jak widzimy - jeśli pominąć końcowy fragment (kopiuj & wklej) - moduł uległ uproszczeniu - App nie ma w ogóle własnego stanu.
Jeszcze mała zmiana w ReactDOM.render ....
import { store } from './store.js'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
... i możemy się cieszć działającą aplikacją - zob. przykład [024]