TypeScriptとReact-#2応答の適用


https://velog.io/@velopert/create-typescript-react-component

プロジェクトの作成


https://velog.io/@swimme/react-typescript-起動
npx create-react-app ts-tutorial --template typescript
既存のCRAでは、typescriptを後に付けるだけで、タイプスクリプト設定を適用した項目を生成できます.
作成したアイテムにタイプスクリプトを適用する場合は、次のように設定します.
npm install typescript @types/node @types/react @types/react-dom @types/jest
// yarn add typescript @types/node @types/react @types/react-dom @types/jest

タイプスクリプトで作成されたコンポーネント


App.tsx

import React from 'react';
import logo from './logo.svg';
import './App.css';

const App: React.FC = () => {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
最近の傾向は、矢印関数ではなく関数キーを使用することです.React.FCのタイプ指定タイプを使用できますが、長い列があるそうです.

Greeting.tsx

import React from 'react';

type GreetingsProps = {
  name: string;
};

const Greetings: React.FC<GreetingsProps> = ({ name }) => (
  <div>Hello, {name}</div>
);

export default Greetings;
構成部品のpropsタイプを宣言する場合はtypeとinterfaceを使用できますが、1つのプロジェクト内で一貫性を保つ必要があります.

React.FCのメリットとデメリット


長所
子供たちは
  • 道具に入りますか?
  • defaultProps、ProType、ContextTypeを設定すると自動的に完了しますか?
  • 短所
  • 名の子供がゆったりした形で入っているので、道具の種類は不明です.
  • 一部のコンポーネントにはサブコンポーネントが含まれていないため、各Propsのタイプにはサブコンポーネントが明記されている必要があります.
  • n/a.結論
    React.FCに子供道具があるのがメリットじゃないですか?
    React.FCを使うと子供達が道具の中に入るそうですReactFCはなく、子供達も確認して入ってきました!

    子供って何?


    https://velog.io/@donggu/文系生-説明の-react-pprosproperties-children
    A素子の間にBという素子がある場合、B素子の内容を表示するためのprops.
    // App.js
    import React from 'react';
    import Greeting from './Greeting'
    import './App.css';
    
    function App() {
      return (
        <div className="App">
          <header className="App-header">
            <Greeting text="hello">
              <div>between1</div>
              <div>between2</div>
            </Greeting>
          </header>
        </div>
      );
    }
    
    export default App;
    
    // Greeting.js
    
    import React from 'react';
    
    type GreetingsProps = {
        text: string;
    };
    
    const Greetings: React.FC<GreetingsProps> = (props) => {
        console.log(props);
        return (
            <div>
                Hello, {props.text}
                {props.children}
            </div>
        );
    };
    
    export default Greetings;
    従来の方法では、ある素子内に別の素子を配置したい場合はその素子から戻るが、propsのchildrenを使用すれば、中の素子を直接放出することができる.

    React.FCがないほうがいい!

    // App.tsx
    import React from 'react';
    import Greetings from './Greeting';
    
    const App: React.FC = () => {
      return <Greetings name="Hello" />; // Property 'mark' is missing in type '{ name: string; }' but required in type 'GreetingsProps'.  TS2741
    };
    
    export default App;
    
    // Greeting.tsx
    import React from 'react';
    
    type GreetingsProps = {
        name: string;
        mark: string;
    };
    
    const Greeting: React.FC<GreetingsProps> = ({ name, mark }) => ( // mark = '!' 로 교체
        <div>
            Hello, {name} {mark}
        </div>
    );
    
    Greeting.defaultProps = {
        mark: '!'
    };
    
    export default Greeting;
    次のコードのmark 動作を表す  defaultProps  にもかかわらず  markの価格がなければ、正常に働けないと言っています.非構造的な割り当てを行う過程では、デフォルト値を設定することで解決できますが、defaultPropsは意味がありません.
    import React from 'react';
    
    type GreetingsProps = {
      name: string;
      mark: string;
    };
    
    const Greetings = ({ name, mark }: GreetingsProps) => (
      <div>
        Hello, {name} {mark}
      </div>
    );
    
    Greetings.defaultProps = {
      mark: '!'
    };
    
    export default Greetings;
    
    // function 형태
    
    function Greetings({ name, mark }: GreetingsProps) {
      return (
        <div>
          Hello, {name} {mark}
        </div>
      );
    }
    
    Greetings.defaultProps = {
      mark: '!'
    };
    
    export default Greetings;
    React.FCを外すと、よく働きます.

    省略可能なprops設定


    propsで省略できる値は  ?  テキストを使用できます.
    // Greeting.tsx
    
    import React from 'react';
    
    type GreetingsProps = {
      name: string;
      mark: string;
      optional?: string;
    };
    
    function Greetings({ name, mark, optional }: GreetingsProps) {
      return (
        <div>
          Hello, {name} {mark}
          {optional && <p>{optional}</p>}
        </div>
      );
    }
    
    Greetings.defaultProps = {
      mark: '!'
    };
    
    export default Greetings;
    関数を追加するpropsのタイプとしてname、mark、optionalを指定します.加えたoptionalは、値を入力する必要がないことを意味します.

    propsが関数を返すと

    // App.tsx
    import React from 'react';
    import Greetings from './Greeting';
    
    const App: React.FC = () => {
      const handleClick = (text: string) => {
        console.log(text)
        return (text);
      }
      return <Greetings name="Hello" optional="asd" onClick={handleClick} />;
    };
    
    export default App;
    
    // Greeting.tsx
    import React, { useState } from 'react';
    
    type GreetingsProps = {
        name: string;
        mark: string;
        optional?: string;
        onClick: (name: string) => string; // string인 name을 인자로 받아 string을 리턴함을 의미한다.
    };
    
    function Greetings({ name, mark, optional, onClick }: GreetingsProps) {
        const [data, setData] = useState("");
    
        const handleClick = () => {
            setData(onClick(name));
        }
        return (
            <div>
                Hello, {name} {mark}
                {optional && <p>{optional}</p>}
                <div>
                    <button onClick={handleClick}>Click Me</button>
                    data : {data}
                </div>
            </div>
        );
    }
    
    Greetings.defaultProps = {
        mark: '!'
    };
    
    export default Greetings;
    コールバック関数がpropsに移動すると、パラメータのタイプ、戻り値のタイプを指定できます.

    整理する

  • React.FC  あまりよくないです.
  • 関数型構成部品を作成する場合は、矢印関数を使用します.  function  キーワードを使ってもいいです.
  • Propsのタイプが発表されたとき  interface  または  type  プロジェクト内で一貫性を保つ限り、使用できます.
  • タイプスクリプトとしてHooksを使用


    useState

    // App.tsx
    import React from 'react';
    import Counter from './Counter';
    
    const App: React.FC = () => {
    
      return <Counter />;
    };
    
    export default App;
    
    // Counter.tsx
    
    import React, { useState } from 'react';
    
    function Counter() {
        const [count, setCount] = useState<number>(0);
        const onIncrease = () => setCount(count + 1);
        const onDecrease = () => setCount(count - 1);
        const wrongSet = () => setCount("wrong");
        return (
            <div>
                <h1>{count}</h1>
                <div>
                    <button onClick={onIncrease}>+1</button>
                    <button onClick={onDecrease}>-1</button>
                    <button onClick={wrongSet}>wrong</button> // Argument of type 'string' is not assignable to parameter of type 'SetStateAction<number>'.
    
                </div>
            </div>
        );
    }
    
    export default Counter;
    反応したものとあまり変わらない.useStateを宣言する場合は、処理する値のタイプを指定するだけです.
    実際にJENERICを使わなくても、設定した初期値に基づいてタイプを類推して設定します.ステータスが空であっても、空でなくても、またはステータスタイプが複雑なオブジェクトや配列であればJENERICを使用できます.
    type Information = { name: string; description: string };
    const [info, setInformation] = useState<Information | null>(null);
    type Todo = { id: number; text: string; done: boolean };
    const [todos, setTodos] = useState<Todo[]>([]);  // { id: number; text: string; done: boolean } 형태의 객체를 요소로 가지는 배열의 타입이라는 뜻.
    const [todos, setTodos] = useState([] as Todo[]);
    配列については、上記のように空の配列だけを入れるとタイプを推定できないため、Genericsを指定するか、asキーワードを使用する必要があります.

    onChange, onSubmit

    // App.tsx
    import React from 'react';
    import MyForm from './MyForm';
    
    const App: React.FC = () => {
      const onSubmit = (form: { name: string; description: string }) => {
        console.log(form);
      };
      return <MyForm onSubmit={onSubmit} />;
    };
    
    export default App;
    
    // MyForm.tsx
    import React, { useState } from 'react';
    
    type MyFormProps = {
        onSubmit: (form: { name: string; description: string }) => void;
    };
    
    function MyForm({ onSubmit }: MyFormProps) {
        const [form, setForm] = useState({
            name: '',
            description: ''
        });
    
        const { name, description } = form;
    
        const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
            console.log(e);
            const { name, value } = e.target;
            setForm({
                ...form,
                [name]: value
            });
        };
    
        const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
            e.preventDefault();
            onSubmit(form);
            setForm({
                name: '',
                description: ''
            }); // 초기화
        };
    
        return (
            <form onSubmit={handleSubmit}>
                <input name="name" value={name} onChange={onChange} />
                <input name="description" value={description} onChange={onChange} />
                <button type="submit">등록</button>
            </form>
        );
    }
    
    export default MyForm;
    コードはかなり複雑で、順番に徐々に解いてみましょう.
    type MyFormPropsの条件には、formというオブジェクトをパラメータとして受け入れ、何も返さないonSubmitという関数が含まれている必要があります.パラメータformは文字列nameとdescriptionキーから構成されます.
    関数要素MyFormはpropsとしてonSubmitを受信し、onSubmitのタイプは設定したMyFormPropsに一致する必要があります.
    onChange,onSubmitで起動したコールバック関数はeを超え,各イベントリスナーには独自のタイプがある.vscodeでイベントリスナーにマウスを置くと、どのタイプのマウスを置くべきか親切に教えてくれます.

    「--jsx」フラグが指定されていない限り、jsxは使用できません。


    https://chacha73.tistory.com/44
    tsconfig.jsonでは、jsx:react-jsxをpreserveに変更できますが、再実行するとreact-jsxに戻ります.
    vscodeの設定.jsonに"typescript.tsdk": "/Users/sham/.brew/lib/node_modules/typescript/lib"を設定し、tsファイルの下部にあるstatus barでtypescriptバージョンを更新すればこの問題を解決できます.

    useReducer

    import React, { useReducer } from 'react';
    
    type Action = { type: 'INCREASE' } | { type: 'DECREASE' }; // 사용되는 모든 액션을 나열한다.
    
    function reducer(state: number, action: Action): number {
        switch (action.type) {
            case 'INCREASE':
                return state + 1;
            case 'DECREASE':
                return state - 1;
            default:
                throw new Error('Unhandled action');
        }
    }
    
    function Counter() {
        const [count, dispatch] = useReducer(reducer, 0);
        const onIncrease = () => dispatch({ type: 'INCREASE' });
        const onDecrease = () => dispatch({ type: 'DECREASE' });
    
        return (
            <div>
                <h1>{count}</h1>
                <div>
                    <button onClick={onIncrease}>+1</button>
                    <button onClick={onDecrease}>-1</button>
                </div>
            </div>
        );
    }
    
    export default Counter;
    reduceを宣言する場合は、ステータスタイプと戻りタイプが同じである必要があります.上のコードは簡単なカウンタでstateタイプnumberを指定していますが、stateがオブジェクトタイプの場合はタイプまたはインタフェースを指定する必要があります.
    import React, { useReducer } from 'react';
    
    type Color = 'red' | 'orange' | 'yellow';
    
    type State = {
      count: number;
      text: string;
      color: Color;
      isGood: boolean;
    };
    
    type Action =
      | { type: 'SET_COUNT'; count: number }
      | { type: 'SET_TEXT'; text: string }
      | { type: 'SET_COLOR'; color: Color }
      | { type: 'TOGGLE_GOOD' };
    
    function reducer(state: State, action: Action): State {
      switch (action.type) {
        case 'SET_COUNT':
          return {
            ...state,
            count: action.count // count가 자동완성되며, number 타입인걸 알 수 있습니다.
          };
        case 'SET_TEXT':
          return {
            ...state,
            text: action.text // text가 자동완성되며, string 타입인걸 알 수 있습니다.
          };
        case 'SET_COLOR':
          return {
            ...state,
            color: action.color // color 가 자동완성되며 color 가 Color 타입인걸 알 수 있습니다.
          };
        case 'TOGGLE_GOOD':
          return {
            ...state,
            isGood: !state.isGood
          };
        default:
          throw new Error('Unhandled action');
      }
    }
    
    function ReducerSample() {
      const [state, dispatch] = useReducer(reducer, {
        count: 0,
        text: 'hello',
        color: 'red',
        isGood: true
      });
    
      const setCount = () => dispatch({ type: 'SET_COUNT', count: 5 }); // count 를 넣지 않으면 에러발생
      const setText = () => dispatch({ type: 'SET_TEXT', text: 'bye' }); // text 를 넣지 않으면 에러 발생
      const setColor = () => dispatch({ type: 'SET_COLOR', color: 'orange' }); // orange 를 넣지 않으면 에러 발생
      const toggleGood = () => dispatch({ type: 'TOGGLE_GOOD' });
    
      return (
        <div>
          <p>
            <code>count: </code> {state.count}
          </p>
          <p>
            <code>text: </code> {state.text}
          </p>
          <p>
            <code>color: </code> {state.color}
          </p>
          <p>
            <code>isGood: </code> {state.isGood ? 'true' : 'false'}
          </p>
          <div>
            <button onClick={setCount}>SET_COUNT</button>
            <button onClick={setText}>SET_TEXT</button>
            <button onClick={setColor}>SET_COLOR</button>
            <button onClick={toggleGood}>TOGGLE_GOOD</button>
          </div>
        </div>
      );
    }
    
    export default useRef;
    タイプの指定が追加されただけで、ユーザーリーダーとはあまり違いません.

    useRef


    応答要素が外部ライブラリのインスタンスまたはDOMを特定の値に含める場合、useRefが使用される.refが変更されてもレンダリングされません.

    変数値の管理


    userefを使用する場合、実際の値はです.電流で近似して得る.ムカデでタイプを明記することができます.
    const id = useRef<number>(0);
      const increaseId = () => {
        id.current += 1;
      }
    import React, { useState, useRef } from 'react';
    
    type MyFormProps = {
      onSubmit: (form: { name: string; description: string }) => void;
    };
    
    function MyForm({ onSubmit }: MyFormProps) {
      const inputRef = useRef<HTMLInputElement>(null);
    
      const [form, setForm] = useState({
        name: '',
        description: ''
      });
    
      const { name, description } = form;
    
      const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        setForm({
          ...form,
          [name]: value
        });
      };
    
      const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        onSubmit(form);
        setForm({
          name: '',
          description: ''
        });
        if (!inputRef.current) {
          return;
        }
        inputRef.current.focus();
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input name="name" value={name} onChange={onChange} ref={inputRef} />
          <input name="description" value={description} onChange={onChange} />
          <button type="submit">등록</button>
        </form>
      );
    }
    
    export default MyForm;
    入力した名前のinput要素を操作するために、inputRefをrefに掛けます.handleSubmitを実行する場合は、inputRefを使用してinputを操作し、入力のハイライトにします.
    inputRef.current値を使用する場合はnullチェックが必要ですが、無視してレンダリングしようとするとObject is possibly 'null'. TS2531というエラーが発生します.
    import React from 'react';
    import MyForm from './MyForm';
    
    const App: React.FC = () => {
      const onSubmit = (form: { name: string; description: string }) => {
        console.log(form);
      };
      return <MyForm onSubmit={onSubmit} />;
    };
    
    export default App;

    タイプスクリプトとしてContext APIを使用


    ステータスのみのContextとスケジューリング用のContextを2つ作成します.
    ステータス、dispatchを1つのContextで管理することはできないと思いますが、ステータス値は必要なく、dispatchだけが必要な構成部品もステータス更新時に一緒にレンダリングされます.無駄なレンダリングを防ぐことができます.

    ステータスContext、派遣Contextの設定

    import { createContext, Dispatch } from 'react';
    
    export type Todo = {
      id: number;
      text: string;
      done: boolean;
    };
    
    type TodosState = Todo[];
    
    const TodosStateContext = createContext<TodosState | undefined>(undefined);
    
    type Action =
      | { type: 'CREATE'; text: string }
      | { type: 'TOGGLE'; id: number }
      | { type: 'REMOVE'; id: number };
    
    type TodosDispatch = Dispatch<Action>;
    const TodosDispatchContext = createContext<TodosDispatch | undefined>(
      undefined
    );
    モジュールのように書き込むために、個別のファイルを作成して管理します.上のTodosStateContextは、Todoを配列要素とするTodosStateタイプに従います.TodosDispatchContextsには、Actionと同じ形式のdispatchが含まれています.動作に必要な値が存在しない場合は、エラーが放出されます.
    Providerを使用しない場合、Context値は未定義である必要があります.タイプに明記してください.

    Reduser設定

    import { createContext, Dispatch } from 'react';
    
    export type Todo = {
      id: number;
      text: string;
      done: boolean;
    };
    
    type TodosState = Todo[];
    
    const TodosStateContext = createContext<TodosState | undefined>(undefined);
    
    type Action =
      | { type: 'CREATE'; text: string }
      | { type: 'TOGGLE'; id: number }
      | { type: 'REMOVE'; id: number };
    
    type TodosDispatch = Dispatch<Action>;
    const TodosDispatchContext = createContext<TodosDispatch | undefined>(
      undefined
    );
    
    function todosReducer(state: TodosState, action: Action): TodosState {
      switch (action.type) {
        case 'CREATE':
          const nextId = Math.max(...state.map(todo => todo.id)) + 1;
          return state.concat({
            id: nextId,
            text: action.text,
            done: false
          });
        case 'TOGGLE':
          return state.map(todo =>
            todo.id === action.id ? { ...todo, done: !todo.done } : todo
          );
        case 'REMOVE':
          return state.filter(todo => todo.id !== action.id);
        default:
          throw new Error('Unhandled action');
      }
    }

    プロバイダ設定

    import React, { createContext, Dispatch, useReducer } from 'react';
    
    (...) // (이전 코드 생략)
    
    export function TodosContextProvider({ children }: { children: React.ReactNode }) {
        const [todos, dispatch] = useReducer(todosReducer, [
            {
                id: 1,
                text: 'Context API 배우기',
                done: true
            },
            {
                id: 2,
                text: 'TypeScript 배우기',
                done: true
            },
            {
                id: 3,
                text: 'TypeScript 와 Context API 함께 사용하기',
                done: false
            }
        ]);
    
        return (
            <TodosDispatchContext.Provider value={dispatch}>
                <TodosStateContext.Provider value={todos}>
                    {children}
                </TodosStateContext.Provider>
            </TodosDispatchContext.Provider>
        );
    }

    カスタムホームページの作成

    import React, { createContext, Dispatch, useReducer, useContext } from 'react';
    
    export function useTodosState() {
      const state = useContext(TodosStateContext);
      if (!state) throw new Error('TodosProvider not found');
      return state;
    }
    
    export function useTodosDispatch() {
      const dispatch = useContext(TodosDispatchContext);
      if (!dispatch) throw new Error('TodosProvider not found');
      return dispatch;
    }
    useContextを使用してContextの値を使用する場合は、この値が有効かどうかを確認する必要があります.Custom Hookを使用して互換性チェックを自動的に行うことができます.

    構成部品でのContextの使用


    アプリケーションコンポーネントは、既存のコンテンツを保護するためにTodosContextProviderを呼び出す必要があります.
    // src/App.tsx
    
    import React from 'react';
    import TodoForm from './components/TodoForm';
    import TodoList from './components/TodoList';
    import { TodosContextProvider } from './contexts/TodosContext';
    
    const App = () => {
      return (
        <TodosContextProvider>
          <TodoForm />
          <TodoList />
        </TodosContextProvider>
      );
    };
    
    export default App;

    Contextの検索(userTodoState)

    // src/components/TodoList.tsx
    
    import React from 'react';
    import TodoItem from './TodoItem';
    import { useTodosState } from '../contexts/TodosContext';
    
    function TodoList() {
      const todos = useTodosState();
      return (
        <ul>
          {todos.map(todo => (
            <TodoItem todo={todo} key={todo.id} />
          ))}
        </ul>
      );
    }
    
    export default TodoList;

    Dispatchの使用-登録

    // src/components/TodoForm.tsx
    
    import React, { useState } from 'react';
    import { useTodosDispatch } from '../contexts/TodosContext';
    
    function TodoForm() {
      const [value, setValue] = useState('');
      const dispatch = useTodosDispatch();
    
      const onSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        dispatch({
          type: 'CREATE',
          text: value
        });
        setValue('');
      };
    
      return (
        <form onSubmit={onSubmit}>
          <input
            value={value}
            placeholder="무엇을 하실 건가요?"
            onChange={e => setValue(e.target.value)}
          />
          <button>등록</button>
        </form>
      );
    }
    
    export default TodoForm;

    Dispatch-切り替え、削除

    import React from 'react';
    import './TodoItem.css';
    import { useTodosDispatch, Todo } from '../contexts/TodosContext';
    
    type TodoItemProps = {
      todo: Todo; // TodoContext 에서 선언했던 타입을 불러왔습니다.
    };
    
    function TodoItem({ todo }: TodoItemProps) {
      const dispatch = useTodosDispatch();
    
      const onToggle = () => {
        dispatch({
          type: 'TOGGLE',
          id: todo.id
        });
      };
    
      const onRemove = () => {
        dispatch({
          type: 'REMOVE',
          id: todo.id
        });
      };
    
      return (
        <li className={`TodoItem ${todo.done ? 'done' : ''}`}>
          <span className="text" onClick={onToggle}>
            {todo.text}
          </span>
          <span className="remove" onClick={onRemove}>
            (X)
          </span>
        </li>
      );
    }
    
    export default TodoItem;