💬

【React入門者向け】NativeBaseのかっこいいToDoアプリのサンプルコードの挙動がわかったような気になる解説

2022/07/17に公開

Native Baseはかっこいい。
特にNative BaseのToDoアプリのサンプルデモは格段に格好いい。

https://docs.nativebase.io/todo-list

動きがあって、デザインもよく、コードも簡潔で余計なものがないので、React初学者でもテンションを維持したまま「どれどれ・・・」とコードを眺める気になります。

一方で、ToDoアプリのサンプルコードはReact等の基礎知識がないと初学者が読み解くのは難しい要素もあり、Native Base普及の目的(と、私のコード解説記事作成の練習)も兼ねて初学者向けのコード解説をします。

対象者

  • Reactのことはあまり知識がない
  • Native Baseは使ったことがない
  • 簡単なJavaScriptは読めるが、特殊な構文が来ると構えてしまう
  • サンプルコードを見てみたけど、何やってるのか読み取れない

以下は初学者向けのサンプルコード解説がひたすら続くので、サンプルコードの一部のみよく意味が分からなかった場合は、キーワード検索してそこだけチラ見するので十分と思います。(無駄に記事が長くなってしまったので)

コード解説の流れ

コード解説の流れは以下です。

  • 前提や全体的な話をざっくりとした表現で一問一答形式で雰囲気を説明(ふーん、そうなんだ・・・がゴール)
  • コードの各行で初学者が引っかかりやすい箇所を解説(へー、そういう意味なんだ・・・がゴール)
  • まとめとして(ReactやJavaScript構文の)用語への関連付けを行い、引っかかっていた箇所の深堀を促します(なんとなくコード全体とReactの概念が理解できたような気がする・・・がゴール)

実行環境

後述のWEB-IDE(Expo Snack)でコード確認できるため、ローカルに環境を作る必要はないですが、
ローカルで動かすとvsCodeで定義の参照ができるので確認が捗ります。

vsCodeでライブラリにある関数をプレビューで確認するの図

なにより手元で動くとテンション(と学習意欲)が上がるのでオススメです!


全体を雰囲気で感じるための一問一答(初学者向け概要)

これはなんだ?

NativeBaseというかっこいいReactの部品コンポーネントを使ったサンプルコード(NativeBase公式が提供しているもの)
ToDoアプリのイメージで、リスト要素の追加・削除・更新処理の良いサンプルにもなっている。
挙動を理解するのにReactやJavaScriptの構文知識と、各コンポーネントの仕様理解が多少必要なので本記事で補足していく所存

どうやって動かすの?

https://docs.nativebase.io/todo-list
上記URLで開くPlayground画面から直接動きを確認できますが、そこからさらにコード欄(playground欄)上部の「Open Expo Snack」アイコンを押すと、

IDE風の画面構成でコード全体の確認ができ、警告とかも出してくれるので超素敵です。(Expo Snack画面)

どういうファイル構成?

App.tsx がコード本体(*.tsxは、JSXというJavaScriptの拡張構文ファイル)
1ファイルのみのプログラムコードで、1つのファイルにビューとロジックが混じってます。

どういう処理の流れ?

  1. 最初にライブラリから再利用したい部品(コンポーネント)を指定
  2. 画面表示用の部品を設定して
  3. HTML風の画面ビュー設定に、上記の部品を指定して画面表示の処理を完了させる
App.tsx
import React from "react";   // ・・・1
...

const Example = () => {   // ・・・2
...
};

export default () => {   // ・・・3
        return (
          <NativeBaseProvider>
          ...
          </NativeBaseProvider>
        );
};

上記2の中で、ボタンを押したりのイベント処理やイベント後の画面変更は、
部品の中でHTML風の画面ビューを個別に書いたり、ライブラリのメソッドを呼び出んだりで実現します。

全体をバッとまとめて見てしまうと戸惑うことが多いですが、個別の箇所を一つ一つ丁寧に見ていくと、「なんとなくじわじわわかってくる」のが面白いところで、個別の解説を次に行います。


