🪝

よく聞く副作用とは何か?

2024/03/14に公開

Tips💡

副作用(Side Effects)とは、プログラムの状態を変更する操作や、プログラム外部との相互作用を指します。例えば、APIからデータをフェッチする、タイマーを設定する、ログをコンソールに出力する、ドキュメントのタイトルを変更するなどが副作用に該当します。

Reactでは、副作用はuseEffectフックを通じて管理されます。useEffect内部で定義された関数は、コンポーネントのレンダリング後に実行されます。また、useEffectはクリーンアップ関数を返すことができ、これは副作用が不要になったとき(例えば、コンポーネントがアンマウントされたとき)に実行されます。

🫛豆知識

  • マウント(Mount):
    コンポーネントがDOMに初めて描画されることをマウントと言います。これは、コンポーネントが初めて作成され、そのレンダーメソッドが呼び出され、結果がDOMに挿入されるときに発生します。

  • アンマウント(Unmount):
    コンポーネントがDOMから削除されることをアンマウントと言います。これは、コンポーネントがもはや必要とされないとき、または親コンポーネントが再レンダリングされて子コンポーネントが削除されるときに発生します。

  • クリーンアップ(Cleanup):
    コンポーネントがアンマウントされるとき、または特定の副作用が再実行される前に、以前の副作用からのクリーンアップが必要な場合があります。これは、リソース(例えば、タイマーや外部データの購読)を解放するために行われます。ReactのuseEffectフックでは、クリーンアップ関数を返すことでこれを行うことができます。

import { useState, useEffect } from 'react';

export default function App() {
  // `useState`フックを使用して、`count`という名前の状態変数と、その更新関数`setCount`を定義します。
  // 初期値は0です。
  const [count, setCount] = useState<number>(0);

  // `useEffect`フックを使用して、副作用を実行します。この副作用は、`count`の値が変更されるたびに実行されます。
  useEffect(() => {
    // `count`の値が変更されるたびに、ドキュメントのタイトルを更新します。
    document.title = `You clicked ${count} times`;

    // cleanup関数を返します。この関数は、次の副作用が実行される前か、コンポーネントがアンマウントされるときに実行されます。
    // ここでは、ドキュメントのタイトルを元の値に戻します。
    return () => {
      document.title = "React App";
      console.log("cleanup");
    };
  }, [count]); // `count`を依存配列に含めることで、`count`の値が変更されるたびに副作用が実行されます。

  // ユーザーがボタンをクリックした回数を表示し、ボタンをクリックするたびに`count`の値を増やすボタンをレンダリングします。
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  )
}

Flutterだとflutter_hooksで比較しましたね。これでないと副作用が実行されているかわからない...

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const CounterView(),
    );
  }
}

class CounterView extends HookWidget {
  const CounterView({super.key});

  
  Widget build(BuildContext context) {
    final counter = useState(0);

    useEffect(() {
      debugPrint('Counter: ${counter.value}');
      return () {
        debugPrint('Counterのstateが変更されました。');
      };
    }, [counter.value]);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.value++;
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '${counter.value}',
              style: const TextStyle(fontSize: 30, color: Colors.red),
            ),
          ],
        ),
      ),
    );
  }
}

最後に

今回は、副作用について解説してみました。昔からある言語の解説もあった方が理解が深まると思いJavaの本を読むことがあるので、Javaで表現してみました。

Javaでは、副作用を通常のメソッド呼び出しやインスタンス変数の変更として表現します。以下は、Javaでの副作用の例です。

public class Example {
    private int count = 0;

    public void incrementCount() {
        count++; // インスタンス変数の変更:副作用
    }

    public int getCount() {
        return count; // 主たる作用:評価値を返す
    }

    public static void main(String[] args) {
        Example example = new Example();
        example.incrementCount(); // メソッド呼び出し:副作用
        System.out.println("Count: " + example.getCount());
    }
}

この例では、incrementCount() メソッドの呼び出しにより、count 変数の値が変更されています。このような変数の変更は副作用です。一方で、getCount() メソッドは単に count 変数の値を返すだけであり、副作用は発生しません。

ロジックを実行して、変数の状態が変われば副作用な気がしますね。副作用と表現して良いのか...

プログミングの世界での副作用についてはこちらのサイトが参考になりました。
https://ja.wikipedia.org/wiki/副作用_(プログラム)

Discussion