在UI应用中使用高级ES6功能和单元测试
#javascript #react #过滤器 #reduce

概述

在此TIC TAC TOE应用中,我演示了JavaScript ES6功能的使用,包括还原,过滤器,MAP,SPLICE和FINDINDEX。这还包括使用多个用户事件的单元测试。

项目设置

该项目用Create React App进行了引导,还包括Eslint和更漂亮的改进清洁代码。

项目结构

data.js:提供对董事会JSON数据的促进访问

组件:带单位测试的板和单元组件。

可以在此处找到完整的代码:https://github.com/LawrenceKarasek/TicTacToe

运行项目


npm install npm start

此外,要检查任何编码问题并正确格式:


npm run eslint 
npm run format

加载数据中

董事会

使用承诺将数据异步加载。使用效果挂钩在其依赖项数组中包含fetchdata,并在初始加载时调用fetchdata方法。为了防止不准确的重新加载,fetchdata包含在usecallback中。这样可以确保fetchdata函数被记忆(缓存)。否则,每次使用使用效果,都会创建一个新版本,并且使用效率将再次调用fetchdata。


const [cells, setCells] = useState();
const fetchData = useCallback(async () => {
getData()
.then(result => setCells(result))
.catch(err => console.error(err));
}, []);

useEffect(() => {
fetchData();
}, [fetchData]);

fetchdata方法在data.js中调用getData函数。由于承诺与“当时”一起使用,因此允许将结果分配给SetVideos中的状态。

data.js

data.js中的GETDATA方法使用承诺使用“ Resolve”来异步加载JSON数据。如果发生错误,则承诺将带有错误消息的“拒绝”。


const getData = () => {
return new Promise((resolve, reject) => {
try{
if (data) {
resolve(data);
} else {
reject('No data is available.');
}
}
catch(e){
reject('An error occurred fetching data:' + e);
};
});
};

渲染

董事会

将单元数据加载到状态后,一次将单元写入一行。 WriteCellrow方法过滤每行的单元格,然后返回一系列单元格:


  const writeCellRow = (row) => {
    return cells
      .filter((f) => f.row === row)
      .map((item) => (
        <Cell
          cellData={item}
          key={item.row + "_" + item.column}
          updateCell={updateCell}
        />
      ));
  };

UI包括一个使用USEREF Webhook的WONREF来维持国家重新汇款之间的董事会状态。之所以使用,是因为如果这种状态与细胞状态一起保持状态,则重新渲染董事会的行为是不可预测的。


  return (
    <Fragment>
      <h1 className="header"> Board </h1>
      <h2 className="won"> {wonRef.current === true && "YOU WON!!"} </h2>
      {cells && (
        <Fragment>
          <div className="board">
            <div className="cellRow">{writeCellRow(1)}</div>
            <div className="cellRow">{writeCellRow(2)}</div>
            <div className="cellRow">{writeCellRow(3)}</div>
          </div>
          <div>
            <button onClick={() => clearBoard()}>
              Clear Board
            </button>
          </div>
        </Fragment>
      )}
    </Fragment>
  );
};

cell.js

每个单元通过董事会接收其状态,并带有用于更新状态的回调。


const Cell = ({ cellData, updateCell }) => {
  return (
    <button className="cell" role="cell" onClick={() => updateCell(cellData)}>
      <span className="cellText">{cellData.state}</span>
    </button>
  );
};

Cell.propTypes = {
  cellData: PropTypes.object,
  updateCell: PropTypes.func,
};

更新单元格

董事会

板上的单元格从“ x”更新为“ o”,然后如下返回到null。注意,必须在使用setCells更新状态之前复制单元格的初始数组以在内存中创建新的参考。单个单元格是通过使用数组FindIndex然后剪接方法更新的。


  const updateCell = (cell) => {
    let updatedState = "";
    switch (cell.state) {
      case "X":
        updatedState = "O";
        break;
      case "O":
        updatedState = null;
        break;
      default:
        updatedState = "X";
        break;
    }
    cell.state = updatedState;

    let cellFilteredIndex = cells.findIndex(
      (c) => c.row === cell.row && c.column === cell.column
    );

    let cellsCopy = [...cells];
    cellsCopy.splice(cellFilteredIndex, 1, cell);
    setCells(cellsCopy);
    checkScore();
  };

检查得分

为了检查分数,必须同时检查“ x”和“ o”的每行和列以及Tewo对角线。这是使用CheckRowsColumnSwon函数依次完成的,该功能采用检查类型(“行”,“列”或“对角线”),如果适用,则采用行或行号。


  const checkScore = () => {
    let isWin = false;

    for (let x = 1; x <= 3; x++) {
      isWin = checkRowsColumnsWon("row", x, "X");
      if (isWin) {
        break;
      }
      isWin = checkRowsColumnsWon("row", x, "O");
      if (isWin) {
        break;
      }
      isWin = checkRowsColumnsWon("column", x, "X");
      if (isWin) {
        break;
      }
      isWin = checkRowsColumnsWon("column", x, "O");
      if (isWin) {
        break;
      }
    }
    if (!isWin) {
      isWin = checkRowsColumnsWon("diagonal", null, "X");
      if (!isWin) {
        isWin = checkRowsColumnsWon("diagonal", null, "O");
      }
    }
    if (isWin) {
      wonRef.current = true;
    }
  };

