React-01

介绍

React 是一个用于构建用户界面的 JavaScript 库。

使用 React 可以创建复杂的 UI,同时使代码易于理解。

React:UI = render(data) 单向数据流

vue:ref 能够直接操作dom,并非是 view model
react:单向数据流,不能通过双向绑定,实现响应式

JSX

JSX: JavaScript XML,将 UI 与逻辑层耦合在一起,提高代码的可读性和可维护性,在组件中使用JSX语法,可以直接在 JS 代码中编写 HTML 结构,而不需要用 JS 字符串拼接。

1
2
<div class="" table-index>
<div className="" tableIndex>
  • 支持使用表达式

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
// 变量
const name = 'react'
const element = <h1>Hello, {name}</h1>

// 函数
function formatName(user) {
return user.firstName + ' ' + user.lastName
}

const user = {
firstName: 'Harper',
lastName: 'Perez'
}

const element = (
<h1>
Hello, {formatName(user)}!
</h1>
)

// 作为函数的返回
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>
}
return <h1>Hello, Stranger.</h1>
}

const element2 = getGreeting(user)
  • 支持指定属性

1
2
const element = <div tabIndex="0"></div>
const element = <img src={user.avatarUrl}></img>

React 式预防 XSS 攻击,会对所有的输入进行转义,所以不会导致 XSS 攻击。

XSS 攻击:跨站脚本攻击,是一种代码注入攻击,攻击者在网页中注入恶意脚本,当用户浏览网页时,脚本会被执行,从而达到攻击目的。

  • 反射 XSS 攻击

1
https://www.baidu.com?name=<script>alert(XSS)</script>
  • 存储 XSS 攻击

1
2
3
<textarea>
<img src="error.png" onError="alert('XSS')"/>
</textarea>

其实就是使用转义字符,将特殊字符转义为HTML实体,React中使用{}来转义。

1
2
3
4
5
6
7
8
9
10
// 存储XSS攻击
// 评论区
for (let i = 0; i < comments.length; i++) {
comments[i] = '<li>' + comments[i] + '</li>'
}
return (
<ul>
{comments}
</ul>
)
  • JSX 支持对象

1
2
3
4
5
6
7
8
9
10
11
const element = (
<h1 className="greet">
Hello, world!
</h1>
)

const element = /*__PURE__*/React.createElement(
'h1',
{className: 'greet'},
'Hello, world!'
)
  • JSX 渲染成 DOM

1
2
3
4
5
const element = <h1>Hello, world</h1>

const container = document.getElementById('root')

ReactDOM.render(element, container)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="root">
<h1>Hello, world</h1>
</div>

// render 只能够标识当前时刻状态
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) // 每秒更新一次

props 和 state

组件

组件是 React 应用的基本构建块,通过组件可以将 UI 拆分成独立的、可复用的部分。

1
2
3
4
5
6
7
8
9
10
11
// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}

// 类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}

组件组合以及拆分

组件可以在其输出中引用其他组件,这使得我们可以使用相同组件抽象出任意层次的细节。

1
2
3
4
5
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 函数组件
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')
)

这段代码会渲染出一个包含三个 Welcome 组件的 App 组件。通过这个例子,我们可以看到组件在输出中可以引用其他组件,这让我们可以用同一组件来抽象出任意层次的细节。按钮、表单、对话框、整个屏幕的内容:在 React 应用中,所有这些通常都会以组件的形式表示。

拆分组件是 React 应用中最常见的模式。这里有一个关于 App 组件如何拆分成更小的组件的示例:

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
function formatDate(date) {
return date.toLocaleDateString()
}

function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
)
}

const comment = {
date: new Date(),
text: 'I hope you enjoy learning React!',
}

const author = {
name: 'Hello Kitty',
avatarUrl: 'https://google.com/favicon.ico',
}

ReactDOM.render(
<Comment
date={comment.date}
text={comment.text}
author={author}
/>,
document.getElementById('root')
)

这个示例演示了如何将一个更复杂的组件拆分成更小的组件。Comment 组件描述了一个评论,包括作者的名字、头像和评论内容。这个组件在其结构中使用了 Avatar、UserInfo 和 UserInfoName 组件,将其拆分成更小的组件有助于理清应用的 UI 架构。

受控组件以及非受控组件

  • 受控组件:表单元素的值由 React 控制,表单元素的值由 state 控制,通过 onChange 事件来更新 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
class NameForm extends React.Component {
constructor(props) {
super(props)
this.state = {value: ''}

this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}

handleChange(event) {
this.setState({value: event.target.value})
}

handleSubmit(event) {
alert('A name was submitted: ' + this.state.value)
event.preventDefault()
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
)
}
}

ReactDOM.render(
<NameForm />,
document.getElementById('root')
)

