JSXを使わないReactチュートリアル
JSXを使わないReact公式チュートリアル
よくJSXが嫌いという人をTwitterなどで見かけますが、JSXがないとどうなるか実際にReact公式チュートリアルを用いて体験してみました。
※ 筆者は特にJSXが嫌いというわけではなく、むしろシンタックスシュガーとしての素晴らしい拡張だと思っています。
create-react-app
はbabelによって、jsでもjsxが使えるように勝手にトランスコンパイルしてくれるので、これは使わずにbabelなしでやっていきます。
環境構築
## package.jsonの作成
$ npm init -y
## webpackのインストール
$ npm install -D webpack webpack-cli webpack-dev-server @webpack-cli/generators
$ npx webpack-cli init
? Which of the following JS solutions do you want to use? none
? Do you want to use webpack-dev-server? Yes
? Do you want to simplify the creation of HTML files for your bundle? Yes
? Do you want to add PWA support? No
? Which of the following CSS solutions do you want to use? CSS only
? Will you be using PostCSS in your project? No
? Do you want to extract CSS for every file? Yes
? Do you like to install prettier to format generated configuration? Yes
? Pick a package manager: npm
## reactのインストール
$ npm install react react-dom
webpack serveを起動すると
$ npm run serve
index.htmlに初期で書かれている Hello world! が表示されていることを確認する。
作成した環境でJSXを使ってみる(使えないことの確認)
index.jsを以下に書き換えて、JSXが使えないことを確認する。
import { ReactDOM } from "react"
const Sample = () => {
return (
<div>
<h1>Hello world!</h1>
<h2>Tip: Check your console</h2>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<Sample />)
JSXが使えないことを確認
Compiled with problems:X
ERROR in ./src/index.js 5:4
Module parse failed: Unexpected token (5:4)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| const Sample = () => {
| return (
> <>
| <h1>Hello world!</h1>
| <h2>Tip: Check your console</h2>
ちなみに index.js
から index.jsx
に書き換えて、webpack.config.jsのエントリーポイントを index.jsx
にしてもエラーになります。(コンパイルしてないので、、、)
JSXを使わずに表示してみる
index.js
を以下に書き換える。
JSXを使わない場合は React.createElement(component, props, …children)
を呼び出す必要があります。JSXの要素はこれのシンタックスシュガーなのでJSX使わずにReactでアプリ開発することは可能です。
というわけで、Fragmentを先頭に、childrenに定義したDOMを詰めていきます。
import React from "react"
import ReactDOM from 'react-dom/client'
const Sample = () => {
return React.createElement(
React.Fragment, null,
React.createElement("h1", null,
"Hello world!"),
React.createElement("h2", null,
"Tip: Check your console"));
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(React.createElement("div", null, Sample()))
すでに苦しいのがわかります。。。(もうJSXが嫌いではなくなりましたか?)
ここまでで事前準備とJSXを使わずにReactによるアプリケーションの表示ができるようになったので、Reactチュートリアルをやっていきましょう!
JSXを使わないReactチュートリアル
スターターコードです。
import React from "react"
import ReactDOM from 'react-dom/client'
import "../index.css"
const Square = () => {
return React.createElement("button", {
className: "square"
});
}
const Board = () => {
const renderSquare = (i) => {
return React.createElement(Square, null);
}
const status = 'Next player: X';
return React.createElement("div", null, React.createElement("div", {
className: "status"
}, status), React.createElement("div", {
className: "board-row"
}, renderSquare(0), renderSquare(1), renderSquare(2)), React.createElement("div", {
className: "board-row"
}, renderSquare(3), renderSquare(4), renderSquare(5)), React.createElement("div", {
className: "board-row"
}, renderSquare(6), renderSquare(7), renderSquare(8)));
}
const Game = () => {
return React.createElement("div", {
className: "game"
}, React.createElement("div", {
className: "game-board"
}, React.createElement(Board, null)), React.createElement("div", {
className: "game-info"
}, React.createElement("div", null), React.createElement("ol", null)));
}
// ========================================
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( React.createElement(Game, null));
あれ、意外といける?と思いましたが 全く階層がわからない笑
数字表示
propsを渡して、数字を表示します。
-const Square = () => {
+const Square = (props) => {
return React.createElement("button", {
className: "square"
- });
+ }, props.value);
}
const Board = () => {
const renderSquare = (i) => {
- return React.createElement(Square, null);
+ return React.createElement(Square, { value: i });
}
const status = 'Next player: X';
onClickイベントでXを表示させる
-import React from "react"
+import React, { useState } from "react"
import ReactDOM from 'react-dom/client'
import "../index.css"
-const Square = (props) => {
+const Square = () => {
+ const [value, setValue] = useState(null);
+
return React.createElement("button", {
- className: "square"
- }, props.value);
+ className: "square",
+ onClick: () => setValue('X'),
+ }, value);
}
const Board = () => {
const renderSquare = (i) => {
propsにonClickイベントが入ってくるようになって、さらに見づらくなってきました。。。
Stateのリフトアップ
SquareコンポーネントからBoardコンポーネントへState情報を移し、親でStateを管理させるようにします。
-const Square = () => {
- const [value, setValue] = useState(null);
-
+const Square = (props) => {
return React.createElement("button", {
className: "square",
- onClick: () => setValue('X'),
- }, value);
+ onClick: () => props.onClick()
+ }, props.value);
}
const Board = () => {
+ const [squares, setSquares] = useState(Array(9).fill(null))
+
+ const handleClick = (i) => {
+ const newSquares = squares.slice();
+ newSquares[i] = "X";
+ setSquares(newSquares)
+ }
+
const renderSquare = (i) => {
- return React.createElement(Square, { value: i });
+ return React.createElement(Square, {
+ value: squares[i],
+ onClick: () => handleClick(i)
+ });
}
const status = 'Next player: X';
ここはDOM に関わる部分の変更がほぼないので、そんなに影響はないですね。
ゲームの勝敗判定
const Board = () => {
const [squares, setSquares] = useState(Array(9).fill(null))
+ const [xIsNext, setXIsNext] = useState(true)
const handleClick = (i) => {
const newSquares = squares.slice();
- newSquares[i] = "X";
+ if (calculateWinner(newSquares) || newSquares[i]) {
+ return;
+ }
+ newSquares[i] = xIsNext ? "X" : "O"
setSquares(newSquares)
+ setXIsNext(!xIsNext)
}
- const status = 'Next player: X';
+ const winner = calculateWinner(squares);
+ let status;
+ if (winner) {
+ status = 'Winner: ' + winner;
+ } else {
+ status = 'Next player: ' + (xIsNext ? 'X' : 'O');
+ }
+
return React.createElement("div", null, React.createElement("div", {
className: "status"
}, status), React.createElement("div", {
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( React.createElement(Game, null));
+
+
+const calculateWinner = (squares) => {
+ const lines = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8],
+ [0, 3, 6],
+ [1, 4, 7],
+ [2, 5, 8],
+ [0, 4, 8],
+ [2, 4, 6],
+ ];
+ for (let i = 0; i < lines.length; i++) {
+ const [a, b, c] = lines[i];
+ if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
+ return squares[a];
+ }
+ }
+ return null;
+}
タイムトラベル機能(履歴表示)
ここからは、手順を戻すためのタイムトラベル機能を作成していきます。
Stateのリフトアップ等もまとめてやってしまいます。
diff --git a/src/index.js b/src/index.js
index 56cf2ab..b6c8f75 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,35 +8,14 @@ const Square = (props) => {
onClick: () => props.onClick()
}, props.value);
}
-const Board = () => {
- const [squares, setSquares] = useState(Array(9).fill(null))
- const [xIsNext, setXIsNext] = useState(true)
-
- const handleClick = (i) => {
- const newSquares = squares.slice();
- if (calculateWinner(newSquares) || newSquares[i]) {
- return;
- }
- newSquares[i] = xIsNext ? "X" : "O"
- setSquares(newSquares)
- setXIsNext(!xIsNext)
- }
-
+const Board = (props) => {
const renderSquare = (i) => {
return React.createElement(Square, {
- value: squares[i],
- onClick: () => handleClick(i)
+ value: props.squares[i],
+ onClick: () => props.onClick(i)
});
}
- const winner = calculateWinner(squares);
- let status;
- if (winner) {
- status = 'Winner: ' + winner;
- } else {
- status = 'Next player: ' + (xIsNext ? 'X' : 'O');
- }
-
return React.createElement("div", null, React.createElement("div", {
className: "status"
}, status), React.createElement("div", {
@@ -49,13 +28,57 @@ const Board = () => {
}
const Game = () => {
+ const [history, setHistory] = useState([{ squares: Array(9).fill(null) }]);
+ const [stepNumber, setStepNumber] = useState(0);
+ const [xIsNext, setXIsNext] = useState(true);
+
+ const handleClick = (i) => {
+ const historySlice = history.slice(0, stepNumber + 1);
+ const current = historySlice[historySlice.length - 1];
+ const squares = current.squares.slice();
+ if (calculateWinner(squares) || squares[i]) {
+ return;
+ }
+ squares[i] = xIsNext ? 'X' : 'O';
+ setHistory(
+ historySlice.concat([
+ {
+ squares: squares,
+ },
+ ])
+ );
+ setStepNumber(historySlice.length);
+ setXIsNext(!xIsNext);
+ };
+
+ const current = history[history.length - 1];
+ const winner = calculateWinner(current.squares);
+ const moves = history.map((step, move) => {
+ const desc = move ? 'Go to move #' + move : 'Go to game start';
+ return React.createElement("li", {
+ key: move
+ },React.createElement("button", {
+ onClick: () => jumpTo(move)
+ }, desc));
+ });
+ let status;
+ if (winner) {
+ status = 'Winner: ' + winner;
+ } else {
+ status = 'Next player: ' + (xIsNext ? 'X' : 'O');
+ }
+
return React.createElement("div", {
className: "game"
}, React.createElement("div", {
className: "game-board"
- }, React.createElement(Board, null)), React.createElement("div", {
+ }, React.createElement(Board, {
+ squares: current.squares,
+ onClick: i => handleClick(i)
+ })), React.createElement("div", {
className: "game-info"
- }, React.createElement("div", null), React.createElement("ol", null)));
+ }, React.createElement("div", null, status),
+ React.createElement("ol", null, moves)));
}
statusやmovesのchildrenを渡す部分に時間がかかったり
- }, React.createElement("div", null), React.createElement("ol", null)));
+ }, React.createElement("div", null, status),
+ React.createElement("ol", null, moves)));
レンダリングのkeyに設定するpropsの渡し方や場所が見当たらず、とてもやりづらい。。。
return React.createElement("li", {
+ key: move
+ }
タイムトラベル機能(履歴ジャンプ)
最後に jumpTo
を実装して、 current
をstepNumberを見るようにしたら完成!
diff --git a/src/index.js b/src/index.js
index b6c8f75..3f72c7a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -51,7 +51,12 @@ const Game = () => {
setXIsNext(!xIsNext);
};
- const current = history[history.length - 1];
+ const jumpTo = (step) => {
+ setStepNumber(step);
+ setXIsNext(step % 2 === 0);
+ };
+
+ const current = history[stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ? 'Go to move #' + move : 'Go to game start
最後に
本記事ではJSXを使用せずにReact公式チュートリアルをやってきました。
JSX嫌いな人は calss
が className
になったりという理由だと思うのですが、JSXを使わない方が大変な場合があります。あくまでも React.createElement(component, props, …children)
のシンタックスシュガーなので、JSXのことが嫌いから普通になってれば幸いです。
例え嫌いでも、Reactは書けるのはわかったのでドンドンReactで開発しましょう!
(このやり方してたら、JSXじゃなくて 自分が嫌われそう。。。)
また、Babelで JSX → JS 試せるので遊んでみてはいかがでしょうか。
それでもJSXが嫌いな人へ
違うシンタックスでのReactのマークアップのライブラリもあります!!
参考
Pete Huntさんの素晴らしい発表を見て、JSXについての考えを改めましょう。
Discussion