10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「大きな後退」からデファクトスタンダードへ:React 12年の進化の歴史

Posted at

はじめに

こんにちは!フロントエンド開発の世界に飛び込んでもう数年、Reactの進化を間近で見てきた筆者です。

Reactは今やWebフロントエンド開発のデファクトスタンダードとして君臨していますが、そこに至るまでには驚くべき歴史がありました。

「テンプレートエンジンを捨てて、JavaScriptでビューを構築する?」
「仮想DOMって何?ただの無駄じゃないの?」

2013年のJSConf USで初めてReactが発表されたとき、参加者のほとんどが「これは大きな後退だ」と感じたそうです。

それから12年。Reactは週間2,000万ダウンロードを超え、130万以上のWebサイトで使用される巨大なエコシステムへと成長しました。この記事では、Reactがどのように誕生し、どんな困難を乗り越え、現在の地位を確立したのか、その歴史を丁寧に紐解いていきます。

Reactの歴史:時代ごとの変遷

第1期: Facebook内部での誕生(2010-2012年)

FaxJS:すべての始まり
Reactの物語は、2010年(または2011年)にFacebookのソフトウェアエンジニアJordan Walkeが作成した内部プロトタイプ「FaxJS」から始まります。

当時、Facebookの広告システムは複雑さを増し、データの更新が連鎖的に発生する問題に直面していました。

Jordan Walkeは、FacebookがPHP向けに開発したXHPというUIライブラリの概念に着想を得て、同様のアプローチをJavaScriptに適用できないかと考えました。

XHPからの影響
XHPは、PHPコード内にHTMLライクな構文を埋め込めるライブラリで、コンポーネントベースのUI構築を可能にしていました。

// XHPの例(PHPコード)
$user_profile = <ui:profile user={$user} />;

この考え方が、後のJSXの基礎となります。

Facebook News Feedへの導入(2011年)

FaxJSは最初、Facebookの広告システムで使用され、その後2011年にFacebook News Feedに導入されました。この時点では、まだ社内ツールに過ぎませんでした。

Instagramへの展開(2012年)

2012年、FacebookはInstagramを買収。
InstagramのWebバージョンにもFaxJS(後のReact)が採用されました。Pete Huntは、Instagram.comの唯一のエンジニアとして、この移植作業に関わっていました。この成功が、Reactをオープンソース化する決断につながります。

第2期: オープンソース化と衝撃のデビュー(2013年)

JSConf US 2013:世界へのお披露目

2013年5月29日〜31日、JSConf USでJordan WalkeがReactを発表し、オープンソース化を宣言しました。初期バージョンはReact 0.3.0でした。

しかし、この発表は期待とは真逆の反応を引き起こします。会場の反応は懐疑的でした。

「聴衆は懐疑的で、ほとんどの人がReactは大きな後退だと考えた」

なぜでしょうか?
当時のWeb開発の常識は:

  • HTMLとJavaScriptは分離すべき
  • テンプレートエンジンを使うべき
  • 関心の分離が重要

しかし、Reactは、これらすべてを否定するものでした。

// JSX - JavaScriptの中にHTMLが!?
function HelloMessage({ name }) {
  return <div>Hello {name}</div>;
}

「JavaScriptの中にHTMLを書くなんて!」と多くの開発者が拒否反応を示しました。

Pete Huntの弁明:JSConf EU 2013

JSConf US 2013の反応を受けて、Pete Huntは同年のJSConf EU 2013とJSConf Asia 2013で「React: Rethinking Best Practices(ベストプラクティスの再考)」という講演を行いました。

この講演では、以下の3つの論争的なトピックを扱いました:

  1. テンプレートを捨て、JavaScriptでビューを構築
  2. データが変更されたら、アプリ全体を再レンダリング
  3. 軽量なDOM&イベントシステムの実装

「2013年のJSConfでは、誰もがそれを嫌っていた。まさに大惨事だった」

しかし、この「大惨事」こそが、Reactの革命の始まりでした。

第3期: 成長期とエコシステムの形成(2014-2016年)

Flux Architectureの提唱(2014年)

2014年、FacebookはFlux Architectureを発表しました。

Action → Dispatcher → Store → View

これは、従来のMVCパターンとは異なる、単方向データフローのアーキテクチャでした。しかし、Facebookは完全なライブラリを提供せず、概念のみを発表したため、コミュニティは数十ものFlux実装を生み出しました。

