1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ずぼらによるずぼらのための React Suspense

Last updated at Posted at 2018-09-23

この記事について

もうすぐ登場しそうな次期 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 楽しみですね。
少しでも皆様の力になれればうれしいです。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?