0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【UiPath】【RPA開発者向け】Coded Apps(React+Typescript)キャッチアップ

0
Last updated at Posted at 2026-09-25

はじめに

  • 本記事は、UiPath Coded Apps(React+Typescript)の学習用コンテンツです。
  • UiPath Coded Apps でアプリを開発するにあたって、UiPathがTypescriptの開発用モジュールやReact+Typescriptで動作するテンプレートを用意してくれているので、基本的にはそれらをもちいて開発をおこないます。
  • 筆者自身、HTML/CSS/JavaScriptは扱ったことがあるものの、Reactは初めてだったため、最初理解するのに苦労した部分などを多く書き留めています。
  • 記事の内容は、個人の見解または確認結果であり、UiPath の公式見解ではありません。

RPA開発者のための React + TypeScript キャッチアップ


目次


0. この記事の前提

  • RPA 開発者は「画面を操作する側」の経験は豊富ですが、「画面を作る側」の発想には慣れていません。React はまさにその「作る側」の道具です。
  • 素の JavaScript(document.getElementById(...).innerHTML = ...)の知識があると、かえって React の作法に戸惑います。筆者もそうでした。「素の JS ではこう書くのに、なぜ React ではダメなのか」 を意識すると理解が早くなります。
  • TypeScript は「JavaScript に型の注釈を足したもの」です。最初は React の理解を優先し、TypeScript は少しずつ混ぜていくのがおすすめです。

1. 考え方の転換 UI は state の関数

命令的 と 宣言的

  • 命令的(素の JS):「ボタンが押されたら、この要素を探して、文字を書き換えて、クラスを付けて…」と 手順 を書く。RPA のワークフローと同じ発想です。
  • 宣言的(React):「データがこうなら、画面はこう見える」と 完成形 を書く。書き換えの手順は React がやってくれます。

UI = f(state)

  • 画面(UI)は、データ(state)を入れると出てくる関数の結果、という考え方です。
  • 開発者がやるのは 「state を変えること」だけ。画面を直接いじることはしません。
  • RPA で言えば「セレクターで要素を探して値を書き込む」作業を、もう自分ではやらない、ということです。

2. コンポーネントと JSX

コンポーネントは「画面の部品を返す関数」

function Greeting() {
  return <h1>こんにちは</h1>;
}
  • 名前は 大文字始まり が決まりです。
  • return の中に書いたものが画面に出ます。

JSX は HTML ではなく JavaScript

  • <h1>こんにちは</h1> は HTML に見えますが、実体は React.createElement("h1", null, "こんにちは") という関数呼び出しに変換されます。
  • そのため HTML との差分があります:
    • class → className
    • for → htmlFor
    • onclick="..." → onClick={...}(キャメルケース・文字列ではなく関数を渡す)
    • 閉じタグ必須(<br />、<input />)
    • return できるのは 1つのかたまり だけ(複数あるなら <>...</> で囲む)

{} の中には「式」だけ

  • JSX の中で JavaScript を書くときは {} で囲みます。
  • 入れられるのは 値を返す式 だけ。if 文や for 文は書けません。
    • 条件分岐 → 三項演算子 a ? b : c や a && b
    • 繰り返し → array.map(...)

⚠️ つまずきポイント 3段に分けて読む

JSX がコンパイル後に Card({ children: ... }) になる、という説明を見て「それも自分で書くの?」と混乱しました。次の3つは別物として読みましょう。

段 書き方 意味
定義する側 function Card() { ... } 部品の設計図を作る
使う側 <Card>...</Card> 部品を呼び出す(大文字タグ = 自作関数の呼び出し)
画面に出るもの Card の return の中身 実際に表示される HTML

コンパイル後の姿は「React の内部ではこうなっている」という裏話であり、自分で書くものではありません。


3. 書いてある場所と実行されるタイミングは別物

筆者が一度完全に詰まったポイントです。 コンポーネント関数の中には、性格の違う行が同居しています。

