React - 用于构建用户界面的 JavaScript 库
React是一个声明式,高效且灵活的用于构建用户界面的JavaScript库。使用React可以将一些简短、独立的代码片段组合成复杂的UI界面,这些代码片段被称作“组件”。
1. 环境准备
// 创建项目
npx create-react-app my-app
cd my-app
npm start
2. React.Component
3. props 参数
4. render ()
render()
方法返回需要展示在屏幕上的视图的层次结构。其返回值描述了你希望在屏幕上看到的内容。React根据描述,然后把结果展示出来。更具体地来说,render()
返回了一个{React元素},这是一种对渲染内容的轻量级描述。
语法
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
// 用法示例: <ShoppingList name="Mark" />
上述的代码等同于:
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* ... h1 children ... */),
React.createElement('ul', /* ... ul children ... */)
);
5. JSX
- 特性
- 可以使用任意JS表达式,只需用一个大括号把表达式括起来
- 每一个React元素实际上都为一个JS对象,我们可以在程序中将它保存在变量中或者作为参数传递。
6. 井字小游戏相关代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
// react入门小游戏 -- 井字棋
// 创建Square组件
/* class Square extends React.Component {
// 添加构造函数
// constructor(props) {
// // 在JS class中定义其子类的构造函数时,都需要调用super方法。故,在所有含有构造函数的React组件中,构造函数必须以super(props)开头
// super(props);
// // 初始化私有属性
// this.state = {
// value: null
// }
// }
render() {
return (
// 注意绑定函数时 () => , 若写成onClick{alert('click')} 会导致每次这个组件渲染时都会触发弹出框
//
<button
className="square"
//调用this.setState 通知React去重新渲染Square组件
onClick={() => this.props.onClick()}>
{this.props.value}
</button>
);
}
} */
// 因Square组件中只包含render(), 且没有state,故将其转写为函数组件
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
)
}
// 创建Board 组件
class Board extends React.Component {
/* constructor (props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
};
} */
/* handleClick (i) {
// 创建squares数组的一个副本
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
} */
renderSquare(i) {
// 创建一个名为value的prop到子组件Square中
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
)
}
render() {
return (
<div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
// 创建Game组件
class Game extends React.Component {
constructor (props) {
super(props);
this.state = {
history: [
{squares: Array(9).fill(null)},
],
xIsNext: true,
stepNumber: 0, // 表示当前正在查看的历史记录
}
}
jumpTo (step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0,
})
}
handleClick (i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([
{squares: squares}
]),
xIsNext: !this.state.xIsNext,
stepNumber: history.length
});
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ? 'Go to move #' + move : 'Go to game start';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<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
}
// ========================================
root.render(
<Game />,
);
7. 注意
-
为了少输入代码,同时为了避免
this
造成的困扰,在这里使用箭头函数 来进行事件处理。 -
在 JavaScript class 中,每次你定义其子类的构造函数时,都需要调用
super
方法。因此,在所有含有构造函数的的 React 组件中,构造函数必须以super(props)
开头。 -
当你遇到需要同时获取多个子组件数据,或者两个组件之间需要相互通讯的情况时,需要把子组件的 state 数据提升至其共同的父组件当中保存。之后父组件可以通过 props 将状态数据传递到子组件当中。这样应用当中所有组件的状态数据就能够更方便地同步共享了。
-
为了提高可读性,我们把返回的 React 元素拆分成了多行,同时在最外层加了小括号,这样 JavaScript 解析的时候就不会在
return
的后面自动插入一个分号从而破坏代码结构了。 -
因为 DOM 元素
<button>
是一个内置组件,因此其onClick
属性在 React 中有特殊的含义。而对于用户自定义的组件来说,命名就可以由用户自己来定义了。我们给 Square 的onClick
和 Board 的handleClick
赋予任意的名称,代码依旧有效。在 React 中,有一个命名规范,通常会将代表事件的监听 prop 命名为on[Event]
,将处理事件的监听方法命名为handle[Event]
这样的格式。
8. React中的不可变性
改变数据的方式:
(1)直接修改变量的值
(2)不直接修改,使用新数据替换旧数据
不直接修改(或改变底层数据)的好处:
-
简化复杂的功能
-
跟踪数据的改变
若直接修改数据,很难跟踪到数据的改变。跟踪数据的改变需要可变对象可以与改变之前的版本进行对比,这样整个对象树都需要被遍历一次。
跟踪不可变数据的变化相对较易,若发现对象变成了一个新对象,就可以说对象发生了改变。
-
确定在React中何时重新渲染
不可变性主要的优势在于它可以帮助我们在React中创建pure components,我们可以很轻松的确定不可变数据是否发生了改变,从而确定何时对组件进行重新渲染。
9. 函数组件
如果你想写的组件只包含一个render()
方法,并且不包含state
时,使用函数组件会更简单。我们无需定义一个继承于React.Componen的类,可以定义一个函数,将props作为参数,并return需要渲染的元素。示例如下:
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}