React.js v16.6.0 にて追加された新機能を試してみました。
環境づくり
サクッと、create-react-app
を利用しました。
$ npm -g install create-react-app $ npx create-react-app my-app $ cd my-app $ yarn start
初めて create-react-app
を利用したのですが、簡単すぎてビビりました…。React で何か試したい時は積極的に使っていきたいですね。
React.memo
プレゼンテーションコンポーネント(独自に state を保持したり、ライフサイクル関数を使うことなく、描画の行うだけのコンポーネント)を定義するする時は、クラスコンポーネントではなく SFC
を利用しています。
import React from 'react'; // ふつーの SFC const Hello = () => { return ( <div>Hello React v16.6!</div> ) }
しかし、最近業務で引っかかった問題として、SFC で不要な再レンダリングが起こっていました。
再レンダリングの原因は、空配列であった state に空配列を再代入していたことでした。値は変わっていないのに、参照が変わっていたため、SFC が props に更新ありと判定していました。
参考リンク: optimizing-performance - reactjs.org
そこで、PureComponent
への書き換えを行いました。
PureComponent
は props を shallow
比較するのでパフォーマンス向上に寄与しました。
React.memo
を使うと、SFC を PureComponent に書き換える事なく同様のことが実現できます。
参考リンク: React.memo - reactjs.org
import React from 'react'; // ふつーの SFC const Hello = (props) => { return ( <div>Hello React v16.6!</div> ) } // props を shallow 比較するコンポーネント const PureHello = React.memo(Hello)
クラスへの書き換えの手間がかからなくて良さげです。
React.Suspense と React.lazy
ここで、API フェッチを行い、そして待ち合わせを行うようなコンポーネントのケースを考えてみます。 従来ならこのように書いていました。
import React, { Component } from 'react'; // 与えられた Todo を表示する const Todo = (props) => { if(props.isLoading) { return <div>Loading...</div> } return ( <div>{ props.todo.title }</div> ) } // Todo のフェッチを行い、フェッチ状況も管理する class App extends Component { state = { isLoading: false, todo: null } componentDidMount() { this.setState({ isLoading: true }) fetch('https://jsonplaceholder.typicode.com/todos/1') .then(res => res.json()) .then(json => { this.setState({ isLoading: false, todo: json }) }) } render() { return ( <Todo isLoading={this.state.isLoading} todo={this.state.todo} /> ); } }
React.Suspense を使って書くと以下のようになります。(多分、細部では違った処理になっているかもしれません)
import React, { Component, Suspense } from 'react'; const Todo = (props) => { if(todo === null) { throw new Promise((resolve, reject) => { fetch(`https://jsonplaceholder.typicode.com/todos/${props.id}`) .then(res => res.json()) .then(json => { todo = json resolve() }) .catch(e => { console.warn("Not fetched yet") reject() }) }) } return ( <div>{ todo.title }</div> ) } class App extends Component { render() { return ( {/* Suspense にフェッチ完了までのリンダリングを引き受けさせる*/} <Suspense fallback={<div>Loading...</div>}> <Todos id={1} /> </Suspense> ); } }
参考URL: React 16.6で追加されたReact.Suspenseについて - qiita.com
さらに、この Suspense ですが、React.lazy を使った dynamic import
によって Code splitting を噛ませることもできるようです。
import React, { Component, Suspense, lazy } from 'react'; const Todo = lazy(() => import('path/to/Todo.jsx')) class App extends Component { render() { return ( <Suspense fallback={<div>Loading...</div>}> <Todo id={1} /> </Suspense> ); } } export default App;
SSRではまだ使えないようですが…。
参考URL:
static contextType
Context は以前から実装されていた機能ですが(実はあんまり使ったこと無い)、 クラスの static 変数として context にアクセスできるようになったみたいです。
import React, { Component } from 'react'; const MyContext = React.createContext("default value"); class MyClass extends React.Component { static contextType = MyContext; render() { console.log(this.context) // "default value" return ( <div>{ this.context }</div> ) } }
参考URL: static contextType - reactjs.org
static getDerivedStateFromError()
従来の componentDidCatch
に加えて、 getDerivedStateFromError
が追加されました。
componentDidCatch の問題点は、SSR に対応していないことでした。 getDerivedStateFromError は将来的には SSR 対応されるようなので、ユニバーサルなアプリケーションには役立ちそうです。
参考URL: static getDerivedStateFromError()
以上、 React v16.6 で追加された機能について試したり、調べたりしてみました。