function App() {
  const [count, setCount] = useState(0);   // ★ 描画のたびに即実行

  function handleClick() {                 // ☆ 定義されるだけ(まだ動かない)
    setCount(count + 1);
  }

  return <button onClick={handleClick}>{count}</button>;  // ★ 即実行
}
  • ★ 即実行される行:フック呼び出し、変数の計算、return
  • ☆ 定義されるだけの行:function handleXxx() {} の中身。ボタンが押されるまで動かない

素の HTML + JavaScript で言えば、「<script> に直書きした alert() はページを開いた瞬間に走る/function の中身は onclick などで呼ばれるまで走らない」のと同じです。

onClick={fn} と onClick={fn()} の違い

  • onClick={handleClick} → 「押されたらこの関数を呼んでね」と 関数そのものを渡す(正しい)
  • onClick={handleClick()} → 描画した瞬間に実行して、その戻り値を渡してしまう(バグ)
  • 引数を渡したいときは onClick={() => handleDelete(id)} のように、関数で包みます。

4. props と state

props = 関数の引数

  • 親から子へ渡すデータです。JavaScript の関数に引数を渡すのと同じ感覚でOK。
  • 実体は オブジェクト1個。<Card title="A" size={3} /> は Card({ title: "A", size: 3 }) のイメージです。
  • 子は props を 読むだけ。書き換えてはいけません。
type Props = { title: string };

function Card({ title }: Props) {
  return <h2>{title}</h2>;
}

state = コンポーネントが覚えておく値

const [count, setCount] = useState(0);
  • count:今の値
  • setCount:値を変える関数(これを呼ぶと再描画が起きる)
  • 普通の変数 let count = 0 ではダメな理由:関数が呼ばれるたびに 0 に戻ってしまうし、変えても React が気付かないからです。

5. スナップショット

state は「その回の描画時点の値」

  • ある描画の中で、count は ずっと同じ値 です。
  • setCount(count + 1) を呼んでも、その場で count が変わるわけではありません。次の描画で新しい値になります。
function handleClick() {
  setCount(count + 1);
  console.log(count); // ← まだ古い値が出る
}

⚠️ つまずきポイント setter の直後に読むと古い

setXxx や dispatch を呼んだ直後に state を読むと古い値です。何度でも引っかかるので、「写真は撮り直さない限り変わらない」と覚えておきましょう。

更新関数形式

setCount(c => c + 1);
setCount(c => c + 1); // これなら +2 になる
  • 前の値をもとに計算したいときは、値ではなく 関数 を渡します。
  • setCount(count + 1) を2回書いても、両方とも同じ値の count を見ているので +1 にしかなりません。

6. 不変更新

配列やオブジェクトは「書き換えずに作り直す」

  • React は state が変わったかを Object.is(参照比較) で判定します。
  • push などで中身を直接いじると、「箱(参照)が同じ」なので 変わっていないと判断され、画面が更新されません。
// ❌ NG:同じ配列を書き換えている
memos.push(newMemo);
setMemos(memos);

// ✅ OK:新しい配列を作って渡す
setMemos([...memos, newMemo]);

よく使う非破壊的な書き方

やりたいこと 書き方
追加 [...list, item]
削除 list.filter(x => x.id !== id)
1件だけ更新 list.map(x => x.id === id ? { ...x, done: !x.done } : x)
オブジェクトの一部を変更 { ...obj, name: "新しい名前" }
  • 破壊的メソッド(元を書き換える):push pop splice sort reverse
  • 非破壊的メソッド(新しいものを返す):map filter concat slice、スプレッド ...

参照比較は重くない

  • 「大きなオブジェクトを比較すると重いのでは?」と思いがちですが、Object.is は 中身を見ずに住所(参照)が同じかだけ を見るので一瞬です。

7. 派生値と state を減らす考え方

  • 他の state から計算できる値は、state にせずその場で計算 します。
