TODO 응용 프로그램을 통해use Reducter+useContext에 들어가기

개시하다


React Hooks 및 Type Script를 사용한 간단한 TODO 적용》의 속편.
https://zenn.dev/sprout2000/articles/60cc8f1aa08b4b

저번까지의 토도.


useReducter 가져오기


https://ja.reactjs.org/docs/hooks-reference.html#usereducer useReduceruseState 대체품으로 여러 값을 뛰어넘는 복잡한state 논리가 존재하거나 이전state를 바탕으로 다음state를 확정해야 하는 상황에서 유용하다.

1. useReducter의 구문


const [state, dispatch] = useReducer(reducer, 'ステートの初期値');
  • reducer 상태 업데이트에 사용되는 함수
  • dispatch는 실행reducer에 사용되는 호출 함수
  • 2. 디스패치를 통해 Reducter 방법을 호출


    이전에 실행된 곳setState(newState)의 업데이트는 다음과 같은 상태입니다.
    dispatch(action);
    
    action는 표지부type의 속성과 값 속성으로 구성된 대상의 동작을 지시한다.구체적으로 아래와 같다.
    // count ステートを +1 する
    dispatch({ type: 'add', value: state.count + 1 });
    
    /*
     * 以下と(ほぼ)同義
     * setCount((count) => count + 1);
     */
    

    3. 자르기 유형 정의 파일


    TODO 응용 프로그램에 사용되는 두 가지 유형의 앨리어스와 상태 유형을 유형 정의 파일로 나열합니다.
    src/@types/Todo.d.ts
    declare type Todo = {
      value: string;
      id: number;
      checked: boolean;
      removed: boolean;
    };
    
    src/@types/Filter.d.ts
    declare type Filter = 'all' | 'checked' | 'unchecked' | 'removed';
    
    src/@types/State.d.ts
    declare type State = {
      text: string;
      todos: Todo[];
      filter: Filter;
    };
    

    4. 상태의 초기 값 설정


    위의 유형 정의를 사용하여 각 상태의 초기 값을 설정합니다.
    src/initialState.ts
    export const initialState: State = {
      text: '',
      todos: [],
      filter: 'all',
    };
    

    5. 액션의 필요조건 논의


    이전 TODO 응용 프로그램에서 상태를 업데이트하는 콜백 함수는 다음 7개입니다.
    handleOnChange: (e: Event) => void;
    handleOnFilter: (e: Event) => void;
    handleOnSubmit: () => void;
    handleOnEmpty: () => void;
    handleOnEdit: (id: number, value: string) => void;
    handleOnCheck: (id: number, checked: boolean) => void;
    handleOnRemove: (id: number, removed: boolean) => void;
    
    이 7개의 교체action를 맞추어라.

    handle On Change 및 handle On Filter


    src/@types/Action.d.ts
    declare type Action =
      | { type: 'change'; text: string }
      | { type: 'filter'; filter: Filter };
    

    handleOn Submit 및 handleOn Entery 추가


    src/@types/Action.d.ts
    declare type Action =
      | { type: 'change'; text: string }
      | { type: 'filter'; filter: Filter }
      | { type: 'submit' }
      | { type: 'empty' };
    

    handleOn Edit, handleOn Check, handleOn Remove 추가


    src/@types/Action.d.ts
    declare type Action =
      | { type: 'change'; text: string }
      | { type: 'filter'; filter: Filter }
      | { type: 'submit' }
      | { type: 'empty' }
      | { type: 'edit'; id: number; value: string }
      | { type: 'check'; id: number; checked: boolean }
      | { type: 'remove'; id: number; removed: boolean };
    

    6. 업데이트 상태를 만드는 Reducter 방법


    reducter 방법의 유형


    (state: State, action: Action) => newState: State
    
    수신State형과 Action형을 매개 변수로 하고 새로운 상태로 되돌려줍니다.

    이루어지다


    src/reducer.ts
    const reducer = (state: State, action: Action): State => {};
    
    상기 7개의 호출 함수를 Action에 넣으세요.
    src/reducer.ts
    export const reducer = (state: State, action: Action): State => {
      switch (action.type) {
        case 'change': {
          return { ...state, text: action.text };
        }
    
        case 'check': {
          const deepCopy = state.todos.map((todo) => ({ ...todo }));
    
          const newTodos = deepCopy.map((todo) => {
            if (todo.id === action.id) {
              todo.checked = !action.checked;
            }
            return todo;
          });
    
          return { ...state, todos: newTodos };
        }
    
        case 'edit': {
          const deepCopy = state.todos.map((todo) => ({ ...todo }));
    
          const newTodos = deepCopy.map((todo) => {
            if (todo.id === action.id) {
              todo.value = action.value;
            }
            return todo;
          });
    
          return { ...state, todos: newTodos };
        }
    
        case 'empty': {
          const newTodos = state.todos.filter((todo) => !todo.removed);
    
          return { ...state, todos: newTodos };
        }
    
        case 'filter': {
          return { ...state, filter: action.filter };
        }
    
        case 'remove': {
          const deepCopy = state.todos.map((todo) => ({ ...todo }));
    
          const newTodos = deepCopy.map((todo) => {
            if (todo.id === action.id) {
              todo.removed = !action.removed;
            }
            return todo;
          });
    
          return { ...state, todos: newTodos };
        }
    
        case 'submit': {
          if (!state.text) return state;
    
          const newTodo: Todo = {
            value: state.text,
            id: new Date().getTime(),
            checked: false,
            removed: false,
          };
    
          return { ...state, todos: [newTodo, ...state.todos], text: '' };
        }
    
        default: {
          return state;
        }
      }
    };
    

    7.useState를useReducer로 교체

    useReducer 등 가져오기:
    src/index.tsx
    - import { useState } from 'react';
    + import { useReducer } from 'react';
    
    + import { reducer } from './reducer';
    + import { initialState } from './initialState';
    
    useState 연결을 삭제하고 reducer 방법과 초기 상태에 따라 창설statedispatch.
    src/index.tsx
    const App = () => {
      const [state, dispatch] = useReducer(reducer, initialState);
    

    8. 호출 함수 7개의 setHoge를 dispatch로 대체

  • handleOnChage
  • index.tsx
      const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        dispatch({ type: 'change', text: e.target.value });
      };
    
  • handleOnSubmit
  • index.tsx
      const handleOnSubmit = () => {
        dispatch({ type: 'submit' });
      };
    
  • handleOnEdit
  • index.tsx
      const handleOnEdit = (id: number, value: string) => {
        dispatch({ type: 'edit', id, value });
      };
    
  • handleOnCheck
  • index.tsx
      const handleOnCheck = (id: number, checked: boolean) => {
        dispatch({ type: 'check', id, checked });
      };
    
  • handleOnRemove
  • index.tsx
      const handleOnRemove = (id: number, removed: boolean) => {
        dispatch({ type: 'remove', id, removed });
      };
    
  • handleOnEmpty
  • index.tsx
      const handleOnEmpty = () => {
        dispatch({ type: 'empty' });
      };
    
  • handleOnFilter
  • index.tsx
      const handleOnFilter = (e: React.ChangeEvent<HTMLSelectElement>) => {
        dispatch({ type: 'filter', value: e.target.value as Filter });
      };
    

    9. 함수 내 또는 JSX 내 참조 상태의 일부statehoge로 수정


    src/index.tsx
      const filteredTodos = state.todos.filter((todo) => {
        switch (state.filter) {
          case 'all':
            return !todo.removed;
          case 'checked':
            return todo.checked && !todo.removed;
          case 'unchecked':
            return !todo.checked && !todo.removed;
          case 'removed':
            return todo.removed;
          default:
            return todo;
        }
      });
    
      return (
        <div>
          <select defaultValue="all" onChange={handleOnFilter}>
            <option value="all">すべてのタスク</option>
            <option value="checked">完了したタスク</option>
            <option value="unchecked">現在のタスク</option>
            <option value="removed">ごみ箱</option>
          </select>
          {state.filter === 'removed' ? (
            <button
              onClick={handleOnEmpty}
              disabled={state.todos.filter((todo) => todo.removed).length === 0}
            >
              ゴミ箱を空にする
            </button>
          ) : (
            <form
              onSubmit={(e) => {
                e.preventDefault();
                handleOnSubmit();
              }}
            >
              <input
                type="text"
                value={state.text}
                disabled={state.filter === 'checked'}
                onChange={(e) => handleOnChange(e)}
              />
              <input
                type="submit"
                value="追加"
                disabled={state.filter === 'checked'}
                onSubmit={handleOnSubmit}
              />
            </form>
          )}
          <ul>
            {filteredTodos.map((todo) => {
              return (
                <li key={todo.id}>
                  <input
                    type="checkbox"
                    disabled={todo.removed}
                    checked={todo.checked}
                    onChange={() => handleOnCheck(todo.id, todo.checked)}
                  />
                  <input
                    type="text"
                    disabled={todo.checked || todo.removed}
                    value={todo.value}
                    onChange={(e) => handleOnEdit(todo.id, e.target.value)}
                  />
                  <button onClick={() => handleOnRemove(todo.id, todo.removed)}>
                    {todo.removed ? '復元' : '削除'}
                  </button>
                </li>
              );
            })}
          </ul>
        </div>
      );
    

    10. 각 부분을 구성 요소로 잘라서 메모화

  • 구성 요소memo화하면props가 변하지 않으면 다시 계산하지 않기 때문에 성능이 향상될 수 있음
  • dispatch를props로 전달하거나 각 구성 요소에서 상태 업데이트
  • 구성 요소 내 참고 상태state도props로 수신
  • Selector 구성 요소


    src/Selector.tsx
    import { Dispatch, memo } from 'react';
    
    type Props = {
      dispatch: Dispatch<Action>;
    };
    
    export const Selector = memo((props: Props) => {
      const handleOnFilter = (e: React.ChangeEvent<HTMLSelectElement>) => {
        props.dispatch({ type: 'filter', filter: e.target.value as Filter });
      };
    
      return (
        <select defaultValue="all" onChange={handleOnFilter}>
          <option value="all">すべてのタスク</option>
          <option value="checked">完了したタスク</option>
          <option value="unchecked">現在のタスク</option>
          <option value="removed">ごみ箱</option>
        </select>
      );
    });
    
    Selector.displayName = 'Selector';
    

    구성 요소


    src/EmptyButton.tsx
    import { Dispatch, memo } from 'react';
    
    type Props = {
      dispatch: Dispatch<Action>;
    };
    
    export const EmptyButton = memo((props: Props) => {
      const handleOnEmpty = () => {
        props.dispatch({ type: 'empty' });
      };
    
      return <button onClick={handleOnEmpty}>ごみ箱を空にする</button>;
    });
    
    EmptyButton.displayName = 'EmptyButton';
    

    Form 구성 요소


    src/Form.tsx
    import { Dispatch, memo } from 'react';
    
    type Props = {
      state: State;
      dispatch: Dispatch<Action>;
    };
    
    export const Form = memo((props: Props) => {
      const handleOnSubmit = () => {
        props.dispatch({ type: 'submit' });
      };
    
      const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        props.dispatch({ type: 'change', text: e.target.value });
      };
    
      return (
        <form
          onSubmit={(e) => {
            e.preventDefault();
            handleOnSubmit();
          }}
        >
          <input
            type="text"
            disabled={props.state.filter === 'checked'}
            value={props.state.text}
            onChange={handleOnChange}
          />
          <input
            type="submit"
            disabled={props.state.filter === 'checked'}
            value="追加"
            onSubmit={handleOnSubmit}
          />
        </form>
      );
    });
    
    Form.displayName = 'Form';
    

    FilteredTodos 구성 요소


    src/FilteredTodos.tsx
    import { Dispatch, memo } from 'react';
    
    type Props = {
      state: State;
      dispatch: Dispatch<Action>;
    };
    
    export const FilteredTodos = memo((props: Props) => {
      const handleOnEdit = (id: number, value: string) => {
        props.dispatch({ type: 'edit', id, value });
      };
    
      const handleOnCheck = (id: number, checked: boolean) => {
        props.dispatch({ type: 'check', id, checked });
      };
    
      const handleOnRemove = (id: number, removed: boolean) => {
        props.dispatch({ type: 'remove', id, removed });
      };
    
      const filteredTodos = props.state.todos.filter((todo) => {
        switch (props.state.filter) {
          case 'all':
            return !todo.removed;
          case 'checked':
            return !todo.removed && todo.checked;
          case 'unchecked':
            return !todo.removed && !todo.checked;
          case 'removed':
            return todo.removed;
          default:
            return todo;
        }
      });
    
      return (
        <ul>
          {filteredTodos.map((todo) => {
            return (
              <li key={todo.id}>
                <input
                  type="checkbox"
                  disabled={todo.removed}
                  checked={todo.checked}
                  onChange={() => handleOnCheck(todo.id, todo.checked)}
                />
                <input
                  type="text"
                  disabled={todo.checked || todo.removed}
                  value={todo.value}
                  onChange={(e) => handleOnEdit(todo.id, e.target.value)}
                />
                <button onClick={() => handleOnRemove(todo.id, todo.removed)}>
                  {todo.removed ? '復元' : '削除'}
                </button>
              </li>
            );
          })}
        </ul>
      );
    });
    
    FilteredTodos.displayName = 'FilteredTodos';
    

    모듈


    src/index.tsx
    import { useReducer } from 'react';
    import './App.scss';
    
    import { reducer } from './reducer';
    import { initialState } from './initialState';
    
    import { Form } from './Form';
    import { Selector } from './Selector';
    import { EmptyButton } from './EmptyButton';
    import { FilteredTodos } from './FilteredTodos';
    
    export const App = (): JSX.Element => {
      const [state, dispatch] = useReducer(reducer, initialState);
    
      return (
        <div>
          <Selector dispatch={dispatch} />
          {state.filter === 'removed' ? (
            <EmptyButton dispatch={dispatch} />
          ) : (
            <Form state={state} dispatch={dispatch} />
          )}
          <FilteredTodos state={state} dispatch={dispatch} />
        </div>
      );
    };
    

    지금까지 TODO였습니다.


    useContext 가져오기


    전형적인 React 응용 프로그램에서 데이터는 프롬프트를 통해 아버지에서 아들로 이동하고 손자에게 차례로 전달된다.그러나 useContext 갈고리를 사용하면 나무의 각 단계에서 속성을 현저하게 전달하지 않고 구성 요소 간에 이 값을 공유할 수 있다.
    이미지 참조 소스: React hooks(useContext 편) 이해

    https://ja.reactjs.org/docs/context.html

    1. 컨텍스트 작성


    createContext 구문


    const MyContext = React.createContext(defaultValue);
    

    AppContext 만들기


    여기에 전항에서 유지수요props로 각 부품에 전달되는 StateDispatch의 상하문으로 한다.
    src/AppContext.ts
    import { createContext, Dispatch } from 'react';
    
    export const AppContext = createContext(
      {} as { state: State; dispatch: Dispatch<Action> }
    );
    

    3. Context 공급자(=모 어셈블리)의 설정(1)

    return 文JSXAppContext.Provider으로 싸주세요.
    src/index.tsx
    + import { AppContext } from './AppContext';
    
      export const App = (): JSX.Element => {
        const [state, dispatch] = useReducer(reducer, initialState);
    
        return (
    +     <AppContext.Provider value={{ state, dispatch }}>
    +        <div>
              <Selector dispatch={dispatch} />
              {state.filter === 'removed' ? (
                <EmptyButton dispatch={dispatch} />
              ) : (
                <Form state={state} dispatch={dispatch} />
              )}
              <FilteredTodos state={state} dispatch={dispatch} />
            </div>
    +     </AppContext.Provider>
      );
    };
    

    4. Context 공급자(=모 어셈블리)의 설정(2)


    기존 props에서 제공한 AppContext.Provider의 값이나 state 방법을 대체하기 위해 각 구성 요소의 dispatch를 삭제합니다.
    src/index.tsx
      return (
        <AppContext.Provider value={{ state, dispatch }}>
          <div>
            <Selector />
            {state.filter === 'removed' ? <EmptyButton /> : <Form />}
            <FilteredTodos />
          </div>
        </AppContext.Provider>
      );
    

    5.props에서 제공하는 구성 요소의 설정을 수락합니다


    Context 구문


    컨텍스트 객체(React.createContext에서 반환된 값)를 수신하고 컨텍스트의 현재 값을 반환합니다.
    const value = useContext('コンテキストオブジェクト');
    
    useContext의 매개 변수에 대한 제공useContext フック을 통해 제공된 상하문(=AppContextState을 활용할 수 있기 때문에 삭제Dispatch.
    Form.tsx
    import { memo, useContext } from 'react';
    import { AppContext } from './AppContext';
    
    export const Form = memo(() => {
      const { state, dispatch } = useContext(AppContext);
    
      const handleOnSubmit = () => {
        dispatch({ type: 'submit' });
      };
    
      const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        dispatch({ type: 'change', text: e.target.value });
      };
    
      return (
        <form
          onSubmit={(e) => {
            e.preventDefault();
            handleOnSubmit();
          }}
        >
          <input
            type="text"
            disabled={state.filter === 'checked'}
            value={state.text}
            onChange={handleOnChange}
          />
          <input
            type="submit"
            disabled={state.filter === 'checked'}
            value="追加"
            onSubmit={handleOnSubmit}
          />
        </form>
      );
    });
    
    Form.displayName = 'Form';
    

    지금까지 TODO였습니다.

    좋은 웹페이지 즐겨찾기