概述
在此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!