const [memos, setMemos] = useState<Memo[]>([]);
const doneCount = memos.filter(m => m.done).length; // ← state にしない
  • state を増やすと「両方を同期させ忘れる」バグの温床になります。
  • ただし派生値にできるのは 何度呼んでも同じ答えを返す純粋な計算 だけです。URL.createObjectURL のように呼ぶたびに新しいものを「発行」する処理(副作用)は、派生値にできません。

8. 単方向データフローと state の持ち上げ

データは上から下へだけ流れる

  • 親 → 子へ props でデータを渡す。子から親へ直接データを戻す手段はありません。
  • 子が親の state を変えたいときは、親から「変える関数」を props で受け取って呼ぶ(コールバック props)。
// 親
<MemoItem memo={memo} onDelete={handleDelete} />

// 子
<button onClick={() => onDelete(memo.id)}>削除</button>

state の持ち上げ

  • 兄弟コンポーネント同士で同じデータを使いたいときは、state を 共通の親まで引き上げる のが基本です。
  • ただしコストもあります:props の数が増える、型が複雑になる(例:boolean だったものが「どれを編集中か」を表す number | null になる)。
  • 逆に、1つの子しか使わない state は 子へ押し下げる 方が見通しが良くなります。

9. イベント処理

合成イベント

  • React は画面の各要素にリスナーを付けるのではなく、ルートに1個だけ リスナーを付けて、まとめて振り分けています。
  • 受け取るイベントは React が包んだ「合成イベント」です。

TypeScript でのイベントの型

function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  setText(e.target.value);
}
  • <> の中の HTMLInputElement は「どの要素のイベントか」を示す型引数です。

target と currentTarget

  • target:実際にクリックされた要素(子要素の場合もある)
  • currentTarget:リスナーを付けた要素(ハンドラを書いた要素)

10. 制御コンポーネントと非制御コンポーネント

制御コンポーネント

<input value={text} onChange={e => setText(e.target.value)} />
  • 入力欄の値を React の state が握る 方式。React の基本です。
  • value を握るなら onChange を書く義務がセット。書かないと入力できない欄になります。

非制御コンポーネント

  • 値は DOM 自身に持たせておき、必要なときに ref で読みに行く方式。

file input は制御できない

  • <input type="file"> は セキュリティ上の理由で value を代入できません(勝手にファイルを選ばせないため)。
  • 例外として "" でリセットすることだけは許されています。

11. フックの基本ルール

  • use で始まる関数(useState useEffect useRef など)がフックです。
  • ルール1:コンポーネントのトップレベルで呼ぶ(if や for の中で呼ばない)
  • ルール2:React のコンポーネントかカスタムフックの中でだけ呼ぶ

なぜこのルールがあるのか

  • React はフックを 名前ではなく「呼ばれた順番」で管理 しています(1番目の useState、2番目の useState…)。
  • if の中で呼ぶと描画ごとに順番がずれて、別の state と取り違えてしまいます。

12. useEffect とクリーンアップ

useEffect は「React の外の世界とつなぐ」ためのもの

useEffect(() => {
  const url = URL.createObjectURL(file);
  setPreview(url);

  return () => URL.revokeObjectURL(url); // クリーンアップ
}, [file]);
  • 第2引数(依存配列)の値が変わったときに実行されます。[] なら初回だけ。
  • 「繋いだら切る」:タイマー、イベント登録、URL の発行など、始めたものは return で返す関数(クリーンアップ)で後始末します。

不要な effect を書かない

  • 「state から計算できる値を effect で別 state にコピーする」のはアンチパターンです。→ 派生値にしましょう(7章)。
  • 「ボタンが押されたら〜する」は effect ではなく イベントハンドラ に書きます。
  • effect は最後の手段、くらいに考えるのがちょうどいいです。

⚠️ つまずきポイント [] の effect は初回の値に閉じ込められる

  • 依存配列 [] の effect の中から state を読むと、ずっと初回の値 が見えます。
  • 最新値を読みたいなら、state ではなく ref を使います(次章)。