サンプルコードの逐次解説

コードの各行で初学者が引っかかりやすい箇所を解説していきます。
表現はReact用語の使用を避け、類似する一般的に伝わりそうな呼称(?)を使ってニュアンスが伝わるようにしています。

全14ステップで、目次からよくわからなかった箇所を選択して確認ください。

1.ライブラリ参照箇所(import部)

import React from "react";

「react」というライブラリ(./node_modules/react ディレクトリに対応)の中にある「React」という名称の部品コンポーネント(主にReactが提供する共通関数)を利用可能にする宣言

import { Input, IconButton, ... } from "native-base";

これも同様に「native-base」というライブラリ(今回の主役であり、./node_modules/native-base ディレクトリに対応)の「Input」「IconButton」という部品コンポーネントを利用可能にする宣言で、カンマ区切りで複数指定可能です。
HTMLのInputタグを、NativeBaseが拡張したバージョンのものに置き換えて使うぜ・・・という宣言です。

import { Feather, Entypo } from "@expo/vector-icons";

「@expo」という名前のライブラリ内にある「vector-icons」というライブラリ(./node_modules/@expo/vector-icons)から、FeatherとEntypoというアイコングループセットを参照しています。
vsCodeで「Feather」にカーソルあてるとアイコンセット内のアイコン名一覧が確認できます。
また以下のサイトでアイコンイメージの確認ができ、さらにFilterボタンでアイコングループセットのフィルターができます。
https://icons.expo.fyi/

2.「Example」という名称の関数定義(画面全体の表示部品)

const Example = () => {
};

この中がやや長いですが、ブロックとしては一つに収まっています。
コード中に=>とありますが、例として

() => {alert(1);}

という表現は、JavaScriptの関数表現の省略形で、下記と同じ意味になります。

// 引数なしの無名ファンクション
function() {
  alert(1);
}

つまり => に続く、{} 内の処理(画面表示オブジェクト返す処理)を、Exampleという名前の関数に割り当てる・・・という意味になります。

3.「Example」関数の中身(画面内の個々の表示部品)の構造を確認

Example関数が直接保持するコンポーネントをまとめると、以下のイメージになります。

const Example = () => {
  const instState = [];
  const [list, setList] = React.useState(instState);
  const [inputValue, setInputValue] = React.useState("");
  const toast = useToast();
  const addItem = title => {};
  const handleDelete = index => {};
  const handleStatusChange = index => {};
  
  return <Center w="100%">...</Center>;
};

7つの画面部品を定義して、
そのうえで(上記変数部品の参照を含めた)HTML風のタグをreturnで返しています。
ExampleはHTML風のタグを返す関数部品で、その画面内部の構成要素として、instState等のデータやsetList等のメソッドがExample内部に定義されている・・・というイメージです。

4.「Example」を画面表示対象に指定

    export default () => {
        return (
          <NativeBaseProvider>
	    ...
             <Example />
	    ...
          </NativeBaseProvider>
        );
    };

関数「Example」がタグとして表現され、Exsampleの内容が画面表示されるのだな・・・と伺えます。
「NativeBaseProvider」タグはテーマ設定(ダークモードに切り替えたり)を可能にするためのものです。

これでコード全体のざっくりした骨格が見えてきたと思うので、Exsampleの中身の個々の処理を今から見ていきます。

5.リスト初期値 (const instState)

基本的な構造は配列で

  const instState = [item1, item2, item3];

よく見るとオブジェクト配列で

  const instState = [{}, {},...];

オブジェクトの中身は「title」と「isCompleted」という要素を持つことが伺えます。

  const instState = [{title:"",isCompleted:false},...];

これは画面初期表示時のToDoのデフォルト表示タスクのデータ定義で、Titleがタスク内容の文字列、isCompletedがチェック状態に対応します。

6.リスト現在値の状態管理(const [list, setList] )

  const [list, setList] = React.useState(instState);

