ReactJS
ReactNative
npx create-expo-app demo --template blank
cd demo
# Version Web
npx expo install react-dom react-native-web
# Utilitaire d'affichage appareils mobiles
npx expo install react-native-safe-area-context├── App.js # Composant racine
├── index.js # Point d'entrée du code, package.json > main: ...
├── app.json # Metadonnées du projet: nom, version, configurations...
├── assets # Ressources statiques: images, sons, pdf, etc.
│ ├── icon.png
│ └── splash.png
├── babel.config.js # Configuration de build
├── .expo # Fichiers locaux de l'environnement de build Expo
├── .gitignore
├── package.json # Liste des dépendances JS
└── package-lock.jsonLe langage de templating de React Native
{ } est la porte d'entrée pour JavaScriptimport { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, TextInput, Switch, Button, View } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
export default function App() {
return (
<SafeAreaProvider>
<SafeAreaView style={styles.container}>
<Text style={{ fontSize: 28, fontWeight: 'bold', textAlign: 'center' }}>Todoer</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TextInput
placeholder='New task'
style={[styles.input, { height: 48, flexGrow: 1 }]}
/>
<Switch />
</View>
<TextInput
placeholder='Optional description'
style={[styles.input, { width: '100%', height: 96, verticalAlign: 'top' }]}
multiline={ true }
/>
<View style={{ flexDirection: 'row', justifyContent: 'center' }}>
{/* Envelopper dans une View pour eviter une largeur de 100% */}
<Button
title='Add'
color='green'
/>
</View>
</SafeAreaView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
gap: 16,
padding: 16,
justifyContent: 'start',
},
input: {
borderWidth: 1,
borderColor: 'lightgray',
padding: 8,
},
text: {
fontSize: 18,
},
});Mécanisme de réactivité de React, suivi des changements et mise à jour/re-render des components
import { useState } from 'react';
const [name, setName] = useState('');
const [completed, setCompleted] = useState(false);
// ...
<TextInput
placeholder='New task'
style={[styles.input, { height: 48, flexGrow: 1 }]}
value={ name }
onChangeText={ (text) => setName(text) }
/>
<Switch
value={ completed }
onValueChange={ (value) => setCompleted(value) }
/>
// ...
<Text>{ JSON.stringify({ name, completed }) }</Text>import { useState } from 'react';
const [todos, setTodos] = useState([])
function handleAdd() {
setTodos([
...todos,
{
name,
completed,
}
])
setName('')
setCompleted(false)
}
// ...
<Button
title='Add'
color='green'
onPress={ handleAdd }
/>
// ...
<Text>{ JSON.stringify(todos) }</Text>On favorise le regroupe de states reliés dans un objet!
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, TextInput, Switch, Button, View } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { useState } from 'react';
export default function App() {
const [newTodo, setNewTodo] = useState({ name: '', completed: false })
const [todos, setTodos] = useState([])
function handleAdd() {
setTodos([
...todos,
newTodo
])
setNewTodo({ name: '', completed: false })
}
return (
<SafeAreaProvider>
<SafeAreaView style={styles.container}>
<Text style={{ fontSize: 28, fontWeight: 'bold', textAlign: 'center' }}>Todoer</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TextInput
placeholder='New task'
style={[styles.input, { height: 48, flexGrow: 1 }]}
value={ newTodo.name }
onChangeText={ (text) => setNewTodo({ ...newTodo, name: text}) }
/>
<Switch
value={ newTodo.completed }
onValueChange={ (value) => setNewTodo({ ...newTodo, completed: value}) }
/>
</View>
<Text>{ JSON.stringify(newTodo) }</Text>
<TextInput
placeholder='Optional description'
style={[styles.input, { width: '100%', height: 96, verticalAlign: 'top' }]}
multiline={ true }
/>
<View style={{ flexDirection: 'row', justifyContent: 'center' }}>
{/* Envelopper dans une View pour eviter une largeur de 100% */}
<Button
title='Add'
color='green'
onPress={ handleAdd }
/>
</View>
<Text>{ JSON.stringify(todos) }</Text>
</SafeAreaView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
gap: 16,
padding: 16,
justifyContent: 'start',
},
input: {
borderWidth: 1,
borderColor: 'lightgray',
padding: 8,
},
text: {
fontSize: 18,
},
});Mécanisme d'encapsulation et de réutilisation
import { StyleSheet, TextInput } from 'react-native';
export default function Input({style, ...otherProps}) {
return (
<TextInput
style={[styles.input, style]}
{...otherProps}
/>
)
}
const styles = StyleSheet.create({
input: {
borderWidth: 1,
borderColor: 'lightgray',
padding: 8,
},
});import Input from './Input';
//...
// TextInput -> Input
// Menage des StyleSheet inutilesLes méthodes de trace traditionnelles sont tout à fait pertinentes!
JSON.stringifyL'utilisation du debugger via l'environnement distant de la machine virtuelle est plutôt ardue, on peut exécuter la version Web et utiliser l'inspecteur du navigateur. L'extension navigateur React Dev Tools peut offrir des informations spécifiques à la hiérarchie de components.
Sinon, en mode local, une version dédiée des outils développeurs est accessible en appuyant sur j dans le terminal du serveur de développement.
# Ajouter quelques utilitaires multi-plateformes
npx expo install toastify-react-native @expo/vector-icons react-native-simple-dialogsimport { useState } from 'react';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, View, TouchableHighlight, ScrollView, Switch, Button, Text, TextInput } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import ToastManager, { Toast } from 'toastify-react-native'
import { Dialog } from 'react-native-simple-dialogs';
import { Ionicons } from '@expo/vector-icons';
function TodoItem({ todo, isLast, setToggling }) {
return (
<TouchableHighlight onPress={ () => setToggling(todo) }>
<View
style={{
flexDirection: 'row',
gap: 8,
paddingVertical: 16,
paddingHorizontal: 8,
backgroundColor: 'white', /* requis pour highlight */
borderBottomWidth: isLast ? 0 : 1,
borderBottomColor: 'lightgray'
}}
>
<Ionicons name="checkmark-circle" size={22} color={ todo.done ? 'green' : 'lightgray' } />
<View style={{ flex: 1, flexDirection: 'column' }}>
<Text style={[styles.text, { fontWeight: 'bold' }]}>{ todo.name }</Text>
{
!!todo.description &&
<Text style={ styles.text }>{ todo.description }</Text>
}
</View>
</View>
</TouchableHighlight>
)
}
export default function App() {
const EMPTY_TODO = () => { return {
name: null,
done: false,
description: null
}}
const SEED_COUNT = 4;
const SEED = [...Array(SEED_COUNT).keys()].map((item, index, array) => {
const name = `Todo ${item}`
return {
id: Math.random().toString(16).substring(2),
name: name,
description: `Description ${name} `.repeat(index),
done: (index % 3)
}
})
const [todos, setTodos] = useState(SEED);
const [newTodo, setNewTodo] = useState(EMPTY_TODO());
const [toggling, setToggling] = useState(null)
function handleAdd() {
if ((newTodo.name?.trim() ?? '') == '') {
Toast.error('Provide a Todo name', 'bottom')
} else {
newTodo.id = Math.random().toString(16).substring(2);
newTodo.name = newTodo.name.trim()
setTodos([newTodo, ...todos]);
setNewTodo(EMPTY_TODO());
}
}
function toggle(id) {
setTodos(
todos.map( t => {
if (t.id == id) {
return {
...t,
done : !t.done
}
}
return t;
})
)
}
function list() {
if (todos.length == 0) {
return (
<View style={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 48, color: 'gray' }}>No todos...</Text>
</View>
)
} else {
/* Pas la facon ideale pour afficher une liste, simplification pour la demonstration */
return (
<ScrollView style={{ flexGrow: 1 }}>
{
todos.map((todo, index, array) => {
return (
<TodoItem
key={todo.id}
todo={ todo }
isLast={ index + 1 == array.length }
setToggling={ setToggling }
/>
)
})
}
</ScrollView>
)
}
}
return (
<>
<StatusBar style="auto" />
<ToastManager />
<SafeAreaProvider>
<SafeAreaView style={styles.container}>
<Text style={{ fontSize: 28, fontWeight: 'bold', textAlign: 'center' }}>Todoer Starter</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TextInput
style={[styles.input, { height: 48, flexGrow: 1 }]}
placeholder='New task'
value={ newTodo.name }
onChangeText={ (text) => setNewTodo({...newTodo, name: text}) }
/>
<Switch
value={ newTodo.done }
onValueChange={ checked => setNewTodo({...newTodo, done: checked}) }
/>
</View>
<TextInput
style={[styles.input, { width: '100%', height: 96, verticalAlign: 'top' }]}
placeholder='Optional description'
multiline={ true }
/>
<View style={{ flexDirection: 'row', justifyContent: 'center' }}>
{/* Envelopper dans une View pour eviter une largeur de 100% */}
<Button
title='Add'
color='green'
onPress={ handleAdd }
/>
</View>
{ list() }
<Dialog
title={ toggling?.name }
visible={ !!toggling }
onTouchOutside={ () => setToggling(null) }
animationType="fade"
>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', gap: 32 }}>
<Button title="Cancel" color="gray" onPress={ () => setToggling(null) } />
<Button title={ toggling?.done ? 'Incomplete' : 'Completed' } onPress={ () => { setToggling(false); toggle(toggling?.id); } } />
</View>
</Dialog>
</SafeAreaView>
</SafeAreaProvider>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
gap: 16,
justifyContent: 'center',
padding: 16,
},
input: {
borderWidth: 1,
borderColor: 'lightgray',
padding: 8,
},
text: {
fontSize: 18,
},
});