CheckRowsColumnSwon函数使用RELAD功能来返回每种类型的支票的布尔值。对于数组中的EEACE项目,减少更新Accummulator参数,在这种情况下,“ ISWON”。如果行号或列号和状态匹配,则rowcount会增加,当RowCount = 3时,ISWON设置为true。


  const checkRowsColumnsWon = (type, number, state) => {
    let wonRowsColumns = false;
    let rowCount = 0;

    switch (type) {
      case "row":
        wonRowsColumns = cells.reduce((isWon, f) => {
          rowCount =
            f.row === number && f.state === state ? rowCount + 1 : rowCount;
          return (isWon || rowCount === 3);
        }, false);
        break;
      case "column":
        wonRowsColumns = cells.reduce((isWon, f) => {
          rowCount =
            f.column === number && f.state === state ? rowCount + 1 : rowCount;
          return (isWon || rowCount === 3);
        }, false);
        break;
      case "diagonal":
        wonRowsColumns = cells.reduce((isWon, f) => {
          rowCount =
            (f.row === 1 && f.column === 1 && f.state === state) ||
            (f.row === 2 && f.column === 2 && f.state === state) ||
            (f.row === 3 && f.column === 3 && f.state === state)
              ? rowCount + 1
              : rowCount;
          return (isWon || rowCount === 3);
        }, false);
        if (!wonRowsColumns) {
          wonRowsColumns = cells.reduce((isWon, f) => {
            rowCount =
              (f.row === 3 && f.column === 3 && f.state === state) ||
              (f.row === 2 && f.column === 2 && f.state === state) ||
              (f.row === 3 && f.column === 1 && f.state === state)
                ? rowCount + 1
                : rowCount;
            return (isWon || rowCount === 3);
          }, false);
        }
        break;
      default:
        wonRowsColumns = false;
        break;
    }
    return wonRowsColumns;
  };

单位测试

对板和单元组件进行了测试,以验证它们已正确加载,并且更新和评分功能正常工作。使用React测试库检查实际的用户交互。

board.test.js


describe("Board is rendered correctly", () => {
  it("renders the Board and displays correct number of cells", async () => {
    render(<Board />);
    const clearButton = await screen.findByText("Clear Board");
    expect(clearButton).toBeDefined();
    const cells = await screen.findAllByRole("cell");
    expect(cells).toHaveLength(9);
  });
});

describe("selecting 3 X's or O's in a row results in winning", () => {
  let cellList = null;
  let cellButton = null;
  let clearButton = null;

  it("3 X's in a row result in winning", async () => {
    render(<Board />);

    await waitFor(async () => {
      cellList = await screen.findAllByRole("cell");
      cellButton = cellList[0];
    });

    fireEvent.click(cellButton);

    await waitFor(async () => {
      const updatedFirstButton = await screen.findByText("X");
      expect(updatedFirstButton).not.toBeNull();
    });

    cellButton = cellList[1];
    fireEvent.click(cellButton);

    await waitFor(async () => {
      const updatedButtons = await screen.findAllByText("X");
      expect(updatedButtons).toHaveLength(2);
    });

    cellButton = cellList[2];
    fireEvent.click(cellButton);

    await waitFor(async () => {
      const updatedButtons = await screen.findAllByText("X");
      expect(updatedButtons).toHaveLength(3);
    });

    await waitFor(async () => {
      const wonButton = await screen.findByText("YOU WON!!");
      expect(wonButton).toBeDefined();
    });

    await waitFor(async () => {
      clearButton = await screen.findByText("Clear Board");
    });

    fireEvent.click(clearButton);

    await waitFor(async () => {
      cellList = await screen.findAllByRole("cell");
      cellButton = cellList[0];
      expect(cellButton).toHaveValue("");
    });
  });
});


## Cell.test.js

The cell is mocked to verify it is rendering correctly and the updateCell function is called. Mockingthe data for a component is less realistic then actual user interaction but necessary in many cases since a component depends on live data. 

const celldata = {
行:1,
列:1,
状态:“ x”,
};

描述(“正确渲染单元格”,()=> {
const updatecell = jest.fn();
令cellbutton = null;

it(“正确渲染单元格”,async()=> {
渲染(
celldata = {celldata}
key = {celldata.row +“ _” + celldata.column}
updatecell = {updatecell}
/>
);

await waitFor(async () => {
  cellButton = await screen.findByText("X");
  expect(cellButton).toBeDefined();
});

});

it(“单击单元呼叫更新”,async()=> {
让cellbutton = null;
渲染(
celldata = {celldata}
key = {celldata.row +“ _” + celldata.column}
updatecell = {updatecell}
/>
);

await waitFor(async () => {
  cellButton = await screen.findByText("X");
  expect(cellButton).not.toBeNull();
});

fireEvent.click(cellButton);

await waitFor(async () => {
  expect(updateCell).toHaveBeenCalledTimes(1);
});

});
});



# Conclusion

I hope this is helpful for those learning how to use advanced javascript ES6 functions effectively in front end code. I welcome your feedback to improve this article. All the Best!