深入解析:菜鸟速通:React入门 01

目录

一、React的定义

二、功能

1. 创建和嵌套组件

 2. 标签语言

 3. 组件用法举例

3.1 双括号

 3.2 数组转列表

3.3 界面交互-事件处理函数

3.4 界面更新-组件状态和更新函数

3.5 组件共享状态

三、实践

1. 文件结构

2. 项目-井字棋游戏

2.1 设计目标

2.2  步骤一:创建基础组件Board

2.3  步骤二:创建嵌套、可复用组件Square

2.4 步骤三:设计可交互、具有状态的组件Square

2.5 步骤四:重构组件-状态共享

2.6  步骤五:增加交替落子

 2.7 步骤六:宣布获胜者

2.8 步骤七:增加时间旅行并显示落子历史

2.9 步骤八:最后清理

恭喜通关!


一、React的定义

React是一个前端Javascript工具库,基于UI(用户)组件构建用户界面。

二、功能

1. 创建和嵌套组件

React 应用程序是由 组件 组成的。一个组件是 UI(用户界面)的一部分,它拥有自己的逻辑和外观。组件可以小到一个按钮,也可以大到整个页面。

//这种返回标签的javascript函数就是组件
function MyButton() {
return (
);
}
//嵌套组件,这种大写字母开头的标签就是react标签
export default function MyApp() { //默认import无需指定名
return (
欢迎来到我的应用
);
}

 2. 标签语言

使用JSX而不是HTML,JSX类似更严格的HTML,必须闭合标签,也必须有共享父级

function AboutPage() {
return (
<>
关于
你好。最近怎么样?
);
}

如果你有大量的 HTML 需要移植到 JSX 中,你可以使用 在线转换器

 3. 组件用法举例

3.1 双括号

注意双括号的用法,第一个表javascript引用,第二个表对象

export default function Profile() {
return (
<>
{user.name}
);
}
 3.2 数组转列表

数组渲染为列表,常用map,注意li的属性key

const products = [
{ title: '卷心菜', isFruit: false, id: 1 },
{ title: '大蒜', isFruit: false, id: 2 },
{ title: '苹果', isFruit: true, id: 3 },
];
export default function ShoppingList() {
const listItems = products.map(product =>
{product.title}
);
return (
{listItems}
);
}
3.3 界面交互-事件处理函数

响应事件:可以在组件里定义事件处理函数

unction MyButton() {
function handleClick() {
alert('You clicked me!');
}
return (
);
}
3.4 界面更新-组件状态和更新函数

更新界面:导入组件状态 和 更新函数

import { useState } from 'react';
export default function MyApp() {
return (
独立更新的计数器
);
}
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
);
}

使用hook:以use开头的函数就是hook,如刚刚的usestate

3.5 组件共享状态

组件间共享数据 :1.状态放在公共组件app;2.mybutton共享状态count和事件处理函数;3. 子组件传入父组件的prop。这种被传递的state和函数被称为prop。

import { useState } from 'react';
export default function MyApp() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
共同更新的计数器
);
}
function MyButton({ count, onClick }) {
return (
);
}

三、实践

1. 文件结构

App.js:创建jsx组件

index.js:导入react、react-dom、css样式和app.js中的组件,一同注入public-index.html文件

styles.css :描绘网页样式

public文件夹

2. 项目-井字棋游戏

2.1 设计目标

2.2  步骤一:创建基础组件Board

创建Board组件为九宫格形式

//app.js
export default function Board() {
return (
<>
);
}
2.3  步骤二:创建嵌套、可复用组件Square

// app.js
function Square({ value }) {
return ;
}
export default function Board() {
return (
<>
);
}
2.4 步骤三:设计可交互、具有状态的组件Square

  • 在square中增加事件响应函数

        Option + ⌘ + J(在 macOS 上)查看控制台

function Square({ value }) {
function handleClick() {
console.log('clicked!');
}
return (
);
}
  •  界面更新:导入useState,增加square的组件状态和更新函数,使它每次点击更新"X"