Reduxの登場(2015年)

2015年中頃、Dan AbramovがReduxを開発しました。きっかけは、React Europe 2015での「Hot Reloading with Time Travel」という講演の準備中でした。

Reduxは、Fluxの複雑さを解消し、「予測可能な状態管理」を実現するために、以下の3つの基本原則を導入しました。

  1. 単一の信頼できる情報源(Single source of truth): アプリケーション全体のstateを、単一のストアと呼ばれるオブジェクトツリー内に保持します。
  2. Stateは読み取り専用(State is read-only): Stateを直接変更することはできず、何が起こったかを示すアクション(プレーンなオブジェクト)を送信することによってのみ変更が可能です。
  3. 変更は純粋関数で行う(Changes are made with pure functions): アクションに応じてstateがどのように変化するかを定義するために、リデューサーと呼ばれる純粋関数を作成します。

このシンプルな設計が、タイムトラベルデバッグのような強力な開発ツールを可能にしました。

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    default:
      return state;
  }
};

Reduxは瞬く間に他のFluxライブラリを駆逐し、2016年には「Reactを使うならReduxを使うべき」という風潮が生まれました。

React 15:安定性の向上(2016年)

React 15は、0.14で非推奨となった機能を削除し、安定性に焦点を当てたリリースでした。この頃から、Reactは「実験的なライブラリ」から「プロダクション向けライブラリ」へと認識が変わっていきます。

Create React App(2016年)

Facebookは、Reactアプリの構築を簡単にするために、公式CLIツール Create React App(CRA) をリリースしました。

# 複雑な設定なしでReactアプリを作成
npx create-react-app my-app
cd my-app
npm start

webpackやBabelなどの複雑な設定から開発者を解放し、Reactの普及を加速させました。

React Router
シングルページアプリケーション(SPA)の台頭に伴い、React Routerはルーティングのデファクトスタンダードになりました。

import { BrowserRouter, Route, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Route path="/" component={Home} />
      <Route path="/about" component={About} />
    </BrowserRouter>
  );
}

Gatsby(2017年〜)

Gatsbyは、Reactを使った静的サイトジェネレータとして登場しました。

// Gatsbyでブログを構築
export default function BlogPost({ data }) {
  const post = data.markdownRemark;
  return (
    <div>
      <h1>{post.frontmatter.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
    </div>
  );
}

SEO対応と高速なパフォーマンスで、多くのブログやマーケティングサイトで採用されました。

第4期: 大改革・Fiber(2017年)

React 16.0: Fiberアーキテクチャ完全な書き直し

2017年9月26日、React 16.0がリリースされました。これは、React 15以前の「Stack Reconciler」を完全に書き直したFiber Reconcilerによる大改革でした。

Stack Reconcilerの問題
旧来のStack Reconcilerは、再帰的で同期的な処理を行っていました。つまり、一度レンダリングを始めたら、完了するまでブラウザをブロックしてしまうという問題がありました。これでは、大規模なアプリケーションで数百ミリ秒もブラウザがフリーズする可能性がありました。

Fiberの革新
Fiber Reconcilerは、レンダリング作業を小さな単位(Fiber)に分割し、優先度付けと中断・再開を可能にしました。

Fragments
Fragmentsは、コンポーネントが複数の要素を返す際に、不要なDOMノードを追加せずに済む機能です。それまでのReactでは、コンポーネントは単一のルート要素しか返せなかったため、複数の要素を返す場合は、意味のない<div>などで囲む必要がありました。

Fragmentsを使うことで、この余分なラッパー要素をなくし、よりクリーンなHTMLを出力できます。<></>という短縮構文も用意されています。

// 不要なdivラッパーを排除
function App() {
  return (
    <>
      <Header />
      <Main />
      <Footer />
    </>
  );
}

Error Boundaries
Error Boundariesは、UIの一部で発生したJavaScriptエラーがアプリケーション全体をクラッシュさせるのを防ぐための仕組みです。これが導入される前は、コンポーネントツリーのどこか1つでエラーが発生すると、Reactはコンポーネントツリー全体をアンマウントしてしまい、画面が真っ白になるという問題がありました。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 次のレンダリングでフォールバックUIを表示するためにstateを更新します。
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // エラー情報をログサービスに送信するなどの副作用を実行できます。
    console.log('エラーをキャッチ:', error, info);
  }

  render() {
    if (this.state.hasError) {
      // フォールバック用のUIをレンダリングします。
      return <h1>問題が発生しました。</h1>;
    }
    return this.props.children;
  }
}

