deve.K

エンジニアが未来を切り開く。

React コンポーネントを強制的に再レンダリングする方法

Reactコンポーネントの強制的に再レンダリング

Reactは、PropsまたはStateが更新されるたびに、コンポーネントを自動的に再レン​​ダリングします。

どちらも変更されない場合は、再レンダリングは発生しません。

しかし、初心者はコンポーネントを再レンダリングするのが非常に難しいと感じることがよくあります。

まず、コンポーネントを再レンダリングするために使用できる方法を見て、本当に再レンダリングを強制するべきか、 Reactに処理させるべきかについて説明します。

Reactコンポーネントを再レンダリングさせる方法とタイミングを真に理解するには、Reactの内部動作を理解する必要があります。

つまり、コンポーネントを強制的に再レンダリングするべきかどうかを学習すれば、Reactの内部動作を理解するのに非常に役立ちます。

Reactコンポーネントを強制的に再レン​​ダリングする必要はあるのか?

ほとんどの場合、再レンダリング強制しないでください。

公式のReact Docsに記載されているように、Stateは不変として扱われなければいけません。

通常では、コンポーネントを強制的に再レン​​ダリングすることは嫌われており、Reactでの自動再レンダリングの失敗は、多くの場合ではコードベースの根本的なバグが原因となっています。

その、バグが原因な一般的な例を示します。

Reactの状態が正しく更新されない

例えば、useStateフックを使用しボタンをクリックしたらユーザーデータの状態を更新する場合です。

const App = () =>  {
  const [user, setUser] = useState({ name: "", age: 0, });

  const updateUser = () => {
    user.name = "Taro";
    setUser(user)
  }

  return (
    <div>
      <h1>Name: {user.name}</h1>
      <button onClick={updateUser}>
       Click me
      </button>
    </div>
  );
}

上記の場合では、ボタンがクリックされてもコンポーネントはユーザーの名前を再レンダリングする事はありません。

Reactでは、状態の現在の値新しい値の両方が同じオブジェクトを参照しているかどうかが確認されます。

ユーザーオブジェクトのプロパティの1つを更新しようしています、nameプロパティです。

しかし、setUserは同じオブジェクト参照を作成したため、Reactはその状態の変化を認識しません。

この問題を解決する方法はあります。

更新された値で新しいオブジェクトを作成できます。

つまり、スプレッド構文(...)を使用し、状態オブジェクトのコピーを作成します。

  const updateUser = () => {
    setUser({
      ...user,
      name: "Taro",
    })
  }

これで、誤って更新された状態を再レンダリングできるようになります。

See the Pen React 強制再レンダリング useCallback and useStateフック 例1 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.

不適切に更新されたProps

状態を変更せずにPropsを誤って更新すると、通常ではバグが発生します。

let time = new Date();

const App = () =>  {
useEffect(() => {
const intervalId = setInterval(() => {
time = new Date()
}, 1000);
    return () => clearInterval(intervalId);
  }, []);

return (
    <Clock time={time} />
  );
}

上記では、初回ロード後にClockは更新される事はありません。

正しく更新するには、毎秒のランタイムで変数であるtimeを更新してあげなければいけません。

setInterval(() => {
   console.log(time)

 }, 1000);

こうする事で変数のtimeを更新し、レンダリングのためにClockコンポーネントに渡されるようになります。

状態が変化すると、Appである親コンポーネントが再レンダリングされ、その結果Clockの子コンポーネントにも更新された時刻で再レンダリングがトリガーされます。

このように、状態を更新することが再レンダリングの実際のトリガーとなり、それがpropsを通じて伝搬されます。

つまり、Reactでは状態を更新することは非常に重要となっています。

さらに、上記のコードを修正した下記のような方法も可能です。

useEffectとuseStateフックを使用します。

const App = () =>  {
const [time, setTime] = useState(new Date());

useEffect(() => {
const intervalId = setInterval(() => {
setTime(new Date());
 }, 1000);
    console.log(time)
return () => clearInterval(intervalId)
}, [])
return (
    <div>
    </div>
  );
}

出力は下記で確認が可能です。

https://codepen.io/enjinia_f/pen/YzarXQw/?editors=1011

それでは、コンポーネントを強制的に再レンダリングする方法です。

デフォルトの自動レンダリング動作は、複雑なReactアプリケーションを構築して実行するには十分すぎるほどです。

しかし、開発者はまれにデフォルトの動作をバイパスして、コンポーネントを手動で再レンダリングする必要がある場合があります。

バイパスは交通ではありません。

IT用語でのバイパスとは、ネットワーク機器などに不具合が生じた場合やアプリの不良またはリンク落ちなどが発生した際に、それらを回避する為に正常動作を維持していく事です。

まずクラスコンポーネントでは、forceupdateメソッドを呼び出して強制的に再レン​​ダリングするオプションがあります。

ただし、関数コンポーネントでは、同等のものがないため、強制的に更新される可能性はありませんが、ある方法を使用して強制的に更新する事が可能です。

クラスコンポーネントでの解説は、当記事では深掘り致しませんが、クラスコンポーネントで強制的に再レンダリングする方法を下記に示します。

クラスコンポーネントの更新を強制する

class App extends React.Component {
constructor(props) {
 super(props);
  this.state = {
   counter: 0
};
this.changeClick = this.changeClick.bind(this)
}

changeClick = () => {
//強制的に再レンダリング
this.forceUpdate()

this.state.counter = + 1
}
render() {
 return (
<div>
 <p>
counter: {this.state.counter}.
 </p>
<button 
type="button" 
onClick={this.changeClick}>Change
</button>
</div>
    );
  }
}

