상태 관리에 사용되는 훅

sza0203·2022년 1월 12일
0

TIL

목록 보기
40/50
  • 2022/01/12

상태 관리에 사용되는 훅

  • 외부 라이브러리 없이 React가 제공하는 훅 만으로 상태 관리를 구현하기 위해 사용.
  • 함수형 컴포넌트에 상태를 두고, 여러 컴포넌트 간 데이터와 데이터 변경 함수를 공유하는 방식으로 상태를 관리하게 됨.

useState

  • 단순한 하나의 상태를 관리하기에 적합함.
  • const[ state, setState ] = useState(initState|initFn)
  • state가 바뀌면, state를 사용하는 컴포넌트를 리렌더함.
  • useEffect와 함께, state에 반응하는 훅을 구축.

useRef

  • 상태가 바뀌어도 리렌더링하지 않는 상태를 정의함.
  • 즉, 상태가 UI의 변경과 관계없을 때 사용.
    • ex) setTimeout의 timerId 저장
  • uncontrolled component의 상태를 조작하는 등, 리렌더링을 최소화 하는 상태 관리에 사용됨.
    • ex) Dynamic Form 예시

useContext

  • 컴포넌트와 컴포넌트 간 상태를 공유할 때 사용.
  • 부분적인 컴포넌트들의 상태 관리, 전체 앱의 상태 관리를 모두 구현.
  • Context Provider 안에서 렌더링되는 컴포넌트는, useContext를 이용해 깊이 nested 된 컴포넌트 라도 바로 context value를 가져옴.
  • context value가 바뀌면 내부 컴포넌트는 모두 리렌더링 됨.

useReducer

  • useState보다 복잡한 상태를 다룰 때 사용.
  • 별도의 라이브러리 없이 flux pattern에 기반한 상태 관리를 구현.
  • const[state, dispatch] = useReducer(reducer,initState)
  • nested state 등 복잡한 여러 개의 상태를 한꺼번에 관리하거나, 어떤 상태에 여러 가지 처리를 적용할 때 유용.
  • 상태 복잡하다면, useState에 관한 callback을 내려주는 것보다 dispatch를 prop으로 내려 리렌더링을 최적화 하는것을 권장.

useState를 활용한 상태 관리

useState를 활용한 상태 관리

  • 상위 컴포넌트에서 state와 state 변경 함수를 정의하고,그 state나 변경 함수를 사용하는 컴포넌트 까지 prop으로 내려주는 패턴.
  • state가 변경되면, 중간에 state를 넘기기만 하는 컴포넌트들도 모두 리렌더링 됨.
  • 상태와 상태에 대한 변화가 단순하거나, 상대적으로 소규모 앱에서 사용하기에 적합.

useState 예시 - TodoApp - TodoApp.jsx

// TodoApp.jsx
function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState("all");
  const [globalId, setGlobalId] = useState(3000);

  const toggleTodo = (id) => {
    setTodos((todos) =>
      todos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const deleteTodo = (id) => {
    setTodos((todos) => todos.filter((todo) => todo.id !== id));
  };

  const addTodo = (title) => {
    setTodos((todos) => [{ title, id: globalId + 1 }, ...todos]);
    setGlobalId((id) => id + 1);
  };

  return (
    <TodosPage
      state={{ todos, filter }}
      togggleTodo={toggleTodo}
      addTodo={addTodo}
      deleteTodo={deleteTodo}
      changeFilter={setFilter}
    />
  );
}

useState 예시 - TodoApp - TodosPage.jsx

// TodosPage.jsx
function TodosPage({ state, addTodo, deleteTodo, toggleTodo, changeFilter }) {
  const filteredTodos = state.todos.filter((todo) => {
    const { filter } = state;

    return (
      filter === "all" ||
      (filter === "completed" && todo.completed) ||
      (filter === "todo" && !todo.completed)
    );
  });

  return (
    <div>
      <h3>TodosPage</h3>

      <TodoForm onSubmit={addTodo} />
      <TodoFilter filter={state.filter} changeFilter={changeFilter} />
      <TodoList
        todos={filteredTodos}
        toggleTodo={toggleTodo}
        deleteTodo={deleteTodo}
      />
    </div>
  );
}

useState 예시 - TodoApp - TodoForm.jsx, TodoList.jsx

// TodoForm.jsx
function TodoForm({ onSubmit }) {
  const [title, setTitle] = useState("");

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        onSubmit(title);
        setTitle("");
      }}
    >
      <label htmlFor="todo-title">Title</label>
      <input
        id="todo-title"
        type="text"
        name="todo-title"
        onChagne={(e) => setTitle(e.target.value)}
        value={title}
      />
      <button type="submit">Make</button>
    </form>
  );
}
// TodoList.jsx
function TodoList({ todos, toggleTodo, deleteTodo }) {
  return (
    <ul>
      {todos.map(({ title, completed, id }) => (
        <li onClick={() => toggleTodo(id)}>
          <h5>{title}</h5>
          <div>
            {completed ? "✅ " : "🖊"}
            <button onClick={() => deleteTodo(id)}>Delete</button>
          </div>
        </li>
      ))}
    </ul>
  );
}

useState 예시 - TodoApp - TodoFilter.jsx