リストの現在値のアクセッサを定義しています。
importで定義したReactという部品のuseStateメソッドを使用しており、
useStateメソッドは戻り値が配列になっています。
これは「Reactフック」と呼ばれるReact独自の作法になるのですが、動きとしては以下になります。

  • useStateメソッドの戻り値の第1引数は、useStateメソッドの引数と同じ値が設定される。その値は状態の現在値として扱う。
  • useStateメソッドの戻り値の第2引数は、戻り値の第1引数を操作するメソッドである。このメソッドに値を渡すと第一引数の現在値が更新される

今回のケースでは、デフォルト表示するタスク一覧の変数「instState」を、
タスク一覧の現在値である「list」という変数に格納し、
かつ前述のlistを操作する関数「setList」も合わせて定義する
・・・という意味合いになります。
list変数を更新したいときは、setListメソッドを呼び出せばよい・・・という形になります。

7.テキスト入力現在値の状態管理(const [inputValue, setInputValue])

  const [inputValue, setInputValue] = React.useState("");

新規タスクのテキスト入力のアクセッサです。
これも同じuseStateメソッドで、画面初期表示時のタスク入力欄の初期値(inputValue)は"" となります。
inputValueの値を更新したいときは、setInputValueを呼び出すことになります。

8.アラート用のポップアップ (const toast)

  const toast = useToast();

useToastメソッドは、import文で定義された通り、native-baseライブラリのトーストポップアップ(下からスライドでせり上がってくるポップアップウィンドウ)のオブジェクト取得メソッドです。

9.タスク追加処理 (const addItem)

  const addItem = title => {
    if (title === "") {
      toast.show({
        title: "Please Enter Text",
        status: "warning"
      });
      return;
    }

    setList(prevList => {
      return [...prevList, {
        title: title,
        isCompleted: false
      }];
    });
  };

タスク追加処理です。

addItem = title => {}
・・・は、
addItem = function (title) {}
・・・を意味します。

追加メソッドが呼び出されたとき、
タスク入力欄が未入力状態だったら、未入力を咎めるために例のトーストポップアップを表示します。

タスク入力欄が入力済だったら、編集前のリストの最後にタスク入力欄に記載されたタスクを追加します。
ここで前述のsetListメソッドがでてきます。

setList(prevList => {}) は
setList( function (prevList) {} ) と同じ意味です。

setListの引数に関数を渡す場合は、仮引数prevListに、useStateの戻り値の第1引数であるlistが関数に割り当てられ呼び出されます。

また「...」は、配列要素の展開を意味します。

