useStateは初期値に関数を受け取ると、その関数を初期化時に呼び出してそれ以降は初期化処理を行いません。
const [todos, setTodos] = useState(createInitialTodos); // 初期化コストが高い
対してuseRefは関数を受け取って初期化するという機能がありません。
const todos = useRef(createInitialTodos); // ただの関数のrefという扱いになってしまう
かといって毎回初期値を生成するのは無駄が大きいです。これを回避する方法を思いついたので紹介です
解決策: 初期値をuseMemoで作る
const todos = useRef(useMemo(() => createInitialTodos(), [])); // createInitialTodosは1回のみ呼び出される
このパターンであれば「useMemoはパフォーマンス向上させるときにのみ使うべき」という原則とも合致しています。