likes
comments
collection
share

React做了什么?

作者站长头像
站长
· 阅读数 75

将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.jsindex.js,app.js中只留下一个div,index中的代码全部删除即可

React做了什么?

  • 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);

运行结果

React做了什么?

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做了什么?

React做了什么?

用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做了什么?

备注

使用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)

运行结果

结果还是可以正常运行

React做了什么?

备注

心里一定要清楚,虽然写的像html标签,但是实际上是React.createElement()!!!

2.React提供的组件化思路

a. 创建组件

const Header = (
    <header>
        header部分
    </header>
)

b. 使用组件

{Header}

敲代码

如下所示: HeaderBottom都是组件

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做了什么?

至此,我们就可以像组合变量一样任意的组合页面了,这就是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);

运行结果

React做了什么?

备注: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);

运行结果

React做了什么?

备注

解释下<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做了什么?

至此,我们只使用了React为我们提供的一个API,其他都是JS本身自带的内容,,这就是React和vue的最大区别API极少,但是功能是一样的

总结:React提供的功能

  1. 它可以使用标签的形式创建虚拟的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

React做了什么?

  1. 一个函数如果想要拥有自己的状态,不应该用变量,而是用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但是页面上却没有任何反应的原因

React做了什么?

我们只是修改了cell中的值,修改前后cells对应的内存地址又没有发生变化,React为什么要更新DOM呢?

怎么办呢?

==》 使用深拷贝,一种简单的深拷贝的办法

const copy = JSON.parse(JSON.stringify(cells)) 
copy[row][col] = 'x'
setCells(copy)

这个拷贝出来的对象copy就是一个全新的对象,也就是说同cells是不同的内存地址。

这样初始的时候使用的是对象cellssetCells(copy)修改cells之后传的是对象copy,内存地址发生了变化,页面内容也就随着发生了变化

React做了什么?

这样就实现了点击打"X"的效果(每次点击的时候,都会深拷贝一个全新的对象)

React做了什么?

使用深拷贝一直创建对象,会不会影响性能?

微乎其微

为什么函数式会火?

因为内存够了

后端写代码,一台服务器要给所有人使用

但是前端就不一样,前端不存在后端出现的问题,我们写的代码只会在用户的电脑上(浏览器)跑起来,用户电脑的全部内存你都可以使用(操作内存比操作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>);

运行结果

React做了什么?

4.判断谁获胜- 横着3个格子为×或○的一方获胜

备注

为什么页面上横着3个“X”,还没有判断“X”赢,而是在这之后的一次点击后才显示“X”赢了?

React做了什么?

当我们点击第六步的时候获得到的cells的值确是第五步的

React做了什么?

为什么?

敲代码

 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>);

运行结果

React做了什么?

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
评论
请登录