import { useState } from 'react';
function Square() {
const [value, setValue] = useState(null);
function handleClick() {
setValue('X');
}
return (
);
}

     Board也随之改变

    export default function Board() {
    return (
    <>
    );
    }

    安装拓展后,在网页F12打开开发者工具,可以查看组件的props和state

     至此,基本构件块已完成。

    2.5 步骤四:重构组件-状态共享

    但是在上面的情况,为了决出游戏胜负,Board 需要以某种方式知道 9 个 Square 组件中每个组件的 state。Board 一个个询问 Square显然太复杂,因此最好的方法是将游戏的 state 存储在 Board 父组件中,而不是每个 Square

    重构 React 组件时,将状态提升到父组件中很常见。

    1. 状态提升到父组件

    2. 父组件向下传递props

    3. 子组件接受prop

    import { useState } from 'react';
    function Square({ value, onSquareClick }) {
    return (
    //3. 子组件接受prop
    );
    }
    export default function Board() {
    //1. 状态上升到父组件
    const [squares, setSquares] = useState(Array(9).fill(null));
    //复用事件响应函数
    function handleClick(i) {
    const nextSquares = squares.slice();
    nextSquares[i] = 'X';
    setSquares(nextSquares);
    }
    //2. 父组件传递props给子组件
    return (
    <>
     handleClick(0)} />
     handleClick(1)} />
     handleClick(2)} />
     handleClick(3)} />
     handleClick(4)} />
     handleClick(5)} />
     handleClick(6)} />
     handleClick(7)} />
     handleClick(8)} />
    );
    }

    为什么 需要const nextSquares = squares.slice();来创建数组的副本而不是直接修改?

    1. ”不直接改变底层数据“允许撤销回顾历史

    2.6  步骤五:增加交替落子

    向 Board 组件添加另一个 state:xIsNext来跟踪这一点,即默认情况下,你会将第一步设置为“X”。

    在事件响应函数中,通过xIsNext来选择x或o,且每次点击翻转一次xIsNext状态

    通过检查value,排除单个格子反复点击变化的错误

    import { useState } from 'react';
    function Square({value, onSquareClick}) {
    return (
    );
    }
    export default function Board() {
    const [xIsNext, setXIsNext] = useState(true);
    const [squares, setSquares] = useState(Array(9).fill(null));
    function handleClick(i) {
    if (squares[i]) {
    return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
    nextSquares[i] = 'X';
    } else {
    nextSquares[i] = 'O';
    }
    setSquares(nextSquares);
    setXIsNext(!xIsNext);
    }
    return (
    <>
     handleClick(0)} />
     handleClick(1)} />
     handleClick(2)} />
     handleClick(3)} />
     handleClick(4)} />
     handleClick(5)} />
     handleClick(6)} />
     handleClick(7)} />
     handleClick(8)} />
    );
    }
     2.7 步骤六:宣布获胜者

    需要建立一个函数calculateWinner来判断胜者,它接受 9 个方块的数组,检查获胜者并根据需要返回 'X''O'null。

    import { useState } from 'react';
    function Square({value, onSquareClick}) {
    return (
    );
    }
    export default function Board() {
    const [xIsNext, setXIsNext] = useState(true);
    const [squares, setSquares] = useState(Array(9).fill(null));
    function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
    return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
    nextSquares[i] = 'X';
    } else {
    nextSquares[i] = 'O';
    }
    setSquares(nextSquares);
    setXIsNext(!xIsNext);
    }
    const winner = calculateWinner(squares);
    let status;
    if (winner) {
    status = 'Winner: ' + winner;
    } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
    }
    return (
    <>
    {status}
     handleClick(0)} />
     handleClick(1)} />
     handleClick(2)} />
     handleClick(3)} />
     handleClick(4)} />
     handleClick(5)} />
     handleClick(6)} />
     handleClick(7)} />
     handleClick(8)} />
    );
    }
    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;
    }
    2.8 步骤七:增加时间旅行并显示落子历史

    正如刚才说的,如果你改变了 squares 数组,实现时间旅行将非常困难。

    1. 把过去的 squares 数组存储在另一个名为 history 的数组中,把它存储为一个新的 state 变量。
    2. 同时,新增一个新的最顶级组件Game,让它渲染board和历史history。

    history通过setHistory([...history, nextSquares]);扩展,并需要转化成列表显示,即

    const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
    description = 'Go to move #' + move;
    } else {
    description = 'Go to game start';
    }
    return (
    );

    其中jumpTo表示一个按钮列表中每个按钮的函数,下一节我们实现。

    完整代码如下: 最后,虽然页面成功显示,但是有报错如下:

    Warning: Each child in an array or iterator should have a unique “key” prop. Check the render method of `Game`.

    解决方法:增加key,key 告诉 React 每个组件的身份,这使得 React 可以在重新渲染时保持 state。如果组件的 key 发生变化,组件将被销毁,新 state 将重新创建。

            3. 因此,我们将落子索引move作为key 

    import { useState } from 'react';
    function Square({ value, onSquareClick }) {
    return (
    );
    }
    function Board({ xIsNext, squares, onPlay }) {
    function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
    return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
    nextSquares[i] = 'X';
    } else {
    nextSquares[i] = 'O';
    }
    onPlay(nextSquares);
    }
    const winner = calculateWinner(squares);
    let status;
    if (winner) {
    status = 'Winner: ' + winner;
    } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
    }
    return (
    <>
    {status}
     handleClick(0)} />
     handleClick(1)} />
     handleClick(2)} />
     handleClick(3)} />
     handleClick(4)} />
     handleClick(5)} />
     handleClick(6)} />
     handleClick(7)} />
     handleClick(8)} />
    );
    }
    export default function Game() {
    const [xIsNext, setXIsNext] = useState(true);
    const [history, setHistory] = useState([Array(9).fill(null)]);
    const currentSquares = history[history.length - 1];
    function handlePlay(nextSquares) {
    setHistory([...history, nextSquares]);
    setXIsNext(!xIsNext);
    }
    function jumpTo(nextMove) {
    // TODO
    }
    const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
    description = 'Go to move #' + move;
    } else {
    description = 'Go to game start';
    }
    return (
    );
    });
    return (
    {moves}
    );
    }
    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;
    }

            4. Game新增状态来记住当前步骤。

    const [currentMove, setCurrentMove] = useState(0);

            5. 

    如果你点击游戏历史中的任何一步,井字棋棋盘应立即更新以显示该步骤发生后棋盘的样子。

    import { useState } from 'react';
    function Square({value, onSquareClick}) {
    return (
    );
    }
    function Board({ xIsNext, squares, onPlay }) {
    function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
    return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
    nextSquares[i] = 'X';
    } else {
    nextSquares[i] = 'O';
    }
    onPlay(nextSquares);
    }
    const winner = calculateWinner(squares);
    let status;
    if (winner) {
    status = 'Winner: ' + winner;
    } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
    }
    return (
    <>
    {status}
     handleClick(0)} />
     handleClick(1)} />
     handleClick(2)} />
     handleClick(3)} />
     handleClick(4)} />
     handleClick(5)} />
     handleClick(6)} />
     handleClick(7)} />
     handleClick(8)} />
    );
    }
    export default function Game() {
    const [xIsNext, setXIsNext] = useState(true);
    const [history, setHistory] = useState([Array(9).fill(null)]);
    const [currentMove, setCurrentMove] = useState(0);
    const currentSquares = history[currentMove];
    function handlePlay(nextSquares) {
    const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
    setHistory(nextHistory);
    setCurrentMove(nextHistory.length - 1);
    setXIsNext(!xIsNext);
    }
    function jumpTo(nextMove) {
    setCurrentMove(nextMove);
    setXIsNext(nextMove % 2 === 0);
    }
    const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
    description = 'Go to move #' + move;
    } else {
    description = 'Go to game start';
    }
    return (
    );
    });
    return (
    {moves}
    );
    }
    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;
    }
    2.9 步骤八:最后清理

    如果你知道 currentMove 的值,那么你总能算出 xIsNext 应该是什么,因此没必要两者都存储成state。更改 Game 使其不将 xIsNext 存储为单独的 state 变量,而是根据 currentMove 计算出来。

    const [history, setHistory] = useState([Array(9).fill(null)]);
    const [currentMove, setCurrentMove] = useState(0);
    const xIsNext = currentMove % 2 === 0;
    const currentSquares = history[currentMove];
    恭喜通关!

    posted @ 2025-07-31 15:01  yjbjingcha  阅读(9)  评论(0)    收藏  举报