const addItem = title => {
    .....
    
    setList(prevList => {
      return [...prevList, {
        title: title,
        isCompleted: false
      }];
    });

・・・の動きを言葉で説明すると

  1. prevListにlist(更新前のリスト値)が割り当てられる
  2. prevList(実体は変数「list」)の要素が展開される(配列に10個要素があれば、10個分のカンマ区切りされた要素が出力されたのと同じ動きになる)
  3. 上記展開したあとに一つ新規追加した要素を配列の最後に追加する
  4. その追加要素のtitleプロパティの値には、addItemメソッドの仮引数値であるtitle変数の値が設定される

構文として慣れればそうでもないのですが、初回は複雑で難しく感じるかもしれません。
慣れないうちは、useStateで得られるメソッドで、setList(prevList => {}) という構文の時は現在値が自動的に引数に設定される・・・と一旦機械的に覚えて、慣れてからコールバックの動きを見直すといいかもしれません。

10.タスク削除処理 (const handleDelete)

  const handleDelete = index => {
    setList(prevList => {
      const temp = prevList.filter((_, itemI) => itemI !== index);
      return temp;
    });
  };

タスク削除処理です。
指定されたインデックスのタスクをリストから削除します。

handleDeleteはindexを仮引数に持つメソッドです。
setListメソッドは、タスク追加時と同様にprevListにlist変数が割り当てられ
関数内でフィルター処理されます。

prevList.filter((_, itemI) => itemI !== index);
・・・は以下の動きとなります。

  1. フィルターメソッドの第1引数は配列要素であり、通常はitemという変数名が使用される。しかし今回の処理では第1引数の値は関数内で使用しないので、使用しないダミーの変数・・・という意味合いで慣例的に_という変数名が使われているようです。
  2. フィルターメソッドの第2引数は0始まりの配列インデックス値が渡されます。itemIの「I」はIndexのIでしょう。
  3. 関数はhandleDeleteの引数indexに一致しない行は残し、一致する行はリストから削除する・・・です
  4. prevListの全要素に対して上記の操作を行います

11.チェックボックスの更新処理 (const handleStatusChange)

  const handleStatusChange = index => {
    setList(prevList => {
      const newList = [...prevList];
      newList[index].isCompleted = !newList[index].isCompleted;
      return newList;
    });
  };

タスク完了のチェックボックスのオンオフ制御を行う。
処理の流れは以下

  1. handleStatusChangeメソッドの引数indexに更新対象のリストのインデックス値が渡される
  2. setListメソッドの引数prevListに更新前のリスト(list)が渡される
  3. prevListは配列展開されて、同内容のコピーが別オブジェクトのnewListとして格納される
  4. 変更対象のリスト項目のisCompletedのbool値が反転してnewListに格納される
  5. 変更後のリストであるnewListがsetListに渡され、新しい現在値としてlistに反映される

12.const Exsampleの戻り値 (画面表示部品の設定_List表示部以外)

list.mapメソッドの中身はここでは省略して、それ以外の箇所を説明します。

  return <Center w="100%">
      <Box maxW="300" w="100%">
        <Heading mb="2" size="md">
          Wednesday
        </Heading>
        <VStack space={4}>
          <HStack space={2}>
            <Input flex={1} onChangeText={v => setInputValue(v)} value={inputValue} placeholder="Add Task" />
            <IconButton borderRadius="sm" variant="solid" icon={<Icon as={Feather} name="plus" size="sm" color="warmGray.50" />} onPress={() => {
            addItem(inputValue);
            setInputValue("");
          }} />
          </HStack>
          <VStack space={2}>
            {list.map((item, itemI) => <HStack>.....</HStack>)}
          </VStack>
        </VStack>
      </Box>
    </Center>;

構成についての所感

  • タグの内容や意味は直観的に理解できそう(例:Box, Heading)
  • タグの要素は略語で書かれているが、これも意味を推測しやすそう(例:maxW="300" w="100%")
  • 要素名で意味が分からないものはvsCodeでカーソルあてれば、説明をpopupしてくれるので便利(ただし説明の記述がない場合もある)
  • 具体的にどういう効果を持つかは、playgroudで値をいじって動きを確認するといい
  • {}で囲んだ範囲はJavaScriptコードが書ける
  • VStack(行)とHStack(列)で要素の配置方向を設定し、それでレイアウトの段組みをしてる
  • タスク追加ボタンにはaddItemメソッドを割り当て、実行後にテキスト入力欄を空にしている

タグや要素の解説

タグ名 要素名 説明
Box maxW 最大幅
Box w
Heading mb 見出し表示後の隙間。大きいほど隙間が大きい
Heading size mdはmidiumの意味
VStack space 行間の隙間サイズ VStackは縦方向(vertical)に画面表示を並べていく
HStack space 列間の隙間サイズ HStackは横方向(horizontal)に画面表示を並べていく
Input flex 1を設定した場合は、使用タグは領域いっぱいに引き伸ばされる
Input placeholder 未入力時のガイドテキスト
IconButton borderRadius 境界線の丸みの程度。smはsmall。大きい程丸みを帯びる
IconButton variant アイコン等の見た目のスタイル。solidは塗りつぶし系。outlineは塗りつぶしなし系
IconButton icon nameと組み合わせてアイコンを指定。Featherアイコンセットを指定
IconButton name Featherアイコンセット内のplusアイコンを指定
IconButton color NativeBaseのDefaultThemeのカラーセットから選択(warmGray.50)。数字は明度で50刻みで小さいほど明るい

13.const Exsampleの戻り値 (画面表示部品の設定_List表示部)

list.mapメソッドの中身を説明します。

            {list.map((item, itemI) => <HStack w="100%" justifyContent="space-between" alignItems="center" key={item.title + itemI.toString()}>
                <Checkbox isChecked={item.isCompleted} onChange={() => handleStatusChange(itemI)} value={item.title}></Checkbox>
                <Text width="100%" flexShrink={1} textAlign="left" mx="2" strikeThrough={item.isCompleted} _light={{
              color: item.isCompleted ? "gray.400" : "coolGray.800"
            }} _dark={{
              color: item.isCompleted ? "gray.400" : "coolGray.50"
            }} onPress={() => handleStatusChange(itemI)}>
                  {item.title}
                </Text>
                <IconButton size="sm" colorScheme="trueGray" icon={<Icon as={Entypo} name="minus" size="xs" color="trueGray.400" />} onPress={() => handleDelete(itemI)} />
              </HStack>)}

構成についての所感

  • list.mapメソッドは、list要素すべてに対し、各要素自身(item)と要素インデックス(itemI)を次の関数(といってもタグ表記の画面設定)に渡します。
  • 各タスクのチェックボックスとテキストにhandleStatusChangeメソッドを割り当て
  • 各タスクの削除アイコンにhandleDeleteメソッドを割り当て

タグや要素の解説

タグ名 要素名 説明
HStack justifyContent space-between は「余白をもって等間隔に配置」
Text flexShrink 1は等幅に分割配置
Text strikeThrough 取り消し線の有無を動的に設定
Text _light ライトモード有効時のカラー設定 PlatformProps参照
Text _dark ダークモード有効時のカラー設定
IconButton colorScheme アイコンのカラーテーマを設定

14.export default (Expampleの内容を画面出力)

    export default () => {
        return (
          <NativeBaseProvider>
            <Center flex={1} px="3">
                <Example />
            </Center>
          </NativeBaseProvider>
        );
    };

export default で画面出力内容を指定します。
NativeBaseProviderタグは、テーマ等の変更を反映する設定。
Exampleタグで設定した画面表示部品が表示される

タグや要素の解説

タグ名 要素名 説明
Center flex 1を設定した場合、領域いっぱいに引き伸ばされる
Center px Center自身の幅。マージン的に使用し大きくなると周囲の余白が増える

まとめ

後付けで、今まで見てきた構文がどういう概念にあたるか見て頂くのも理解を深める助けになると思います。

(ReactやJavaScript構文の)用語への関連付け

用語 サンプル中の該当コード 説明
アロー関数 () => {} または title => {} または (item, itemI) => {} JavaScriptの関数表記 関数の引数の個数で書き方が変わる
JSX return <Checkbox isChecked={item.isCompleted}> マークアップとコードを混在表記可能なJavaScriptの拡張構文 拡張子は*.tsx
関数コンポーネント const Exsample = () => {return JSX} JSXを返す関数のこと
コンポーネント指向 <Example /> 画面表示するのに、まず関数コンポーネントを作ってそれを表示する・・・という一連の処理の流れ
[React Hooks](#React Hooks) React.useState 状態管理を簡易化する仕組み
スプレッド構文 [...prevList, {}] 配列を要素に復元する構文 配列編集が簡潔に記述できる

すごくわかりやすかった解説サイト

アロー関数

https://qiita.com/may88seiji/items/4a49c7c78b55d75d693b

JSX

https://ja.reactjs.org/docs/introducing-jsx.html

関数コンポーネント

https://zenn.dev/web_tips/articles/c3851133f52d16#関数コンポーネント

React Hooks

https://qiita.com/seira/items/f063e262b1d57d7e78b4

スプレッド構文

https://zenn.dev/web_tips/articles/69a63bc8a91459

Discussion

ログインするとコメントできます