Reactでconsole.logの実行を2回ではなく1回にする方法

Reactでconsole.logが2回実行される

ReactではApp.tsx内などでconsole.logが2回実行されることがあります。

例えば以下のコマンドでReactの環境を構築したあと、App.tsxのApp()内にconsole.log(count)を記述して実行すると、2回実行されていることが確認できます。

npm create vite@latest my-react-ts -- --template react-swc-ts
App.tsx
function App() {
  const [count, setCount] = useState(0)
  console.log(count)
  // 中略
}

2回実行される原因は<StrictMode>

<StrictMode>は開発環境においてコンポーネントの一般的なバグを早期に見つけるのに役立ちます。

<StrictMode> – React

Strict Modeは常にレンダー関数を2回呼び出すため、console.logも2回実行されます。

逆に言うとStrict Modeでなければ、レンダー関数の実行は1回なので、main.tsx内の<React.StrictMode>を削除すれば、console.logの実行も1回になります。

main.tsx
ReactDOM.createRoot(document.getElementById('root')!).render(
  /*<React.StrictMode>
    <App />
  </React.StrictMode>*/
  <App />
)

しかし、最初に書いたように<StrictMode>はバグを早期に見つけるためのものなので、<StrictMode>を削除するのは好ましくないです。

そのため、この記事では他の2つの方法について説明します。

React Developer Toolsをインストールする

React Developer Toolsはブラウザの拡張機能で、Reactのコンポーネントを調査し、propsやstateを編集し、パフォーマンスの問題を特定できます。

React Developer Toolsをインストールしていると、Strict ModeによるConsoleの2回目の結果はグレー色で表示されるので、違いがわかりやすくなります。

React Developer Tools – React

consoleOnce関数を作成する

console.logの引数が同じ場合は実行しない、consoleOnce関数を作成すると、console.logを2回実行させないことができます。

関数の作り方は、引数を貯めるための配列を作成して、初めて取得した値なら配列に入れて、すでに配列にあればconsole.logを実行しないようにします。

ネット上にはisConsole: booleanのような判定でconsole.logを1回だけ実行しているコードもありますが、以下のように関数にしたほうが使い回せて便利です。

App.tsx
const consoleExecuted: any[] = []

function consoleOnce(val: any): void {
  if (!consoleExecuted.includes(val)) {
    console.log(val)
    consoleExecuted.push(val)
  }
}

function App() {
  const [count, setCount] = useState(0)
  consoleOnce(count)
  // 中略
}
React consoleOnce関数を作成する

例として以下のようなコードも書きました。

引数の値が同じfuncA(10)は1回だけ実行されていることがわかります。

Sample.tsx
const consoleExecuted: any[] = []

function consoleOnce(val: any): void {
  if (!consoleExecuted.includes(val)) {
    console.log(val)
    consoleExecuted.push(val)
  }
}

function Sample() {
  const funcA = (num: number): void => {
    consoleOnce(num)
  }

  const funcB = (num: number): void => {
    consoleOnce(num * 2)
  }

  const funcC = (str: string): void => {
    consoleOnce(str + ' world!')
  }

  funcA(10)
  funcB(7)
  funcA(10)
  funcC('Hello')

  return <h1>Sample</h1>
}

export default Sample
ConsoleOnce関数実行結果

もし同じ引数のconsole.logを実行したい場合は、引数にidを追加して実行できるように機能を拡張すると良いでしょう。

Sample.tsx
const consoleExecuted: any[] = []

function consoleOnce(val: any, id?: number): void {
  if (!consoleExecuted.includes(`${id}:${val}`)) {
    console.log(val)
    consoleExecuted.push(`${id}:${val}`)
  }
}

function Sample() {
  const funcA = (num: number, id: number): void => {
    consoleOnce(num, id)
  }

  const funcB = (num: number): void => {
    consoleOnce(num * 2)
  }

  const funcC = (str: string): void => {
    consoleOnce(str + ' world!')
  }

  funcA(10, 1)
  funcB(7)
  funcA(10, 2)
  funcC('Hello')

  return <h1>Sample</h1>
}

export default Sample