React入门
JavaScript
JavaScript for Cats
ES6 for React Native Developers
let
vs. var
vs. const
let
和var
相似,但是scope不同。var
是function scoped, let
是block scoped.
Array Functions
1 | x => x*x |
等同于1
2
3function(x) {
return x * x;
}
箭头函数
相当于匿名函数。有两种格式。一种是只包含一个表达式,不带{…}和return。一种可以包含多条语句,比如1
2
3
4
5
6
7
8x => {
if(x > 0){
return x * x;
}
else {
return - x * x;
}
}
注意,如果返回的是一个对象的话,要避免和函数体语法冲突,比如:1
x => ({foo: x})
箭头函数和匿名函数的区别: 箭头函数内部的this
是词法作用域,由上下文确定。1
2
3
4
5
6
7
8
9var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25
否则要用var that = this
来解决。
1 | var arr = [10, 20, 1, 2]; |
1 | JavaScript Array.sort() |
React
JSX
一种JavaScript的语法扩展,推荐在React中使用JSX来描述用户界面。
注 因为 JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用 camelCase 小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称。例如,class
变成了className
,而tabindex
则对应着 tabIndex。
React DOM在渲染之前默认过滤所有传入的值。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击。
元素渲染
元素是构成React应用的最小单位。元素用来描述你在屏幕上看到的内容。
与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致。
注 在React开发中一般只会定义一个根节点,但是如果是在已有项目中引入React的话,可能需要在不同部分单独定义React根节点。
要把React元素渲染到根DOM节点中,通过将他们传递给ReactDOM.render()
方法中。1
2
3
4
5const element = <h1>Hello</h1>;
ReactDOM.render(
element,
document.getElementById('root');
);
React元素是不可变的,当元素被创建后,是无法改变其内容或者属性的。它代表应用界面在某一个时间点的样子。
可以通过创建一个新元素,将它传入ReactDOM.render()
方法.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
// 每秒钟调用一次tick方法
在实际开发中,大多数React应用只会调用一次 ReactDOM.render()
。
React DOM 首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分。
组件和Props
组件可以将UI切分成一些可以复用的单独的部件。
组件可以接受任意输入值(称之为props),并返回一个需要在页面上展示的React元素。
定义一个组件最简单的方式是使用JavaScript函数。1
2
3function Welcome(props){
return <h1>Hello, {props.name}</h1>;
}
也可以用ES6 class来定义一个组件1
2
3
4
5class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
当React遇到的元素是用户自定义的组件,它会将JSX属性作为单个对象传递给该组件,这个对象称之为“props”。1
2
3
4
5
6
7
8
9function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
注 组件名称必须以大写字母开头。
例如,<div />
表示一个DOM标签,但
组件可以在它的输出中引用其它组件,这就可以让我们用同一组件来抽象出任意层次的细节。在React应用中,按钮、表单、对话框、整个屏幕的内容等,这些通常都被表示为组件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
通常,一个新的React应用程序的顶部是一个App组件。但是,如果要将React集成到现有应用程序中,则可以从下而上使用像Button这样的小组件作为开始,并逐渐运用到视图层的顶部。
组件的返回值只能有一个根元素。这也是我们要用一个<div>
来包裹所有<Welcome />
元素的原因。
组件提取
略
React有一个规则,所有的React组件必须像纯函数那样使用它们的props。
纯函数是指不改变自身输入的值的函数。当传入的值相同,总是会返回相同的结果。
state和生命周期
更新UI的正确方法
状态与属性十分相似,但是状态是私有的,完全受控于当前组件。
我们之前提到过,定义为类的组件有一些特性。局部状态就是如此:一个功能只适用于类。
可以通过5个步骤将函数组件 Clock 转换为类:
- 创建一个名称扩展为 React.Component 的ES6 类
- 创建一个叫做render()的空方法
- 将函数体移动到 render() 方法中
- 在 render() 方法中,使用 this.props 替换 props
- 删除剩余的空函数声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
Clock 现在被定义为一个类而不只是一个函数。使用类就允许我们使用其它特性,例如局部状态、生命周期钩子。
为一个类添加局部状态
通过3个步骤将date
从属性移动到状态中。
在
render()
方法中使用this.state.date
代替this.props.date
1
2
3
4
5
6
7
8
9
10class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}添加一个类构造函数来初始化状态
this.state
1
2
3
4
5
6
7
8
9class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
// ...
}
}
类组件应始终使用props
调用基础构造函数。
- 从
<Clock />
元素中移除date
属性。1
2
3
4ReactDOM.render(
<Clock />,
document.getElementById('root')
);
将生命周期方法添加到类中
销毁组件时要释放组建所占用的资源。
每当Clock组件第一次加载到DOM中的时候,我们都想生成定时器,这在React中被称为挂载
同样,每当Clock生成的这个DOM被移除的时候,我们也会想要清除定时器,这在React中被称为卸载。1
2
3
4
5
6
7
8
9
10
11
12// 生命周期钩子
componentDidMount() {
// 当组件输出到DOM后会执行
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
// 卸载
clearInterval(this.timeID);
}
注意如何在this
中保存定时器ID。
虽然 this.props 由React本身设置以及this.state 具有特殊的含义,但如果需要存储不用于视觉输出的东西,则可以手动向类中添加其他字段。如果你不在 render() 中使用某些东西,它就不应该在状态中。
tick()
方法使用this.setState()
来更新组件局部状态。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
整个过程如下:
当
被传递给 ReactDOM.render() 时,React 调用 Clock 组件的构造函数。 由于 Clock 需要显示当前时间,所以使用包含当前时间的对象来初始化 this.state 。 我们稍后会更新此状态。 React 然后调用 Clock 组件的 render() 方法。这是 React 了解屏幕上应该显示什么内容,然后 React 更新 DOM 以匹配 Clock 的渲染输出。
当 Clock 的输出插入到 DOM 中时,React 调用 componentDidMount() 生命周期钩子。 在其中,Clock 组件要求浏览器设置一个定时器,每秒钟调用一次 tick()。
浏览器每秒钟调用 tick() 方法。 在其中,Clock 组件通过使用包含当前时间的对象调用 setState() 来调度UI更新。 通过调用 setState() ,React 知道状态已经改变,并再次调用 render() 方法来确定屏幕上应当显示什么。 这一次,render() 方法中的 this.state.date 将不同,所以渲染输出将包含更新的时间,并相应地更新DOM。
一旦Clock组件被从DOM中移除,React会调用componentWillUnmount()这个钩子函数,定时器也就会被清除。
正确使用状态
- 不要直接更新状态
不要直接1
this.state.comment = "Hello";
而是使用setState()
1
this.setState({comment: "Hello"});
构造函数是唯一能够初始化this.state
的地方
- 状态更新可能是异步的
React 可以将多个setState()
调用合并成一个调用来提高性能。
因为this.props
和this.state
可能是异步更新的,你不应该依靠它们的值来计算下一个状态。1
2
3this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数.
- 状态更新合并
当你调用setState()
时,React将你提供的对象合并到当前状态。
例如,你的状态可能包含一些独立的变量:1
2
3
4
5
6
7constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
你可以调用setState()
独立地更新它们:1
2
3
4
5
6
7
8
9
10
11
12
13componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
这里的合并是浅合并,也就是说this.setState({comments})
完整保留了this.state.posts
,但完全替换了this.state.comments
。
数据自顶向下流动
自顶向下
或单向数据流
: 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。
如果你想象一个组件树作为属性的瀑布,每个组件的状态就像一个额外的水源,它连接在一个任意点,但也流下来。
父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。