这个示例展示了一个受控组件,输入框的值由 React 控制。每次按键时,handleChange 事件处理程序都会被调用,更新 React 的 state,因此输入框的值始终与 state 中的值保持一致。

  • 非受控组件:表单元素的值由 DOM 控制,通过 ref 来获取表单元素的值。

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
class NameForm extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.input = React.createRef()
}

handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value)
event.preventDefault()
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
)
}
}

ReactDOM.render(
<NameForm />,
document.getElementById('root')
)

这个示例展示了一个非受控组件,输入框的值由 DOM 控制。每次按键时,handleSubmit 事件处理程序都会被调用,从输入框中获取值。

props 和 state 的区别

  • props:是父组件传递给子组件的数据,子组件不能修改 props,props 是只读的。

  • state:是组件内部维护的数据,组件内部可以修改 state,当 state 发生变化时,组件会重新渲染。

props 和 state 的使用场景

  • props:用于父子组件通信,父组件通过 props 将数据传递给子组件。

  • state:用于组件内部管理数据,当组件内部数据发生变化时,组件会重新渲染。

props 和 state 示例

props 示例:

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

ReactDOM.render(
<Welcome name="Sara" />,
document.getElementById('root')
)

state 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Clock extends React.Component {
constructor(props) {
super(props)
this.state = {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')
)

这两个示例展示了 props 和 state 的使用场景。Welcome 组件通过 props 接收父组件传递的数据,Clock 组件通过 state 维护内部数据。区别在于:props 是父组件传递给子组件的数据,子组件不能修改 props;state 是组件内部维护的数据,组件内部可以修改 state。

生命周期

React 组件有三个生命周期阶段:

  • Mounting:组件被创建并插入到 DOM 中。

  • Updating:组件被重新渲染。

  • Unmounting:组件被移除 DOM。

1.render

  • class 组件中必须有 render 方法,render 方法是 class 组件中唯一必须的方法。

  • 获取最新的 state 和 props,返回一个 React 元素。

  • 在不想修改 state 或 props 的情况下,不要修改 render 方法中的数据。

2.constructor

构造函数,用于初始化 state 和绑定事件处理方法。

  • 声明 state,初始化 state。

  • 绑定事件处理方法。

避免将 props 复制到 state 中,因为 props 是只读的,state 可以修改。

1
2
3
4
5
constructor(props) {
super(props)
this.state = {count: 0};
this.handleClick = this.handleClick.bind(this);
}

不要用 setState 方法来修改 state,因为 setState 方法是异步的,可能会导致 state 不是最新的。

1
2
this.state = { color: props.color }
this.setState({ color: props.color })

3.componentDidMount

组件挂载后调用,只调用一次。

setState 方法会触发重新渲染
addEventListener 添加事件监听

4.componentDidUpdate

组件更新后调用,除了首次渲染,其他渲染都会调用。

1
2
3
4
5
6
7
8
9
10
componentDidUpdate(prevProps, prevState, snapshot) 

componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
// 加条件判断,避免无限循环
if (prevProps.color !== this.props.color) {
this.setState({ color: this.props.color })
}
}
// 如果组件实现了 getSnapshotBeforeUpdate 生命周期,那么它的返回值将作为 componentDidUpdate 的第三个参数 snapshot 传递。

5.componentWillUnmount

组件卸载前调用,用于清理工作。

6.shouldComponentUpdate

这个方法用于判断是否需要重新渲染组件,默认返回 true。并不是很常用,因为 React 会自动判断是否需要重新渲染组件。

1
2
3
4
5
6
7
8
9
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true
}
if (this.state.count !== nextState.count) {
return true
}
return false
}

7.getDerivedStateFromProps

用于取代 componentWillReceiveProps,每次组件接收新的 props 时都会调用。

但是不推荐使用,因为会导致组件状态不可预测。

根据新的 props 更新 state,返回一个对象,或者返回 null 表示不更新 state。

1
2
3
4
5
6
static getDerivedStateFromProps(props, state) {
if (props.color !== state.color) {
return { color: props.color }
}
return null
}

8.getSnapshotBeforeUpdate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
getSnapshotBeforeUpdate(prevProps, prevState)

class ScrollingList extends React.Component {
listRef = React.createRef()

getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current
return list.scrollHeight - list.scrollTop
}
return null
}

componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const list = this.listRef.current
list.scrollTop = list.scrollHeight - snapshot
}
}

render() {
return <div ref={this.listRef}>{/* ...contents... */}</div>
}
}

9.static getDerivedStateFromError

错误边界 error boundary,用于捕获子组件的错误,只能捕获子组件的错误,无法捕获自身的错误。

1
2
3
4
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级 UI
return { hasError: true }
}

10.componentDidCatch

1
2
3
4
componentDidCatch(error, info) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, info)
}