第5期: パラダイムシフト・Hooks(2019年)

React 16.8

React 16.8のリリースは、React開発の歴史における最大のパラダイムシフトでした。
React Hooksの導入です。

クラスコンポーネントの問題
それまで、状態管理やライフサイクルメソッドを使うには、クラスコンポーネントを書く必要がありました。

// 旧来のクラスコンポーネント
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    document.title = `クリック数: ${this.state.count}`;
  }

  componentDidUpdate() {
    document.title = `クリック数: ${this.state.count}`;
  }

  render() {
    return (
      <div>
        <p>クリック数: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          クリック
        </button>
      </div>
    );
  }
}

これには、いくつかの問題がありました:

  • 冗長なコード
  • ロジックの再利用が困難
  • thisの理解が必要
  • ライフサイクルメソッドの複雑さ

Hooksによる解決
Hooksは、これらのクラスコンポーネントが抱える問題を解決するために導入されました。

  • シンプルな記述: constructorsuper(props)renderメソッドといった定型的なコードが不要になり、より少ないコードでコンポーネントを記述できます。
  • thisからの解放: 関数コンポーネント内ではthisキーワードの挙動に悩まされることがなくなり、JavaScriptのクロージャの仕組みを素直に利用できます。
  • ロジックの再利用性向上: 関連するロジック(例えば、componentDidMountcomponentDidUpdateにまたがる副作用の処理)をuseEffectのような単一のAPIにまとめることができます。さらに、そのロジックを「カスタムフック」として抽出することで、コンポーネント間で簡単に再利用できるようになりました。

以下のコードは、前のクラスコンポーネントと全く同じ機能を持つコンポーネントをHooksで書き換えたものです。

// React Hooksによる関数コンポーネント
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `クリック数: ${count}`;
  }, [count]);

  return (
    <div>
      <p>クリック数: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        クリック
      </button>
    </div>
  );
}

Hooksの登場により、Reactのコード書き方は根本的に変わりました。

第6期: 安定化とモダン化(2020年~現在)

Next.jsの台頭

2020年3月10日、Next.js 9.3がリリースされ、静的サイト生成(SSG)とサーバーサイドレンダリング(SSR)の両方をサポートしました。

// Next.js - ファイルベースルーティング
// pages/index.js
export default function Home() {
  return <h1>Welcome to Next.js!</h1>;
}

// pages/about.js
export default function About() {
  return <h1>About Page</h1>;
}

Next.jsは、Reactアプリケーションのフルスタックフレームワークとして急速に普及し、Vercelのサポートを受けて成長を続けます。

React 18:並行レンダリングの実現(2022年)

2022年3月29日、React 18がリリースされました。

Automatic Batching(自動バッチ処理)

バッチ処理(Batching)とは、パフォーマンス向上のために、複数のstate更新を1回の再レンダリングにまとめるReactの仕組みです。

React 17以前では、このバッチ処理はReactのイベントハンドラ(onClickなど)内でのみ機能していました。setTimeoutやPromise、ネイティブのイベントハンドラ内でのstate更新はバッチ処理されず、更新のたびに再レンダリングが実行されてしまい、パフォーマンスの低下につながることがありました。

React 18では、自動バッチ処理が導入され、どこでstate更新が行われようとも、デフォルトでバッチ処理が適用されるようになりました。これにより、開発者が意識することなく、アプリケーションのパフォーマンスが向上します。

// React 18以前:個別に再レンダリング
setTimeout(() => {
  setCount(c => c + 1); // 再レンダリング1
  setFlag(f => !f);     // 再レンダリング2
}, 1000);

// React 18:自動的にバッチ化
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);     // 1回だけ再レンダリング
}, 1000);

useTransition
useTransitionは、緊急性の低いstate更新を「トランジション(遷移)」としてマークすることができます。例えば、検索入力に応じて重いリストをフィルタリングする場合、入力のたびにリスト全体を再描画するとUIが固まってしまうことがあります。useTransitionを使うと、リストのフィルタリングという重い処理を緊急性の低い更新としてマークし、ユーザーの入力(緊急性の高い更新)を妨げないようにすることができます。