13. useRef の2つの顔

顔その1 値の箱

const timerRef = useRef<number | null>(null);
timerRef.current = setTimeout(...);
  • .current に何でも入れておける箱です。
  • ref の性質は「変わらない」ではなく、「変わっても React に知らせない」(再描画が起きない)。

顔その2 DOM への参照

const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} />
inputRef.current?.focus();
  • 実際の DOM 要素をつかまえられます。フォーカスや canvas 操作など、React だけではできないことに使います。
  • 通常は React → DOM の一方向ですが、ref は DOM → React への逆流 であり、単方向データフローの唯一の例外です。だからこそ使いどころは限定しましょう。

14. state にするか しないかの判断

迷ったら次の順で考えます。

  1. 他の値から計算できる? → 派生値(state にしない)
  2. 親から props で届く? → state にしない
  3. ずっと変わらない? → 普通の定数
  4. 変わったとき、画面の見た目が変わる?
    • 変わる → useState
    • 変わらない → useRef(例:画面外の canvas、タイマー ID、前回の値)

「静かに間違う」バグに注意

  • ref の後始末(ref.current = null)を忘れても、エラーは出ません。前回の値が出る など、静かに間違った値を返します。React のバグはこのパターンが多いです。

15. メモ化とカスタムフック

useMemo / useCallback / React.memo

  • 描画のたびに再計算・再生成しないよう、結果をとっておく仕組みです。
  • 「比較する人がいるときだけ使う」 が原則。React.memo で包んだ子に渡す関数、useEffect の依存配列に入る関数などです。
  • 最初は使わなくて大丈夫です。遅くなってから考えましょう。

カスタムフック

  • useXxx という名前で、フックを組み合わせた処理を関数に切り出したもの。
  • 「ロジックの再利用」のための仕組みで、state そのものが共有されるわけではありません(呼んだコンポーネントごとに別の state)。

16. key の本当の役割

役割その1 リストの名札

{memos.map(memo => <MemoItem key={memo.id} memo={memo} />)}
  • map で並べるときは key が必須。React が「どれがどれか」を見分けるための名札です。
  • 配列のインデックスを key にすると、並び替えや削除で取り違えが起きます。一意で変わらない ID を使いましょう。

役割その2 別物として扱う = state のリセット

  • key が変わると、React は「別の部品になった」と判断して、中の state を捨てて作り直します。
  • 「表示する対象が変わったら入力欄をリセットしたい」といった場面で、effect を書かずに key で解決できます。

17. レンダリングの流れ

4つの段階

  1. Trigger:state が更新される(setter が呼ばれる)
  2. Render:React がコンポーネント関数を呼び直し、新しい画面の設計図を作る
  3. Commit:前回との 差分だけ を実際の DOM に反映
  4. Paint:ブラウザが画面を描く

よくある誤解

  • 再レンダリング ≠ ちらつき。 Render は「関数を呼び直すこと」で、実際の DOM は差分しか書き換わりません。innerHTML で全部書き換える素の JS とは違います。
  • バッチ処理:1つのイベント内で複数の setter を呼んでも、再描画はまとめて1回です。
  • bail out:新しい値が前と同じ(Object.is で一致)なら、描画をスキップします。
  • 再描画は下方向にだけ伝わる:親が再描画されると子も再描画されますが、親や兄弟には伝わりません。

18. children とコンポジション

children = タグの内側に書いた props

function Card({ children }: { children: React.ReactNode }) {
  return <div className="card">{children}</div>;
}

<Card>
  <p>ここが children になる</p>
</Card>
  • children という名前は React が決めた固定の名前 です。
  • 型は React.ReactNode(JSX、文字列、数値など何でも入る)。

コンポジション

  • 「材料(データ)を渡して子に組み立てさせる」のではなく、「完成品(JSX)を渡す」 考え方。
  • 途中のコンポーネントが使わない props を中継する「バケツリレー」を減らせます。