上記の例では、コンポーネントがマウントされたときの状態が更新されます。

See the Pen React クラス強制再レンダリング by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


setState()を使用せず、ボタンをクリックするとReactのforceUpdate()メソッドで0から1へ強制的に再レンダリングさせている事が確認できます。

stateコンポーネントまたはpropsの変更が行われるたびに自動的にトリガーされます。

このように、クラスコンポーネントでは再レンダリングをトリガーするための組み込みメソッドを提供しています。

ですがこの方法はお勧めできません。

新しいレンダリングを作成するには、常にPropsとStateを使用する必要があります。

基本的には、テストやその他のエッジケースでの使用となります。

Reactでの関数コンポーネントを強制的に再レン​​ダリングする必要がある場合は、いくつかの方法があります。

関数コンポーネントの更新を強制する

先述した通り、クラスでは専用のメソッドがありますが、関数コンポーネントを強制的に再レンダリングするための公式APIも、専用のReactフックもありません。

関数コンポーネントでは、インスタンスthisがないので、this.forceUpdate()を呼び出すことができません。

ただし、コンポーネントを更新する必要があることをReactに通知するための巧妙なトリックがいくつかあります。

関数コンポーネントは、状態を維持しuseEffect()フックを使用して副作用を処理することができるようになりました。

useStateuseReducerまたはuseCallbackフックを使用して、Reactコンポーネントを強制的に再レンダリングさせることができます。

それぞれのフックがどのように、機能していくのかを見ていきましょう。

まずはuseStateフックでの強制再レンダリングの例を下記に示します。

カスタムフックを作成していきます。

useState()は、2つの配列を返します、現在の状態を表す値とそのセッターです。

値を更新するには、そのセッターを使用します。

セッターで値を更新すると、関数コンポーネントは強制的に再レンダリングされます。

// custom hook

const useForceUpdate = () => {
const [count, setCount] = useState(0); 
return () => setCount(e => count + 1); 
}

let value = 0;

const App = () => {

const forceUpdate = useForceUpdate();
    
return (
<div>
<h1>Count: {value++}</h1>
<button onClick={forceUpdate}>
 re-render
</button>
</div>
    );
}

上記では、ブール値ではなく数値を使用しております。

useStateを利用したカスタムフック関数である、『useForceUpdate』となります。

コンポーネントのStateの値をインクリメントし、Reactにコンポーネントを強制的に再レンダリングするように指示しています。

See the Pen React 強制再レンダリング useStateフック by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


では、useReducerフック単体での動作を見てみましょう。

const App = () => {
const [, updateState] = useReducer((x) => x + alert(1, 0))

return (
<div className="App">
<button onClick={() => updateState()}>
Force Update
</button>
</div>
  );
}


useReducerフックの配列内が普段とは、異なる扱い方を確認できると思います。

useReducer()フックを使用して更新を強制するだけなので、これは状態を格納するための変数を設定することもしていません。

配列内の2番目は関数です。

onClick属性を持つシンプルなボタンを用意し、ボタンがクリックされるたびにupdateState関数を実行しています。

See the Pen React 強制再レンダリング useReducer by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


公式のReact Docsでも、カウンターを使用し状態が変化していない場合でも再レンダリングを強制することが可能な事が記載されております。

フックに関するよくある質問 – React

useCallbackおよびuseStateフック

状態に新しいオブジェクトを作成するという、興味深い方法です。

const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);

forceUpdate関数をメモ化するためにuseCallbackフックを使用しております。

ですので、コンポーネントのライフサイクルを通じて一定に保ち、子のコンポーネントにpropsとして安全に渡せるようにします。

const App = () => {

const [, updateState] = useState();

const forceUpdate = useCallback(() => updateState({}), []);

 console.log("rendering...");

return (
<div className="App">
<button onClick={forceUpdate}>
Click me
</button>
   </div>
 );
}

ボタンをクリックするたびに、コンポーネントが強制的に再レンダリングされるようになりました。

See the Pen React 強制再レンダリング useReducer フック by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


しかしこのアプローチは再検討する必要があり、ほとんどの場合では、更新を強制する必要がある場合は、おそらく何か間違ったことをしていることに注意してください。

他の例でも見てみましょう。

必要に応じて、updateState関数にコールバックを渡すことも可能です。

const  App = () => {
const [, updateState] = useState(0);
const forceUpdate = useCallback(() => updateState((x) => x + console.log("rendering...")), []);

return (
<div className="App">
<button onClick={() => forceUpdate()}>
Click me
</button>

</div>
  );
}

updateState関数にコールバックを渡しています。

状態の更新もトリガーされるため、再レンダリングが行われます。

See the Pen React 強制再レンダリング コールバックバック by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.

最後に

状態を更新することで、Reactフックで作成された関数コンポーネントを強制的に再レン​​ダリング可能です。

しかし一般的には、Reactコンポーネントの再レンダリングを強制することはベストプラクティスではありません。

Reactがコンポーネントの自動再レンダリングに失敗した場合は、プロジェクトの根本的な問題としてコンポーネントの正しい更新を妨げている可能性があります。

レンダリングを強制することを検討する前に、あなたはコードを分析する必要があります。

当記事では、Reactコンポーネントを強制的に再レン​​ダリングする方法とReactコンポーネントの再レンダリングに失敗する例のいくつかを説明しました。

本日は以上となります。

最後までこの記事を読んで頂きありがとうございます。

プライバシーポリシー