最近在看react,写了一些笔记,放上来跟大家分享一下
一、官网例子解读(对官方给出的例子进行一个解读,更好理解)
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
function Square(props) { //函数定义组件,接收一个单一的“props”对象并返回了一个React元素
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
//如果不需要在 constructor里面使用 props,是可以不用写 constructor的
//只有 render 方法的组件:函数定义组件.只需要简单写一个以props为参数的function返回JSX元素。
//其中所有的 this.props 替换成参数 props,
//onClick={() => props.onClick()} 直接修改为 onClick={props.onClick} ,
//注意不能写成 onClick={props.onClick()}
//否则 props.onClick 方法会在 Square 组件渲染时被直接触发而不是等到 Board 组件渲染完成时通过点击触发
class Board extends React.Component {
renderSquare(i) {
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>
);
}
}
class Game extends React.Component { //类定义组件
constructor(props) {//constructor:构造方法 this.state 为该组件设置自身的状态数据
super(props);//子类中调用父类构造函数,子类没有自己的this对象,而是继承父类的this对象,如果不调用super方法,子类就得不到this对象
this.state = {
history: [
{
squares: Array(9).fill(null)
}
],
stepNumber: 0,
xIsNext: true //X为先手
};
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1); //返回此刻所有步骤(每走一步向history存入一个squares数组)
const current = history[history.length - 1];//当前的squares数组
const squares = current.squares.slice();//返回当前的squares数组(相当于复制一个新的数组)
if (calculateWinner(squares) || squares[i]) {
return;
} //当一方已经胜利或是方框里已落子则不可以在此处再落子,否则执行下行程序
squares[i] = this.state.xIsNext ? "X" : "O";
this.setState({
history: history.concat([
{
squares: squares
}
]),
//将之前的n个步骤数组和此步的步骤数组连接成为一个新的二维数组history,
//history里放所有的squares(每走一步出现一个新的squares),squares里放每个格子的值
stepNumber: history.length,//一共走了多少步
xIsNext: !this.state.xIsNext
//更新squares(步骤)数组
});
}
//slice() 方法会选取从 start 到数组结尾的所有元素。
//var arr = ['George','John','Thomas','Adrew','Martin'];
//document.write(arr.slice(2,4))
//输出:Thomas,Adrew
//[2,4)
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0
});
}
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)}
/>
//Board是用户自定义组件,将JXS属性(squares:{current.squares})作为单个对象(props)传递给组件;
//-> {current.squares} = props.squares
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(<Game />, document.getElementById("root"));
//此id为root的div中的所有内容都将由React DOM来管理,所以我们将其称之为“根”DOM 节点
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.React DOM 首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分,将界面视为一个个特定时刻的固定内容(就像一帧一帧的动画),而不是随时处于变化之中(而不是处于变化中的一整段动画)。
2.组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的React元素。
3.组件名称必须以大写字母开头。
4.组件的返回值只能有一个根元素。所以要用一个<div>来包裹所有组件元素。
5.所有的React组件必须像纯函数那样使用它们的props,即不能改变props,纯函数:不改变它自己的输入值。
纯函数:
function sum(a, b) {
return a + b;
}
非纯函数:
unction withdraw(account, amount) {
account.total -= amount;
}
6.类定义组件和函数定义组件区别:
类定义组件允许我们使用其它特性,例如局部状态、生命周期钩子。
7.this.props和this.state可能是异步更新,可使用第二种形式的 setState() 来接受一个函数而不是一个对象。 该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数:
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
8.状态通常被称为局部或封装。 除了拥有并设置它的组件外,其它组件不可访问。
9.在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为,你必须明确的使用 preventDefault。
10.e 是一个合成事件,不需要担心跨浏览器的兼容性问题,react封装了浏览器原生事件对象,并对浏览器做了兼容,和浏览器原生事件对象有相同的接口,如果出于某些原因想使用浏览器原生事件,可以使用 nativeEvent 属性获取。
11.类的方法必须绑定this,必须谨慎对待 JSX 回调函数中的 this,类的方法默认是不会绑定 this 的。如果你忘记绑定 this.handleClick 并把它传入 onClick, 当你调用这个函数的时候 this 的值会是 undefined。通常情况下,如果你没有在方法后面添加 () ,例如 onClick={this.handleClick},你应该为这个方法绑定 this。
(1)在构造函数中使用bind绑定this
class Button extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
(2)在调用的时候使用bind绑定this
class Button extends React.Component {
handleClick(){
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick.bind(this)}>
Click me
</button>
);
}
}
(3)在调用的时候使用箭头函数绑定this
class Button extends React.Component {
handleClick(){
console.log('this is:', this);
}
render() {
return (
<button onClick={()=>this.handleClick()}>
Click me
</button>
);
}
}
(4)使用属性初始化器语法绑定this(实验性)
class Button extends React.Component {
handleClick=()=>{
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
比较:
方式2和方式3都是在调用的时候再绑定this。
- 优点:写法比较简单,当组件中没有state的时候就不需要添加类构造函数来绑定this
- 缺点:每一次调用的时候都会生成一个新的方法实例,因此对性能有影响,并且当这个函数作为属性值传入低阶组件的时候,这些组件可能会进行额外的重新渲染,因为每一次都是新的方法实例作为的新的属性传递。
方式1在类构造函数中绑定this,调用的时候不需要再绑定
- 优点:只会生成一个方法实例,并且绑定一次之后如果多次用到这个方法也不需要再绑定。
- 缺点:即使不用到state,也需要添加类构造函数来绑定this,代码量多一点。。。
方式4:利用属性初始化语法,将方法初始化为箭头函数,因此在创建函数的时候就绑定了this。
- 优点:创建方法就绑定this,不需要在类构造函数中绑定,调用的时候不需要再作绑定。结合了方式1、方式2、方式3的优点
- 缺点:目前仍然是实验性语法,需要用babel转译。
总结:
方式1是官方推荐的绑定方式,也是性能最好的方式。方式2和方式3会有性能影响并且当方法作为属性传递给子组件的时候会引起重渲问题。方式4目前属于实验性语法,但是是最好的绑定方式,需要结合bable转译。
12.<input type="text">, <textarea>, 和 <select> 都十分类似 - 他们都通过传入一个value属性来实现对组件的控制。
未完待续。。。