19. useReducer

state の更新ロジックを1か所にまとめる

type Action =
  | { type: "add"; text: string }
  | { type: "delete"; id: number };

function reducer(state: Memo[], action: Action): Memo[] {
  switch (action.type) {
    case "add":    return [...state, { id: Date.now(), text: action.text, done: false }];
    case "delete": return state.filter(m => m.id !== action.id);
  }
}

const [memos, dispatch] = useReducer(reducer, []);
dispatch({ type: "delete", id: 3 });
  • dispatch で「何が起きたか(Action)」を伝え、reducer が「次の state」を計算します。
  • 更新の種類が増えてきたら useState から乗り換えを検討します。
  • Action の型は ユニオン型(| でつないだ型)で書くのが定番です。
  • dispatch の直後に state を読むと古い値なのは setXxx と同じ(スナップショット)。

20. Context は後回しでいい

  • 深い階層まで props を渡さずにデータを共有する仕組みです。
  • 正直、「使わない方が分かりやすい」と感じました。それで正解です。
  • 実務でも props → children(コンポジション)→ Context の順で検討するのが定石。必要になってから学べば十分です。

21. コンポーネント設計と作る順番

設計の5ステップ

  1. 静的な JSX を先に書く(state なし、見た目だけ)
  2. 変わるものを洗い出す
  3. state を最小限にする(派生値にできるものは除く)
  4. state の置き場所を決める(使うコンポーネントの共通の親)
  5. イベントハンドラを書く(逆方向のデータフロー)

分割の判断基準

  • 1つのコンポーネントが複数の役割を持ち始めたら分割を検討。
  • リストの1行(MemoItem など)は切り出し候補の筆頭。
  • 命名慣習:イベントを受け取る props は onXxx、それを処理する関数は handleXxx。

⚠️ つまずきポイント 完成品を一度にもらうと壊れる

  • 完成したコードを一気に受け取ったら、構造が理解できずに壊してしまいました。
  • Step 0 から1段ずつ積み上げる(毎段必ず動く状態にする)方式に変えたら劇的に理解が進みました。
    • 静的JSX → map/key → useState → 追加 → 削除 → 完了チェック → 行コンポーネント切り出し → 編集 → state の持ち上げ
  • 画面が変わらない「純粋なリファクタリング」の段を挟むのもポイントです。

22. TypeScript 型の世界と値の世界

React + TypeScript のソースで頭がバグる最大の理由がここです。

同じ記号が2つの世界で使い回されている

type Props = { onDelete: (id: number) => void };  // 型の世界
const onDelete = (id: number) => { ... };          // 値の世界
  • どちらにも = {} => が出てきますが、意味が全く違います。
  • 上の onDelete: (id: number) => void を「number 型の引数」と誤読しましたが、正しくは 「number を受け取って何も返さない関数」という型 です。

型の世界への入口は4つだけ

入口 例
① : の右 const n: number = 1
② type / interface で始まる行まるごと type Memo = { id: number }
③ <> の中 useState<Memo[]>([])
④ as の右 value as string
  • この入口の中に入ったら、{} = | => は 全部型の記号 に化けます。
  • type X = {...} の = は代入ではありません。オブジェクトは作られません。

見分けのコツ

  • : の右は型、= の右は値(ただし type の行は丸ごと型)。
  • 型の中に => があれば、それは関数の型。
  • => の右が 型名(void string など)なら型、処理 が書いてあれば実コード。
  • 関数の型 = 関数から 名前と中身を取り去った残り。
  • 型は ビルドすると消える。実行時には存在しません。

import type

import type { Memo } from "./types";
  • 「型の世界だけで使うもの」を取り込む書き方。ビルド後には消えます。

React でよく見る型

用途 型
state の型を明示 useState<Memo[]>([])(空配列だと中身の型が推論できないため)
children React.ReactNode
input の変更イベント React.ChangeEvent<HTMLInputElement>
何も返さない関数 () => void
未選択を表す number | null

