写在前面:
磨磨唧唧了好久终于下定决心开始学react,刚刚接触感觉有点无从下脚...新的语法新的格式跟vue就像两种物种...倒是很好奇路由和store是怎么实现的了~v~,一点一点来吧!!!
(一)创建项目
使用vite创建
npm create vite
选择react项目,欧啦现在开始:
main.tsx文件:
还是熟悉的味道,鉴于我还搞不清楚组件和路由,就直接在app.tsx里面开干
(二)制作井字棋游戏
1. 搭建九宫格形状
(1)用Square组件实现小格子
function Square() {return (<><button className='square'>x</button></>)}
(2)使用数组map方法渲染出九个Square组件
不同于vue的v-for,react使用数组方法实现列表的渲染
function Board() {// 存储每个格子的值 初始化为空const squares = new Array(9).fill('')const listSquare = squares.map((value, index) => (<Square key={index} />))return (<>{listSquare}</>)}
(3)通过props传递数据
将square组件的值存储在Board里面,通过props传值
function Square({ content }) {return (<><button className='square'>{content}</button></>)}
function Board() {...const listSquare = squares.map((value, index) => (<Square key={index} content={value} />))...}
这样的props传值感觉更清晰,子组件可以接收到props对象,展开的content变量直接就用
再提一嘴react的这个单括号:既可以像vue的插值语法{{}},也像v-bind数据绑定一样传递数据,蛮有意思蛮有意思
2.实现交互效果
(1)点击square显示×号
在sqare组件中实现:
使用useState钩子来实现对数据的记忆和更改,类似于vue的响应式数据
const [content, setContent] = useState('')中:content相当于数据的getter,setContent相当于数据的setter
function Square() {const [content, setContent] = useState('')function handleClick() {setContent('X')}return (<><button className='square' onClick={handleClick}>{content}</button></>)}
这样虽然实现了点击就改变square的内容,不过改变后的结果Board组件并不能得知
(2)状态提升
数据存储在square中是无法实现完成O和X的交替点击,以及判定赢家等操作的
因此将点击事件和state存储在父组件Board上,通过props传递给各个square组件即可
function Square({ content, onSquareClick }) {return (<><button className='square' onClick={onSquareClick}>{content}</button></>)}
function Board() {// 存储每个格子的值 初始化为空const [squares, setSquares] = useState(new Array(9).fill(''))// 点击对应索引的square,更新数组function handleClick(index) {const nextSquares = squares.slice()nextSquares[index] = 'X'setSquares(nextSquares)}// 遍历渲染九个square组件const listSquare = squares.map((value, index) => (<Square key={index} content={value} onSquareClick={() => { handleClick(index) }} />))...}
注意:不能直接 onSquareClick={ handleClick(index) } 将函数传给Square组件,因为这样会直接调用点击事件更新state,造成死循环;
可以通过套一层函数调用handleClick解决,因此最便捷的就是直接使用箭头函数调用
(3)不变性
为什么在handleClick函数中要使用slice()复制一个新数组,对其进行更改后再setState?
为了维持不变性,如果直接对state进行修改,当父组件的state改变后,所有的子组件都会跟着重新渲染(包括未受影响的子组件)
不行...我只觉得直接改变state的话那setState的意义在哪里...?为什么要遵守不变性我这个例子没办法充分证明,后续遇到了再看看
(4)交替传值
直接用一个state来保存当前应该填充的内容
// 记录本次点击内容是O还是X
const [isX, setIsX] = useState(true)
// 点击对应索引的square,更新数组
function handleClick(index) {// 如果square内容已经被填充为o或者x 就不进行后续操作if (squares[index] !== '') returnconst nextSquares = squares.slice()if (isX) {nextSquares[index] = 'X'} else {nextSquares[index] = 'O'}setSquares(nextSquares)setIsX(!isX)
}
(5)判定赢家
判断是否胜利的逻辑就直接copy了,就是能够胜利的情况的数组集合
// 判断是否胜利
function calculateWinner(squares) {const lines = [[0, 1, 2],[3, 4, 5],[6, 7, 8],[0, 3, 6],[1, 4, 7],[2, 5, 8],[0, 4, 8],[2, 4, 6]];for (let i = 0; i < lines.length; i++) {const [a, b, c] = lines[i];if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {return squares[a];}}return null;}
使用变量winner存储结果
const winner = calculateWinner(squares)
如果winner有值了就不能触发点击事件
function handleClick(index) {// 如果square内容已经被填充为o或者x或者游戏结束,就不进行后续操作if (squares[index] !== '' || calculateWinner(squares)) return...
}
写上提示,主题功能就完成了!
function Board() {...const winner = calculateWinner(squares)return (<><div className="content">{/* 判断是正在游戏还是有赢家了 */}{winner ? (<p className='player'>赢家:{winner}</p>) : (<p className='player'>本轮玩家:{isX ? 'X' : 'O'}</p>)}<div className='board'>{listSquare}</div></div></>)}
注释都要包大括号,蛮诡异的^-^
3.全部代码
其实还有个历史回退功能的,懒得写了,这个入门教程已经大致涵盖了react的基础特色;
function App() {function Square({ content, onSquareClick }) {return (<><button className='square' onClick={onSquareClick}>{content}</button></>)}function Board() {// 存储每个格子的值 初始化为空const [squares, setSquares] = useState(new Array(9).fill(''))// 记录本次点击内容是O还是Xconst [isX, setIsX] = useState(true)// 点击对应索引的square,更新数组function handleClick(index) {// 如果square内容已经被填充为o或者x或者游戏结束,就不进行后续操作if (squares[index] !== '' || calculateWinner(squares)) returnconst nextSquares = squares.slice()if (isX) {nextSquares[index] = 'X'} else {nextSquares[index] = 'O'}setSquares(nextSquares)setIsX(!isX)}// 遍历渲染九个square组件const listSquare = squares.map((value, index) => (<Square key={index} content={value} onSquareClick={() => { handleClick(index) }} />))// 判断是否胜利function calculateWinner(squares) {const lines = [[0, 1, 2],[3, 4, 5],[6, 7, 8],[0, 3, 6],[1, 4, 7],[2, 5, 8],[0, 4, 8],[2, 4, 6]];for (let i = 0; i < lines.length; i++) {const [a, b, c] = lines[i];if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {return squares[a];}}return null;}const winner = calculateWinner(squares)return (<><div className="content">{/* 判断是正在游戏还是有赢家了 */}{winner ? (<p className='player'>赢家:{winner}</p>) : (<p className='player'>本轮玩家:{isX ? 'X' : 'O'}</p>)}<div className='board'>{listSquare}</div></div></>)}return (<><Board /></>)
}
(三)总结
已经学过一门框架了,再看react的话只是思维不太一样,在react里也能看到蛮多vue仿鉴的东西,所以最开始的基础就不像以前一样写的又慢又细了
这个入门井字棋游戏概括了很多的基础知识
1.jsx的函数式编程
2.父子组件的props传递
3.列表的渲染
4.useState HOOK
5.条件渲染
6.响应事件
速度速度,再不学学不完了QAQ