#Angular開発者のためのReact基本
Angularの人気がよろしくない。
「今後学習するつもりもないと回答するユーザーの割合多い。徐々に使用割合が減っていく可能性が見えてきている。」JavaScriptフロントエンドフレームワーク、Angularの人気が下落中
ということでReactやVueへ移行するプロジェクト、それらの習得に軸足を移していくフロントエンジニアもいるでしょう。私もそう。Googleのネームバリューとフルスタックフレームワークという豪奢性に飛びついたのだが。
というわけでこれからはAngularのメンテをしながら新たにReactをやっていこう。なによりReact Nativeのコミュニティが妙に熱い。
##初期設定
####まずnode.jsをインストール
####Reactアプリケーションを作成
$ npx create-react-app <アプリケーション名>
または
$ npm init react-app <アプリケーション名>
最後にHappy hacking!と表示されていればOK。
####Reactアプリケーションを起動(http://localhost:3000)
プロジェクトフォルダに移動して、
$ npm start
または
$ yarn start
####Reactアプリケーションをビルド
$ npm run build
または
$ yarn build
####React Developper Tools のインストール
######Chromeプラグイン
https://chrome.google.com/webstore
でインストールする。
######スタンドアロン版
以下のコマンドでインストール
$ npm install -g react-devtools
以下のコマンドで起動
$ react-devtools
そして、アプリケーションのほうのindex.htmlに、
<script src="http://localhost:8097"></script>
を<head>
タグ内に入れておく。ビルドするときは外す。
##開発途中いろいろ
###コンポーネント
ng generate component
みたくコマンドで作成する必要ない。TSとHTMLとCSSでファイルが分けてあるとかもない。もちろん分けてもよい。原則としては.jsだけで完結できる。デフォルトでindex.jsとApp.jsが存在する。index.jsが最初に読み込まれると考えてよい。App.jsがAngularにおけるapp.component.ts的なもの。例えば以下のように、App.jsファイルにひとつまたは複数のクラス(関数でも可)を書くと、クラスそのものがそのまんまコンポーネントになる。
//App.js
import React, { Component } from 'react';
// Appコンポーネントクラス
class App extends Component {
h1 = {
size:"30px"
}
constructor(props){
super(props); // ← おまじない
}
render() {
return (
<div>
<h1 style={this.h1}>Home</h1>
</div>
);
}
}
export default App // Appコンポーネントをエクスポート
HTMLとCSSもApp.js内に書いてあり、最終的にはrender(){return(……)}関数
の中にHTMLを書いてJSXにレンダリングさせる。「①TSファイル②HTMLファイル③CSSファイル」の3つでワンセットとするAngularと比べるとカオスな感じがある。
コンストラクターの中のsuper(props);
は上位コンポーネントのプロパティを継承するという意味だが必ずつけるおまじないと思ってよい。
このようにして作成したAppコンポーネント、これを利用する側のコンポーネントでは(ここではindex.js)、
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'; // ← インポートして
ReactDOM.render(
<App /> // ← 読み込む
document.getElementById('root') // ← おまじない
);
のように、<App />
のような形で記述して読み込む。それでApp.jsで処理された内容がindex.jsに埋め込まれて表示される。
表示するにはReactDom.render()
という関数を使ってレンダリングする。
######属性 this.props
index.jsからAppコンポーネント側に渡したい属性があるとする。例えばそれがnameという属性で値は"アトム"だったとしたら
<App name="アトム" />
のように書くことができる。この勝手に加えた属性nameは必ずthis.propsという名前のオブジェクトの中に入る。そういうキマリ。App.jsを以下のように修正すれば、{this.props.name}
という形でその値を取り出せる。
//App.js
import React, { Component } from 'react';
// Appコンポーネントクラス
class App extends Component {
h1 = {
size:"30px"
}
constructor(props){
super(props);
}
render() {
return (
<div>
<h1 style={this.h1}>Home</h1>
<p>ハロー, {this.props.name}</p> //← ハロー, アトム と表示される.
</div>
);
}
}
export default App
※ ちなみにthis.props.children
にはすべての属性(およびDOM)が含まれている。
###フォーム
AngularみたくFormsModule
とかいらなくてReact.Component
だけで済む。
以下のコードはinputテキストボックスに入力された文字列をeventキャッチしてmessageステート変数に代入し(doChange()関数)、ボタンクリックで実際の処理を行っている(doAction()関数)。【doAction()関数は、ここではstate変数のmessageをログ出力してから元のカラにしている】
関数についてはコンストラクターでバインドするというおまじないが必要。意味は知らなくていい。
イベント処理についてもe.preventDefault();というおまじないが必要。意味は知らなくていい。
//Form.jsx
import React, { Component } from 'react';
class Form extends Component {
input = {
fontSize:"16pt",
color:"#006",
padding:"1px",
margin:"5px 0px"
}
btn = {
fontSize:"14pt",
color:"#006",
padding:"2px 10px"
}
constructor(props){
super(props); ← おまじない
this.state = {
message:''
}
this.doChange = this.doChange.bind(this); // ← おまじない
this.doAction = this.doAction.bind(this); // ← おまじない
}
doChange(e){
this.setState({
message: e.target.value
});
}
doAction(){
e.preventDefault(); ← おまじない
console.log('メッセージ' + massage); // ← 処理
this.setState((state)=> ({ // ← 処理
message: ''
}));
}
render(){
return (
<div>
<p>{this.props.message}</p>
<form onSubmit={this.doAction}>
<input type="text" size="40" onChange={this.doChange}
style={this.input} value={this.state.message} required />
<input type="submit" style={this.btn} value="Add"/>
</form>
</div>
);
}
}
export default App;
####バリデーション
requiredやpatternなど。変わらない。
//App.js
import React, { Component } from 'react';
class App extends Component {
input = '';
msgStyle = {
fontSize:"20pt",
color:"#900",
margin:"20px 0px",
padding: "5px",
}
inputStyle = {
fontSize:"12pt",
padding:"5px"
}
input:invalid {
border: 2px dashed red;
}
input:valid {
border: 1px solid blue;
}
constructor(props){
super(props); ← おまじない
this.state = {
message:'type your name:'
};
this.doChange = this.doChange.bind(this); // ← おまじない
this.doSubmit = this.doSubmit.bind(this); // ← おまじない
}
doChange(event) {
this.input = event.target.value;
}
doSubmit(event) {
this.setState({
message: 'Hello, ' + this.input + '!!'
});
event.preventDefault(); // ← おまじない
}
render(){
return <div>
<h1>React</h1>
<h2>{this.state.message}</h2>
<form onSubmit={this.doSubmit}>
<label>
<span style={this.inputStyle}></span>Message:
<input type="text" style={this.inputStyle}
onChange={this.doChange}
required //← 必須
pattern="[A-Za-z _,.]+" //← 正規表現パターン
minlength="4" //← 最小文字数
maxlength="20" //← 最大文字数
/>
</label>
<input type="submit" style={this.inputStyle} value="Click" />
</form>
</div>;
}
}
###コンテキスト
グローバル変数を設定するみたいな?
使うかのう?。とりあえずスルー。
##Redux
Angularのngrxみたく色々なライブラリをインストールする必要なくてシンプル。
####Reduxのインストール
$ npm install --save redux
$ npm install --save react-redux
$ npm install --save-dev redux-devtools
####Redux Persistのインストール
永続化Reduxを使う場合は以下のコマンドも。
$ npm install --save redux-persist
使い方はここでは省略。書ききれん。Angularでngrx使うよりコードは少なく済みわかりやすい。また、もしかしたらReact Hooksという新しいモノがReact Reduxに取って代わる可能性あり。でもReact-Reduxでもとても良い。
##ルーティング
####インストール
$ npm install --save react-router-dom
ほかにもreact-router、react-router-reduxがあるがブラウザで動くページを書くにはreact-router-dom。react-routerはReact Nativeで使うようだ。
####書き方
Angularのように原則app.module.tsファイルに書くとかapp.route.tsファイルを用意するとかない。App.jsに書くべし、なのか?
ほとんど同じでしょと思ったがAngularと勝手が違う。ひじょう~にめんどくさい。
Home、Aboutページ(コンポーネント)があるだけのシンプルな例:
// App.js
import React, { Component } from 'react'
import { BrowserRouter, Route, Link } from 'react-router-dom'
class App extends Component {
constructor(props){
super(props);
}
render(){
return(
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/About">About</Link>
</nav>
<Route exact path='/' component={Home} />
<Route path='/about' component={About} />
</BrowserRouter>
)
}
}
class Home extends Component { // Homeコンポーネント
render(){
return(
<div>
<h2>Home</h2>
<p>Homeページだボヨヨ~ン</p>
</div>
)
}
}
class About extends Component { // Aboutコンポーネント
render(){
return(
<div>
<h2>About</h2>
<p>Aboutページだオロローン</p>
</div>
)
}
}
export default App;
ルーティングの設定は<BrowserRouter>
というHTMLタグのようなもので挟んで、<Route exact path='/' component={Home} />
という風に<Route>
で設定。
exact
は完全一致を意味してて、それをつけないと/about
も前方一致してしまう、という。
パスへのリンクには <Link>
を使う。toの指定先は"/About"とシンプルなテキストだけでなく例えば下記のようにオブジェクトもOK。
<Link to={{
pathname: '/about', // パス文字列
search: '?yourself=atom', // クエリパラメータ
hash: '#the-hash', // URLハッシュ
state: { flag: true } // 任意のデータ
}}>About</Link>
ほかにも<Switch>
で囲んだりchildren={<SomeComponent />}
指定、クエリパラメータの扱いなどいろいろある。Hookにも対応!Vue.jsエンジニアのためのReact Router v5入門に詳しく書いてある。
##Next.js
ReactのSSR化ライブラリー。
AngularにおけるUniversalよりはるかに良いかもしれない。まだ試してないが。(Universalはこんなに大変→Angular8: パフォーマンス改善のためUniversalを導入しServer Side Rendering化する)
また、Reactだけだとあくまでシングルページアプリケーションを考えてのモノであり、複数ページを持つWebサイトなりWeb appを想定するとNext.jsがほぼ必須?のようだ。なのでNext.jsまで学んで初めてAngularというのはオールマイティの全部入りだったのだなあ、と気づく。
####インストール
新しいプロジェクトフォルダを作ってその中にpackage.jsonを前もって作成(後で編集してもいいけど)。
// package.json
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"export": "next export"
}
}
あと、後ででもいいけどpagesというサブフォルダも作成しておく。
next_app
|
|---package.json
|---pages
そんでインストールコマンド
$ npm install --save next react react-dom
起動コマンド
$ npm run dev
ブラウザからlocalhost:3000へアクセスで、以下のように404エラー。これでよい。
###開発途中いろいろ ####まずHello World pagesフォルダの配下にindex.jsを作成して、そのファイルに下記のコード。pagesというフォルダの下にメインとなるコンポーネント(ページ)を置いていくキマリ。pagesフォルダはNext.jsのAPIとマッピングしているそうだ。
以下ではクラスの代わりに無名関数がコンポーネントになっている。
// pages/index.js
export default () =><div>
<div>Hello World!</div>
</div>
また、
./pages/_app.js
を作成することによって、 <App>
を上書きできる。
Next.jsではReactのようにApp.jsはない(隠れた所にある)。以下はNext.jsでReduxを使うときに使うべき./pages/_app.jsのコード。
// pages/_app.js
import App, {Container} from 'next/app';
import React from 'react';
import withReduxStore from '../lib/redux-store';
import { Provider } from 'react-redux';
class _App extends App {
render () {
const {Component, pageProps, reduxStore} = this.props
return (
<Container>
<Provider store={reduxStore}>
<Component {...pageProps} />
</Provider>
</Container>
)
}
}
export default withReduxStore(_App)
./pages/_error.js
を作成することにより、 404 または 500 エラーの上書きができる。
// pages/_error.js
import React from 'react'
class Error extends React.Component {
static getInitialProps({ res, err }) {
const statusCode = res ? res.statusCode : err ? err.statusCode : null
return { statusCode }
}
render() {
return (
<p>
{this.props.statusCode
? `An error ${this.props.statusCode} occurred on server`
: 'An error occurred on client'}
</p>
)
}
}
export default Error
Nextをインストール後のデフォルトバンドルはこういうことになっているようだ。つまりdocument.jsもまた./pages/_document.jsで上書きできる。まあやらんだろう。
$ cd .next/dist/bundles
$ tree
.
└── pages
├── _app.js
├── _app.js.map
├── _document.js
├── _document.js.map
├── _error.js
├── _error.js.map
####コンフィグファイル
プロジェクトルートにnext.config.js
ファイルを置く。angular.json的なものかなあ。デフォルトではこのファイルはないので使いたかったら新規で作成。いろいろな設定ができる。
例えば静的ファイルにエキスポートする設定コードは、
// next.config.js
module.exports = {
exportPathMap: function (
defaultPathMap,
{ dev, dir, outDir, distDir, buildId }
) {
return {
'/': { page: '/' }
}
}
}
そして以下のコマンドでエキスポート
$ npm run build
$ npm run export
これでhello worldだけの1ページであればoutフォルダができてそこにindex.htmlと404.htmlが生成されているはず。
参考:Static HTML export
####スタティックアセット
CSSや画像ファイルなどはstaticというサブフォルダを作成してそこに置く。ちなみにCSSファイルは使えないのでスクリプトの形にしてそれを読み込む。以下のように。
// statc/Style.js
import css from 'styled-jsx/css';
export default <style>{`
body {
margin:10px;
padding:5px;
color:#669;
}
h1 {
font-size:22pt;
font-weight:bold;
text-align:left;
letter-spacing:0px;
color:#77a;
margin:-50px 0px 50px 0px;
}
p {
margin:0px;
color:#669;
font-size:16pt;
}
`}</style>;
そして読み込む側のJSファイルでは、
import style from '../static/Style';
と。
// pages/sample.js
import React, { Component } from 'react';
import style from '../static/Style'; // ← ココ
class Sample extends Component {
render() {
return (<div>
{style} // ← ココで使ってる
<hr/>
<h1>タイトル</h1>
<p>メッセージ</p>
</div>);
}
}
export default Sample;
####画像へのリンク
画像ファイルをstaticフォルダに置いて<img src="" />
でOK。たとえば
<img src="/static/image.png"/>
####ルーティング
pagesフォルダ配下のファイル同士で移動できる。つまりpageフォルダにhoge.jsやfuga.jsなどを置いておけばよいだけ。Angularのルーティング設定みたいなのはいらない。
// pages/index.js
import Link from 'next/link'; // ← Linkをインポートして
export default () =>(
<hr/>
<Link href="./hoge"> // Link href=でhogeやfugaなどを指定するだけ
<button>
go to HOGE >>
</button>
</Link>
);
<Link>
タグで囲む。<Link>
タグの中に<a>
タグを置いてもいいけどそこにhref属性は置けない。
<Link href="./hoge"> // ← href="./hoge"... <Link>タグの中に
<a> // ← a href="./hoge"... とはできない
go to HOGE >> // ← <a>タグならいちおうこの文字がリンカブルブルーになるが別に<p>タグでもいい
</a>
</Link>
※ 外部リンクだったら普通にアンカータグ<a href="https://qiita.com/">Google</a>
で良いようだ。
- href:pages/ ディレクトリ内のパスとクエリ文字列
- as:ブラウザのURLバーに表示されるパス。
<Link href="/post?slug=something" as="/post/something">
※ 動的リンクについてはこちら参照:動的ルーティング
####動的コンポーネント
JavaScriptモジュールやReact Componentsを動的にインポートしてそれらを使用できるようだ。AngularではJavaScriptをそのまま使えないのでこれは随分ラクだろう。
以下ではcomponentsフォルダの下に作った動的コンポーネントhello.jsファイルを読み込んでいる。
// pages/home.js
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() => import('../components/hello'))
function Home() {
return (
<div>
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
####ヘッダー情報を編集
HTMLのヘッダー情報を編集できるビルトインコンポーネントがある。<Head>タグ
で囲むだけ。AngularではTSファイルのコンストラクターでmeta.addTag()して編集できるが…TwitterカードなどのOGPボットが拾ってくれない問題(Universal化してもsizeが大きいせいか拾ってくれない)を解決してくれるかもしれない。
import Head from 'next/head'
function IndexPage() {
return (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Hello world!</p>
</div>
)
}
export default IndexPage
##Redux on Next.js
複数ページ間で共通データをまたいで扱うことができるReduxを、Next.jsでも使える
####インストール
$ npm install --save redux
$ npm install --save react-redux
$ npm install --save redux-thunk
####使い方
詳しくはReact.js & Next.js超入門に載っている。とても書ききれん。
###補足 Angularでの経験が役に立つのは、現在ReactでもTypescriptを使うケースが増えていること。TypeScriptでのサンプルコードの少なさに悶え、苦しんだ経験が報われるだろう。
TypeScriptでのReactプロジェクト作成は物凄く簡単になったらしく、以下のコマンドだけ。
$ npx create-react-app <アプリケーション名> --typescript
参照: React.js & Next.js超入門 👈 ReduxとNext.jsの説明が非常にわかりやすくオススメ(超入門とあるがそれなりのレベルの内容)
JSフレームワーク事情2020年始め
Use React Hooks with Storage as Global State Management
react-router@v4を使ってみよう:シンプルなtutorial
【超簡単】Reactのルーティング設定方法
Hookにも対応!Vue.jsエンジニアのためのReact Router v5入門
Next.js
Next.jsの機能
なぜReact+TypeScriptでコンポーネント作成が早くなるのか