typescript+react学习笔记2

原生js如何获取子元素:element.childNodes;原生js如何获取父元素:element.parentNode;

题外话,背个书:

1
2
3
let a = 2;
let b = 1;
[a, b] = [b, a];

1.render方法所返回的是什么类型数据?

1
2
3
4
5
6
7
8
9
10
11
import * as React from "react";

class App extends React.Component {
public render(): React.ReactNode {
return (
<div>
<h1>learn</h1>
</div>
);
}
}

2.jsx大致的解析过程?

先看在jsx中一个简单的元素是怎么被转化的(https:/​/​babeljs.​io/​repl可在线尝试)

1
<span>hello world</span>

它将被转化成下面这样:

1
2
3
4
5
React.createElement(
"h1",
null,
"hello babel"
);

解释一下React.createElement方法的三个参数,参数一:表示元素类型,可以是以下几种 HTML tag name,React Component Type,React Fragment Type。参数二:应用在元素上面的props object。参数三:元素的children。

因此,对于像下面这样的jsx代码的话将会转化成这样的代码:

1
2
3
<div className="c1">
<h1>aha</h1>
</div>

translate:

1
2
3
4
5
6
7
8
9
React.createElement(
"div",
{ className: "c1" },
React.createElement(
"h1",
null,
"aha"
)
);

从上面的转换我们也可以看出为什么在react中class是叫做className,因为class是关键字,jsx最终还是要走js的。

再来看看当jsx里面引用js会发生什么情况:

1
2
3
4
const props = { title: 'babel' };
<div className="c1">
<span>{props.title ? props.title : 'react and typescript'}</span>
</div>

jsx解析结果:

1
2
3
4
5
6
7
8
9
10
var props = { title: "babel" };
React.createElement(
"div",
{ className: "c1" },
React.createElement(
"span",
null,
props.title ? props.title : "react and typescript"
)
);

3.Component props

React.Component是一个泛型类,它能接受一个表示props的类型,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface IProps = {
title: string;
content: string;
classPrefix?: string; // 表示这个props是可选的
};
class App extends React.Component<IProps> {
public render() {
return (
<div>
<h1>{this.props.title}</h1>
<h1>{this.props.content}</h1>
</div>
);
}
}

4.Default Prop Values

1
2
3
4
5
6
7
8
9
10
11
12
interface IProps = {
title: string;
classPrefix?: string;
};
class App extends React.Component<IProps> {
public static defaultProps = {
classPrefix: "confirm_"
};
public render(): React.ReactNode {

}
}

5.component state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface IState {
showModal: boolean;
}

class App extends React.Component<{}, IState>{
constructor(props: {}) {
super(props);
this.state = {
showModal: true
};
}

private modalOkHandler = () => {
this.setState({ showModal: false });
}
}

暂时也不知{}是什么类型。可以确定的是,如果显示说明了一个component的props类型是{}的话,那么在使用这个component的时候就不能手动传入props,传来的话就会报错。

6.React正确实践,不该作为state的切勿作为state处理:

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
38
39
40
41
interface IState {
countDown: number;
showModal: boolean;
};
class App extends React.Component
private _timer: number = 0;

constructor(props: {}) {
super(props);
this.state = {
countDown: 10,
showModal: false
};
}

// setTimeout, setInterval里面setState是同步更新
public componentDidMount() {
this._timer = window.setInterval(this.timerHandler, 1000);
}

public componentWillUnmount() {
clearInterval(this._timer);
}

private timerHandler() {
this.setState({
countDown: this.state.countDown - 1
}, () => {
if (this.state.countDown <= 0) {
clearInterval(this._timer);
this.setState({ showModal: true });
}
});
}

public render(): React.ReactNode {
return (
<ComponentA show={this.state.showModal} />
);
}
}

7.React的生命周期

react16版本对生命周期方法做了较大的更新处理,最新的生命周期流程图如下所示:

react16生命周期流程图

接下来进行逐一介绍:

componentDidMount:当component已经insert进DOM的时候被触发,一般的,在componentDidMount会完成以下逻辑处理:

  • 1.网络请求;
  • 2.事件监听
  • 3.初始化计时器
  • 4.初始化第三方库

componentWillUnmount:当component已经从DOM中remove的时候触发。一般的,会在里面完成以下逻辑:

  • 1.移除event listener
  • 2.取消active network;
  • 3.移除计时器

getDerivedStateFromProps:这个生命周期方法会在挂载和更新这两个生命周期得到调用机会。它是一个静态方法。通过上面的表述我们可以发现,如果你setState的话,那么是会触发getDerivedStateFromProps方法的,同时他也接受state作为第二个参数,如下所示:

1
2
3
public static getDerivedStateFromProps(props: {}, state: IState) {
// setState会触发它,然后在它参数中获取到state对象就是最新的state
}

getSnapshotBeforeUpdate:这个生命周期方法在更新的时候会得到调用机会,它接受两个参数,分别是prevProps, prevState,同时他需要返回一个值,这个值将会作为componentDidUpdate方法的第三个参数。也就是说componentDidUpdate会在getSnapshotBeforeUpdate方法,示例用法如下所示:

1
2
3
4
5
6
7
public getSnapshotBeforeUpdate(prevProps: {}, prevState: IState) {
console.log('prevState', prevState);
return 'fromSnapshot';
}
public componentDidUpdate(prevProps: {}, prevState: IState, snapshot: string) {
console.log('snapshot', snapshot); // fromSnapshot
}

