"Learning React" を読んだAlex Banks & Eve Porcello の “Learning React” Second Edition。ざっと読んでみたが、この本は正直微妙だった。というのも、React の公式ドキュメントを読めば十分だし、そちらの方が説明の漏れもない。最近のメジャーなプログラミング技術は、ウェブ上の公式ドキュメントが簡潔・正確・包括的で、それで十分ということが普通だけれど、それはそれとして良書は良書としてよいものだと思っている。しかし “Learning React” はそれでも評価しづらい。“Learning React” という本のポイントとして、React Hooks を全面的に利用していて、クラスコンポーネントは非推奨としてまったく使わなくなっていることが挙げられる。一方で、公式ドキュメントは残念ながら、基本的にクラスコンポーネントを使った記法が残っている。その点で、Hooks を使った最新の書き方だけを学びたければ “Learning React” がよい、はずなんだけど、逆を言えばそれくらいしかこの本のメリットがない。それ以外は、たとえばコンポーネントのリストを生成するときに key 属性を付与すべき理由とか、説明が一部足りていないので、そもそも良書と言いづらい。“Learning React” 独自に得られる話があるかというと、もちろんないわけでもないけれど、少ない。第2章で、ES6 以降の JavaScript の主な機能を紹介しているが、こういうのは JS 本で学ぶべき話。第3章で、関数敵プログラミングについて書かれているが、これも同様。第4章と第5章の React の基本的な話は、公式ドキュメントにもほぼ書かれているような内容で、深みもない。第6章と第7章のステート管理と Hooks の話は重要だが、公式ドキュメントも Hooks だけで複数のページが割かれていて、ここと次の第8章だけは公式ドキュメントより説明が豊富で、意味がある。ただ、説明のクオリティはそんなに高くない感じがする。第8章は API から取得したデータを実際に扱う話で、React そのものと直接関係はないが React を使う上で一緒に活用されることが多い話なので、たしかに1章を使うのはありがたい。第9章は React のアドバンスドな機能いくつかを紹介しているが、これは公式ドキュメントでもよい。第10章のテストの説明も公式ドキュメントでもまあいいかなという程度で、ESLint, Prettier の紹介とその設定ファイルの書き方の説明があるのはよい。第11章の React Router、第12章の React and the Server は公式ドキュメントで省かれている部分なので、よい。そんな感じで、ところどころに価値がある箇所もあるけれど、少ない。いや、別に公式ドキュメントがあるからといっても、それを超えていなくても同レベルの内容であればよかったんだけど、公式ドキュメントに劣っているのがよくない。以下、読書メモ。1 Welcome to ReactReact チームの Pete Hunt が “Why React?” という記事を書いて、React への批判へのアンサーとした、とある。この “Why React?” という記事はどうやら今は消えているが、かつては React 公式ドキュメントの中にあった。React で開発するときは React Developer Tools をインストールして使うことを薦めている。React コンポーネントツリーを調べたり、props/state を見たりすることができる。React を使う場合、Node.js をインストールする必要がある。バージョンは 8.6.2 以上であることが望ましい。インストール方法は Node.js ウェブサイト に行けば分かる。Node.js をインストールすれば、Node.js のパッケージマネージャである npm もインストールされる。ただし、npm の代替として Yarn もある。結局 npm を使うべきか Yarn を使うべきか分からない。今でも Yarn の方が速いらしいが、npm の速度もかなり改善されたらしいし、package-lock.json ファイルも追加されたし、別に npm でもよさそうに思える。2 JavaScript for React各ブラウザの JavaScript サポート状況は kangax 互換性テーブル が参考になる。この章では、ES6 以降の機能の中で、本書で使われるものを紹介する。詳しくは David Flanagan の “JavaScript: The Definitive Guide (7th Edition)” などを参照。何気なく DOM の知識というか HTMLElement クラスの知識が必要。2.1 変数を宣言するconst キーワードlet キーワードテンプレート文字列 ${}2.2 関数を作成するデフォルトパラメータアロー関数 (...) => { ... }2.3 JavaScript をコンパイルするBabel のような JavaScript コンパイルツールを使って、最新の JavaScript 機能を含んだコードを古いブラウザでも対応したコードにコンパイルできる。JavaScript コンパイル処理は、webpack や Parcel のようなビルドツールを使って自動化される。詳しくは後述される。2.4 オブジェクトと配列分割代入 { bread, meat } = sandwichオブジェクトリテラル拡張 { name, elevation }スプレッド演算子 ...2.5 非同期 JavaScriptfetch(url: string): Promise<Response> API。async/await 関数。Promise オブジェクト。2.6 クラスclass Vacation { constrcutor(destination, length) { this.destination = destination; this.length = length; } print() { console.log(`${this.destination} will take ${this.length} days`); } } const trip = new Vacation("Santiago, Chile", 7); trip.print(); 2.7 ES6 モジュールtext-helpers.js:export const print=(message) => log(message, new Date()) export const log=(message, timestamp) => console.log(`${timestamp.toString()}: ${message}`) mt-freel.js:export default new Expedition("Mt. Freel", 2, ["water", "snack"]); import { print, log } from "./text-helpers"; import freel from "./mt-freel"; print("printing a message"); log("logging a message"); freel.print(); CommonJSNode.js は CommonJS モジュールパターンもサポートしている。txt-helpers.js:const print=(message) => log(message, new Date()) const log=(message, timestamp) => console.log(`${timestamp.toString()}: ${message}`) module.exports = {print, log} const { print, log } = require("./txt-helpers"); 3 Functional Programming with JavaScript3.1 関数的が意味すること3.2 命令的 vs 宣言的3.3 関数的な概念不可変純粋関数データ変換高階関数再帰合成4 How React Works4.1 ページセットアップブラウザで React を動かすためには、2つのライブラリ React と ReactDOM が必要になる。React はビューを作成するライブラリで、ReactDOM は実際に UI をブラウザにレンダリングするライブラリ。両方とも unpkg CDN から取得できる。React が動く HTML ページ:<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>React Samples</title> </head> <body> <div id="root"></div> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script> </script> </body> </html> 本にはないが、CDN Links - React によれば、crossorigin を付けることをすすめている。4.2 React 要素SPA では DOM API を使って UI である HTML 要素を動的に作る。React では React.createElement を使って React 要素を作ることができる。React.createElement("h1", { id: "recipe-0" }, "Baked Salmon"); 4.3 ReactDOMReactDOM の render メソッドを使って、React 要素をレンダリングすることができる。const dish = React.createElement("h1", { id: "recipe-0" }, "Baked Salmon"); ReactDOM.render(dish, document.getElementById("root")); 4.4 React コンポーネントUI の作成を再利用可能・カプセル化した形にしたものが React コンポーネント。これは単に、React 要素を返す1引数をとる関数または render() メソッドを実装したクラス。// 関数コンポーネント。 function IngredientList(props) { return React.createElement("ul", { className: "ingrediens" }, props.items.map((ingredient, i) => React.createElement("li", { key: i }, ingredient))); } // クラスコンポーネント。 class IngredientList extends React.Component { render() { return React.createElement("ul", { className: "ingrediens" }, this.props.items.map((ingredient, i) => React.createElement("li", { key: i }, ingredient))); } } class 属性は、JavaScript で class が予約語なので className となっている。https://reactjs.org/docs/dom-elements.html#classname本書によれば、クラスを使ったコンポーネント記法は deprecated になっていくと予告されている。5 React with JSX5.1 JSX としての React 要素React 要素を作成するための簡潔な記法として JSX がある。// JavaScript React.createElement(IngredientList, {list: [...]}); // JSX <IngredientList list={[...]}/> JSX TIPS:コンポーネントのネストができる。class 属性は予約語なので、className が使われる。{...} で JavaScript 式を評価してその結果が使われる。5.2 BabelBabel を使って JS を含む JavaScript コードを普通の JavaScript コードに変換できる。<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>React Samples</title> </head> <body> <div id="root"></div> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script crossorigin src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> // ... </script> </body> </html> 5.3 React フラグメント複数の React 要素の列をまとめるとき、<div>...</div> でまとめることもできるが不要なタグがたくさんできてしまう。そのために React.Fragment タグを使うことができる。function Cat({ name }) { return ( <React.Fragment> <h1>The cat's name is {name}</h1> <p>He's good.</p> </React.Fragment> ); } <React.Fragment> は空タグ <> で短縮して書くこともできる。Ref: Fragments5.4 webpack の紹介webpack を使って、JSX/ESNext の変換や、JavaScript ライブラリの依存の管理、画像/CSS の最適化を行う。create-react-app や Gatsby などのツールを使えば、こうしたコンパイル作業は抽象化されて見えなくなる。5.4.1 プロジェクトを作成するmkdir recipes-app cd recipes-app npm init -y npm install react react-dom serve 5.4.2 コンポーネントをモジュールに分けるimport React from "react"; import { render } from "react-dom"; import Menu from "./components/Menu"; import data from "./data/recipes"; render(<Menu recipes={data} />, document.getElementById("root")); 5.4.3 webpack ビルドを作成するwebpack.config.jsvar path = require("path"); module.exports = { devtool: "#source-map", entry: "./src/index.js", output: { path: path.join(__dirname, "dist", "assets"), filename: "bundle.js" }, module: { rules: [{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }] } }; .babelrc:{ "presets": [ "@babel/preset-env", "@babel/preset-react" ] } npm install --save-dev webpack webpack-cli npm install --save-dev babel-loader @babel/core npm install -save-dev @babel/preset-env @babel/preset-react npx webpack --mode development 5.4.4 bundle をロードする./dist/index.html:<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>React Recipes App</title> </head> <body> <div id="root"></div> <script src="bundle.js"></script> </body> </html> 5.4.5 ceate-react-appCreate React App を使えば、webpack, Babel, ESLint などの設定を自動で行ってくれる。npm install -g create-rect-app create-react-app my-project src/App.js ファイルから JavaScript コードを修正する。npm start でアプリケーションを 3000 ポートで実行できる。npm run build でアプリケーションをビルドできる。6 React State Managementコンポーネントには、プロパティ(引数)を通してデータを引き渡すことができる。一方、ステートを使って、データの変更を行うことができる。6.1 スターレーティング・コンポーネントを作るconst Star = ({ selected = false }) => <FaStar color={selected ? "red" : "grey"} />; export default function StarRating({ totalStars = 5 }) { return [...Array(totalStars)].map((n, i) => <Star key={i} />); } 6.2 useState フックStarRating コンポーネントをクリックして援交できるようにするためには、ステートを使って星の数を保存する。関数コンポーネントでステートを使うためには、フック機能を使う。React.useHook(default) 関数は配列を返し、配列の第1要素はステート変数で、配列の第2要素はステート値を変更する関数である。Ref: Introducing Hooksimport React from "react"; const Star = ({ selected = false, onSelect = f => f }) => <FaStar color={selected ? "red" : "grey"} onClick={onSelect} />; export default function StarRating({ totalStars = 5 }) { const [selectedStars, setSelectedStars] = React.useState(3); return ( <> {[...Array(totalStars)].map((n, i) => ( <Star key={i} selected={selectedStars > i} onSelect={() => setSelectedSars(i + 1)} /> ))} <p>{selectedStars} of {totalStars} stars</p> </> ); } 以前のバージョンの React のコード:export default class StarRating extends React.Component { constructor(props) { super(props); this.state = { starsSelected: 0 }; this.change = this.change.bind(this); } change(starsSelected) { this.setState({ starsSelected }); } render() { const { totalStars } = this.props; const { starsSelected } = this.state; return ( <> {[...Array(totalStars)].map((n, i) => ( <Star key={i} selected={selectedStars > i} onClick={() => this.change(i + 1)} /> ))} <p>{selectedStars} of {totalStars} stars</p> </> ) } } Note: HTML のイベントハンドラと違って、React では camelCase であり、かつ、関数を値に渡す、というのが重要なんだけれど、明記されていないような気がする。6.3 再利用性を高めるリファクタリングReact では CSS スタイルを文字列でなく JavaScript オブジェクトで表現できる。style - DOM Elements - React{...props} という記法で、props をまとめて JSX のプロパティに引き渡すことができる。Spread Attributes - JSX In Depth - Reactexport default function StarRating({ style = {}, totalStars = 5, ...props }) { const [selectedStars, setSelectedStars] = React.useState(3); return ( <div style={{ padding: 5, ...style }} {...props}> {[...Array(totalStars)].map((n, i) => ( <Star key={i} selected={selectedStars > i} onSelect={() => setSelectedSars(i + 1)} /> ))} <p>{selectedStars} of {totalStars} stars</p> </div> ); } 6.4 コンポーネントツリーのステートたくさんのコンポーネントがステートを使うのはよいやり方じゃない。バグを追いかけたり変更をしたりするのが大変になるから。ステートは1ヶ所にまとめて管理するのがよい。そのための方法はいくつかあって、ここでは1つ目の方法を紹介する。コンポーネントツリーのルートにステートをもたせて、子コンポーネントには props を通して渡す。“Color Organizer” アプリケーションを作るとする。色・そのタイトル・レーティングのリストを含んだサンプルのデータセット color-data.json:[ { "id": "0175d1f0-a8c6-41bf-8d02-df5734d829a4", "title": "ocean at dusk", "color": "#00c4e2", "rating": 5 }, { "id": "83c7ba2f-7392-4d7d-9e23-35adbe186046", "title": "lawn", "color": "#26ac56", "rating": 3 }, { "id": "a11e3995-b0bd-4d58-8c48-5e49ae7f7f23", "title": "bright red", "color": "#ff0000", "rating": 0 } ] import React from "react"; import colorData from "./color-data.json"; function App() { const [colors, setColors] = React.useState(colorData); return ( <ColorList colors={colors} onRateColor={(id, rating) => { setColors(colors.map(color => color.id === id ? { ...color, rating } : color)) }} onRemoveColor={id => { setColors(colors.filter(color => color.id !== id)) }} /> ); } function ColorList({ colors = [], onRemoveColor = f => f, onRateColor = f => f }) { if (!colors.length) return <div>No Colors Listed. (Add a Color)</div>; return ( <div className="color-list"> {colors.map(color => ( <Color key={color.id} {...color} onRemove={onRemoveColor} onRate={onRateColor} /> ))} </div> ); } function Color({ id, title, color, rating, onRemove = f => f, onRate = f => f }) { return ( <section> <h1>{title}</h1> <button onClick={() => onRemove(id)}> <FaTrash /> </button> <div style={{ height: 50, backgroundColor: color }} /> <StarRating selectedStars={rating} onRate={rating => onRate(id, rating)} /> </section> ); } 公式ドキュメントでも、Lifting State Up として紹介されている方法。6.5 フォームを作るuseRef フックを使って ref を作ることで、DOM ノードを直接アクセスする。Ref: useReftxtTitle.current, hexColor.current は <input /> 要素への参照になっている:function AddColorForm({ onNewColor = f => f }) { const txtTitle = useRef(); const hexColor = useRef(); const submit = e => { e.preventDefault(); onNewColor(txtTitle.current.value, hexColor.current.value); txtTitle.current.value = ''; hexTitle.current.value = ''; }; return ( <form onSubmit={submit}> <input ref={txtTitle} type="text" placeholder="color title..." required /> <input ref={hexColor} type="color" required /> <buton>ADD</button> </form> ); } controlled component では、React がフォームのステートをコントロールする:function AddColorForm({ onNewColor = f => f }) { const [title, setTitle] = useState(''); const [color, setColor] = useState('#000000'); const submit = e => { e.preventDefault(); onNewColor(title, color); setTitle(''); setColor(''); }; return ( <form onSubmit={submit}> <input value={title} onChange={event => setTitle(event.target.value)} type="text" placeholder="color title..." required /> <input value={color} onChange={event => setColor(event.target.value)} type="color" required /> <buton>ADD</button> </form> ); } Uncntrolled Document - React によれば、基本的に controlled component を使うことを勧めている。では uncontrolled component はどういう場面で使うといいのか、不明。6.6 React コンテキストコンポーネントツリーのルートにステートをまとめるのは、古いバージョンの React で広く使われてきたパターンだった。しかし、大規模アプリケーションになるとたくさんのステートを管理するのが大変になった。React context がその代わりとなる手段になる。Ref: Context - Reactimport React from 'react'; import colorData from './color-data'; import ReactDOM from 'react-dom'; const ColorContext = React.createContext(); function ColorProvider({ children }) { const [colors, setColors] = React.useState(colorData); const addColor = (title, color) => setColors([...colors, { id: uuid.v4(), rating: 0, title, color }]); const rateColor = (id, rating) => setColors(colors.map(color => (color.id === id ? { ...color, rating } : color))); const removeColor = id => setColors(colors.filter(color => color.id !== id)); return ( <ColorContext.Provider value={{ colors, addColor, removeColor, rateColor }}> {children} </ColorContext.Provider>, ) } function App() { return ( <> <AddColorForm /> <ColorList /> </> ); } function ColorList() { const { colors } = React.useContext(ColorContext); if (!colors.length) return <div>No Colors Listed. (Add a Color)</div>; return ( <div className="color-list"> {colors.map(color => <Color key={color.id} {...color} />)} </div> ); } function Color({ id, title, color, rating }) { const { rateColor, removeColor } = React.useContext(ColorContext); return ( <section> <h1>{title}</h1> <button onClick={() => removeColor(id)}> <FaTrash /> </button> <div style={{ height: 50, backgroundColor: color }} /> <StarRating selectedStars={rating} onRate={rating => rateColor(id, rating)} /> </section> ); } function AddColorForm() { const [title, setTitle] = useState(''); const [color, setColor] = useState('#000000'); const { addColor } = React.useContext(ColorContext); const submit = e => { e.preventDefault(); addColor(title, color); setTitle(''); setColor(''); }; return ( <form onSubmit={submit}> <input value={title} onChange={event => setTitle(event.target.value)} type="text" placeholder="color title..." required /> <input value={color} onChange={event => setColor(event.target.value)} type="color" required /> <buton>ADD</button> </form> ); } ReactDOM.render( <ColorProvider> <App /> </ColorProvider> document.getElementById('root') ); 7 Enhancing Components with Hooks7.1 Introducing useEffectReact.useEffect() を使って、レンダリングのたびに副作用を起こす関数(エフェクト)を登録できる。function Checkbox() { const [checked, setChecked] = React.useState(false); React.useEffect(() => console.log(checked ? 'Yes, checked' : 'No, not checked')); return ( <> <input type='checkbox' value={checked} onChange={() => setChecked(checked => !checked)} /> {checked ? 'checked' : 'not checked'} </> ); } 7.2 The Dependency ArrayuseEffect() を使うと、レンダリングのたびにエフェクトが呼ばれる。第2引数に依存配列 (dependency array) を渡すことで、指定した値が変更されたときだけエフェクトが呼ばれるようになる:function App() { const [val, setVal] = React.useState(''); const [phrase, setPhrase] = React.useStater('example phrase'); const createPhrase = () => { setPhrase(val); setVal(''); }; useEffect(() => { console.log(`typing "${val}"`); }, [val]); useEffect(() => { console.log(`saved phrase: "${phrase}"`); }, [phrase]); return ( <> <label>Favorite phrase:</label> <input value={val} placeholder={phrase} onChange={e => setVal(e.target.value)} /> <button onClick={createPhrase}>send</button> </> ); } 空の依存配列は、最初のレンダリングのときだけ呼び出される。エフェクトが関数を返すと、その関数はコンポーネントがツリーから削除されたときに呼ばれる。つまり、クリーンアップに使える。function useJazzyNews() { const [posts, setPosts] = React.useSate([]); const addPost = post => setPosts(allPosts => [post, ...allPosts]); useEffect(() => { newsFeed.subscribe(addPost); return () => newsFeed.unsubscribe(addPost); }, []); useEffect(() => { welcomeChime.play(); return () => goodbyeChime.play(); }, []); return posts; } 7.3 When to useLayoutEffectuseLayoutEffect() は useEffect() とは少し違うタイミングで呼び出される。通常は useEffect() を使えばよいが、エフェクトがブラウザ描画に不可欠なものなら、useLayoutEffect() を使う。レンダリングする。useLayoutEffect が呼ばれる。ブラウザが描画する。コンポーネント要素が実際に DOM に追加されるとき。useEffect が呼ばれる。function useWindowSize() { const [size, setSize] = React.useState([0, 0]); const resize = () => setSize([window.innerWidth, window.innerHeight]); useLayoutEffect(() => { window.addEventListener('resize', resize); resize(); return () => window.removeEventListener('resize', resize); }, []); return size; } function useMousePosition() { const [position, setPosition] = React.useState({x: 0, y: 0}); useLayoutEffect(() => { window.addEventListener('mousemove', setPosition); return () => window.removeEventListener('mousemove', setPosition); }, []); return position; } 7.4 Rules to Follow with Hooksフックはコンポーネントのスコープの中でのみ動く。機能ごとに複数のフックに分けるとよい。フックはトップレベルからのみ呼ぶべき。7.5 Improving Code with ReduceruseReducer() は、第1引数に reducer 関数をとり、第2引数に初期値をとる。reducer 関数は現在のステートを受け取り新しいステートを返す。functin Checkbox() { const [checked, setChecked] = React.useReducer(checked => !checked, false); return ( <> <input type="checkbox" value={checked} onChange={setChecked} /> {checked ? "checked" : "not checked"} </> ); } 7.5 useReducer to Handle Complex State7.6 Improving Component Performance7.7 shouldComponentUpdate and PureComponent7.8 When to Refactor8 Incorporating Data8.1 Requesting DataJavaScript では、HTTP リクエストを行う一番ポピュラーな方法は fetch を使うこと。fetch(`https://api.github.com/users/moonhighway`) .then(response => response.json()) .then(console.log) .then(console.error); これは URL https://api.github.com/users/moonhighway への非同期リクエストを送る。レスポンスは .then(callback) メソッドを使ってコールバックに渡され、レスポンスデータを JSON としてパースする。パースした JSON の結果をコンソールにログ出力する。何か問題が発生すれば、console.error メソッドでエラー出力する。function App() { const [login, setLogin] = React.useState('moonhighway'); const [repo, setRepo] = React.useState('learning-react'); const handleSearch = login => { if (login) return setLogin(login); setLogin(''); setRepo(''); } if (!login) return <SearchForm value={login} onSearch={handleSearch} />; return ( <> <GitHubUser login={login} /> <UserRepositories login={login} repo={repo} onSelect={setRepo} /> </> ); } function Fetch({ uri, renderSuccess, loadingFallback = <p>loading...</p>, renderError = error => (<pre>{JSON.stringify(error, null, 2)}</pre>)}) { const [data, setData] = React.useState(); const [error, setError] = React.useState(); const [loading, setLoading] = React.useState(); React.useEffect(() => { if (!uri) return; fetch(uri) .then(data => data.json()) .then(setData) .then(() => setLoading(false)) .catch(setError); }, [uri]); if (loading) return loadingFallback; if (error) return renderError(error); if (data) return renderSuccess({ data }); } function GitHubUser({ login }) { const userDetails = ({ data }) => ( <div className="githubUser"> <img src={data.avatar_url} alt={data.login} style={{ width: 200 }} /> <div> <h1>{data.login}</h1> {data.name && <p>{data.name}</p>} {data.location && <p>{data.location}</p>} </div> </div> ); return <Fetch uri={`https://api.github.com/users/${login}`}renderSuccess={userDetails} />; ) } function useIterator(items = [], initialValue = 0) { const [index, setIndex] = React.useState(initialValue); const prev = React.useCallback(() => { if (index === 0) return setIndex(items.length - 1); setIndex(index - 1); }, [index]); const next = React.useCallback(() => { if (index === items.length - 1ndex) return setIndex(0); setIndex(index + 1); }, [index]); const item = React.useMemo(() => items[index], [index]); return [item || items[0], prev, next]; } function RepoMenu({ repositories, onSelct = f => f}) { const [{ name }, previous, next] = useIterator(repositories); React.useEffect(() => { if (name) onSelect(name); }, [name]); return ( <div style={{ display: 'flex' }}> <button onClick={previous}><</button> <p>{name}</p> <button onClick={next}>></button> </div> ); } function UserRepositories({ login, selectedRepo, onSelect = f => f}) { const renderSuccess = ({ data }) => <RepoMenu repositories={data} selectedRepo={selectedRepo} onSelect={onSelect} />; return <Fetch uri={`https://api.github.com/users/${login}/repos`} renderSuccess={renderSuccess} />; } 8.4 Introducing GraphQLGraphQL を扱うライブラリはたくさんあるが、ここでは graphql-request を使う。npm i graphql-request import { GraphQLClient } from 'graphql-request'; const query = ` query findRepos($login:String!) { user(login: $login) { login name location repositories(first:100) { totalCount nodes { name } } } } `; const client = new GraphQLClient('https://api.github.com/graphql', { headers: { Authorization: 'Bearer <PERSONAL_ACCESS_TOKEN>' } }); client .request(query, { login: 'moontahoe' }) .then(results => JSON.stringify(results, null, 2)) .then(console.log) .actch(console.error); レスポンス:{ "data": { "user": { "login": "MoonTahoe", "name": "Alex Banks", "location": "Tahoe City, CA", "repositories": { "totalCount": 51, "nodes": [ { "name": "snowtooth" } // ... ] } } } } 9 Suspense9.1 Error BoundariesRef: https://reactjs.org/docs/error-boundaries.html9.2 Code SplittingRef: https://reactjs.org/docs/code-splitting.html9.3 Introducing: The Suspense ComponenntRef: https://reactjs.org/docs/concurrent-mode-suspense.html10 React Testing10.1 ESLintESLint は JavaScript 用の最新のコードリンタである。npm install eslint --save-dev npx eslint --init npx eslint . 10.2 ESLint Plug-Ins.eslintrc.json:{ "plugins": [ // ... "react-hooks" ], "rules": { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn" } } 10.3 Prettiernpm install -g prettier npm install eslint-config-prettier eslint-plugin--prettier --save-dev # { # "extends": ["plugin:prettier/recommended"], # "plugins": ["prettier"], # "rules": ["prettier/prettier": "error"] # } npx prettier --write "src/*.js" 10.4 Typechecking for React Applicationsnpx create-react-app my-type --template typescript 10.5 Test-Driven DevelopmentStar.test.js:import Star from './Star'; describe('Star', () => { test('renders a star', () => { const div = document.createElement('div'); ReactDOM.render(<Star />, div); expect(div.querySelector('svg')).toHaveAttribute('id', 'hotdog'); }); }); Checkbox.test.js:import { render } from '@testing-librarry/react'; import Checkbox from './Checkbox'; describe('Checkbox', () => { test('Selecting the checkbox should toggle its value', () => { const { getByLabelText } = render(<Checkbox />); const checkbox = getByLabelText(/not checked/i); fireEvent.click(checkbox); expect(checkbox.checked).toEqual(true); fireEvent.Click(checkbox); expect(checkbox.checked).toEqual(false); }); }); 10.6 Using Code Coveragenpm test -- --coverage 11 React RouterAngular, Ember, Backbone と違って、React は標準ルータを持っていない。React Router ライブラリが作られ、広く使われている。pages.js:import React from 'react'; import { Link, useLocation, useParams } from 'react-router-dom'; export function Home() { return ( <div> <h1>[Compoany Website]</h1> <nav> <Liink to='about'>About</Link> <Liink to='events'>Events</Link> <Liink to='products'>Products</Link> <Liink to='contact'>Contact Us</Link> </nav> </div> ); } export function About() { return <div><h1>[About]</h1><Outlet /></div>; } export function Events() { return <div><h1>[Events]</h1></div>; } export function Products() { return <div><h1>[Products]</h1></div>; } export function Product() { let { id } = useParams(); return <div><h1>[Product {id}]</h1></div>; } export function Contact() { return <div><h1>[Contact]</h1></div>; } export function Whoops404() { return <div><h1>Resource not found at {useLocation().pathname}</h1></div>; } export function Services() { return ( <section> <h2>Our Services</h2> <p>...</p> </section> ); } <Outlet /> が使われているけど、2021/01/09 段階ではまだベータバージョンの v6 の機能だった。また v6 自体がなんかしばらく更新がない。App.js:import React from 'react'; import { Routes, Route } from 'react-router-dom'; import { Home, About, Events, Products, Contact, Whoops404, Services } from './pages'; function App() { return ( <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />}> <Route path="services" element={<Services />} /> </Route> <Route path="/events" element={<Events />} /> <Route path="/products" element={<Products />} /> <Route path="/products/:id" element={<Products />} /> <Route path="/contact" element={<Contact />} /> <Route path="*" element={<WHoops404 />} /> </Routes> ); } リダイレクトは Redirect コンポーネントを使う:<Routes> <Redirect from="services" to="about/services" /> </Routes> 12 React and the Serverイソモーフィック(isomorphic) と ユニバーサル(universal) は、クライアントとサーバの両方で動くアプリケーションを意味する。イソモーフィックなアプリケーションは、複数のプラットフォームでレンダリングできること。ユニバーサルなアプリケーションは、複数の環境で同じコードで動くことを意味する。12.4 Server Rendering with Next.jsmkdir project-next cd project-next npm init -y npm install --save react react-dom next mkdir pages pages/Header.js:import Link from 'next/link'; export default function Header() { return ( <div> <Link href="/"><a>Home</a></Link> <Link href="/pets"><a>Pets</a></Link> </div> ) } pages/index.js:import Header from './Header'; export default function Index() { return ( <div> <Header /> <h1>Hello everyone!</h1> </div> ) }