本文はゼロから React の核心知識を紹介します。以下は参考アウトラインです〜
- React
- React とは
- なぜ React を使用するのか
- プロジェクトプレビュー
- JSX
- スタイル
- コンポーネント
- props
- クラスコンポーネント
- State
- イベント処理
- ライフサイクル
- もっと
- スキャフォールディング
- 状態管理とルーティング
それでは、さっそく始めましょう〜
React#
React とは#
公式定義:ユーザーインターフェースを構築するための JavaScript ライブラリ
定義から私たちが認識すべきことは、React 自体が行うのはユーザーインターフェースの構築だけであり、大規模な React プロジェクトは一般的にそのエコシステム(ルーティング: React-Router、状態管理ライブラリ: Redux など)と密接に結びついて実現されることです。この文章は主に React の核心知識に焦点を当てています。
なぜ React を使用するのか#
- 仮想 DOM: 私たちは、JS が頻繁に DOM を操作するコストが非常に高いことを知っていますが、React が初めて提唱した仮想 DOM は、JS レベルで DOM を操作することを実現し、アプリケーションの効率を大幅に向上させました。
- 再利用可能なコンポーネント: React の普及はコンポーネント化の考え方を促進しました。コンポーネント化の核心は再利用性にあり、従来の Web 開発モデルに比べてメンテナンスが容易で、開発効率を大いに向上させます。
- Facebook によるメンテナンス: React は Facebook という大きな後ろ盾があり、多くの優れた開発者がメンテナンスとイテレーションを行っています。また、コミュニティも非常に活発で、開発中に直面するほとんどの問題はすぐに解決できます。
- 現実:最後のポイントは国内の現状で、大手企業の技術スタックは基本的に React に基づいているため、会社に合わせるなら React は必須のスキルです。
プロジェクトプレビュー#
最終的なプロジェクトの効果を見てみましょう。原生 JS を使ってどのように実現するか考えてみてください?
初期の空のテンプレート:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
</body>
</html>
JSX#
まず、React がどのようにインターフェースを構築し、要素をレンダリングするかを見てみましょう:
- React の構文は JSX であり、JavaScript と HTML が混在した構文です。
ここでは、外部リンクを使用して React プロジェクトの基本的な 2 つのリンクを追加します:
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> // reactのコア
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> // react-domブラウザ(DOM)のレンダリング
同時に、JSX の構文を直接使用できるようにするために、babel の外部リンクを追加する必要があります:
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
// スクリプトにtypeを指定
<script type="text/babel">
// コーディング....
</script>
そして、最初の React コードを書きます:
ReactDOM.render(<h1>Hello React!</h1>, document.getElementById('app'));
ブラウザをリフレッシュします:
ReactDOM
オブジェクトはreact-dom
から来ており、ここではそのrender
メソッドを呼び出しています。最初の引数はレンダリングする要素、2 番目は実際の DOM オブジェクトです。これにより、JSX 要素がページに正常にレンダリングされました。
JSX 構文は実際には HTML に非常に似ていることがわかりますが、CSS はどうでしょうか?
スタイル#
インラインスタイル:
ReactDOM.render(<h1 style={{backgroundColor: 'lightblue'}}>Hello React!</h1>, document.getElementById('app'))
外部スタイル:
const h1Style = {
backgroundColor: 'lightblue'
}
ReactDOM.render(<h1 style={h1Style}>Hello React!</h1>, document.getElementById('app'))
PS: CSS クラスセレクタを使用する場合、JSX での書き方はclassNameです(ES6 のclass
と区別するため)。
現在、これらの書き方を学ぶだけで十分です。
コンポーネント#
先ほどrender
メソッドで直接書いた JSX が、JSX 要素が複雑になると、別々にComponent
を定義する必要があります。まずは無状態コンポーネントの書き方を見てみましょう:
function App () {
return (
<div>
<h1>Hello React!</h1>
<p>reactはとても素晴らしいです!</p>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'))
ブラウザをリフレッシュします:
ここで注意が必要なのは、返されるコンポーネントは必ず 1 つの最大のタグで包含されなければならないということです。
次に、少し面白いことをやってみましょう:
function App () {
const books = ['データベース', 'データ構造', 'コンピュータネットワーク']
return (
<div>
<h3>私の本: </h3>
<ul>
{books.map(book =>
<li>{book}</li>
)}
</ul>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'))
books
配列を定義し、関数コンポーネント内で ES6 のmap
メソッドを使用して対応するbook
要素をループレンダリングします。
ブラウザをリフレッシュします:
まず、画面に 3 つのli
が表示されているのがわかりますが、さらに目立つのはコンソールに表示された目立つエラーメッセージです。このエラーメッセージは非常に重要で、ループで生成された各要素にはkey
が必要であることを意味します。これにより、React はリストの変更時にメンバーの追加、変更、削除の操作を識別でき、より良いパフォーマンスを発揮します。したがって、ここではmap
の 2 番目の引数を使用して対応するkey
を追加します:
function App () {
const books = ['データベース', 'データ構造', 'コンピュータネットワーク']
return (
<div>
<h3>私の本: </h3>
<ul>
{books.map((book, i) =>
<li key={i}>{book}</li>
)}
</ul>
</div>
)
}
コンソールにエラーが表示されなくなりました。
ここで、App
コンポーネント内のループリストは、より良い再利用性を実現するために、別のリストコンポーネントとして抽出するのが適切であることもわかります:
function BookList () {
return (
<ul>
{books.map((book, i) =>
<li key={i}>{book}</li>
)}
</ul>
)
}
function App () {
const books = ['データベース', 'データ構造', 'コンピュータネットワーク']
return (
<div>
<h3>私の本: </h3>
<BookList />
</div>
)
}
しかし、明らかに別の問題が発生しました。books
配列はApp
コンポーネント内で定義されているため、BookList
コンポーネントはその値をどのように取得するのでしょうか?
props#
上記の問題は、親子コンポーネント間で値をどのように渡すかということです。非常に直接的な考え方として、親コンポーネント内で子コンポーネントを配置する際に、いくつかのカスタムパラメータを渡すことができます:
function App () {
const books = ['データベース', 'データ構造', 'コンピュータネットワーク']
return (
<div>
<h3>私の本: </h3>
<BookList list={books} />
</div>
)
}
次に、BookList
子コンポーネント内で渡されたパラメータをキャッチします:
function BookList (props) {
console.log('props: ', props)
const books = props.list
return (
<ul>
{books.map((book, i) =>
<li key={i}>{book}</li>
)}
</ul>
)
}
ブラウザをリフレッシュします:
OK!これがprops
です。コンソールにこのオブジェクトを表示しましたので、異なるコンポーネント間でデータを渡す方法がわかります。
さて、新しい問題を考えてみましょう:現在のデータはただ静かに渡されているだけで、異なるコンポーネントは単にそれを表示するだけです。もし私たちがこれらのデータを追加または削除する必要がある場合、React はどのようにこれらのデータが変更されたことを知り、UI を即座に更新するのでしょうか?
クラスコンポーネント#
React が私たちに提供するもう一つのコンポーネント、すなわちクラスコンポーネントを知りましょう。
クラスコンポーネントは ES6 のclass
に由来しています。ここでは、App
コンポーネントをクラスコンポーネントに書き換える方法を見てみましょう:
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
books: ['データベース', 'データ構造', 'コンピュータネットワーク']
}
}
render () {
return (
<div>
<h3>私の本: </h3>
<BookList list={this.state.books} />
</div>
)
}
}
React.Component
は React が提供する一般的なクラスで、すべての React クラスに必要な実装の詳細をカプセル化しています。クラスコンポーネントはこれを継承して実現し、render
メソッドをオーバーライドして返すコンポーネント要素を定義します。
State#
元のbooks
配列は、constructor
コンストラクタ内に配置され、このクラスコンポーネントの内部状態として使用されます:
this.state = {
books: ['データベース', 'データ構造', 'コンピュータネットワーク']
}
state
はthis
を使用してクラスにバインドされており、コンポーネント全体でstate
にアクセスできます。コンポーネントのstate
を変更するたびに、コンポーネントのrender
メソッドが再度実行され、コンポーネントが再レンダリングされます。では、state
を直接変更することはできるのでしょうか?
React には 2 つの重要な原則があります:一つは単方向データフロー、もう一つは明確な状態の変更です。私たちが **state
を変更する唯一の方法はsetState()
を使用することです **。
コンポーネントはrender
内で最新のstate
の情報を取得してレンダリングし、View
層でsetState
を呼び出してstate
を更新し、その後コンポーネントは再度render
メソッドを実行し、画面を更新します。
試してみましょう:
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
books: ['データベース', 'データ構造', 'コンピュータネットワーク']
}
}
render () {
return (
<div>
<h3>私の本: </h3>
<BookList list={this.state.books} />
<button onClick={() => this.setState({ books: ['コンパイラ原理', 'オペレーティングシステム'] })}>変更</button>
</div>
)
}
}
イベント処理#
setState
に関連するロジックが複雑になると、異なるコンポーネント間でsetState
を呼び出す必要がある場合、再利用性とメンテナンス性の観点から、イベント処理をカスタム関数として抽出して呼び出す必要があります。React では、イベント処理関数のプレフィックスはすべてhandle
であり、リスナー関数のプレフィックスはon
です:
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
books: ['データベース', 'データ構造', 'コンピュータネットワーク'],
input: ''
}
this.handleAddBook = this.handleAddBook.bind(this)
this.handleRemoveBook = this.handleRemoveBook.bind(this)
this.updateInput = this.updateInput.bind(this)
}
handleAddBook () {
this.setState(currentState => {
return {
books: currentState.books.concat([this.state.input])
}
})
}
handleRemoveBook (name) {
this.setState(currentState => {
return {
books: currentState.books.filter(book => book !== name)
}
})
}
updateInput (e) {
this.setState({
input: e.target.value
})
}
render () {
return (
<div>
<h3>私の本: </h3>
<input
type="text"
placeholder="新しい本"
value={this.state.input}
onChange={this.updateInput}
/>
<button onClick={this.handleAddBook}>追加</button>
<BookList
list={this.state.books}
onRemoveBook={this.handleRemoveBook}
/>
</div>
)
}
}
handleAddBook
とhandleRemoveBook
は追加と変更の操作です。ここで特に強調すべきは、コンストラクタ内のこの 3 行のコードです:
this.handleAddBook = this.handleAddBook.bind(this)
this.handleRemoveBook = this.handleRemoveBook.bind(this)
this.updateInput = this.updateInput.bind(this)
カスタムクラスメソッド内でthis.setState
を呼び出したい場合、ここでのthis
はundefined
です。したがって、クラスコンポーネントのthis
がクラスメソッド内でアクセスできるようにするために、this
をクラスメソッドにバインドする必要があります。コンストラクタ内に配置すれば、バインドはコンポーネントがインスタンス化されるときに 1 回だけ実行され、パフォーマンスの消耗が少なくなります。
OK!実際にはここまでで、プロジェクトプレビューで表示される内容を基本的に完成させることができました。今、あなたはさらなる改善を試みて、以下の効果を達成してください:
もしあなたが完成したなら、以下のコードを参考にしてください:
function ActiveBooks (props) {
return (
<div>
<h2>読書中の本</h2>
<ul>
{props.list.map((book, i) => (
<li key={i}>
<span>{book.name}</span>
<button onClick={() => props.onRemoveBook(book.name)}>削除</button>
<button onClick={() => props.onDeactive(book.name)}>読了</button>
</li>
))}
</ul>
</div>
)
}
function InactiveBooks (props) {
return (
<div>
<h2>読了した本</h2>
<ul>
{props.list.map((book, i) => (
<li key={i}>
<span>{book.name}</span>
<button onClick={() => props.onActive(book.name)}>読書中</button>
</li>
))}
</ul>
</div>
)
}
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
books: [
{
name: 'データベース',
active: true
},
{
name: 'データ構造',
active: true
},
{
name: 'コンピュータネットワーク',
active: true
}],
input: ''
}
this.handleAddBook = this.handleAddBook.bind(this)
this.handleRemoveBook = this.handleRemoveBook.bind(this)
this.handleToggleBook = this.handleToggleBook.bind(this)
this.updateInput = this.updateInput.bind(this)
}
handleAddBook () {
this.setState(currentState => {
return {
books: currentState.books.concat([{
name: this.state.input,
active: true
}]),
input: ''
}
})
}
handleRemoveBook (name) {
this.setState(currentState => {
return {
books: currentState.books.filter(book => book.name !== name)
}
})
}
handleToggleBook (name) {
this.setState(currentState => {
const book = currentState.books.find(book => book.name === name)
return {
books: currentState.books.filter(book => book.name !== name)
.concat([{
name,
active: !book.active
}])
}
})
}
updateInput (e) {
this.setState({
input: e.target.value
})
}
render () {
return (
<div>
<h3>私の本: </h3>
<input
type="text"
placeholder="新しい本"
value={this.state.input}
onChange={this.updateInput}
/>
<button onClick={this.handleAddBook}>追加</button>
<button onClick={() => this.setState({ books: [] })}> すべてクリア </button>
<ActiveBooks
list={this.state.books.filter(book => book.active)}
onRemoveBook={this.handleRemoveBook}
onDeactive={this.handleToggleBook}
/>
<InactiveBooks
list={this.state.books.filter(book => !book.active)}
onActive={this.handleToggleBook}
/>
</div>
)
}
}
ここまで来ると、あなたは自分でいくつかの小さなデモを書いて慣れることができるでしょう。それでは、最後の質問を考えてみましょう:
- プロジェクトでは、多くの場合データがバックエンドとやり取りされます。つまり、非同期操作が行われ、データがまだリクエストされていないときにローディングスタイルを表示し、リクエストが完了した後に UI を更新したいと考えています。このようなロジックはどこに置くべきでしょうか?
ライフサイクル#
上記の問題に対して、私たちが実際に望むのは、コンポーネントが DOM にマウントされた後に画面をレンダリングし、同時に多くのコンポーネントを持つアプリケーションでは、コンポーネントが破棄されるときにその占有していたリソースを解放する必要があるということです。これが React のライフサイクルにおいて非常に重要な 2 つの関数、componentDidMount
とcomponentWillUnmount
です。
ライフサイクル関数の実行プロセスを全体的に感じてみましょう:
class App extends React.Component {
constructor (props) {
......
console.log('--constructor--')
}
componentDidMount () {
console.log('--componentDidMount--')
}
componentDidUpdate () {
console.log('--componentDidUpdate--')
}
componentWillUnmount () {
console.log('--componentWillUnmount--')
}
......
render () {
console.log('--render--')
return (
......
)
}
}
コンポーネントの全体的なライフサイクルは、constructor
--> render
--> componentDidMount
、その後コンポーネントが更新されると再度render
--> componentDidUpdate
、コンポーネントが破棄される前にcomponentWillUnmount
が呼び出されることがわかります。
次に、これらの関数を深く使用していきます。
まず、API を手動でシミュレートしてみましょう:
window.API = {
fetchBooks () {
return new Promise((res, rej) => {
const books = [
{
name: 'データベース',
active: true
},
{
name: 'データ構造',
active: true
},
{
name: 'コンピュータネットワーク',
active: false
}
]
setTimeout(() => res(books), 2000)
})
}
}
次に、componentDidMount
関数内でそれを呼び出します:
componentDidMount () {
console.log('--componentDidMount--')
API.fetchBooks()
.then(books => {
this.setState({
books
})
})
}
componentDidMount
の後にデータをリクエストし、その後render
が再度実行され、componentDidUpdate
が実行されるのがわかります。
ユーザー体験を向上させるために、ローディングのロジックを追加してみましょう:
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
books: [],
loading: true,
input: ''
}
......
console.log('--constructor--')
}
componentDidMount () {
console.log('--componentDidMount--')
API.fetchBooks()
.then(books => {
this.setState({
books,
loading: false
})
})
}
componentDidUpdate () {
console.log('--componentDidUpdate--')
}
componentWillUnmount () {
console.log('--componentWillUnmount--')
}
......
render () {
console.log('--render--')
if (this.state.loading === true) {
return <h2>ローディング中...</h2>
}
return (
......
)
}
}
OK!これで React 入門の全過程が終了しました。もちろん、プレビューの効果を完全に実現したわけではありませんが、Loading
コンポーネントを独自に封装することをお勧めします。最後に、さらなる開発操作について簡単にお話ししましょう。
もっと#
スキャフォールディング#
私たちの入門チュートリアルは、React を使用するための従来の外部リンク引き込み方式を使用しています。また、JSX を使用するために babel を引き込む必要があります。現代の Web 開発プロセスは、Webpackのモジュール化構築とデプロイプロセスに基づいています。実際のプロジェクトでは、公式のスキャフォールディングcreate-react-appを使用して一歩で構築し、依存関係のインストールと環境のデプロイプロセスを簡素化し、コードロジックの作成により多くの焦点を当てることが一般的に推奨されます。
状態管理とルーティング#
React の定義を覚えていますか?それはユーザーインターフェースの構築にのみ焦点を当てています。クラスコンポーネントを使用して一定の内部状態を管理できますが、プロジェクトがある程度複雑になると、外部の状態管理ライブラリを導入せざるを得ません。ここでは、React の理念に合ったReduxの使用をお勧めします。また、現在のシングルページアプリケーションではルーティング管理が必要ですので、React-Routerの使用をお勧めします。
最後に言いたいのは、フロントエンド技術は表面的には非常に速く進化していますが、内部の原理は基本的に変わらないということです。React は新しい革新的な開発方法をもたらしました。この出発点を基に、React の設計理念を結びつけて、さらに多くの特性を深く理解していくことを願っています。