TIL 22-04-20


React.js


Today I Learned ... react.js🙋‍♂️ React.js Lecture
🙋‍ My Dev Blog

React Lecture CH 7


1 - useReducer
2 -reducer, action, dispatch
3-action dispatchの作成
4-Tick Taxtoゲーム
5-テーブル最適化
  • 時間結果

  • 🙋‍♂️ 解決すべき問題
    1.クリックしない場所を再度クリック
    2.勝者を決めるロジックの実現(勝者)
    +引き分けの場合

    タッチダウンをクリア


    1.一度押さないユニット


    Td.jsx
    const Td = ({ rowIndex, cellIndex, dispatch, cellData }) => {
      const onClickTd = useCallback(() => {
        if (cellData) {
          return; // 셀 데이터가 존재하면 빠져나옴 -> 한번 클릭한 셀은 변경되지 않게
        }
        dispatch({ type: CLICK_CELL, row: rowIndex, cell: cellIndex });
        dispatch({ type: CHANGE_TURN });
      }, [cellData]);
    
      return <td onClick={onClickTd}>{cellData}</td>;
    };
  • if文を簡単に追加するだけです.
  • if (cellData) break;
  • を初めてクリックした場所であれば、CellDataは「」になるのでfalseになります.
  • をクリックした場合、CellDataは「O」または「X」であるため、trueとなりifゲートを満たす.
    ->breakを使用してonClicktd関数を終了します.
  • 注記-cellDataとTable Data

    注意2-stateは非同期です!
    cf)Reduxは同期している
    ->非同期状態を処理するには、useEffectを使用する必要があります.

    2.勝者(勝者)判断ロジック


    OかXが勝つ場合は大体4種類あります.
    let win = false;
        if (
          tableData[row][0] === turn &&
          tableData[row][1] == turn &&
          tableData[row][2] === turn
        ) {
          win = true;
        }
        if (
          tableData[0][cell] === turn &&
          tableData[1][cell] === turn &&
          tableData[2][cell] === turn
        ) {
          win = true;
        }
        if (
          tableData[0][0] === turn &&
          tableData[1][1] === turn &&
          tableData[2][2] === turn
        ) {
          win = true;
        }
        if (
          tableData[0][2] === turn &&
          tableData[1][1] === turn &&
          tableData[2][0] === turn
        ) {
          win = true;
        }

    tableDataは非同期で変更されているため、usStateを使用する必要があります.
    TicTacToe.jsx
    const reducer = (state, action) => {
      switch (action.type) {
       // 생략
        case CLICK_CELL: {
          const tableData = [...state.tableData];
          tableData[action.row] = [...tableData[action.row]];
          tableData[action.row][action.cell] = state.turn;
          return {
            ...state,
            tableData,
            recentCell: [action.row, action.cell], // 👈 추가
          };
    現在のセルの位置を表すstate(recentCell)を追加します.
    CLICK CELLをreduceで実行するたびに、現在のセルの位置が保存されます.
    const initialState = {
      winner: '',
      turn: 'O',
      tableData: [
        ['', '', ''],
        ['', '', ''],
        ['', '', ''],
      ],
      recentCell: [-1, -1], // 👈 추가
    };
    
    recentcellを
  • InitialStateに追加します.
    (初期値は-1、すなわちインデックスなし)
  • // useEffect
    useEffect(() => {}, [recentCell]);
    useEffect(() => {
        const [row, cell] = recentCell;
        if (row < 0) {
          return;
          // useEffect는 첫 렌더링시에도 실행되는데, 이를 막기 위함.
        }
        let win = false;
        if (
          tableData[row][0] === turn &&
          tableData[row][1] == turn &&
          tableData[row][2] === turn
        ) {
          win = true;
        }
        if (
          tableData[0][cell] === turn &&
          tableData[1][cell] === turn &&
          tableData[2][cell] === turn
        ) {
          win = true;
        }
        if (
          tableData[0][0] === turn &&
          tableData[1][1] === turn &&
          tableData[2][2] === turn
        ) {
          win = true;
        }
        if (
          tableData[0][2] === turn &&
          tableData[1][1] === turn &&
          tableData[2][0] === turn
        ) {
          win = true;
        }
        if (win) {
          dispatch({ type: SET_WINNER, winner: turn });
        } else {
          // 무승부 검사
        }
      }, [recentCell]);

    useEffectの変更

  • userEffectは、最初のレンダリング時にも実行されます.
    運転しないようにドアを閉めます.
  • recentCellの行とセルの初期値は-1です.
    条件はif(row<0)です.
  • 勝利条件winがtrueなら
    -> dispatch({ type: SET_WINNER, winner: turn });
  • winがfalseの場合
    ->1)引き分けチェック(テーブルがいっぱいかどうか)
    ->2)次のラウンド(OからXまで)
  • 1)引き分け検査
    let all = true;
          tableData.forEach((row) => {
            row.forEach((cell) => {
              if (!cell) {
                all = false;
              }
            });
          });
  • forEachを重ね、row、cellで遍歴
    チャットルームは空いていますか?
  • が空(=if(!cell)の場合、allはfalseです.
    1-2)引き分けの場合=つまりテーブルがいっぱい(all=true)
    if (all) {
            // 무승부면 리셋
            dispatch({ type: SET_WINNER, winner: null });
            dispatch({ type: RESET_GAME });
          } 
    2)引き分けでなければ次のラウンドに移る
    else {
            // 무승부 아니면 턴 넘김
            dispatch({ type: CHANGE_TURN });
          }

    🔻 useEffectフルコード

    useEffect(() => {
        const [row, cell] = recentCell;
        if (row < 0) {
          return;
          // useEffect는 첫 렌더링시에도 실행되는데, 이를 막기 위함.
        }
        let win = false;
        if (
          tableData[row][0] === turn &&
          tableData[row][1] == turn &&
          tableData[row][2] === turn
        ) {
          win = true;
        }
        if (
          tableData[0][cell] === turn &&
          tableData[1][cell] === turn &&
          tableData[2][cell] === turn
        ) {
          win = true;
        }
        if (
          tableData[0][0] === turn &&
          tableData[1][1] === turn &&
          tableData[2][2] === turn
        ) {
          win = true;
        }
        if (
          tableData[0][2] === turn &&
          tableData[1][1] === turn &&
          tableData[2][0] === turn
        ) {
          win = true;
        }
        if (win) {
          // 승리시
          dispatch({ type: SET_WINNER, winner: turn });
          dispatch({ type: RESET_GAME });
        } else {
          // 무승부 검사 - 칸이 다 차있으면 (즉, all이 true면) 무승부임
          let all = true;
          tableData.forEach((row) => {
            row.forEach((cell) => {
              if (!cell) {
                all = false;
              }
            });
          });
          if (all) {
            // 무승부면 리셋
            dispatch({ type: SET_WINNER, winner: null });
            dispatch({ type: RESET_GAME });
          } else {
            // 무승부 아니면 턴 넘김
            dispatch({ type: CHANGE_TURN });
          }
        }
      }, [recentCell]);

    <結果>



    パフォーマンスの最適化


    Chrome Dev Toolを利用
    レンダーセクションを表示します.

    明確なセルをクリックして変更するだけで、1つのセルを変更するたびにセル全体がレンダリングされます.
    ->useEffectとuseRefを使用してパフォーマンスを最適化します.

    useRef, useEffect

  • レンダリングを開始するものが何なのか分からない場合に使用します.
  • Td.jsx
    
    const Td = ({ rowIndex, cellIndex, dispatch, cellData }) => {
      
      const ref = useRef([]);
      
      useEffect(() => {
        console.log(
          rowIndex === ref.current[0],
          cellIndex === ref.current[1],
          dispatch === ref.current[2],
          cellData === ref.current[3]
        );
        ref.current = [rowIndex, cellIndex, dispatch, cellData];
      }, [rowIndex, cellIndex, dispatch, cellData]);
    	// 모든 props를 다 적어줌
      ...
    }
    
  • ref.現在のすべてのアイテムを配列形式で保存し、保存値と同じかどうかを比較します.
  • ここでfalseが表示された場合、値が変更されたことを示します.
    だからこそ、レンダリングが発生します.
  • これは、パフォーマンスを最適化する際によく使用される方法です.
    (useref+userEffectの比較)

    ->cellDataが再レンダリングに置き換えられています.
    console.ロゴを撮ったところ、td自体が自分の欲しいものだけを変えていることに気づきました.
    この時は反応してmemoでPureComponentに変えればいいです

    React.memoの使用

  • TおよびTD要素の全てを再アセンブリした.memoで包みます.
  • Td.jsx
    import React, { useCallback, memo } from 'react';
    import { CLICK_CELL } from './TicTacToe';
    
    const Td = memo(({ rowIndex, cellIndex, dispatch, cellData }) => {
      console.log('Td render');
    
      const onClickTd = useCallback(() => {
        if (cellData) {
          return; // 셀 데이터가 존재하면 빠져나옴 -> 한번 클릭한 셀은 변경되지 않게
        }
        dispatch({ type: CLICK_CELL, row: rowIndex, cell: cellIndex });
      }, [cellData]);
    
      return <td onClick={onClickTd}>{cellData}</td>;
    });
    
    export default Td;
    
    Tr.jsx
    import React, { memo } from 'react';
    import Td from './Td';
    
    const Tr = memo(({ rowData, rowIndex, dispatch }) => {
      console.log('Tr render');
      return (
        <tr>
          {Array(rowData.length)
            .fill()
            .map((td, i) => (
              <Td
                key={i}
                rowIndex={rowIndex}
                cellIndex={i}
                cellData={rowData[i]}
                dispatch={dispatch}
              >
                {''}
              </Td>
            ))}
        </tr>
      );
    });
    
    export default Tr;
    

    tdをクリックすると
  • td->trがレンダーされ、->テーブルがレンダーされます.
    (子をレンダリングするときに親がレンダリングされるように見える)
  • は、実際にはtdのみをレンダリングします.
  • * * *

    +) React.userMemoを使用する場合

  • React.userMemoは関数を記憶する値ですが、素子を記憶することもできます.
  • import React, { useMemo } from 'react';
    import Td from './Td';
    
    const Tr = ({ rowData, rowIndex, dispatch }) => {
      console.log('Tr render');
      return (
        <tr>
          {Array(rowData.length)
            .fill()
            .map((td, i) =>
              useMemo(
                () => (
                  <Td 
                    key={i} rowIndex={rowIndex} cellIndex={i} cellData={rowData[i]} dispatch={dispatch}
                  >
                    {''}
                  </Td>
                ),
                [rowData[i]]
              )
            )}
        </tr>
      );
    };
    
    export default Tr;
  • rowData[i]の変更時にのみ再レンダリングされます.
    (関数の値を記憶)
  • 🙋‍♀️ 反応すれば.memo()で包まれていますが、再レンダリングできたら?
    最後の手段としての反応.useMemo()を使用します.
    ->構成部品自体を覚えます.