Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
21
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@kanye__east

【必読】React開発で役に立つTips 8選

以下は、jsmanifestさんの記事、8 Useful Practices for React Apps You Should Knowの日本語訳です。

【必読】Reactアプリで役に立つプラクティス8選(8 Useful Practices for React Apps You Should Know)

mediumで私を見つけてくださいね。

Reactはとても多くのステージへの変化を行ってきました。そしてそれらは、必ず私達を驚かせます。

最初、私達はmixinsを使って、インターフェースを作ったり管理したりしていました。そして次にクラスコンポーネントというコンセプトが到来し、現在は、Reactでの私達の開発方法を変えるreact hooksです。

他になにか素晴らしいことを知っていますか? 例えば、Reactで使える、アプリをより良くするのに役立つ巧みなトリックです。

この記事では、全てのReactデベロッパーが知っておくべき8つの高度なトリックを紹介します。このリストの全てがあなたにとって目新しいものではないと思いまが、最低でも1つ、便利なトリックを見つけることを願っています。

これが、あなたが知っておくべきreactで使える8つのトリックです。

1. 文字列でReact要素を生成する(Create react elements with strings)

まず1つ目は、HTML DOMタグを表すシンプルな文字列でReact DOM要素を作る方法です。

例えば、変数にdivという文字列を代入することで、Reactコンポーネントを作ることができます。


import React from 'react'

const MyComponent = 'div'

function App() {
  return (
    <div>
      <h1>Hello</h1>
      <hr />
      <MyComponent>
        <h3>I am inside a {'<div />'} element</h3>
      </MyComponent>
    </div>
  )
}

Reactは、React.createElementを呼び、与えられた文字列を使って、内部で要素を生成します。これってクールでしょ?

Material-UIのようなコンポーネントライブラリでよく使われます。コンポーネントのルートnodeを決めるcomponentpropsを宣言できます。


function MyComponent({ component: Component = 'div', name, age, email }) {
  return (
    <Component>
      <h1>Hi {name}</h1>
      <div>
        <h6>You are {age} years old</h6>
        <small>Your email is {email}</small>
      </div>
    </Component>
  )
}

このように使用することができます。

function App() {
  return (
    <div>
      <MyComponent component="div" name="George" age={16} email="george@gmail.com">
    </div>
  )
}

カスタムコンポーネントを渡すこともできます。


function Dashboard({ children }) {
  return (
    <div style={{ padding: '25px 12px' }}>
      {children}
    </div>
  )
}

function App() {
  return (
    <div>
      <MyComponent component={Dashboard} name="George" age={16} email="george@gmail.com">
    </div>
  )
}

2. Error Boundariesを使う(Use Error Boundaries)

JavaScriptでは、私達はほとんどのエラーをtry/catch ー発生したエラーを"catch"できるコードのブロックー で対応しています。エラーがcatchブロックに補足された時、私達はアプリがクラッシュするのを避けることができます。

例はこのようなものです。

function getFromLocalStorage(key, value) {
  try {
    const data = window.localStorage.get(key)
    return JSON.parse(data)
  } catch (error) {
    console.error
  }
}

Reactは結局ただのJavaScriptなので、エラーをキャッチしてハンドルできると考えるでしょう。しかし、Reactの性質上、コンポーネント内でのJavScriptエラーは内部のstateを破壊し、将来のレンダーにおいてcryptic errors(不可解なエラー)を引き起こします。

このような理由で、ReactチームはError Boundariesを紹介しています。そして全てのReactデベロッパーがそれらを知っておくべきです。そうすれば自らのReactアプリで使用することができます。

Error Boundaries登場以前のエラー発生の問題点は、これらの不可解なエラーが発生した時、Reactはそれらに対処したり、リカバリーする方法を私達に与えていなかったことです。ですので、私達は皆Error Boundariesが必要なのです。

Error Boundariesは、Reactコンポーネントです。コンポーネントツリーのどこででもエラーをキャッチすることができ、ログ出力し、クラッシュしたコンポーネントツリーの代わりにフォールバック用UIを表示させることができます。Error Boundariesは、レンダー中、ライフサイクルメソッド内、そしてError Boundaries以下に存在する全体のツリーのコンストラクタ内でエラーをキャッチします(そしてこれが、私達のアプリのトップのどこかでError Boundariesを宣言しレンダーする理由です)。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true }
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>
    }

    return this.props.children
  }
}

