前書き
個人的な学習のためReact Nativeの公式チュートリアルをやります。ただ、チュートリアルはReact Class APIを利用しているため、今回はそれをReact Hooks + TypeScriptに置き換えながら学習していこうと思います。
本記事は、「該当ページ」 「ClassAPIでの書き方」 「Hooksでの書き方」をセットにして記述していきます。
Learn the Basics
該当ページ: https://facebook.github.io/react-native/docs/tutorial
import React, { Component } from 'react';
import { Text, View } from 'react-native';
export default class HelloWorldApp extends Component {
render() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Hello, world!</Text>
</View>
);
}
}
import React, {Component} from 'react';
import {Text, View} from 'react-native';
export default () => {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>Hello, world!</Text>
</View>
);
};
Props
該当ページ: https://facebook.github.io/react-native/docs/props
import React, { Component } from 'react';
import { Text, View } from 'react-native';
class Greeting extends Component {
render() {
return (
<View style={{alignItems: 'center'}}>
<Text>Hello {this.props.name}!</Text>
</View>
);
}
}
export default class LotsOfGreetings extends Component {
render() {
return (
<View style={{alignItems: 'center', top: 50}}>
<Greeting name='Rexxar' />
<Greeting name='Jaina' />
<Greeting name='Valeera' />
</View>
);
}
}
import React, {Component} from 'react';
import {Text, View} from 'react-native';
interface Props {
name: string;
}
const Greeting = (props: Props) => {
return (
<View style={{alignItems: 'center'}}>
<Text>Hello {props.name}!</Text>
</View>
);
};
export default class LotsOfGreetings extends Component {
render() {
return (
<View style={{alignItems: 'center', top: 50}}>
<Greeting name="Rexxar" />
<Greeting name="Jaina" />
<Greeting name="Valeera" />
</View>
);
}
}
State
該当ページ: https://facebook.github.io/react-native/docs/state
import React, {Component} from 'react';
import {Text, View} from 'react-native';
class Blink extends Component {
componentDidMount() {
// Toggle the state every second
setInterval(
() =>
this.setState(previousState => ({
isShowingText: !previousState.isShowingText,
})),
1000,
);
}
//state object
state = {isShowingText: true};
render() {
if (!this.state.isShowingText) {
return null;
}
return <Text>{this.props.text}</Text>;
}
}
export default class BlinkApp extends Component {
render() {
return (
<View>
<Blink text="I love to blink" />
<Blink text="Yes blinking is so great" />
<Blink text="Why did they ever take this out of HTML" />
<Blink text="Look at me look at me look at me" />
</View>
);
}
}
import React, {Component, useEffect, useState} from 'react';
import {Text, View} from 'react-native';
interface Props {
text: string;
}
const Blink = (props: Props) => {
useEffect(() => {
// Toggle the state every second
setInterval(() => setState(!isShowingText), 1000);
}, []);
//state object
const [isShowingText, setState] = useState(true);
if (!isShowingText) {
return null;
}
return <Text>{props.text}</Text>;
};
export default () => {
return (
<View>
<Blink text="I love to blink" />
<Blink text="Yes blinking is so great" />
<Blink text="Why did they ever take this out of HTML" />
<Blink text="Look at me look at me look at me" />
</View>
);
};
Style
該当ページ: https://facebook.github.io/react-native/docs/style
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
const styles = StyleSheet.create({
bigBlue: {
color: 'blue',
fontWeight: 'bold',
fontSize: 30,
},
red: {
color: 'red',
},
});
export default class LotsOfStyles extends Component {
render() {
return (
<View>
<Text style={styles.red}>just red</Text>
<Text style={styles.bigBlue}>just bigBlue</Text>
<Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
<Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
</View>
);
}
}
import React from 'react';
import {StyleSheet, Text, View} from 'react-native';
const styles = StyleSheet.create({
bigBlue: {
color: 'blue',
fontWeight: 'bold',
fontSize: 30,
},
red: {
color: 'red',
},
});
export default () => {
return (
<View>
<Text style={styles.red}>just red</Text>
<Text style={styles.bigBlue}>just bigBlue</Text>
<Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
<Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
</View>
);
};
Height and Width
該当ページ: https://facebook.github.io/react-native/docs/height-and-width
import React, { Component } from 'react';
import { View } from 'react-native';
export default class FlexDimensionsBasics extends Component {
render() {
return (
// Try removing the `flex: 1` on the parent View.
// The parent will not have dimensions, so the children can't expand.
// What if you add `height: 300` instead of `flex: 1`?
<View style={{flex: 1}}>
<View style={{flex: 1, backgroundColor: 'powderblue'}} />
<View style={{flex: 2, backgroundColor: 'skyblue'}} />
<View style={{flex: 3, backgroundColor: 'steelblue'}} />
</View>
);
}
}
import React, {Component} from 'react';
import {View} from 'react-native';
export default () => {
return (
// Try removing the `flex: 1` on the parent View.
// The parent will not have dimensions, so the children can't expand.
// What if you add `height: 300` instead of `flex: 1`?
<View style={{flex: 2}}>
<View style={{flex: 1, backgroundColor: 'powderblue'}} />
<View style={{flex: 2, backgroundColor: 'skyblue'}} />
<View style={{flex: 3, backgroundColor: 'steelblue'}} />
</View>
);
};
Layout with Flexbox
該当ページ: https://facebook.github.io/react-native/docs/flexbox
import React, { Component } from 'react';
import { View } from 'react-native';
export default class FlexDirectionBasics extends Component {
render() {
return (
// Try setting `flexDirection` to `column`.
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
</View>
);
}
};
import React, {Component} from 'react';
import {View} from 'react-native';
export default () => {
return (
// Try setting `flexDirection` to `column`.
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
</View>
);
};
import React, {Component} from 'react';
import {View} from 'react-native';
export default class JustifyContentBasics extends Component {
render() {
return (
// Try setting `justifyContent` to `center`.
// Try setting `flexDirection` to `row`.
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
}}>
<View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
</View>
);
}
}
import React, {Component} from 'react';
import {View} from 'react-native';
export default () => {
return (
// Try setting `justifyContent` to `center`.
// Try setting `flexDirection` to `row`.
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
}}>
<View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
</View>
);
};
import React, { Component } from 'react';
import { View } from 'react-native';
export default class AlignItemsBasics extends Component {
render() {
return (
// Try setting `alignItems` to 'flex-start'
// Try setting `justifyContent` to `flex-end`.
// Try setting `flexDirection` to `row`.
<View style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'stretch',
}}>
<View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
<View style={{height: 50, backgroundColor: 'skyblue'}} />
<View style={{height: 100, backgroundColor: 'steelblue'}} />
</View>
);
}
};
import React, {Component} from 'react';
import {View} from 'react-native';
export default () => {
return (
// Try setting `alignItems` to 'flex-start'
// Try setting `justifyContent` to `flex-end`.
// Try setting `flexDirection` to `row`.
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'stretch',
}}>
<View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
<View style={{height: 50, backgroundColor: 'skyblue'}} />
<View style={{height: 100, backgroundColor: 'steelblue'}} />
</View>
);
};
Handling Text Input
該当ページ: https://facebook.github.io/react-native/docs/handling-text-input
import React, { Component } from 'react';
import { Text, TextInput, View } from 'react-native';
export default class PizzaTranslator extends Component {
constructor(props) {
super(props);
this.state = {text: ''};
}
render() {
return (
<View style={{padding: 10}}>
<TextInput
style={{height: 40}}
placeholder="Type here to translate!"
onChangeText={(text) => this.setState({text})}
value={this.state.text}
/>
<Text style={{padding: 10, fontSize: 42}}>
{this.state.text.split(' ').map((word) => word && '🍕').join(' ')}
</Text>
</View>
);
}
}
import React, {Component, useState} from 'react';
import {Text, TextInput, View} from 'react-native';
export default () => {
const [text, setText] = useState('');
return (
<View style={{padding: 10}}>
<TextInput
style={{height: 40}}
placeholder="Type here to translate!"
onChangeText={text => setText(text)}
value={text}
/>
<Text style={{padding: 10, fontSize: 42}}>
{text
.split(' ')
.map(word => word && '🍕')
.join(' ')}
</Text>
</View>
);
};
Handling Touches
該当ページ: https://facebook.github.io/react-native/docs/handling-touches
import React, {Component} from 'react';
import {Button, StyleSheet, View} from 'react-native';
export default class ButtonBasics extends Component {
_onPressButton() {
alert('You tapped the button!');
}
render() {
return (
<View style={styles.container}>
<View style={styles.buttonContainer}>
<Button onPress={this._onPressButton} title="Press Me" />
</View>
<View style={styles.buttonContainer}>
<Button
onPress={this._onPressButton}
title="Press Me"
color="#841584"
/>
</View>
<View style={styles.alternativeLayoutButtonContainer}>
<Button onPress={this._onPressButton} title="This looks great!" />
<Button onPress={this._onPressButton} title="OK!" color="#841584" />
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
buttonContainer: {
margin: 20,
},
alternativeLayoutButtonContainer: {
margin: 20,
flexDirection: 'row',
justifyContent: 'space-between',
},
});
import React, {Component} from 'react';
import {Button, StyleSheet, View} from 'react-native';
export default () => {
const _onPressButton = () => {
alert('You tapped the button!');
};
return (
<View style={styles.container}>
<View style={styles.buttonContainer}>
<Button onPress={_onPressButton} title="Press Me" />
</View>
<View style={styles.buttonContainer}>
<Button onPress={_onPressButton} title="Press Me" color="#841584" />
</View>
<View style={styles.alternativeLayoutButtonContainer}>
<Button onPress={_onPressButton} title="This looks great!" />
<Button onPress={_onPressButton} title="OK!" color="#841584" />
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
buttonContainer: {
margin: 20,
},
alternativeLayoutButtonContainer: {
margin: 20,
flexDirection: 'row',
justifyContent: 'space-between',
},
});
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
TouchableHighlight,
TouchableOpacity,
TouchableNativeFeedback,
TouchableWithoutFeedback,
View,
} from 'react-native';
export default class Touchables extends Component {
_onPressButton() {
alert('You tapped the button!');
}
_onLongPressButton() {
alert('You long-pressed the button!');
}
render() {
return (
<View style={styles.container}>
<TouchableHighlight onPress={this._onPressButton} underlayColor="white">
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableHighlight</Text>
</View>
</TouchableHighlight>
<TouchableOpacity onPress={this._onPressButton}>
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableOpacity</Text>
</View>
</TouchableOpacity>
<TouchableNativeFeedback
onPress={this._onPressButton}
background={
Platform.OS === 'android'
? TouchableNativeFeedback.SelectableBackground()
: ''
}>
<View style={styles.button}>
<Text style={styles.buttonText}>
TouchableNativeFeedback{' '}
{Platform.OS !== 'android' ? '(Android only)' : ''}
</Text>
</View>
</TouchableNativeFeedback>
<TouchableWithoutFeedback onPress={this._onPressButton}>
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableWithoutFeedback</Text>
</View>
</TouchableWithoutFeedback>
<TouchableHighlight
onPress={this._onPressButton}
onLongPress={this._onLongPressButton}
underlayColor="white">
<View style={styles.button}>
<Text style={styles.buttonText}>Touchable with Long Press</Text>
</View>
</TouchableHighlight>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
paddingTop: 60,
alignItems: 'center',
},
button: {
marginBottom: 30,
width: 260,
alignItems: 'center',
backgroundColor: '#2196F3',
},
buttonText: {
textAlign: 'center',
padding: 20,
color: 'white',
},
});
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
TouchableHighlight,
TouchableOpacity,
TouchableNativeFeedback,
TouchableWithoutFeedback,
View,
} from 'react-native';
export default () => {
const _onPressButton = () => {
alert('You tapped the button!');
};
const _onLongPressButton = () => {
alert('You long-pressed the button!');
};
return (
<View style={styles.container}>
<TouchableHighlight onPress={_onPressButton} underlayColor="white">
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableHighlight</Text>
</View>
</TouchableHighlight>
<TouchableOpacity onPress={_onPressButton}>
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableOpacity</Text>
</View>
</TouchableOpacity>
<TouchableNativeFeedback
onPress={_onPressButton}
background={
Platform.OS === 'android'
? TouchableNativeFeedback.SelectableBackground()
: ''
}>
<View style={styles.button}>
<Text style={styles.buttonText}>
TouchableNativeFeedback{' '}
{Platform.OS !== 'android' ? '(Android only)' : ''}
</Text>
</View>
</TouchableNativeFeedback>
<TouchableWithoutFeedback onPress={_onPressButton}>
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableWithoutFeedback</Text>
</View>
</TouchableWithoutFeedback>
<TouchableHighlight
onPress={_onPressButton}
onLongPress={_onLongPressButton}
underlayColor="white">
<View style={styles.button}>
<Text style={styles.buttonText}>Touchable with Long Press</Text>
</View>
</TouchableHighlight>
</View>
);
};
const styles = StyleSheet.create({
container: {
paddingTop: 60,
alignItems: 'center',
},
button: {
marginBottom: 30,
width: 260,
alignItems: 'center',
backgroundColor: '#2196F3',
},
buttonText: {
textAlign: 'center',
padding: 20,
color: 'white',
},
});
Using a ScrollView
該当ページ: https://facebook.github.io/react-native/docs/using-a-scrollview
省略
Using List Views
該当ページ: https://facebook.github.io/react-native/docs/using-a-listview
import React, { Component } from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
export default class FlatListBasics extends Component {
render() {
return (
<View style={styles.container}>
<FlatList
data={[
{key: 'Devin'},
{key: 'Dan'},
{key: 'Dominic'},
{key: 'Jackson'},
{key: 'James'},
{key: 'Joel'},
{key: 'John'},
{key: 'Jillian'},
{key: 'Jimmy'},
{key: 'Julie'},
]}
renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22
},
item: {
padding: 10,
fontSize: 18,
height: 44,
},
})
import React, {Component} from 'react';
import {FlatList, StyleSheet, Text, View} from 'react-native';
export default () => {
return (
<View style={styles.container}>
<FlatList
data={[
{key: 'Devin'},
{key: 'Dan'},
{key: 'Dominic'},
{key: 'Jackson'},
{key: 'James'},
{key: 'Joel'},
{key: 'John'},
{key: 'Jillian'},
{key: 'Jimmy'},
{key: 'Julie'},
]}
renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22,
},
item: {
padding: 10,
fontSize: 18,
height: 44,
},
});
Networking
該当ページ: https://facebook.github.io/react-native/docs/network
import React from 'react';
import { FlatList, ActivityIndicator, Text, View } from 'react-native';
export default class FetchExample extends React.Component {
constructor(props){
super(props);
this.state ={ isLoading: true}
}
componentDidMount(){
return fetch('https://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson.movies,
}, function(){
});
})
.catch((error) =>{
console.error(error);
});
}
render(){
if(this.state.isLoading){
return(
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
return(
<View style={{flex: 1, paddingTop:20}}>
<FlatList
data={this.state.dataSource}
renderItem={({item}) => <Text>{item.title}, {item.releaseYear}</Text>}
keyExtractor={({id}, index) => id}
/>
</View>
);
}
}
import React, {useState, useEffect} from 'react';
import {FlatList, ActivityIndicator, Text, View} from 'react-native';
export default () => {
const [isLoading, setIsLoading] = useState(true);
const [dataSource, setDataSource] = useState([]);
useEffect(() => {
const fetchMovies = async () => {
try {
const response = await fetch(
'https://facebook.github.io/react-native/movies.json',
);
const responseJson = await response.json();
setIsLoading(false);
setDataSource(responseJson.movies);
} catch (error) {
console.error(error);
}
};
fetchMovies();
}, []);
if (isLoading) {
return (
<View style={{flex: 1, padding: 200}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{flex: 1, paddingTop: 20}}>
<FlatList
data={dataSource}
renderItem={({item}) => (
<Text>
{item.title}, {item.releaseYear}
</Text>
)}
keyExtractor={({id}, index) => id}
/>
</View>
);
};
感想
Class APIに対してReact Hooksはコンパクトで無駄のない記述ができて素晴らしいですね。
React Nativeは今回初めて触ったのですが、Reactに慣れていればとても使いやすそうだと感じました。
SwiftやKotlinでのネイティブアプリ開発をやってきましたが、もう戻れないんじゃないかと思うくらい開発体験が良さそうです。
もちろん実務で使うとなるとつらみは出てくるのでしょうが、それはそれとしてとにかく使ってみたいと思えるチュートリアルでした。