React做了什么?
将React代码先跑起来
1.使用React提供的CLIcreate-react-app
创建一个React应用
npx create-react-app my-app
cd my-app
yarn start
由于我们已经新建了文件夹,因此我们只需要运行npx create-react-app .
表示在当前目录下创建项目(推荐使用nrm,切换为taobao源,nrm use taobao
)
2.删掉无用的部分
删除包括css文件代码、文件、注释和引用等
只留下app.js
和 index.js
,app.js中只留下一个div,index中的代码全部删除即可
-
index.js 是程序的入口
-
app.js 是第一个组件
在index.js
中输入console.log('Helo world')
,测试是否可以正常运行
用原生JS实现一个页面
创建一个div标签,div标签含一个p标签,p标签包含一个span标签,span标签中有文本内容 - “我是一个p”标签
【敲代码】
如下所示,页面中创建了很多标签
const div = document.createElement('div');
const p = document.createElement('p');
const span = document.createElement('span');
div.appendChild(p);
p.appendChild(span)
span.innerText = "我是一个段落P";
document.body.appendChild(div);
【运行结果】
1.优化1 ====> 函数createElement
创建DOM,不想每次都写docuent.createElement
const div = createElement('div');
const p = createElement('p');
const span = createElement('span');
div.appendChild(p);
p.appendChild(span)
span.innerText = "我是一个段落P";
document.body.appendChild(div);
function createElement(tagName){
return document.createElement(tagName)
}
2.优化2 ===> 函数createElement
支持创建标签及其子元素children
【敲代码】
const div = createElement('div',createElement('p',createElement('span')));
document.body.appendChild(div);
function createElement(tagName,children){
const element = document.createElement(tagName)
if(children){
element.appendChild(children)
}
return element
}
3.优化3 ===> 函数createElement
支持创建标签及其子元素children
,其中children
不仅是元素还可以是文本节点
【敲代码】
如下所示函数createElement
const div = createElement('div',createElement('p',createElement('span',"Hello React!")));
document.body.appendChild(div);
function createElement(tagName,children){
const element = document.createElement(tagName)
if(children){
if(typeof children === 'string'){
var childElement = document.createTextNode(children)
element.appendChild(childElement)
}else{
element.appendChild(children)
}
}
return element
}
备注
document.createElement()
中只支持创建标签,不支持创建文本,因此我们需要创建文本节点- 如果有
ctrateElement
这样的函数,我们就可以把用JS创建标签写的很简单
4.优化4 ===> 函数重命为t,缩进函数t调用格式
【敲代码】
如下所示,将之前的函数名重命名为t
const div = (
t('div',
t('p',
t('span',"Hello React!")))
);
document.body.appendChild(div);
function t(tagName,children){
const element = document.createElement(tagName)
if(children){
if(typeof children === 'string'){
var childElement = document.createTextNode(children)
element.appendChild(childElement)
}else{
element.appendChild(children)
}
}
return element
}
【观察格式】
用React写这个功能可以做的更好
5.优化5 ===> 发明一种语法,像写标签一样写函数t呢?
- 将 t ----> 替换为"<"
- 将 "," ----> 替换为">"
- t 函数的第二个参数放到标签里
即开发者像写html标签一样写代码,而这个语法直接将其翻译成函数t式的代码,如果可以做到这一点那就太方便了
const div = (
t('div',
t('p',
t('span',"Hello React!")))
);
const div2 = (
<div>
<p>
<span>
"Hello React!"
</span>
</p>
</div>
)
这也是React的第一个核心思想:
开发者以为自己在写标签,实际上react将其翻译成了函数t的形式,t也就是
React.createElement()
6. 优化6 ===> 使用React.createElement()
,就不需要我们自己写函数t了
【敲代码】
如下所示,就不需要我们自己造函数t了,而是使用React.createElement()
import React from "react";
const div = (
React.createElement('div',
React.createElement('p',
React.createElement('span',"Hello React!")))
);
console.log(div);
【运行结果】
打印出的div是一个对象!!div > p > span > 'Hello React!'
备注
使用react创建的不是真的element,而是一个虚拟的element(也就是一个对象),它不能直接放到body中。这个虚拟的element就是react虚拟dom,虚拟dom比真的dom性能更强大
这也是React的第二个核心思想:虚拟DOM
那么用react创建的虚拟div不能放到页面上,那创建这个div有什么用?
7.优化7 ===> 引入react-dom 它支持虚拟Dom
【敲代码】
如下所示:虚拟dom div元素就可以放到页面中显示了
import React from "react";
import ReactDOM from "react-dom/client";
const div = (
React.createElement('div',null,
React.createElement('p',null,
React.createElement('span',null,"Hello React!")))
);
console.log(div);
// React18后支持的写法
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(div)
这样虚拟DOM即div就使用render函数挂到id为root的div上了
备注
React.createElement
第一个参数是要创建到的元素,第二个参数用来绑定事件(没有就传null),第三个参数是放到元素中的内容
react-dom
的作用是让虚拟DOM可以出现在body中将虚拟的div使用
ReactDOM.render()
放到页面中的div上(index.html中id为root的div上),不要放到body上污染body
React.createElement()
的作用就是创建虚拟DOM
React最核心的思想
React最核心的思想:创建虚拟DOM :
它创建了一个叫createElement的方法,用这个方法就可以写一个虚拟的div,这个虚拟的div使用
render()
方法就可以挂到页面body中的标签上
使用React创建组件
1.React支持更加简单的方式来创建虚拟DOM!
可是使用下面这种方式创建虚拟DOM未免也太麻烦了吧
const div = (
React.createElement('div',null,
React.createElement('p',null,
React.createElement('span',null,"Hello React!")))
);
更简单的书写方式:
【敲代码】
如下所示:使用写标签的形式创建虚拟DOM
import React from "react";
import ReactDOM from "react-dom/client";
const div = (
<div>
<p>
<span>
Hello React!!
</span>
</p>
</div>
)
console.log(div); // 虚拟的element,实际上是一个对象
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(div)
【运行结果】
结果还是可以正常运行
备注
心里一定要清楚,虽然写的像html标签,但是实际上是
React.createElement()!!!
2.React提供的组件化思路
a. 创建组件
const Header = (
<header>
header部分
</header>
)
b. 使用组件
{Header}
【敲代码】
如下所示: Header
和Bottom
都是组件
import React from "react";
import ReactDOM from "react-dom/client";
const Header = (
<header>
header部分
</header>
)
const Bottom = (
<div>
bottom部分
</div>
)
const div = (
<div>
{Header}
<p>
<span>
Hello React!!
</span>
</p>
{Bottom}
</div>
)
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(div);
【运行结果】
至此,我们就可以像组合变量一样任意的组合页面了,这就是React为我们提供的组件化思路,这点和Vue不一样,Vue需要写一个单文件组件,Vue自己发明语法
备注
React不发明任何语法,也不提供任何API,这点不像vue,它只是在写法上做文章,我们只使用纯JS就可以实现组件化
React的第三个核心思想:用JS的组合实现组件化
3.让组件支持外部传参数,将创建虚拟DOM改为函数的形式
JS中什么支持参数?===> 函数
如果我们的组件是一个函数,那么它是不是也可以接受参数?
a. 定义
const Header2 = function(props){
return (
<header>
header2 {props.name}
</header>
)
}
b.使用
{Header2({name:'Reagen'})}
【敲代码】
Header2
就是一个接受参数的组件
import React from "react";
import ReactDOM from "react-dom/client";
const Header = (
<header>
header部分
</header>
)
const Header2 = function(props){
return (
<header>
header2 {props.name}
</header>
)
}
const Bottom = (
<div>
bottom部分
</div>
)
const div = (
<div>
{Header}
{Header2({name:'Reagen'})}
<p>
<span>
Hello React!!
</span>
</p>
{Bottom}
</div>
)
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(div);
【运行结果】
备注:Header组件和Header2组件有什么区别?
- Header是固定内容和格式的组件
- Header2是允许传参数的组件
4.像写标签一样写组件吧!
如果觉得上面的写法太麻烦,React还支持另外一种写法:把函数式组件Header2当标签用
将函数式名当标签名,将要传的值写成属性的形式
{Header2({name:'Reagen'})} 等价于 <Header2 name="Tom and Jerry"/>
【敲代码】
import React from "react";
import ReactDOM from "react-dom/client";
const Header = (
<header>
header部分
</header>
)
const Header2 = function(props){
return (
<header>
header2 {props.name}
</header>
)
}
const Bottom = (
<div>
bottom部分
</div>
)
const div = (
<div>
{Header}
{Header2({name:'Reagen'})}
<Header2 name="Tom and Jerry"/>
<p>
<span>
Hello React!!
</span>
</p>
{Bottom}
</div>
)
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(div);
【运行结果】
备注
解释下
<Header2 name="Tom and Jerry"/>
的含义:React发现你写的不是普通的DOM标签,于是就去调用标签对应的函数,将标签后面写的属性,当做函数的参数传进去
React通过这种简单的方法就可以实现组件的传参
React中用函数形式创建的组件,在使用的时候不能使用花括号的形式调用组件"
{Headder2}
",这种只能是固定格式和内容的组件如Header
组件,而应该使用标签的形式去使用<Header2 name="Tom and Jerry"/>
5. 如何让组件支持它内部的变量?
【敲代码】
如下所示: 创建的组件Bottom2
中,在它内部定义了一个变量n和一个按钮,但是按照下面的写法,点击按钮并不能使n的值加一
import React from "react";
import ReactDOM from "react-dom/client";
const Header = (
<header>
header部分
</header>
)
const Header2 = function(props){
return (
<header>
header2 {props.name}
</header>
)
}
const Bottom = (
<div>
bottom部分
</div>
)
const Bottom2 = function(){
let n = 0;
return (
<div>
{n}
<button onClick={function(){
n = n + 1
}}>加1</button>
</div>
)
}
const div = (
<div>
{Header}
{Header2({name:'Reagen'})}
<Header2 name="Tom and Jerry"/>
<p>
<span>
Hello React!!
</span>
</p>
{Bottom}
<Bottom2/>
</div>
)
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(div);
React的第四个核心思想:const [n,setN] = React.useState(0)
如果要在组件中使用一个内部的状态,就需要用这种方法
它的意思是:状态的初始值是0;状态的名是n;状态的更新方法是setN
备注
const [n,setN] = React.useState(0)
这里使用了析构赋值的方式:
- 如果使用n就
{n}
- 如果要修改b就用
setN
【敲代码】
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
const Header = (
<header>
header部分
</header>
)
const Header2 = function(props){
return (
<header>
header2 {props.name}
</header>
)
}
const Bottom = (
<div>
bottom部分
</div>
)
const Bottom2 = function(){
const [n,setN] = React.useState(0); // 析构赋值
return (
<div>
{n}
<button onClick={function(){
setN(n+1)
}}>加1</button>
</div>
)
}
const div = (
<div>
{Header}
{Header2({name:'Reagen'})}
<Header2 name="Tom and Jerry"/>
<p>
<span>
Hello React!!
</span>
</p>
{Bottom}
<Bottom2/>
</div>
)
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(div);
【运行结果】
至此,我们只使用了React为我们提供的一个API,其他都是JS本身自带的内容,,这就是React和vue的最大区别API极少,但是功能是一样的
总结:React提供的功能
- 它可以使用标签的形式创建虚拟的div,里面可以任意的写它的子元素,如
const div = (
<div>
<p>
<span> Hello React!! </span>
</p>
</div>
)
2. 可以把另外一个组件用花括号'{}'引进来,如:
{Header}
3. 可以把组件写成一个函数,把这个函数调用一下再引进来,如{Header2({name:'Reagen'})}
4. 对上一条的优化:把函数的调用写成标签,把参数写成属性.如:<Header2 name="Tom and Jerry"/>
5. 如果希望组件拥有自己的状态就使用APIconst [n,setN] = React.useState(0)
,给一个读的API和一个写的API
在React中用创建类组件
// 类组件
class Bottom3 extends React.Component{
render(){
return (
<div>bottm3</div>
)
}
}
推荐:使用函数式创建组件
一起使用React来做一个井字棋的小游戏吧!
0. 生成一个棋格
【敲代码】
.cell{
border: 1px solid #000;
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
font-size: 80px;
}
import React from "react";
import ReactDOM from "react-dom/client";
import './style.css'
const Cell = function(){
const [text,setText] = React.useState('')
const onClickButton = function(){
setText("x")
}
return (
<div className="cell" onClick={onClickButton}>
{text}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<div>
<Cell />
</div>);
1. 生成棋盘
【敲代码】
.cell{
border: 1px solid #000;
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
font-size: 80px;
}
.row{
display: flex;
}
import React from "react";
import ReactDOM from "react-dom/client";
import './style.css'
const Cell = function(props){
return (
<div className="cell" >
{props.text}
</div>
)
}
const cells = [
[null,null,null],
[null,null,null],
[null,null,null],
]
const Chessboard = function(){
return (
<div>
{cells.map(items => <div className="row">
{items.map(item => <div className="col">
<Cell text={item}/>
</div>
)}
</div>)}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<div>
<Chessboard />
</div>);
备注
---------------------JS不好就不要用React---------------------
- 使用嵌套map映射一个二维数组,让数组中的每一项映射为一个Cell组件
- 不希望组件
Cell
中使用组件内部的数据text
,即
const [text,setText] = React.useState('')
而是希望从外部传进来,传进来什么就显示什么,于是Cell组件中使用参数props接收,在调用Cell组件的时候使用属性传值
2. 点击格子打X
【敲代码】
import React from "react";
import ReactDOM from "react-dom/client";
import './style.css'
const Cell = function(props){
return (
<div className="cell" onClick={props.onClick}>
{props.text}
</div>
)
}
const Chessboard = function(){
const [cells,setCells] = React.useState([
[null,null,null],
[null,null,null],
[null,null,null],
])
const onClickCell = (row,col)=>{
console.log('行'+'\t'+row);
console.log('列'+'\t'+col);
const copy = JSON.parse(JSON.stringify(cells))
copy[row][col] = 'x'
setCells(copy)
}
return (
<div>
{cells.map((items,row) => <div className="row">
{items.map((item,col) => <div className="col">
<Cell text={item} onClick={()=>onClickCell(row,col)}/>
</div>
)}
</div>)}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<div>
<Chessboard />
</div>);
过程
1.组组件上的props属性是只读的不允许修改,记住:props上的变量是谁的那么就由谁来改。
例子中props上的属性text
是组件Chessboard
调用组件Cell
时传过来的,组件Chessboard
中text的对应的是变量cells
数组。
我们怎样才能修改这个text的值呢?
==> 我们不用子组件上的点击方法,去修改父组件传过来的props,而是在父组件Chessboard
上定义点击方法onClickCell
,通过props将这个方法传给子组件,在父组件Chessboard
上定义点击方法onClickCell
去修改定义在父组件上的数据 cells
,父组件通过属性text
将修改过的cells
传给子组件,子组件去使用这个已经更新过的数据cells
- 一个函数如果想要拥有自己的状态,不应该用变量,而是用
useState
const [cells,setCells] = React.useState([
[null,null,null],
[null,null,null],
[null,null,null],
])
这样组件就可以修改自己的状态了
但是我们不知道应该修改哪一个棋格子对应的cells中的数据
我们需要将:
onClickCell会立即执行:<Cell text={item} onClick={onClickCell}/> 修改为
onClickCell不会立即执行:<Cell text={item} onClick={()=>onClickCell()}/>
理论上任何函数都可以将f改写成f2(只不过做了一层中转):
f 改写成
const f2 =(...args)=> f(...args)
好比:
onClickCell(1) 等价于
const fn = (p)=> onClickCell(p)
fn(1) onClickCell(1)
因此,<Cell text={item} onClick={()=>onClickCell(row,col)}/>
就可以接受参数了
3.点击时拿到行和列的信息后,是不是就可以直接改变cells中的某行某列的数据为X
,然后在setCells(cells)
就可以了呢?
const onClickCell = (row,col)=>{
console.log('行'+'\t'+row);
console.log('列'+'\t'+col);
cells[row][col] = 'x'
setCells(cells)
}
实际点击棋格并没有效果,为什么呢?
因为cells是一个对象,它只是一个内存地址而已
定义的时候用的是cells,即使改变了cells的值 cells[row][col] = 'x'
但是在修改cells的值的时候传的还是cellssetCells(cells)
cells在内存中的地址还是一模一样,这也就是为什么命名已经修改了cells
但是页面上却没有任何反应的原因
我们只是修改了cell中的值,修改前后cells对应的内存地址又没有发生变化,React为什么要更新DOM呢?
怎么办呢?
==》 使用深拷贝,一种简单的深拷贝的办法
const copy = JSON.parse(JSON.stringify(cells))
copy[row][col] = 'x'
setCells(copy)
这个拷贝出来的对象copy
就是一个全新的对象,也就是说同cells是不同的内存地址。
这样初始的时候使用的是对象cells
,setCells(copy)
修改cells之后传的是对象copy
,内存地址发生了变化,页面内容也就随着发生了变化
这样就实现了点击打"X"的效果(每次点击的时候,都会深拷贝一个全新的对象)
使用深拷贝一直创建对象,会不会影响性能?
微乎其微
为什么函数式会火?
因为内存够了
后端写代码,一台服务器要给所有人使用
但是前端就不一样,前端不存在后端出现的问题,我们写的代码只会在用户的电脑上(浏览器)跑起来,用户电脑的全部内存你都可以使用(操作内存比操作DOM快n倍),前端是一对一服务的
面向对象不也是占内存吗?
但是面向对象使用的是永远用同一块内存,用this来引用这块内存,函数式编程中基本没this
3. 判断当前是第几次点击,序号为奇数的格子打×,序号为偶数的格子打○
【敲代码】
import React from "react";
import ReactDOM from "react-dom/client";
import './style.css'
const Cell = function(props){
return (
<div className="cell" onClick={props.onClick}>
{props.text}
</div>
)
}
const Chessboard = function(){
const [cells,setCells] = React.useState([
[null,null,null],
[null,null,null],
[null,null,null],
])
// 点击的序号
const [n,setN] = React.useState(0)
const tell = ()=>{
console.log("判断谁赢了");
}
const onClickCell = (row,col)=>{
// n + 1
setN(n + 1);
// 改变cells
const copy = JSON.parse(JSON.stringify(cells))
// 点击序号为偶数的打"X",奇数打"O""
copy[row][col] = n % 2 === 0?'X': 'O'
setCells(copy)
// 判断谁赢
tell()
}
return (
<div>
{cells.map((items,row) => <div className="row">
{items.map((item,col) => <div className="col">
<Cell text={item} onClick={()=>onClickCell(row,col)}/>
</div>
)}
</div>)}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<div>
<Chessboard />
</div>);
【运行结果】
4.判断谁获胜- 横着3个格子为×或○的一方获胜
备注
为什么页面上横着3个“X”,还没有判断“X”赢,而是在这之后的一次点击后才显示“X”赢了?
当我们点击第六步的时候获得到的cells的值确是第五步的
为什么?
【敲代码】
const tell = ()=>{
if(cells[0][0] === cells[0][1] && cells[0][1] === cells[0][2] && cells[0][0] !== null){
console.log(cells[0][0] + "赢了");
}
}
const onClickCell = (row,col)=>{
// n + 1
setN(n + 1);
// 改变cells
const copy = JSON.parse(JSON.stringify(cells))
// 点击序号为偶数的打"X",奇数打"O""
copy[row][col] = n % 2 === 0?'X': 'O'
setCells(copy)
console.log("copy:" + copy)
console.log("cells:" + cells)
// 判断谁赢
tell()
}
如上所示:
修改的的是copy的值(新的值)而不是cells的值,虽然tell()
写在setCells(copy)
后面,setCells(copy)是异步的,tell中的变量cells拿到的值还是上一步的值(旧的值)
那怎么办?
===> 我们判断谁赢的时候,不用cells
去判断,而是用copy
去判断,即用新的值去判断而不是旧的值去判断
【敲代码】
import React from "react";
import ReactDOM from "react-dom/client";
import './style.css'
const Cell = function(props){
return (
<div className="cell" onClick={props.onClick}>
{props.text}
</div>
)
}
const Chessboard = function(){
const [cells,setCells] = React.useState([
[null,null,null],
[null,null,null],
[null,null,null],
])
// 点击的序号
const [n,setN] = React.useState(0)
const tell = (cells)=>{
if(cells[0][0] === cells[0][1] && cells[0][1] === cells[0][2] && cells[0][0] !== null){
console.log(cells[0][0] + "赢了");
}
}
const onClickCell = (row,col)=>{
// n + 1
setN(n + 1);
// 改变cells
const copy = JSON.parse(JSON.stringify(cells))
// 点击序号为偶数的打"X",奇数打"O""
copy[row][col] = n % 2 === 0?'X': 'O'
setCells(copy)
// 判断谁赢
tell(copy)
}
return (
<div>
{cells.map((items,row) => <div className="row">
{items.map((item,col) => <div className="col">
<Cell text={item} onClick={()=>onClickCell(row,col)}/>
</div>
)}
</div>)}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<div>
<Chessboard />
</div>);
【运行结果】
5.获胜后显示结果谁获胜,并结束游戏
【敲代码】
.cell{
border: 1px solid #000;
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
font-size: 80px;
}
.row{
display: flex;
}
.gameOver{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 80px;
color: #fff;
background: rgba(0,0,0,.5);
}
import React from "react";
import ReactDOM from "react-dom/client";
import './style.css'
const Cell = function(props){
return (
<div className="cell" onClick={props.onClick}>
{props.text}
</div>
)
}
const Chessboard = function(){
const [cells,setCells] = React.useState([
[null,null,null],
[null,null,null],
[null,null,null],
])
// 点击的序号
const [n,setN] = React.useState(0)
const [finished,setFinished] = React.useState(false)
const tell = (cells)=>{
if(cells[0][0] === cells[0][1] && cells[0][1] === cells[0][2] && cells[0][0] !== null){
console.log(cells[0][0] + "赢了");
setFinished(true)
}
}
const onClickCell = (row,col)=>{
// n + 1
setN(n + 1);
// 改变cells
const copy = JSON.parse(JSON.stringify(cells))
// 点击序号为偶数的打"X",奇数打"O""
copy[row][col] = n % 2 === 0?'X': 'O'
// 更新cells
setCells(copy)
// 判断谁赢
tell(copy)
}
return (
<div>
{cells.map((items,row) => <div className="row">
{items.map((item,col) => <div className="col">
<Cell text={item} onClick={()=>onClickCell(row,col)}/>
</div>
)}
</div>)}
{finished &&<div className="gameOver">游戏结束!</div>}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<div>
<Chessboard />
</div>);
6.将之前判断谁获胜- 横3个格子为×或○的一方获胜,补充为横竖对角线3个格子为×或○的一方获胜
import React from "react";
import ReactDOM from "react-dom/client";
import './style.css'
// 棋盘格子
const Cell = function(props){
return (
<div className="cell" onClick={props.onClick}>
{props.text}
</div>
)
}
// 棋盘
const Chessboard = function(){
const [cells,setCells] = React.useState([
[null,null,null],
[null,null,null],
[null,null,null],
])
// 点击的序号
const [n,setN] = React.useState(0)
// 游戏是否结束?
const [finished,setFinished] = React.useState(false)
// 判断谁赢了
const tell = (cells)=>{
// 横着3个X或3个O
for(let i=0;i< 3;i++){
if(cells[i][0] === cells[i][1] && cells[i][1] === cells[i][2] && cells[i][0] !== null){
console.log(cells[i][0] + "赢了");
setFinished(true)
break;
}
}
// 竖着3个X或3个O
for(let i=0;i< 3;i++){
if(cells[0][i] === cells[1][i] && cells[1][i] === cells[2][i] && cells[0][i] !== null){
console.log(cells[0][i] + "赢了");
setFinished(true)
break;
}
}
// 左斜3个X或3个O
if(cells[0][0] === cells[1][1] && cells[1][1] === cells[2][2] && cells[0][0] !== null){
console.log(cells[0][0] + "赢了");
setFinished(true)
}
// 右斜3个X或3个O
if(cells[0][2] === cells[1][1] && cells[1][1] === cells[2][0] && cells[0][2] !== null){
console.log(cells[0][2] + "赢了");
setFinished(true)
}
}
// 点击棋格
const onClickCell = (row,col)=>{
// n + 1
setN(n + 1);
// 改变cells
const copy = JSON.parse(JSON.stringify(cells))
// 点击序号为偶数的打"X",奇数打"O""
copy[row][col] = n % 2 === 0?'X': 'O'
// 更新cells
setCells(copy)
// 判断谁赢
tell(copy)
}
return (
<div>
{cells.map((items,row) => <div className="row">
{items.map((item,col) => <div className="col">
<Cell text={item} onClick={()=>onClickCell(row,col)}/>
</div>
)}
</div>)}
{finished &&<div className="gameOver">游戏结束!</div>}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<div>
<Chessboard />
</div>);
【备注】
关于代码优化:
我们可以使cells的初始值为:
cells = [
[1,2,3],
[10,20,30]
[100,200,300]
]
使某些能赢的结果匹配唯一的值,如横着一样就是和的值为6,60,600;竖着能赢就是和为111,222,333;歇着能赢就是和为321,123。
总结
- React中变量、函数、类都可以表示一个组件
- 当要往下传值的时候用props,当往上传值的时候还是用props?????
- React是一个简单的框架,一切皆JS。React对一个前端JS掌握程度的依赖度太高太高了,一定要清晰的理解JS中运行每一行代码到底发生了什么,不能有一点的含糊和不清楚
- 一定要记住:我们在用标签的形式写react的虚拟DOM的时候,不是在写html。React中要给一个虚拟dom添加class是不能直接写
class="red"
,就好比在JS中要给一个元素添加class,我们要:
const div = document.createElement('div')
div.className="red"
一样,因此在react中定义样式得用className
虽然react中写的是标签的形式,但是我们脑海中要联想到前面谈到的react.createElement()
,所以react更多的还是JS
转载自:https://juejin.cn/post/7244409860833165349