SwiftUI と React Native for Web で簡単な画面を作ってコードを比較してみた
現在開催中の WWDC20 の「SwiftUI」に関するセッションを見ていて「これってReactに近いのでは...」と感じました。そこで実際に SwiftUI と 私が現在業務で使用している React Native for Web + TypeScript を使って簡単な画面を作って比べてみたのでご紹介します。
実装したもの
- 「+」「ー」ボタンで数値を操作する簡単なカウンター画面
- SwiftUI版は「Xcode Version 11.4.1」を、React Native for Web版は「Version 0.12.2」を使用しました。
- パターン1(1つの画面で実装したもの)とパターン2(カウンターを部品化し、2画面に分けたもの)の2つのパターンで実装しました。
パターン1(1つの画面で実装したもの)
SwiftUI版
SwiftUIで使用できるUIコンポーネント(Button、Textなど)を使って「+」「ー」ボタンと、カウンター値を表示するTextを定義しました。また、View内の一時的な状態を管理するための変数として@Stateをつけたプロパティcount
を定義し、「+」「ー」ボタンのタップ時に値を更新しています。
SimpleCounter.swift
import SwiftUI struct SimpleCounter: View { @State private var count = 0 var body: some View { HStack(spacing: 25) { Button("ー") { if (self.count > 0) { self.count -= 1 } } .font(.largeTitle) .foregroundColor(.black) Text("カウント: \(self.count)") .font(.title) Button("+") { self.count += 1 } .font(.largeTitle) .foregroundColor(.black) } } }
実行結果は以下のようになりました。
React Native for Web + TypeScript版
こちらはReact Native for Webで用意されているコンポーネント(View、Text、TouchableOpacityなど)を使って画面を定義しました。先ほどのSwiftUI版で@State
をつけて宣言していた変数と同じ役割をするものが、React.useState()を使って宣言した変数になります。これは[count, setCount]
とセットで定義する変数で、count
が現在の値を保持し、setCount
を使って値の更新を行います。また、useState(0)の0は初期値
です。
SimpleCounter.tsx
import React from 'react' import { Text, TouchableOpacity, StyleSheet, View } from 'react-native' const SimpleCounter = () => { const [count, setCount] = React.useState(0) return ( <View style={styles.counter}> <TouchableOpacity onPress={() => { if (count > 0) { setCount(count - 1) } }} > <Text style={styles.text}>ー</Text> </TouchableOpacity> <Text style={styles.text}>{`カウント: ${count}`}</Text> <TouchableOpacity onPress={() => setCount(count + 1)}> <Text style={styles.text}>+</Text> </TouchableOpacity> </View> ) } const styles = StyleSheet.create({ counter: { marginTop: 300, flexDirection: 'row', alignItems: 'stretch', justifyContent: 'center', }, text: { fontSize: 24, marginHorizontal: 8 } }) export default SimpleCounter
こちらの実行結果は以下の通りとなり、SwiftUI版とほぼ同じUIとなっています。
パターン2(カウンターを部品化し、2画面に分けたもの)
SwiftUI版
パターン1では@State
で定義したプロパティcount
を「+」「ー」ボタンタップ時に更新していましたが、パターン2では、カウンターの箇所を部品化し、親の画面から値を受け取って表示・更新するようにしています。親の画面から値を受け取るための変数が@Bindingをつけたカウンター側にあるプロパティcount
となります。@Bindingをつけることで、親の画面から受け取った値を読み書きできるようになります。
CounterScreen.swift
import SwiftUI struct CounterScreen: View { @State private var count = 0 var body: some View { Counter(count: $count) } } struct Counter: View { @Binding var count: Int var body: some View { HStack(spacing: 25) { Button("ー") { if (self.count > 0) { self.count -= 1 } } .font(.largeTitle) .foregroundColor(.black) Text("カウント: \(self.count)") .font(.title) Button("+") { self.count += 1 } .font(.largeTitle) .foregroundColor(.black) } } }
実行結果は、パターン1と同じになります。
React Native for Web + TypeScript版
こちらのReact Native for Web版ではSwiftUI版で@Binding
を使っていた箇所をpropsという仕組みを使って実現します。カウンター側は、親画面で定義したcount(カウンター値の読み取り用変数)
, setCount(カウンター値を更新するための関数)
を、propsを使って受け取ります。そして、props.count
を使ってカウンター値の表示をし、props.setCount()
を使って値の更新を行ないます。
CounterScreen.tsx
import React from 'react' import { Text, TouchableOpacity, StyleSheet, View } from 'react-native' const CounterScreen = () => { const [count, setCount] = React.useState(0) return ( <Counter count={count} setCount={setCount} /> ) } type CounterProps = { count: number setCount: (count: number) => void } const Counter = (props: CounterProps) => { return ( <View style={styles.counter}> <TouchableOpacity onPress={() => { if (count > 0) { props.setCount(count - 1) } }} > <Text style={styles.text}>ー</Text> </TouchableOpacity> <Text style={styles.text}>{`カウント: ${props.count}`}</Text> <TouchableOpacity onPress={() => props.setCount(count + 1)}> <Text style={styles.text}>+</Text> </TouchableOpacity> </View> ) } const styles = StyleSheet.create({ counter: { marginTop: 300, flexDirection: 'row', alignItems: 'stretch', justifyContent: 'center', }, text: { fontSize: 24, marginHorizontal: 8 } }) export default CounterScreen
こちらの実行結果も、パターン1と同じです。
まとめ
SwiftUIとReact Native for Webを使って簡単な画面を実装した内容についてご紹介しました。それぞれ用意されたコンポーネントを使って画面を組み立てていく点や、画面内の状態を保持する変数の役割・使い方などについて、類似していると感じました。
この記事がどなたかのお役に立てば幸いです。