こちらは NJC Advent Calendar 3日目の記事です。
はじめに
Reactが脚光を浴び始めて、そろそろ4年がたとうとしています。
React.createClassで作成していたコンポーネントはReact.Componentに変わり、このReact.Componentも今変わろうとしています。
React Hooksに登場により、これからは全てのコンポーネントはfunction componentsで作成されるように、やがて変わっていくことでしょう。
そんなReactですが、Qiitaや個人のブログで部分的にまとめられているぐらいで、未だに日本語で体系的にまとめられたドキュメントが存在していません。
Reactは初心者がイチから勉強していく環境が未だに整っていないと筆者は憂いています。
ちなみに、Vueにはこんなものがあったりします。
どうにかできないかなーと思っていたのですが、、、あるじゃん!!
社内にいい感じのサイトがあるじゃん!!
このサイトをReactをやりたいエンジニアに知ってもらえればいいじゃん!!
僕はReactが好きです。
Vue.jsもいいけど、それでも僕はReactが好きです。Reactがいいんです。
もっともっとReact好きなエンジニアが増えてほしいんです。
ドキュメントサイト
静的サイトは大人の事情で公開できないので、現在マークダウンファイルのみを切り出してgithubにおいています。
React, Redux, React/Redux/TypeScriptの3つのドキュメントが現在あります。
内容はチュートリアル形式になっていて、各チュートリアルも別リポジトリで別途管理して公開しています。
React導入の壁
そんなReactですが、学習コストが高いとよく言われます。
Vueの方がサクッとはいりやすいというのもよく聞きます。
では、なぜ学習コストが高いと言われ続けているのか
- ESNext(ES2015)前提のコード
- 開発環境のセットアップ(webpackとかBabel)
- 実運用ではサードパーティの組み合わせが必須の開発
- サードパーティの種類が多すぎる
- 多種多様なコンポーネント作成方法
- ネットで調べた記事が古いと参考にならない
などなど、以上の点が代表的なところじゃないのかなと。
サードパーティの組み合わせ前提の開発というのが、かなり初心者泣かせな点です。
正直Reactだけに限った話ではないのですが、特にReactが顕著で被っているというのが実情だと思います。
僕自身、ES2015,React未経験の状態からいきなりReact,Redux,redux-sagaのプロジェクトに突っ込まれて非常に辛い思いをしました。
わからない箇所がJavaScriptなのかReactなのかReduxなのか、わからないところがわからないという状態でした。
こういう初心者のために、少しでも助けになれるようなサイトがあれば、初期参入のハードルを減らせるのではと考えています。
どういうサイトか
ほんとうは現物をここに貼っつけて公開したいのですが、内部監査部に怒られると面倒なので説明だけに留めておきます。
- サイト自体はReact(v16.3.0)で作成
- 記事は全てmarkdownファイルで管理
- チュートリアル方式でReact, Reduxを学んでいく
- API通信有りの実践的なアプリを作成していく
- 基本言語はチュートリアルはJavaScript、以降はTypeScript
概要はこんな感じです。
各チュートリアルはGit上に個別で管理しており、
git clone => yarn install => yarn start
で開発できるようにしています。
開発環境の構築は全てこちらで行なっており、webpackやprettierなどはこちらで設定済みです。(オートフォーマットにしたい場合は各自で設定をお願いしています。)
現在、サイトはReact v16.7.0-alpha.2でマイグレーションを行なっており、後にTypeScriptで書き直す予定です。
サイト構成はMaterial-UIとほぼほぼ同じような感じです。
左上のハンバーガーメニューをクリックしてメニューを開いて、いきたいページに飛ぶ感じですね。
作成しているチュートリアル
チュートリアル名 | 言語 | ページ数 | 説明 |
---|---|---|---|
HowToReact | JavaScript | 6 | Reactを使った基本的なコンポーネント作成の流れ |
HowToRedux | JavaScript | 5 | React / Reduxで家電管理アプリケーションの構築 |
HowToTypeScript | TypeScript | 3 | TypeScriptの解説と、HowToReduxをTypeScriptで再構築 |
Todoアプリ | JavaScript | 6 | React / Redux でTodoアプリケーションの構築 |
LibraryWebアプリ | JavaScript | 11 | React / Redux で図書館管理アプリケーションの構築 |
UnitTest | JavaScript | 6 | Todoアプリのユニットテストの作成 |
コードはESNextですが、これらの解説はコード中にでてきたら適宜解説というふうに行なっています。
TodoアプリとLibraryWebアプリは、API通信を含んでいて、より本格的なアプリ構築のチュートリアルになっています。
今はJavaScriptで作っていますが、HowToTypeScript移行のものは全てTypeScriptにいずれ移行します。というか、移行中です。
追加したいと思ってるもの
- HowToReactHooks
- redux-sagaかredux-thunkを使ったチュートリアル
- パフォーマンスチューニング
ザッとこんなものを追加していきたいと思っています。
React Hooksは成果物自体は既にあり、HowToReduxをReact Hooksを使ってより作り込んだものを作っています。
パフォーマンスチューニングは、shouldComponentUpdate
メソッドのことだけではなく、User Timing API
やredux-action-timing-middleware
などを使った計測方法、reconciliationらへんの話を書く予定。
公開の障害となっているもの
大きくわけて2つあり、Material-UIを拡張した独自コンポーネントを自社内で持っており、リファレンスがある。
それと、チュートリアル内で叩くAPIのエンドポイントが社内にあるサーバに向けてる。
これが大きな障害になっています。
サイト自体も自社内のコンポーネントを使って実装している部分もあるので、このしがらみを断ち切らないといけないという。。。
自分で自分の首を締めまくってます。
技術的な話
やはりこの話をしなければいけないでしょう!!!
ここ、qiitaだし。
- markdownの読み込み
- アンカーの作成
機能面でこの2つの話を書きます。
コードも少し載せていますが、サンプルとして簡易的な実装にしています。
markdownの読み込み
サイトは、作成したmarkdownを読み込んで画面にレンダリングをしています。
remarkable
というモジュールを使って、markdownファイルを読み込んでプレーンなHTMLにパースをしています。
import React from 'react';
import Remarkable from 'remarkable';
import hljs from 'highlight.js';
const md = new Remarkable({
highlight(str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(lang, str).value;
} catch (err) {
console.error(err);
}
}
try {
return hljs.highlightAuto(str).value;
} catch (err) {
console.error(err);
}
return '';
},
html: true,
});
// md には markdownのテキストデータが入っている
const MarkdownLoader = ({ md }) => (
<div dangerouslySetInnerHTML={{ __html: md.render(md) }} />
);
このように、remarkableはrenderメソッドにプレーンテキストのマークダウンを与えることでパースを行います。
コード中のシンタックスハイライトにはhighlight.jsを使うこともできるのですが、正直微妙です。。。
jsxやtsxをうまく解釈できてない気がします。
なくてもいいかな、、、って感じです。
また、オプションを渡すこともでき、上記のコードではhtml: true
を渡しています。
このオプションを設定することで、マークダウン内で普通のHTMLタグを使うことができるようになります。
別タブでリンクを開いたり、イメージのサイズを固定にしたりといったことができるので地味に便利です。
アンカーの作成
文章を書く際には章や節にわけて書いていきます。
マークダウンではもっぱら#
が各見出しになると思います。
この区切られた部分だけアンカーとして抜き出し、クリックしたら該当部分までスクロールさせるような機能を実装しています。
いく通りかのやり方がありますが、DOM情報からアンカーの作成を行なっています。
class MdComponent extends Component {
constructor(props) {
super(props);
this.mdRef = React.createRef();
this.state = { anchors: [], currentPosY: 0 };
}
componentDidMount() {
window.scrollTo(0, 0); // ページ遷移した際にスクロール位置を先頭に戻す
const headings = this.mdRef.current.querySelectorAll('h1, h2, h3, h4, h5, h6');
if (!headings) return false;
// headingsはNodeListなのでスプレッドオペレーターでArrayに定義し直してからmapする
const anchors = [...headings].map(node => node.getBoundingClientRect());
this.setState({ anchors, currentPosY: window.scrollY });
}
render() {
const { md } = this.props;
const { anchors, currentPosY } = this.state;
return (
<div ref={this.mdRef}>
{/* anchorsの長さ分だけアンカーを作成する */}
<MdAnchors md={md} anchors={anchors} posY={currentPosY} />
<MarkdownLoader md={md} />
</div>
);
}
#
はheadingタグにパースされるので、これをquerySelectorAllを使って全て取得します。
headingタグがページのどこにあるかを示す情報が欲しいので、getBoundingClientRectメソッドでクライアント座標をanchorsという配列に全て突っ込みます。
その際、ユーザのスクロール位置も取得しています。
例えば、ページの途中で更新したりマークダウンのレンダリングよりも前にスクロールします。
refから取得されるクライアントY座標はユーザの現在のY座標を基準にした値になるため、正しい位置にスクロールできなくなります。
この位置調整をするために、クライアント座標を計算した時点でのY座標も返すようにしています。
スクロールの実装にはanimated-scrollto
というモジュールを使っており、
MdAnchorsがレンダリングした要素がクリックされたら
animatedScrollTo(
document.body, // ページ全体の情報
this.props.posY + this.props.anchors[index].top, // スクロール位置
300, // 何msかけてスクロールさせるか
);
このような処理が実行されるようにしておけば、対象の見出しまで飛ぶようになります。
終わりに
Reactに限らずフレームワークって便利だけど、その分学習コストが高くて、しかも英語のドキュメントしかなくて,,,みたいなことが多いです。
トレンディーなものに触りたいけど、色々な事情で触れない人も同様に。
そういうつっかえとなってる部分を取り除いて、もっともっと日本のフロントエンド界隈は盛り上がってくれたらいいなって思います。