← React Course

REACT ADVANCED · 139

useReducer / State Machine:當 state transition 開始變複雜

當一個 screen 有很多互相影響的 state setters,真正問題常不是「缺另一個 Hook」,而是 transition rules 已經值得集中成 reducer。

Learning outcomes

1. Boolean 爆炸

const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const [success, setSuccess] = useState(false);

這種寫法很容易產生 loading=true、success=true、error!=null 同時存在的 impossible state。

2. 用明確 state machine

const initialState = {
  status: "idle",
  courses: [],
  error: null
};

function reducer(state, action) {
  switch (action.type) {
    case "load-started":
      return {
        ...state,
        status: "loading",
        error: null
      };

    case "load-succeeded":
      return {
        status: "success",
        courses: action.courses,
        error: null
      };

    case "load-failed":
      return {
        ...state,
        status: "error",
        error: action.message
      };

    default:
      return state;
  }
}

3. Reducer 是 pure transition function

old state+action→ reducernew state

Reducer 裡不應直接 fetch、改 DOM、寫 localStorage;side effect 放在外面。

4. useReducer

const [state, dispatch] =
  useReducer(
    reducer,
    initialState
  );

dispatch({
  type: "load-started"
});

Project checkpoint:Course Dashboard state machine

async function loadCourses() {
  dispatch({
    type: "load-started"
  });

  try {
    const courses =
      await api.loadCourses();

    dispatch({
      type: "load-succeeded",
      courses
    });
  } catch (error) {
    dispatch({
      type: "load-failed",
      message:
        error instanceof Error
          ? error.message
          : "Unknown error"
    });
  }
}

5. Testing reducer

const next = reducer(
  initialState,
  {
    type: "load-succeeded",
    courses: [course]
  }
);

// assert next.status === "success"

因為 reducer 是 pure function,非常適合 deterministic unit test。

Knowledge check

  1. 什麼時候 useState 已足夠,什麼時候 reducer 更清楚?
  2. 為什麼 reducer 裡不該直接 fetch?
  3. 把 add/remove/update 三個 transition 寫成 actions。
  4. 列出兩個原本 boolean 模型可能出現的 impossible states。