そして、普通のコンポーネントと同じように使用できます。

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

3. 前回の値を保持する(Retain Previous Values)

propsやstateを更新している時、あなたはReact.useRefを使ってそれらの前回の値を保持することができます。

例えば、1つの配列のアイテムの、現在と前回の変更を追跡するために、前回の値が割り当てられるReact.useRefと、現在の値が割り振られるReact.useStateを使用することができます。

function MyComponent() {
  const [names, setNames] = React.useState(['bob'])
  const prevNamesRef = React.useRef([])

  React.useEffect(() => {
    prevNamesRef.current = names
  })

  const prevNames = prevNamesRef.current

  return (
    <div>
      <h4>Current names:</h4>
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
      <h4>Previous names:</h4>
      <ul>
        {prevNames.map((prevName) => (
          <li key={prevName}>{prevName}</li>
        ))}
      </ul>
    </div>
  )
}

これは正しく動作します。なぜならReact.useEffectはレンダーが完了した後に実行されるからです。

setNamesが実行される時、コンポーネントは再度レンダーを行い、prefNamesRefは前回の値を保持します。なぜなら、前回のレンダーから見てReact.useEffectが最後に実行されるコードだからです。
そしてuseEffect内でprevNamesRef.currentに値が再び代入されるので、次回のレンダー時には前回のnamesの値を保持します。

4. 値チェックにReact.useRefを使う(Use React.useRef for flexible non-stale value checks)

React Hooksが登場する以前、ComponentがDOMにマウントされたに、dataのフェッチなどの処理を行いたい場合、私達はcomponentDidMountというクラスコンポーネントのStaticメソッドを使用していました。

React Hooksが発表された時、それはすぐにコンポーネントを書く最も人気の手段となりました。コンポーネントがアンマウントされた後にstateがセットされるのを防ぐために、コンポーネントがマウントされたかを監視したい時、私達はこのようにするでしょう。

import React from 'react'
import axios from 'axios'

class MyComponent extends React.Component {
  mounted = false

  state = {
    frogs: [],
    error: null,
  }

  componentDidMount() {
    this.mounted = true
  }

  componentWillUnmount() {
    this.mounted = false
  }

  async fetchFrogs = (params) => {
    try {
      const response = await axios.get('https://some-frogs-api.com/v1/', { params })
      if (this.mounted) {
        this.setState({ frogs: response.data.items })
      }
    } catch (error) {
      if (this.mounted) {
        this.setState({ error })
      }
    }
  }

  render() {
    return (
      <div>
        <h4>Frogs:</h4>
        <ul>
        {this.state.frogs.map((frog) => <li key={frog.name}>{frog.name}</li>
        )}
        </ul>
    </div>
    )
  }
}

React Hooksに統合後、HooksはcomponentDidMountを持っていません。そして、アンマウント後に起こったstateアップデートによるメモリリークのコンセプトは未だにhooksに当てはまります。しかし、React Hooksを使ったcomponentDidMountに似た方法はReact.useEffectを使用することです。なぜなら、それはコンポーネントがレンダリングされた後に実行されるからです。もしあなたがReact.useRefを使って、マウントされた値を割り当てるなら、以下のクラスコンポーネントの例で同じことができます。

import React from 'react'
import axios from 'axios'

function MyComponent() {
  const [frogs, setFrogs] = React.useState([])
  const [error, setError] = React.useState(null)
  const mounted = React.useRef(false)

  async function fetchFrogs(params) {
    try {
      const response = await axios.get('https://some-frogs-api.com/v1/', {
        params,
      })
      if (mounted.current) {
        setFrogs(response.data.items)
      }
    } catch (error) {
      if (mounted.current) {
        setError(error)
      }
    }
  }

  React.useEffect(() => {
    mounted.current = true

    return function cleanup() {
      mounted.current = false
    }
  }, [])

  return (
    <div>
      <h4>Frogs:</h4>
      <ul>
        {this.state.frogs.map((frog) => (
          <li key={frog.name}>{frog.name}</li>
        ))}
      </ul>
    </div>
  )
}

リレンダーをせず最新の変更をトラッキングするもう一つ別の例は、以下のように、React.useMemoと併せて使用することです。(source)

function setRef(ref, value) {
  // Using function callback version
  if (typeof ref === 'function') {
    ref(value)
    // Using the React.useRef() version
  } else if (ref) {
    ref.current = value
  }
}

