React入门(三)
本节介绍React组件的一些特性。
一、组件的属性
组件的标签和HTML标签相近,所以也可以有自己的属性,例如:<Square value='0' />。
父组件可以在使用子组件时,通过属性,向子组件传递数据。
例如要显示如下的游戏区:
可以这样使用我们的组件。
父组件代码:
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />; // 父组件为子组件设置属性
}
子组件代码:
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value} // 子组件使用属性
</button>
);
}
}
在子组件中,可以使用this.props.[属性名]的方式,从props集合中得到指定属性的值。
二、组件的状态
属性可以在组件初始化时传入,通常用于父组件和子组件之间的数据通信。
但属性值的变化并不会引起组件的界面变化,状态提供了一种另一种可能。组件的状态变化会导致组件的界面刷新。
现在我们给Square类加一个状态,“{ value: null }”,当点击格子时,把状态改成“{ value: 'X' }”,由于状态变化会刷新组件,因此格子上的文字变成了“X”。
代码如下:
class Square extends React.Component {
constructor(props) { // 类的构造方法
super(props); // 调用父类的构造方法
this.state = {
value: null,
}; // 在构造方法中为状态赋初始值
}
render() {
return (
<button className="square" onClick={() => this.setState({value: 'X'})}> // 点击时改变状态
{this.state.value}
</button>
);
}
}
这里,我们在类的构造方法中为状态赋初始值,然后在点击事件添加代码:this.setState({value: 'X'}),setState()方法可以改变状态。
() => 是箭头函数,是ES6新增的语法,相当于:function() { this.setState({value: 'X'}); }
React又回归了传统的事件处理代码的编写模式,用onClick=绑定事件处理代码,要注意的是事件使用驼峰命名法(例如onClick,而非onclick)。
onClick=后面是一个JS函数,所以用{}包围。
三、将状态放在上一级
在上例中,我们把状态放在每一个格子中,但这样做不利于统计。
所以我们把状态移到了上一级中,如下:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
data: Array(9).fill(null),
};
}
现在,我们用一个数组表示九宫格中九个格子的状态,数组中的数据类似于:
[
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]
现在出现一个新的问题:状态放在上一级,当点击格子时,需要修改上一级的状态。
但数据通常是私有的,不应被外界访问,那应该怎么办呢?
我们可以这样:当点击格子时,点击事件执行的是定义在上一级中的回调函数。
代码如下:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => this.props.onClick()}> // 点击事件的回调函数使用属性onClick的值
{this.props.value}
</button>
);
}
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
data: Array(9).fill(null),
};
}
handleClick(i) {
const data = this.state.data.slice();
data[i] = 'X';
this.setState({ data: data }); // 改变状态,此时会重新调用render()方法
}
renderSquare(i) {
return (
<Square
value={this.state.data[i]} // 添加属性value,其值为状态数组中的值
onClick={() => this.handleClick(i)} // 添加属性onClick,其值为内部函数handleClick
/>
);
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</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>
);
}
}
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
上述代码中,可能有一处不好理解,就是改变状态的代码:
handleClick(i) {
const data = this.state.data.slice();
data[i] = 'X';
this.setState({ data: data });
}
这里为什么要用slice()?
这里主要是因为React强调数据的不变性,它说这样可以更容易跟踪数据的变化,所以最好不要直接修改数据,而是复制数据再修改。
数组的slice()方法返回数组的一个副本。