Propriétés (props) = caractéristiques définies au moment de la création du composant.
Les propriétés sont modifiables uniquement par le composant parent.
Etat (state) = ensemble des données susceptibles d’être modifiées pendant l’exécution de l’application.
Chaque composant React Native possède un état interne, géré avec this.state
et this.setState()
.
Toute modification de l’état déclenche un nouveau rendu du composant.
La gestion locale de l’état devient insuffisante lorsqu’un composant doit accéder à ou modifier l’état d’un autre composant.
Nécessité de partager un état commun entre certains composants.
https://github.com/ensc-mobi/TempConverter
class TemperatureInput extends React.Component {
_onChangeText = (text) => {
// Callback passed as component prop is called
this.props.onTemperatureChange(text);
};
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
const placeholder = `Enter temperature in ${scaleNames[scale]}`;
return (
<TextInput
style={styles.text}
placeholder={placeholder}
value={temperature}
onChangeText={this._onChangeText}
/>
);
}
}
class Calculator extends React.Component {
constructor(props) {
super(props);
// Common state is lifted in closest parent of TemperatureInput components
// Temperature can be set either in Celsius or in Fahrenheit
this.state = { temperature: "", scale: "c" };
}
_onCelsiusChange = temperature => {
this.setState({ scale: "c", temperature });
};
_onFahrenheitChange = temperature => {
this.setState({ scale: "f", temperature });
};
// ...
// ...
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const tempCelsius =
scale === "f" ? tryConvert(temperature, toCelsius) : temperature;
const tempFahrenheit =
scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<View>
<TemperatureInput
scale="c"
temperature={tempCelsius}
onTemperatureChange={this._onCelsiusChange}
/>
<TemperatureInput
scale="f"
temperature={tempFahrenheit}
onTemperatureChange={this._onFahrenheitChange}
/>
<BoilingResult tempCelsius={parseFloat(tempCelsius)} />
</View>
);
}
Les composants parents rassemblent trop de choses :
Non-respect du principe de séparation des responsabilités.
https://github.com/ensc-mobi/TodoNative
export default class TodoStore extends Store {
constructor() {
// Call to parent constructor is needed to init observation
super();
// The TODO task is used as key (unique identifier)
// Therefore, each TODO must have a different task
this.todos = [];
}
addTodo = (task) => {
// Add new TODO at beginning of array
this.todos = [{ task, completed: false }, ...this.todos];
this.notifyObservers();
};
// ...
// Used in parent class when notifying observers
getState() {
const todos = this.todos;
return { todos };
}
}
Basée sur le Design Pattern Observateur.
export default class Store {
constructor() {
// Define an empty observer list
this.observers = [];
}
// Add new observer to list
addObserver(observer) {
this.observers.push(observer);
}
// Notify all observers of a state change in the store
notifyObservers() {
this.observers.forEach((observer) => observer.setState(this.getState()));
}
}
const App = () => {
const todoStore = new TodoStore();
return <MainView todoStore={todoStore} />;
};
export default App;
export default class MainView extends React.Component {
constructor(props) {
super(props);
this.state = this.props.todoStore.getState();
this.props.todoStore.addObserver(this);
}
render() {
return (
<View style={styles.container}>
<Header title="TodoNative" />
<Input
placeholder="Saisissez une nouvelle tâche"
onSubmitEditing={this.props.todoStore.addTodo}
/>
// ...
# Add MobX to a React Native project
expo install mobx mobx-react
expo install --save-dev babel-preset-mobx
https://github.com/ensc-mobi/TodoNative-MobX
import { observable } from "mobx";
export default class Todo {
@observable task;
@observable completed;
constructor(task = "", completed = false) {
this.task = task;
this.completed = completed;
}
toggle() {
this.completed = !this.completed;
}
}
import { observable } from "mobx";
import Todo from "../domain/Todo";
export default class TodoStore {
@observable todos;
@observable isLoading = true;
constructor() {
// The TODO task is used as key (unique identifier)
// Therefore, each TODO must have a different task
this.todos = [];
}
addTodo(task) {
const todo = new Todo(task);
// Add new TODO at beginning of array
this.todos = [todo, ...this.todos];
}
// ...
}
const App = () => {
const todoStore = new TodoStore();
return <MainView todoStore={todoStore} />;
};
export default App;
import { observer } from "mobx-react";
// ...
@observer
export default class MainView extends React.Component {
onAddTodo = task => {
this.props.todoStore.addTodo(task);
};
// ...
render() {
return (
<View style={styles.container}>
<Header title="TodoNative" />
<Input
placeholder="Saisissez une nouvelle tâche"
onSubmitEditing={this.onAddTodo}
/>
<FlatList
style={styles.todoContainer}
data={this.props.todoStore.todos}
// ...
}