shouldComponentUpdate:shouldComponent is invoked just before rendering happens,它返回一个布尔值来表明是否需要进行接下的更新流程。不用多说,这是关于性能优化的关键之处。

还是针对上面那个定时器的例子,如果我们使得shouldComponent返回false的话,那么getSnapshotBeforeUpdate以及componentDidUpdate都是不会得到触发机会的。由于不断setState的原因,此时只会触发getDerivedStateFromProps方法,接着再触发shouldComponentUpdate方法。

8.函数组件(无状态函数组件):

首先,无状态函数组件就不能拥有state吗?不是的,也可以有。

对于react的typescript版本来说,无状态函数组件的类是React.SFC。对于无状态函数组件来说,需要注意的地方有:它是不允许有render方法的,它所需要渲染的内容直接通过return jsx来实现;访问props无需this;同时由于对于无状态函数组件来说它是一个函数而不是类了,所以不能直接设置defaultProps静态属性;而是要如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
import * as React from "react";
interface IProps = {
classPrefix: string;
};
const Modal: React.SFC<IProps> = props => {
const eventHandler = () => {};
return (
<div className={props.classPrefix}></div>
);
}
Modal.defaultProps = {
classPrefix: 'modal'
};

9.函数组件就不能够拥有state?? NO!

React16版本使得函数组件也能够出现state,并且能够修改state,这主要多亏了React.useState方法。React.useState返回给我们一个数组,数组的第一项是state,第二项是设置state的方法,将新的state作为这个方法的参数即可。React.useState接受的第一个参数是state的初始值。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import * as React from "react";

interface IProps {
classPrefix: string;
};

class Confirm: React.SFC<IProps> = props => {
const [ count, setCount ] = React.setState(0);

const clickHandler = () => {
const countTemp = count + 1;
setCount(countTemp);
};

return (
<div onClick={this.clickHandler}>
<button>{count}</button>
</div>
);
}

10.函数组件就不能拥有生命周期方法吗?no!

使用React.useEffect()方法来进行生命周期函数的hook,它接受两个参数,第一个参数是箭头函数,在被rendered后调用;正常情况下,这个箭头函数一定会被调用一次(成功挂载后的那次render)。第二个参数是一个数组,用来决定第一个参数箭头函数是否应该被调用。每当第二个数组的值发生变化的时候,那么箭头函数便会得到调用机会。问题:里面是否可以放普通变量?好像是不行的,只能放state,props。放普通变量的话,即使变量发生变化箭头函数也不会被调用。

问题2:第二个参数,能否state和props都放?答案是可以的,只要其中某一个发生变化都会引起箭头函数执行。如下所示:

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
import * as React from "react";

interface IProps {
topCount: number;
};

const Count: React.SFC<IProps> = () => {
const [ count, setCount ] = React.setState(0);

React.useEffect(() => {
console.log('rended!');
return () => {
console.log('unmount!');
}
}, [ count, topCount ]);

const clickHandler = () => {
let temp = count + 1;
setCount(temp);
};

return (
<h1 onClick={clickHandler}>{count}</h1>
);
}

关于使用React.useEffect进行hook还有一点忘记说了,如果第一个箭头函数返回一个函数的话,那么返回的这个函数将会在卸载之后被调用。(更新是会触发卸载过程的)。

一个问题,组件的更新过程是否触发hook?不会的,只要当第二个参数发生变化的时候,hook函数才会被执行。

11.最优化函数组件的渲染过程

我们知道,对于react来说,为了避免在比较两棵虚拟DOM树异同时所造成的性能问题,react会采用各种优化算法进行比较,其中之一就是如果父节点变化了的话,那么子节点也得卸载在更新。大部分时候这都会造成性能浪费,那么如何优化呢?react16大手一挥,提出了React.memo()方法,它接受一个组件,返回一个组件,返回的组件能够做到在自身状态没有发生变化的话,便不进行更新。那么这是不是银弹呢?答案是不是的,它只适用于那些本身就不会经常变化的场景之中,如果一个组件很多时候的渲染都是有必要的话,那么效果会适得其反,因为比对props是否变化本身就耗费性能。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import * as React from "react";

interface IProps {
classPrefix: string;
};

const Text: React.SFC<IProps> = props => {
console.log('I am running');
const [ count, setCount ] = React.useState(0);
React.useEffect(() => {
console.log('rendered');
return () => {
console.log('unmounted');
};
}, [ count ]);
const clickHandler = () => {
const temp = count + 1;
setCount(temp);
};
return (
<h1 onClick={clickHandler}>{count}</h1>
);
}

Text.defaultProps = {
classPrefix: 'aha'
};

const TextMemo = React.memo(Text);

export default TextMemo;

// 父组件

interface IState {
top: number;
}

class App extends React.Component<{}, IState> {
constructor(props: {}) {
super(props);
this.state = {
top: 0
};
};

private clickHandler = () => {
this.setState({ top: this.state.top + 1 });
};

public render() {
return (
<div>
<button onClick={this.clickHandler}>{this.state.top}</button>
<TextMemo classPrefix="test" />
</div>
);
}
}

对于上面这个例子而言,当父组件setState的时候,可以发现I am running并不是多次被触发,所以利用React.memo方法包装组件能够达到避免多次无效渲染的目的。