function useForkRef(refA, refB) {
  return React.useMemo(() => {
    if (refA == null && refB == null) {
      return null
    }
    return (refValue) => {
      setRef(refA, refValue)
      setRef(refB, refValue)
    }
  }, [refA, refB])
}

もしref propsが変更され、定義されたら、新たな関数を作ります。
つまり、Reactは古いフォークされたrefをnull、新しいフォークされたrefを現在のrefで呼ぶことを意味します。そしてReact.useMemoが使われているので、refの変数たちはrefAまたはrefBのref propsが変更されるまで記憶されます。ナチュラルなクリーンアップが起こるのです。

5. 他の要素に依存したカスタム要素にReact.useRefを使う(Use React.useRef for customizing elements that depend on other elements)

React.useRefにはいくつか役に立つユースケースがあります。例えば、React.useRef自体をref propsに割り当てるなどです。

function MyComponent() {
  const [position, setPosition] = React.useState({ x: 0, y: 0 })
  const nodeRef = React.useRef()

  React.useEffect(() => {
    const pos = nodeRef.current.getBoundingClientRect()
    setPosition({
      x: pos.x,
      y: pos.y,
    })
  }, [])

  return (
    <div ref={nodeRef}>
      <h2>Hello</h2>
    </div>
  )
}

もしdiv要素の座標位置を取得したいなら、この例が役に立ちます。しかし、アプリケーションのどこかに存在する別の要素の位置をアップデートしたかったり、それに応じて条件ロジックを変更したり適応する場合、ベストな方法はref callback function patternを使うことです。callback function patternを使用すると、ReactコンポーネントまたはHTML DOM要素を第一引数として受け取ります。

下の例は、コールバック関数であるsetRefがどこでref propsに適応されるかを示すシンプルなものです。直接React.useRefをDOM要素に割り当てるのとは対象的に、setRefの内側では必要なことは何でもできるということがわかります:

const SomeComponent = function({ nodeRef }) {
  const ownRef = React.useRef()

  function setRef(e) {
    if (e && nodeRef.current) {
      const codeElementBounds = nodeRef.current.getBoundingClientRect()
      // Log the <pre> element's position + size
      console.log(`Code element's bounds: ${JSON.stringify(codeElementBounds)}`)
      ownRef.current = e
    }
  }

  return (
    <div
      ref={setRef}
      style={{ width: '100%', height: 100, background: 'green' }}
    />
  )
}

function App() {
  const [items, setItems] = React.useState([])
  const nodeRef = React.useRef()

  const addItems = React.useCallback(() => {
    const itemNum = items.length
    setItems((prevItems) => [
      ...prevItems,
      {
        [`item${itemNum}`]: `I am item # ${itemNum}'`,
      },
    ])
  }, [items, setItems])

  return (
    <div style={{ border: '1px solid teal', width: 500, margin: 'auto' }}>
      <button type="button" onClick={addItems}>
        Add Item
      </button>
      <SomeComponent nodeRef={nodeRef} />
      <div ref={nodeRef}>
        <pre>
          <code>{JSON.stringify(items, null, 2)}</code>
        </pre>
      </div>
    </div>
  )
}

6. 高階コンポーネント(Higher Order Components)

プレインなJavaScriptで強力な再利用性を持つ関数を作るよくあるパターンは高階関数です。Reactも結局はJavaScriptなので、内部で高階関数を使用することができます。

再利用性のあるコンポーネントには、高階コンポーネントとというトリックが使えます。

高階コンポーネントとは、あなたがコンポーネントを引数にして、返り値としてコンポーネントを返す関数を持っているときのことです。高階関数がロジックを抽象化し、他の関数間で共有されるように、高階コンポーネントもコンポーネントをロジックから切り離し、他のコンポーネント間で共有することができます。つまり、たくさんの再利用可能なコンポーネントを採用し、アプリケーション内で実際に繰り返し使うことが可能だということなのです。

これは高階コンポーネントの例です。このスニペットでは、高階コンポーネントであるwithBorderはカスタムコンポーネントを引数として取り込み、隠されたミドルレイヤーコンポーネントを返します。そして、親がこの高階コンポーネントをレンダーするとき、これはコンポーネントとして呼ばれ、ミドルレイヤーコンポーネントからから渡されたpropsを受け取ります。

import React from 'react'

