0%

React学习笔记(1)

React入门

JavaScript

JavaScript for Cats

http://jsforcats.com/

ES6 for React Native Developers

https://medium.com/the-react-native-log/a-brief-overview-of-es6-for-react-native-developers-15e7c68315da

let vs. var vs. const

letvar相似,但是scope不同。var是function scoped, let是block scoped.

constlet有相同scope,但是不能改变值。

Array Functions

1
x => x*x

等同于

1
2
3
function(x) {
return x * x;
}

箭头函数相当于匿名函数。有两种格式。一种是只包含一个表达式,不带{…}和return。一种可以包含多条语句,比如

1
2
3
4
5
6
7
8
x => {
if(x > 0){
return x * x;
}
else {
return - x * x;
}
}

注意,如果返回的是一个对象的话,要避免和函数体语法冲突,比如:

1
x => ({foo: x})

箭头函数和匿名函数的区别: 箭头函数内部的this是词法作用域,由上下文确定。

1
2
3
4
5
6
7
8
9
var 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
2
3
4
5
var arr = [10, 20, 1, 2];
arr.sort((x, y) => {
return x >= y;
});
console.log(arr); // [1, 2, 10, 20]

https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001438565969057627e5435793645b7acaee3b6869d1374000

1
2
3
4
5
6
7
JavaScript Array.sort()

默认是按照字符顺序排列的,如果想自定义顺序,需要构造比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:

- 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
- 若 a 等于 b,则返回 0。
- 若 a 大于 b,则返回一个大于 0 的值。

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
5
const 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
15
function 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
3
function Welcome(props){
return <h1>Hello, {props.name}</h1>;
}

也可以用ES6 class来定义一个组件

1
2
3
4
5
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

当React遇到的元素是用户自定义的组件,它会将JSX属性作为单个对象传递给该组件,这个对象称之为“props”。

1
2
3
4
5
6
7
8
9
function 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
18
function 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 转换为类:

  1. 创建一个名称扩展为 React.Component 的ES6 类
  2. 创建一个叫做render()的空方法
  3. 将函数体移动到 render() 方法中
  4. 在 render() 方法中,使用 this.props 替换 props
  5. 删除剩余的空函数声明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class 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从属性移动到状态中。

  1. render()方法中使用this.state.date代替this.props.date

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Clock extends React.Component {
    render() {
    return (
    <div>
    <h1>Hello, world!</h1>
    <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
    </div>
    )
    }
    }
  2. 添加一个类构造函数来初始化状态this.state

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Clock extends React.Component {
    constructor(props) {
    super(props);
    this.state = {date: new Date()};
    }
    render() {
    // ...
    }
    }

类组件应始终使用props调用基础构造函数。

  1. <Clock />元素中移除date属性。
    1
    2
    3
    4
    ReactDOM.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
37
class 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')
);

整个过程如下:

  1. 被传递给 ReactDOM.render() 时,React 调用 Clock 组件的构造函数。 由于 Clock 需要显示当前时间,所以使用包含当前时间的对象来初始化 this.state 。 我们稍后会更新此状态。

  2. React 然后调用 Clock 组件的 render() 方法。这是 React 了解屏幕上应该显示什么内容,然后 React 更新 DOM 以匹配 Clock 的渲染输出。

  3. 当 Clock 的输出插入到 DOM 中时,React 调用 componentDidMount() 生命周期钩子。 在其中,Clock 组件要求浏览器设置一个定时器,每秒钟调用一次 tick()。

  4. 浏览器每秒钟调用 tick() 方法。 在其中,Clock 组件通过使用包含当前时间的对象调用 setState() 来调度UI更新。 通过调用 setState() ,React 知道状态已经改变,并再次调用 render() 方法来确定屏幕上应当显示什么。 这一次,render() 方法中的 this.state.date 将不同,所以渲染输出将包含更新的时间,并相应地更新DOM。

  5. 一旦Clock组件被从DOM中移除,React会调用componentWillUnmount()这个钩子函数,定时器也就会被清除。

正确使用状态

  1. 不要直接更新状态

不要直接

1
this.state.comment = "Hello";

而是使用setState()

1
this.setState({comment: "Hello"});

构造函数是唯一能够初始化this.state的地方

  1. 状态更新可能是异步的

React 可以将多个setState()调用合并成一个调用来提高性能。

因为this.propsthis.state可能是异步更新的,你不应该依靠它们的值来计算下一个状态。

1
2
3
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));

该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数.

  1. 状态更新合并

当你调用setState()时,React将你提供的对象合并到当前状态。

例如,你的状态可能包含一些独立的变量:

1
2
3
4
5
6
7
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}

你可以调用setState()独立地更新它们:

1
2
3
4
5
6
7
8
9
10
11
12
13
componentDidMount() {
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 只能影响树中下方的组件。

如果你想象一个组件树作为属性的瀑布,每个组件的状态就像一个额外的水源,它连接在一个任意点,但也流下来。

父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。

事件处理