菜鸟速通:React入门 01

目录

一、React的定义

二、功能

[1. 创建和嵌套组件](#1. 创建和嵌套组件)

[2. 标签语言](#2. 标签语言)

[3. 组件用法举例](#3. 组件用法举例)

[3.1 双括号](#3.1 双括号)

[3.2 数组转列表](#3.2 数组转列表)

[3.3 界面交互-事件处理函数](#3.3 界面交互-事件处理函数)

[3.4 界面更新-组件状态和更新函数](#3.4 界面更新-组件状态和更新函数)

[3.5 组件共享状态](#3.5 组件共享状态)

三、实践

[1. 文件结构](#1. 文件结构)

[2. 项目-井字棋游戏](#2. 项目-井字棋游戏)

[2.1 设计目标](#2.1 设计目标)

[2.2 步骤一:创建基础组件Board](#2.2 步骤一:创建基础组件Board)

[2.3 步骤二:创建嵌套、可复用组件Square](#2.3 步骤二:创建嵌套、可复用组件Square)

[2.4 步骤三:设计可交互、具有状态的组件Square](#2.4 步骤三:设计可交互、具有状态的组件Square)

[2.5 步骤四:重构组件-状态共享](#2.5 步骤四:重构组件-状态共享)

[2.6 步骤五:增加交替落子](#2.6 步骤五:增加交替落子)

[2.7 步骤六:宣布获胜者](#2.7 步骤六:宣布获胜者)

[2.8 步骤七:增加时间旅行并显示落子历史](#2.8 步骤七:增加时间旅行并显示落子历史)

[2.9 步骤八:最后清理](#2.9 步骤八:最后清理)

恭喜通关!


一、React的定义

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

二、功能

1. 创建和嵌套组件

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

javascript 复制代码
//这种返回标签的javascript函数就是组件
function MyButton() {
  return (
    <button>我是一个按钮</button>
  );
}

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

2. 标签语言

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

javascript 复制代码
function AboutPage() {
  return (
    <>
      <h1>关于</h1>
      <p>你好。<br />最近怎么样?</p>
    </>
  );
}

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

3. 组件用法举例

3.1 双括号

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

javascript 复制代码
export default function Profile() {
  return (
    <>
      <h1>{user.name}</h1>
      <img
        className="avatar"
        src={user.imageUrl}
        alt={'Photo of ' + user.name}
        style={{
          width: user.imageSize,
          height: user.imageSize
        }}
      />
    </>
  );
}
3.2 数组转列表

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

javascript 复制代码
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 =>
    <li
      key={product.id}
      style={{
        color: product.isFruit ? 'magenta' : 'darkgreen'
      }}
    >
      {product.title}
    </li>
  );

  return (
    <ul>{listItems}</ul>
  );
}
3.3 界面交互-事件处理函数

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

javascript 复制代码
unction MyButton() {
  function handleClick() {
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      点我
    </button>
  );
}
3.4 界面更新-组件状态和更新函数

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

javascript 复制代码
import { useState } from 'react';

export default function MyApp() {
  return (
    <div>
      <h1>独立更新的计数器</h1>
      <MyButton />
      <MyButton />
    </div>
  );
}

function MyButton() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      点了 {count} 次
    </button>
  );
}

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

3.5 组件共享状态

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

javascript 复制代码
import { useState } from 'react';

export default function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1>共同更新的计数器</h1>
      <MyButton count={count} onClick={handleClick} />
      <MyButton count={count} onClick={handleClick} />
    </div>
  );
}

function MyButton({ count, onClick }) {
  return (
    <button onClick={onClick}>
      点了 {count} 次
    </button>
  );
}

三、实践

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组件为九宫格形式

javascript 复制代码
//app.js
export default function Board() {
  return (
    <>
      <div className="board-row">
        <button className="square">1</button>
        <button className="square">2</button>
        <button className="square">3</button>
      </div>
      <div className="board-row">
        <button className="square">4</button>
        <button className="square">5</button>
        <button className="square">6</button>
      </div>
      <div className="board-row">
        <button className="square">7</button>
        <button className="square">8</button>
        <button className="square">9</button>
      </div>
    </>
  );
}
2.3 步骤二:创建嵌套、可复用组件Square
javascript 复制代码
// app.js
function Square({ value }) {
  return <button className="square">{value}</button>;
}

export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square value="1" />
        <Square value="2" />
        <Square value="3" />
      </div>
      <div className="board-row">
        <Square value="4" />
        <Square value="5" />
        <Square value="6" />
      </div>
      <div className="board-row">
        <Square value="7" />
        <Square value="8" />
        <Square value="9" />
      </div>
    </>
  );
}
2.4 步骤三:设计可交互、具有状态的组件Square
  • 在square中增加事件响应函数

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

javascript 复制代码
function Square({ value }) {
  function handleClick() {
    console.log('clicked!');
  }

  return (
    <button
      className="square"
      onClick={handleClick}
    >
      {value}
    </button>
  );
}
  • 界面更新:导入useState,增加square的组件状态和更新函数,使它每次点击更新"X"
javascript 复制代码
import { useState } from 'react';

function Square() {
  const [value, setValue] = useState(null);

  function handleClick() {
    setValue('X');
  }

  return (
    <button
      className="square"
      onClick={handleClick}
    >
      {value}
    </button>
  );
}

Board也随之改变

javascript 复制代码
export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}

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

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

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

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

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

  1. 状态提升到父组件

  2. 父组件向下传递props

  3. 子组件接受prop

javascript 复制代码
import { useState } from 'react';

function Square({ value, onSquareClick }) {
  return (
  //3. 子组件接受prop
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

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 (
    <>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

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

  1. "不直接改变底层数据"允许撤销回顾历史
2.6 步骤五:增加交替落子

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

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

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

javascript 复制代码
import { useState } from 'react';

function Square({value, onSquareClick}) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

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 (
    <>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}
2.7 步骤六:宣布获胜者

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

javascript 复制代码
import { useState } from 'react';

function Square({value, onSquareClick}) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

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 (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

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]);扩展,并需要转化成列表显示,即

javascript 复制代码
const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
      description = 'Go to move #' + move;
    } else {
      description = 'Go to game start';
    }
    return (
      <li>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    );

其中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 将重新创建。

  1. 因此,我们将落子索引move作为key
javascript 复制代码
import { useState } from 'react';

function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

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 (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

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 (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    );
  });

  return (
    <div className="game">
      <div className="game-board">
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
      </div>
      <div className="game-info">
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

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;
}
  1. Game新增状态来记住当前步骤。

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

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

javascript 复制代码
import { useState } from 'react';

function Square({value, onSquareClick}) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

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 (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

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 (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    );
  });

  return (
    <div className="game">
      <div className="game-board">
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
      </div>
      <div className="game-info">
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

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 计算出来。

javascript 复制代码
const [history, setHistory] = useState([Array(9).fill(null)]);
  const [currentMove, setCurrentMove] = useState(0);
  const xIsNext = currentMove % 2 === 0;
  const currentSquares = history[currentMove];
恭喜通关!
相关推荐
好好研究2 小时前
使用JavaScript实现轮播图的自动切换和左右箭头切换效果
开发语言·前端·javascript·css·html
程序视点6 小时前
IObit Uninstaller Pro专业卸载,免激活版本,卸载清理注册表,彻底告别软件残留
前端·windows·后端
前端程序媛-Tian6 小时前
【dropdown组件填坑指南】—怎么实现下拉框的位置计算
前端·javascript·vue
嘉琪0017 小时前
实现视频实时马赛克
linux·前端·javascript
烛阴7 小时前
Smoothstep
前端·webgl
十盒半价7 小时前
React 性能优化秘籍:从渲染顺序到组件粒度
react.js·性能优化·trae
若梦plus7 小时前
Eslint中微内核&插件化思想的应用
前端·eslint
爱分享的程序员7 小时前
前端面试专栏-前沿技术:30.跨端开发技术(React Native、Flutter)
前端·javascript·面试
超级土豆粉7 小时前
Taro 位置相关 API 介绍
前端·javascript·react.js·taro
若梦plus7 小时前
Webpack中微内核&插件化思想的应用
前端·webpack