23. JSX のお作法集

書き方 意味・注意
style={{ color: "red" }} 外側の {} は「JS を書く」、内側の {} はオブジェクト。二重カッコになる
disabled={!photo} 属性に式を渡せる。photo がなければ無効化
className={undefined} undefined は「この属性を設定しない」扱い
{a || b || ""} 左から順に最初の有効な値を使う
{isOpen && <Modal />} 条件付き表示。ただし左辺が 0 だと 0 が表示されるので注意
<>...</> フラグメント。余計な div を増やさずにまとめる
aria-label="閉じる" 画面読み上げ向けのラベル。アイコンだけのボタンに付ける
<dl><dt><dd> 「項目名:値」のペアを表す HTML 要素
visually-hidden クラス 見た目は隠すが読み上げには残すテクニック

24. 学習の進め方のコツ

  • 1回に1トピック、少量ずつ。 一気に進めると消化不良になります。
  • 「なぜ」まで踏み込む。 「フックは呼び出し順で管理される」「変化は参照比較で検知する」など、内部の仕組みを知ると作法に納得できます。
  • わざと壊してみる。 「この行を消すとどう壊れるか」を試すと理解が深まります。React のバグは「エラーが出ずに静かに間違う」ことが多いので、なおさら有効です。
  • 作る前に読む。 完成した小さなアプリのソースを1本、全体マップ → 1テーマずつ、と読み解く方法も非常に効果的でした。
  • 既に知っているものに対応づける。 RPA や素の HTML / JavaScript の知識は無駄になりません(付録A)。
  • 小さなアプリを自分で作る。 メモ帳アプリ(追加・削除・完了・編集)は、React の基本がほぼ全部詰まっていておすすめです。Vite で npm create vite@latest から始められます。

付録A. 素の HTML JavaScript との対応表

素の HTML / JavaScript React
<body> に書いた HTML コンポーネントの return の中の JSX
<script> 内のイベント用 function イベントハンドラ(handleXxx)
<script> に直書きした alert()(すぐ走る) フック呼び出しや return(描画のたびに走る)
function の中身(呼ばれるまで走らない) function handleXxx() {} の中身
関数の引数 props(オブジェクト1個)
innerHTML の書き換え state の更新(画面は React が書き換える)
getElementById で要素をつかむ useRef(ただし最後の手段)
onclick="Foo()" onClick={foo}(文字列ではなく関数を渡す)

付録B. 用語集

用語 ひとことで
コンポーネント 画面の部品を返す関数(大文字始まり)
JSX JS の中に HTML 風に書ける記法。実体は関数呼び出し
props 親から子へ渡す引数。読み取り専用
state コンポーネントが覚えておく値。変えると再描画
スナップショット ある描画時点で固定された state の値
不変更新 元を書き換えず、新しい配列・オブジェクトを作って渡すこと
派生値 他の state から計算で求める値。state にしない
フック use で始まる React の機能関数
副作用 描画以外の外部への影響(通信・タイマー・URL 発行など)
クリーンアップ effect で始めたものを後始末する関数
ref 変わっても React に知らせない箱/DOM への参照
key リストの名札。変えると別物扱いで state リセット
バッチ処理 複数の state 更新をまとめて1回の再描画にすること
コンポジション 完成品の JSX を children などで渡す設計
reducer 今の state と Action から次の state を計算する関数
ユニオン型 A | B のように「どれか」を表す型

さいごに

いかがでしたでしょうか。
最初のうちは、出来上がったソースから読み始めると正直わかりにくいです。
1つのAppファイルで、HTMLをハードコードしたモックから少しずつコンポーネントを切り出していくと個人的には理解しやすかったです。
読んでも?が多くつく場合、付録Aから読んでもらうとよいかもしれません。
以上 最後までお読みいただきありがとうございます_(..)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?