// Higher order component
const withBorder = (Component, customStyle) => {
  class WithBorder extends React.Component {
    render() {
      const style = {
        border: this.props.customStyle
          ? this.props.customStyle.border
          : '3px solid teal',
      }
      return <Component style={style} {...this.props} />
    }
  }

  return WithBorder
}

function MyComponent({ style, ...rest }) {
  return (
    <div style={style} {...rest}>
      <h2>This is my component and I am expecting some styles.</h2>
    </div>
  )
}

export default withBorder(MyComponent, {
  border: '4px solid teal',
})

7. Render Props

React内で使う私のお気に入りのトリックの一つは、render prop patternです。複数のコンポーネントでコードを共有するという問題を解決する点において、これは高階コンポーネントに似ています。Render propsは、レンダーに必要なものを全て差し戻すことが目的の関数をはっきりとさせます。

Reactでコンポーネントをレンダーする最もベーシックな方法は、このようなものでしょう。

function MyComponent() {
  return <p>My component</p>
}

function App() {
  const [fetching, setFetching] = React.useState(false)
  const [fetched, setFetched] = React.useState(false)
  const [fetchError, setFetchError] = React.useState(null)
  const [frogs, setFrogs] = React.useState([])

  React.useEffect(() => {
    setFetching(true)
    api
      .fetchFrogs({ limit: 1000 })
      .then((result) => {
        setFrogs(result.data.items)
        setFetched(true)
        setFetching(false)
      })
      .catch((error) => {
        setError(error)
        setFetching(false)
      })
  }, [])

  return (
    <MyComponent
      fetching={fetching}
      fetched={fetched}
      fetchError={fetchError}
      frogs={frogs}
    />
  )
}

Render Propsを使う場合は、その子をレンダーするpropは慣例的にrenderと呼ばれます。

function MyComponent({ render }) {
  const [fetching, setFetching] = React.useState(false)
  const [fetched, setFetched] = React.useState(false)
  const [fetchError, setFetchError] = React.useState(null)
  const [frogs, setFrogs] = React.useState([])

  React.useEffect(() => {
    setFetching(true)
    api
      .fetchFrogs({ limit: 1000 })
      .then((result) => {
        setFrogs(result.data.items)
        setFetched(true)
        setFetching(false)
      })
      .catch((error) => {
        setError(error)
        setFetching(false)
      })
  }, [])

  return render({
    fetching,
    fetched,
    fetchError,
    frogs,
  })
}

この例では、MyComponentrender prop componentとして参照するコンポーネントの例です。なぜなら、MyComponentはpropとしてのrenderを期待していますし、子をレンダーするためのそれを実行するからです。
これはReactにおいて、とてもパワフルなパターンです。renderのコールバックを通して引数として、共有されたstateとdataを渡すことができるのです。複数のコンポーネントにおいて、そのコンポーネントはリレンダーされ、再利用されることが可能になります。

function App() {
  return (
    <MyComponent
      render={({ fetching, fetched, fetchError, frogs }) => (
        <div>
          {fetching
            ? 'Fetching frogs...'
            : fetched
            ? 'The frogs have been fetched!'
            : fetchError
            ? `An error occurred while fetching the list of frogs: ${fetchError.message}`
            : null}
          <hr />
          <ul
            style={{
              padding: 12,
            }}
          >
            {frogs.map((frog) => (
              <li key={frog.name}>
                <div>Frog's name: {frog.name}</div>
                <div>Frog's age: {frog.age}</div>
                <div>Frog's gender: {frog.gender}</div>
              </li>
            ))}
          </ul>
        </div>
      )}
    />
  )
}

8. Memoize

Reactデベロッパーとして知っておくべき最も大事なことの一つは、React.memoのようなコンポーネントのパフォーマンス最適化です。それを知っておくと、無限ループのようなとてもひどいエラーを防ぐことができます。

Reactアプリにおいて、Memoization(メモ化)を行う、いくつかの方法があるので是非読んでみてください。

締め

ここでこの投稿は終わりです!この記事があなたにとって価値があることを願います。

Mediumも見てくださいね。

感想

まず、今回の記事は翻訳が難しかったです。英語もさることながら、自分のReact Hooksの理解が浅いなと実感しました。
逆にReact HooksやよりReactの理解が進めば、これらのトリックはとても役に立ちそうだと思いました。

間違い等見つけましたら教えていただけるとありがたいです。

21
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
21
Help us understand the problem. What is going on with this article?