// 優先度の低い更新を遅延
const [isPending, startTransition] = useTransition();

// ユーザー入力を妨げずに、検索結果の表示を更新する
startTransition(() => {
  setSearchQuery(input); // 優先度低
});

useDeferredValue
useDeferredValueは、値の更新を遅延させるためのフックです。useTransitionがstate更新のコードをラップするのに対し、useDeferredValueは値そのものをラップします。これにより、新しい値のレンダリングが完了するまで古い値を表示し続けることができ、UIの応答性を保ちます。

React 19:Compilerと Server Components(2024年)

2024年4月25日、React 19が正式リリースされました。
そして2025年10月1日、React 19.2がリリースされました。

React Compiler:自動最適化
React 19の最大の革新は、React Compilerの導入です。

これまでReactのパフォーマンスチューニングでは、開発者がuseMemouseCallbackReact.memoといったAPIを使い、手動で「メモ化(memoization)」を行う必要がありました。しかし、いつ、どこでこれらを使うべきかの判断は難しく、コードの可読性を損なう原因にもなっていました。

React Compilerは、ビルド時にコードを解析し、どの部分をメモ化すべきかを自動的に判断して最適化を行います。これにより、開発者は再レンダリングの最適化について深く悩むことなく、ビジネスロジックの実装に集中できます。

// 従来:手動での最適化が必要
const MemoizedComponent = React.memo(ExpensiveComponent);

const memoizedValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);
// React 19:Compilerが自動で最適化
function ExpensiveComponent({ a, b }) {
  const value = computeExpensiveValue(a, b);
  // Compilerが自動的に最適化してくれる!

  return <div>{value}</div>;
}

React Server Components(RSC)

React Server Componentsは、サーバー上で一度だけ実行され、レンダリング結果のみをクライアントに送信するコンポーネントです。

// Server Component(サーバーでのみ実行)
async function BlogPost({ id }) {
  // サーバー上で直接データベースにアクセス
  const post = await db.posts.find(id);

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

メリット:

  • クライアント側のJavaScriptバンドルサイズを削減
  • データベースやAPIに直接アクセス可能
  • セキュリティの向上(バックエンドロジックをクライアントに公開しない)

React 19では、Server Componentsが正式にサポートされ、Next.jsやTanStack Startなどのフレームワークでの採用が進んでいます。

Actions API
これまでは、フォームの送信を扱うにはonSubmitイベントハンドラでevent.preventDefault()を呼び出し、useStateで状態を管理し、手動でデータ送信を行うのが一般的でした。

React 19では、<form>要素のactionプロパティに直接関数を渡せるようになりました。この関数(アクション)は、フォームが送信されると自動的に実行されます。アクションは同期的にも非同期的にも定義できます。Reactがデータ送信のライフサイクルを管理してくれるため、アクションの実行中はuseFormStatusフックを使って「送信中」の状態をUIに反映させる(例:ボタンを無効化する)ことが簡単にできます。

さらに、'use server'ディレクティブを使うことで、サーバー上で実行される「サーバーアクション」を定義できます。これにより、クライアント側のコンポーネントから直接サーバーサイドの関数を呼び出すことが可能になり、フルスタックでのデータ更新が劇的に簡素化されます。

function CommentForm() {
  async function submitComment(formData) {
    'use server'; // Server Action
    await saveComment(formData);
  }

  return (
    <form action={submitComment}>
      <input name="comment" />
      <button type="submit">送信</button>
    </form>
  );
}

まとめ

2013年に「大きな後退」と酷評されてから12年、ReactはWeb開発のあり方を根本から変えました。

仮想DOM、コンポーネントベースアーキテクチャ、宣言的UIといった革新的な概念を導入し、巨大で複雑なUIを効率的に構築する道を開きました。

Hooksの登場は開発パラダイムを再び転換させ、そして今、CompilerとServer Componentsによって、Reactはパフォーマンスと開発者体験の新たな地平を切り開こうとしています。

Metaという一企業の手からReact Foundationへと移管される未来は、Reactが単なるライブラリではなく、Web全体の公共財として成熟したことを象徴しています。Reactがもたらした最大の変革は、以下の3つの考え方をWeb開発の標準にしたことでしょう。

Reactがもたらした変革

  • コンポーネント思考の普及
  • 宣言的UI
  • 単方向データフロー

参考資料

10
5
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
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?