FrontEnd/[리액트 2부] 고급 주제와 훅

[4.3 리듀서 훅]

Tony Lim 2024. 3. 22. 10:35

관리해야할 state가 많아질 수록 리듀서 훅을 사용할 때가 온다.

function useRegisterForm() {
  const [state, setState] = useState({
    value: { nickname: "", password: "" },
    error: { nickname: "", password: "" },
  });

  const handleChange = (e) => {
    setState({
      ...state,
      value: {
        ...state.value,
        [e.target.name]: e.target.value,
      },
    });
  };
  const handleReset = (e) => {
    setState({
      value: { nickname: "", password: "" },
      error: { nickname: "", password: "" },
    });
  };
  const handleSubmit = (e) => {
    setState({
      ...state,
      error: {
        nickname: /^\w+$/.test(state.value.nickname)
          ? ""
          : "only english, number allowed",
        password: /^.{3,6}$/.test(state.value.password)
          ? ""
          : "more than 3 to 6 number allowed",
      },
    });
  };

  return {
    state,
    handleChange,
    handleReset,
    handleSubmit,
  };
}

function RegisterForm() {
  const { state, handleChange, handleReset, handleSubmit } = useRegisterForm();
  return (
    <>
      <div>
        <label>nickname:</label>
        <input
          type="text"
          name="nickname"
          value={state.value.nickname}
          onChange={handleChange}
        ></input>
        <span>{state.error.nickname}</span>
      </div>
      <div>
        <label>password:</label>
        <input
          type="password"
          name="password"
          value={state.value.password}
          onChange={handleChange}
        ></input>
        <span>{state.error.password}</span>
      </div>
      <button onClick={handleReset}>Reset</button>
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
}

export default RegisterForm;

 

스토어 = 역할은 상태를 가지고 있고 변경하면 외부에 알리는 것이다.

  function createStore(reducer, initialValue) {
    let currentState = initialValue;
    const listeners = [];

    const getState = () => currentState;
    const subscribe = (callback) => listeners.push(callback);
    const dispatch = (action) => {
      const nextState = reducer(currentState, action);
      if (nextState !== currentState) {
        currentState = nextState;
        listeners.forEach((listener) => listener());
      }
    };

    return {
      getState,
      subscribe,
      dispatch,
    };
  }

일종의 event emitter 같은 역할을 하고 있다.

store 가 관리하고 있는 state가 존재하며 dispatch에 전달된 action을 통해서 state를 변경하게 된다.

state가 변경이 되면 listeners에 등록된 callback들을 차레대로 호출 해주어서 변경되었다는것을 알려주게 된다.

function reducer(state, action) {
  if (action.type === "count") {
    return { ...state, value: state.value + 1 };
  }
  throw "given action is undefined in reducer";
}

const initialValue = { value: 0 };

const store = MyReact.createStore(reducer, initialValue);

console.log("after createStore", store.getState()); // {value: 0}

store.subscribe(() => console.log("subscribe", store.getState()));

store.dispatch({ type: "count" });
store.dispatch({ type: "count" });

reducer에 count action을 정의하고 dispatch 호출시 전달하면 해당 action에 맞는 동작을 하게 된다.

store는 state를 보관할뿐 어떻게 state를 변경할 것인지는 전달 되는 reducer에 달려있다.


useReducer

1) 스토어 값을 리액트 앨리먼트에 사용한다 ( 스토어 -> 컴포넌트)

2) 컴포넌트가 스토어에게 변경을 요청할 수 있다. (컴포넌트 -> 스토어)

3) 스토어의 변경된 값을 리액트 앨리먼트에 반영한다. (컴포넌트 리랜더링)

  function useReducer(reducer, initialValue) {
    const {forceUpdate} = useForceUpdate()
    if (!isInitialized[cursor]) {
      const store = createStore(reducer, initialValue);
      memorizedStates[cursor] = store
    }

    const store = memorizedStates[cursor]
    store.subscribe(forceUpdate)
    cursor+=1
    return [store.getState(), store.dispatch]
  }

1) == store.getState()를 통해 값을 사용할 수 있다.

2) == store.dispatch를 통해 컴포넌트가 store의 state 값을 변경할 수 있다.

3) == forceUpdate를 통해 dispatch에 의해 store의 state값이 변경이 되면 re rendering이 발생한다.

 

 

const initialValue = {
  value: { nickname: "", password: "" },
  error: { nickname: "", password: "" },
};

const reducer = (state, action) => {
  if (action.type === "SET_FIELD") {
    return {
      ...state,
      value: {
        ...state.value,
        [action.name]: action.value,
      },
    };
  }
  if (action.type === "RESET") {
    return {
      value: { nickname: "", password: "" },
      error: { nickname: "", password: "" },
    };
  }
  if (action.type === "VALIDATE") {
    return {
      ...state,
      error: {
        nickname: /^\w+$/.test(state.value.nickname)
          ? ""
          : "only english, number allowed",
        password: /^.{3,6}$/.test(state.value.password)
          ? ""
          : "more than 3 to 6 number allowed",
      },
    };
  }
  throw Error("invalid action");
};

function RegisterForm() {
  const [state, dispatch] = MyReact.useReducer(reducer, initialValue);

  const handleChange = (e) => {
    dispatch({ type: "SET_FIELD", name: e.target.name, value: e.target.value });
  };

  const handleReset = (e) => {
    dispatch({ type: "RESET" });
  };

  const handleSubmit = (e) => {
    dispatch({ type: "VALIDATE" });
  };

  return (
    <>
      <div>
        <label>nickname:</label>
        <input
          type="text"
          name="nickname"
          value={state.value.nickname}
          onChange={handleChange}
        ></input>
        <span>{state.error.nickname}</span>
      </div>
      <div>
        <label>password:</label>
        <input
          type="password"
          name="password"
          value={state.value.password}
          onChange={handleChange}
        ></input>
        <span>{state.error.password}</span>
      </div>
      <button onClick={handleReset}>Reset</button>
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
}

export default RegisterForm;

reducer에서 state의 변화를 담당하는 SET_FIELD, 초기화 하는 RESET, 검증하는 VALIDATION을 추가하고 store에게 전달해주었다.

 

function updateState(initialState) {
  return updateReducer(basicStateReducer, initialState);
}

function baiscStateReducer(state, action) {
  return typeof action === "function" ? action(state) : action;
}

useState는 useReducer에서 한가지 action만 정의된 reducer를 사용하는 것과 같다. 그냥 받은 값을 다음 value로 set해주는것

위 코드는 useState의 예시일부인데 basicStateReducer에서 action이 function이 아니라 value이면 그냥 그대로 return하는것을 확인 할 수 있다.

 

useReducer훅이 항상 좋은 것은 아니다. 상태규모가 작은 경우 store, reducer를 만드는게 더 힘들 수 있다.