function TodoFilter({ filter, changeFilter }) {
  return (
    <div>
      <label htmlFor="filter">Filter</label>
      <select
        onChange={(e) => changeFilter(e.target.value)}
        id="filter"
        name="filter"
      >
        {filterList.map((filterText) <=> (
          <option selected={filter === filterText} value={filterText}>
            {capitalize(filterText)}
          </option>
        ))}
      </select>
    </div>
  );
}

useContext를 활용한 상태 관리

useContext를 활용한 상태 관리

  • Provider 단에서 상태를 정의하고, 직접 상태와 변경 함수를 사용하는 컴포넌트에서 useContext를 이용해 바로 상태를 가져와 사용하는 패턴.
  • useReducer와 함께, 복잡한 상태상태에 대한 변경 로직을 두 개 이상의 컴포넌트에서 활용하도록 구현 가능.
  • state는 필요한 곳에서만 사용하므로, 불필요한 컴포넌트 리렌더링을 방지.
  • Prop Drilling(Plumbing)을 방지하여 컴포넌트 간 결합도를 낮춤.

useContext 예시 - TodoApp - TodoContext.jsx

// TodoContext.jsx
const TodoContext = createContext(null);

const initialState = {
  todos: [],
  filter: "all",
  globalId: 3000,
};

function useTodoContext() {
  const context = useContext(TodoContext);
  if (!context) {
    throw new Error("Use TodoContext inside Provider.");
  }
  return context;
}

function TodoContextProvider({ children }) {
  const values = useTodoState();
  return <TodoContext.Provider value={value}>{children}</TodoContext.Provider>;
}

function reducer(state, action) {
  switch (action.type) {
    case "change.filter":
      return { ...state, filter: action.payload.filter };
    case "init.todos":
        return { ...state, todos: action,payload.todos};
    case "add.todo": {
        return { ...state, todos: [{title:action.payload.title, id: state.globalId + 1}], globalId: state.globalId +1}
    }

    case "delete.todo": {
        return { ...state, todos: state.todos.filter((todo) => todo.id !== action,payload.id)};
    }
    case "toglle.todo": {
        return { ...state, todos: state.todos.map((t) => t.id === action.payload.id ? { ...t, completed: !t.completed } : t)}
    }
    default: return state;
  }
}

function useTodoState() {
    const [state, dispatch] = useReducer(reducer, initialState);
    const toggleTodo = useCallback( (id) => dispatch({ type: "toggle.todo", payload: {id} }), []);
    const deleteTodo = useCallback( (id) => dispatch({ type: "delete.todo", payload: {id} }), []);
    const addTodo = useCallback( (title) => dispatch({ type: "add.todo", payload: { title } }),[]);
    const changeFilter = useCallback( (filter) => dispatch({ type: "change.filter", payload: { filter } }),[]);
    const initializeTodos = useCallback( (todos) => dispatch({ type: "init.todos", payload: { todos } }),[]);

    return { state, toggleTodo, deleteTodo, addTodo, changeFilter, initializeTodos };
}

useContext 예시 - TodoApp - TodoApp.jsx, TodosPage.jsx

// TodoApp.jsx
function TodoApp() {
  return (
    <TodoContextProvider>
      <TodosPage />
    </TodoContextProvider>
  );
}
// TodosPage.js
function TodosPage() {
  const { initializeTodos } = useTodoContext();

  useEffect(() => {
    console.log("useEffect");
    fetchTodos().then(initializeTodos);
  }, [initializeTodos]);

  return (
    <div>
      <TodoForm />
      <TodoFilter />
      <TodoList />
    </div>
  );
}

useContext 예시 - TodoApp - TodoForm.jsx, TodoList.jsx

// TodoForm.jsx
function TodoForm() {
  const { addTodo } = useTodoContext();
  const [title, setTitle] = useState("");

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        addTodo(title);
        setTitle("");
      }}
    >
      <label htmlFor="todo-title">Title</label>
      <input
        id="todo-title"
        type="text"
        name="todo-title"
        onChange={(e) => setTitle(e.target.value)}
        value={title}
      />
      <button type="submit">Make</button>
    </form>
  );
}
// TodoList.jsx
function TodoList() {
  const { state, toggleTodo, deleteTodo } = useTodoContext();

  const { todos, filter } = state;

  const filteredTodos = todos.filter((todo) => {
    return (
      filter === "all" ||
      (filter === "completed" && todo.completed) ||
      (filter === "todo" && !todo.completed)
    );
  });

  return (
    <ul>
      {todos.map(({ title, completed, id }) => (
        <li onClick={() => toggleTodo(id)}>
          <h5>{title}</h5>
          <div>
            {completed ? "✅ " : "🖊"}
            <button onClick={() => deleteTodo(id)}>Delete</button>
          </div>
        </li>
      ))}
    </ul>
  );
}

useContext 예시 - TodoApp - TodoFilter.jsx

// TodoFilter.jsx
function TodoFilter() {
  const { state, changeFilter } = useTodoContext();
  const { filter } = state;

  return (
    <div>
      <label htmlFor="filter">Filter</label>
      <select
        onChange={(e) => changeFilter(e.target.value)}
        id="filter"
        name="filter"
        value={filter}
      >
        {filterList.map((filterText) => (
          <option key={filterText} value={filterText}>
            {capitalize(filterText)}
          </option>
        ))}
      </select>
    </div>
  );
}

useRef, useContext, useReducer는 처음 배우는 개념이니 예시와 함께 복습 필요.. 어렵다 리액트! 화이팅!

profile
만들고 싶은 앱이 생겨서 RN 공부중입니다.

0개의 댓글