この記事について
もうすぐ登場しそうな次期 React 17 の新機能 Time Slicing と Suspense を心待ちにしてる人、挙手して!
僕もその1人ですが、ネットサーフィン(死語?)してたら Building a Polyfill for React Suspense - Hacker Noon という記事(以下「元記事」。サスペンスチックなヘッダ画像が表示されますが怪しい記事ではありません)を見つけ、Suspense は React 16 でも簡単に実現できる、ということを知りまして、嬉しさのあまり記事にした次第です。
一緒に React Suspense を予習しましょう!
ずばりソースです
いきなりソースです。それほど簡単です。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>React Suspense Test @16</title>
</head>
<body>
<div id="app"></div>
</body>
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script>
// ms プロパティは 0 固定の Timeout コンポーネント
class Timeout extends React.Component {
constructor(props) {
super(props)
this.state = { didExpire: false }
this._suspender = null
}
componentDidCatch(err, info) {
if (typeof err.then !== 'function') throw err
const suspender = err
this._suspender = suspender
const update = () => {
if (this._suspender !== suspender) return
this.setState({ didExpire: false })
}
suspender.then(update, update)
this.setState({ didExpire: true })
}
render() {
return this.props.children(this.state.didExpire)
}
}
// 再描画時に WordPressRestApi が unmount されえるため、
// キャッシュは WordPressRestApi の外側に確保する必要がある
let cache = null
// WordPress の Rest Api を使用するコンポーネントの例
const WordPressRestApi = props => {
if (cache) return cache // cache をレンダリング
// throw Promise
throw (async() => {
let text
try {
const res = await fetch(
'https://demo.wp-api.org/wp-json/wp/v2/pages/2',
{mode: 'cors', credentials: 'include'},
)
const json = await res.json()
console.log(json)
text = json.title.rendered
} catch (e) {
text = e.message
}
// <div>{text}</div>
cache = React.createElement('div', null, text)
})()
}
// <Timeout>
// { f => f ? (<div>Now Loading...</div>) : (<WordPressRestApi />) }
// </Timeout>
const re = React.createElement(
Timeout,
null,
f =>
f ? React.createElement('div', null, 'Now Loading...')
: React.createElement(WordPressRestApi),
)
ReactDOM.render(re, document.getElementById('app'));
</script>
</html>
ずぼらによるずぼらのためのソースです。必要なのはお気に入りのエディタとお気に入りのブラウザ(IEを除く)だけです。React は難しく考えてはいけません。
Timeout コンポーネントは、簡潔にして理解しやすくするべく、元記事のものから ms プロパティ(指定時間内は「Now Loading」を表示しない機能)を取り除いたものです。このソースを理解できた後、元記事を参考に、同機能を組み込んでみるのも良いかもしれません。
このソースを理解できれば、React Suspense はもうあなたのものです!
・・・というかぶっちゃけ、このソースすら理解できないようでは真の Reactor にはなれませんよ!
おまけ
拙作 rx7.js を使用したソースも載せてきます。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>React Suspense Test @16</title>
</head>
<body>
<div id="app"></div>
</body>
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script crossorigin src="https://unpkg.com/rx7@latest/rx7.js"></script>
<script>
class Timeout extends React.Component {
constructor(props) {
super(props)
this.state = { didExpire: false }
this._suspender = null
}
componentDidCatch(err, info) {
if (typeof err.then !== 'function') throw err
const suspender = err
this._suspender = suspender
const update = () => {
if (this._suspender !== suspender) return
this.setState({ didExpire: false })
}
suspender.then(update, update)
this.setState({ didExpire: true })
}
render() {
return this.props.children(this.state.didExpire)
}
}
let cache = null
class WordPressRestApi extends React.Component {
constructor(props) {
super(props)
}
render() {
if (cache) return cache
throw (async() => {
let text
try {
const res = await fetch(
'https://demo.wp-api.org/wp-json/wp/v2/pages/2',
{mode: 'cors', credentials: 'include'},
)
const json = await res.json()
text = json.title.rendered
} catch (e) {
text = e.message
}
cache = rx7`<div>${text}</div>`
})()
}
}
const re = rx7`
<${Timeout}>
${f => f ? rx7`<div>Now Loading...</div>`: rx7`<${WordPressRestApi} />`}
</Timeout>
`
ReactDOM.render(re, document.getElementById('app'));
</script>
</html>
簡単ですね。僕がいうのもなんですが rx7.js はすごく便利ですのでぜひ使ってみてくださいね。
最後に
いかがでしたでしょうか。React 17 楽しみですね。
少しでも皆様の力になれればうれしいです。