React 基础

课程目标

  1. 入门 React,了解常规用法;
  2. 掌握面试中 React 的基础问题;
  3. 掌握 React 学习路线;

知识要点

React 简介

React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库,使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称为“组件

UI = render(data) -> 单向数据流

  • MVC
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// model

var myapp = {} // 创建这个应用对象

myapp.Model = function () {
var val = 0

this.add = function (v) {
if (val < 100) val += v
}

this.sub = function (v) {
if (val > 0) val -= v
}

this.getVal = function () {
return val
}

/* 观察者模式 */

var self = this
var views = []

this.register = function (view) {
views.push(view)
}

this.notify = function () {
for (var i = 0; i < views.length; i++) {
views[i].render(self)
}
}
}

// view
myapp.View = function (controller) {
var $num = document.getElementById('num')
var $incBtn = document.getElementById('increase')
var $decBtn = document.getElementById('decrease')

this.render = function (model) {
$num.innerText = model.getVal() + 'rmb'
}

/* 绑定事件 */
$incBtn.addEventListener('click', controller.increase)
$decBtn.addEventListener('click', controller.decrease)
}

// controller
myapp.Controller = function () {
var model = null, view = null

this.init = function () {
/* 初始化 Model 和 View */
model = new myapp.Model()
view = new myapp.View(this)
/* View 向 Model 注册,当 Model 更新就会去通知 View */
model.register(view)
model.notify()
}

/* 让 Model 更新数据并通知 View 更新视图 */
this.increase = function () {
model.add(1)
model.notify()
}

this.decrease = function () {
model.sub(1)
model.notify()
}
}

// init

(function () {
var controller = new myapp.Controller()
controller.init()
}())

  • MVVM

JSX 模版语法

JSX 称为 JS 的语法扩展,将 UI 与逻辑层耦合在组件⾥,⽤ {} 标识。
因为 JSX 语法上更接近 JS ⽽不是 HTML,所以使⽤ camelCase(⼩驼峰命名)来定义属性的名称;JSX ⾥的 class 变成了 className,⽽ tabindex 则变为 tabIndex

JSX 支持表达式

支持 JS 表达式、变量、方法名

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


function formatNmae (user) {
return user.firstName + ' ' + user.lastName
}
// 方法
const user = {
firstName: 'Harper',
lastName: 'Perez'
}

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

function getGreeting (user) {
if (user) {
return <h1>Hello, {formatNmae(user)}!</h1>
}
return <h1>Hello, Stranger.</h1>
}

JSX 指定属性

1
const element = <img src={user.avatarUrl}></img>

JSX 表示对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const element = (
<h1 className="greeting">
Hello, world!
</h1>
)
// 等同于 React.createElement
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
)

const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
}

将 JSX 渲染为 DOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用 ReactDOM.render
const element = <h1>Hello, world</h1>
ReactDOM.render(element, document.getElementById('root'))

// render 只能代表当前时刻的状态
// 更新元素只能再次 ReactDOM.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) // 不建议多次 render

JSX 转 JS

JSX 可以当做语法糖,可以在 babel 官⽹中尝试,https://babeljs.io/repl 可以使⽤官⽹提供的 create-react-app npm run eject 来看 babelrc 中的配置,主要使⽤https://www.babeljs.cn/docs/babel-preset-react

1
2
# 安装 babel 及 react 的依赖
npm install core-js @babel/core @babel/preset-env @babel/preset-react @babel/regiser babel-loader @babel/plugin-transform-runtime --sabe-dev

.babelrc

1
2
3
4
5
6
7
8
9
10
{
"presets": [
"@babel/preset-env",
"@babel/preset-es2015",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}

组件 、 Props & state

组件,从概念上类似于 JavaScript 函数。它接受任意的⼊参(即 “props”),并返回⽤于描述⻚⾯展示内容的 React 元素。

组件

  • 函数式组件
  • Class 类组件
1
2
3
4
5
6
7
8
9
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
6
7
8
9
function Welcome (props) {
return <h1>Hello, {props.name}</h1>
}

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

props

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 使用 props 形式
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}

function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}

setInterval(tick, 1000);

// 如何避免多次 ReactDOM.render ?
// 引入生命周期,根组件保留一个

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. setState
    构造函数是唯一可以给 this.state 赋值的地方
    1
    this.setState({comment: 'Hello'})
  2. setState 更新可能是异步的
    出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
    因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
    1
    2
    3
    4
    5
    6
    7
    8
    // Wrong 此代码可能会无法更新计数器
    this.setState({
    counter: this.state.counter + this.props.increment,
    });
    // Correct 这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
    this.setState((state, props) => ({
    counter: state.counter + props.increment
    }));
  3. State 的更新会被合并
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    constructor(props) {
    super(props);
    this.state = {
    posts: [],
    comments: []
    };
    }
    componentDidMount() {
    fetchPosts().then(response => {
    // 相当于 {posts: response.posts, ...otherState}
    this.setState({
    posts: response.posts
    });
    });

    fetchComments().then(response => {
    this.setState({
    comments: response.comments
    });
    });
    }
  4. 数据是向下流动的
    state 只在当前的组件里生效,属于组件内的属性,重复实例化相同的组件,内部的内存地址也是不一样的;
    例如 Clock 中计时器都是独立的

生命周期

render

是class组件必需的⽅法
获取最新的 props 和 state
在不修改组件 state 的情况下,每次调⽤时都返回相同的结果

constructor

如果不初始化 state 或不进⾏⽅法绑定,则不需要为 React 组件实现构造函数。

  • 通过给 this.state 赋值对象来初始化内部 state。
  • 为事件处理函数绑定实例
1
2
3
4
5
6
7
8
9
10
11
12
13
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}

// 避免将 props 的值复制给 state
constructor(props) {
super(props);
// 不要这样做
this.state = { color: props.color };
}

componentDidMount

会在组件挂载后(插⼊ DOM 树中)⽴即调⽤
依赖于 DOM 节点的初始化应该放在这⾥,如需通过⽹络请求获取数据;
可以在此⽣命周期⾥加 setState,但发⽣在浏览器更新屏幕之前,会导致性能问题;
有更新在render阶段的 constructor 中 init State,但有更新可以在此⽅法时 setState

componentDidUpdate

6
1
componentDidUpdate(prevProps, prevState, snapshot)

会在更新后会被立即调用。首次渲染不会执行此方法。

当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。

1
2
3
4
5
6
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):加条件判断,不然死循环
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}

如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用),则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()。

componentWillUnmount

componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

shouldComponentUpdate

(不常用)

6
1
shouldComponentUpdate(nextProps, nextState)

根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。
作为性能优化使⽤,返回false可以跳过re-render
shouldComponentUpdate() 返回 false,不会调⽤ UNSAFE_componentWillUpdate(),render() 和 componentDidUpdate()。

getDerivedStateFromProps

(不常用)
是为了取代 componentWillReceiveProps 和 componentWillUpdate 设置的根据 props 的变化改变 state,它应返回⼀个对象来更新 state,如果返回 null 则不更新任何内容。

  • 在使⽤此⽣命周期时,要注意把传⼊的 prop 值和之前传⼊的 prop 进⾏⽐较;
  • 因为这个⽣命周期是静态⽅法,同时要保持它是纯函数,不要产⽣副作⽤;

getSnapshotBeforeUpdate

(不常用)

6
1
getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。

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
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}

getSnapshotBeforeUpdate(prevProps, prevState) {
// 我们是否在 list 中添加新的 items ?
// 捕获滚动位置以便我们稍后调整滚动位置。
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}

componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
//(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}

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

getDerivedStateFromError

(不常用)

配合Error boundaries使⽤
此⽣命周期会在后代组件抛出错误后被调⽤。 它将抛出的错误作为参数,并返回⼀个值以更新 state;

componentDidCatch

(不常用)
componentDidCatch() 会在“提交”阶段被调⽤,因此允许执⾏副作⽤。 它应该⽤于记录错误之类的情况;

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 ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以显示降级 UI
return { hasError: true };
}

componentDidCatch(error, info) {
// "组件堆栈" 例子:
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
logComponentStackToMyService(info.componentStack);
}

render() {
if (this.state.hasError) {
// 你可以渲染任何自定义的降级 UI
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}

UNSAFE_componentWillMount

(不建议使⽤)
UNSAFE_componentWillMount() 在挂载之前被调⽤;
它在 render() 之前调⽤,因此在此⽅法中同步调⽤ setState() 不会⽣效;
需要的话⽤componentDidMount替代。

UNSAFE_componentWillReceiveProps

(不建议使⽤)
UNSAFE_componentWillReceiveProps() 会在已挂载的组件接收新的 props 之前被调⽤;
如果你需要更新状态以响应 prop 更改(例如,重置它),你可以⽐较 this.props 和 nextProps 并在此⽅法中使⽤ this.setState() 执⾏ state 转换。

UNSAFE_componentWillUpdate

(不建议使⽤)

  • 当组件收到新的 props 或 state 时,会在渲染之前调⽤ UNSAFE_componentWillUpdate();
  • 使⽤此作为在更新发⽣之前执⾏准备更新的机会;
  • 初始渲染不会调⽤此⽅法;
    如果 shouldComponentUpdate() 返回 false,则不会调⽤ UNSAFE_componentWillUpdate();

事件处理

语法格式

  1. 在JSX元素上添加事件,通过on*EventType这种内联⽅式添加,命名采⽤⼩驼峰式(camelCase)的形式,⽽不是纯⼩写(原⽣HTML中对DOM元素绑定事件,事件类型是⼩写的);
  2. ⽆需调⽤addEventListener进⾏事件监听,也⽆需考虑兼容性,React已经封装好了⼀些的事件类型属性;
  3. 使⽤ JSX 语法时你需要传⼊⼀个函数作为事件处理函数,⽽不是⼀个字符串;
  4. 不能通过返回 false 的⽅式阻⽌默认⾏为。你必须显式的使⽤ preventDefault;
1
2
3
4
5
6
7
8
9
10
11
12
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}

return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
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
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};

// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}

render() {
return (
// class 的方法默认不会绑定 this。如果没有绑定 this.handleClick 并把它传入了 onClick
// this 的值为 undefined。
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}

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

为什么要绑定 this

6
1
2
3
4
5
6
7
function createElement (dom, params) {
var domObj = document.createElement(dom)
domObj.onclick = params.onclick
domObj.innerHTML = params.conent
return domObj
}
// createElement 的 onClick 函数是绑定到 domObj 上的,如果 this 不显示绑定,不会绑定到 Toggle 上

绑定 this 的方法:

  1. 不显示使用 bind
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class LoggingButton extends React.Component {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    // 注意: 这是 *实验性* 语法。
    handleClick = () => {
    console.log('this is:', this);
    }

    render() {
    return (
    <button onClick={this.handleClick}>
    Click me
    </button>
    );
    }
    }
  2. 箭头函数,问题:每次 render 都会创建不同的回调函数,如果该回调函数作为 props 传入子组件,每次子组件都要 re-render
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class LoggingButton extends React.Component {
    handleClick() {
    console.log('this is:', this);
    }

    render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return (
    <button onClick={() => this.handleClick()}>
    Click me
    </button>
    );
    }
    }

接收参数

  1. 事件对象 e 会被作为第⼆个参数传递;
    
    1. 通过箭头函数的⽅式,事件对象必须显式的进⾏传递;
      
      1. 通过 Function.prototype.bind 的⽅式,事件对象以及更多的参数将会被隐式的进⾏传递;
        
1
2
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

条件渲染

if else 渲染

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
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}

handleLoginClick() {
this.setState({isLoggedIn: true});
}

handleLogoutClick() {
this.setState({isLoggedIn: false});
}

render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}

return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}

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

与运算符 &&

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);

返回 false 的表达式,会跳过元素,但会返回该表达式

1
2
3
4
5
6
render() {
const count = 0
return (
<div>{count && <h1>Message: {count}</h1>}</div>
)
}

三元运算符

1
2
3
4
5
6
7
8
9
10
11
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}

如何阻止组件渲染

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
function WarningBanner(props) {
if (!props.warn) {
return null;
}

return (
<div className="warning">
Warning!
</div>
);
}

class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true};
this.handleToggleClick = this.handleToggleClick.bind(this);
}

handleToggleClick() {
this.setState(state => ({
showWarning: !state.showWarning
}));
}

render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}

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

列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);

若没有 key,会 warning a key should be provided for list items;
key 可以帮助 react diff,最好不要用 index 作为 key,会导致性能变差;
如果不指定显式的 key 值,那么 React 将默认使用索引用作为列表项目的 key 值。

key 注意点

  1. key 要保留在 map 的遍历元素上

    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
    function ListItem(props) {
    const value = props.value;
    return (
    // 错误!你不需要在这里指定 key:
    <li key={value.toString()}>
    {value}
    </li>
    );
    }

    function NumberList(props) {
    const numbers = props.numbers;
    const listItems = numbers.map((number) =>
    // 正确!key 应该在数组的上下文中被指定
    <ListItem key={number.toString()} value={number} />
    );
    return (
    <ul>
    {listItems}
    </ul>
    );
    }

    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById('root')
    );
  2. key 只是在兄弟节点之间必须唯一

    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
    function Blog(props) {
    const sidebar = (
    <ul>
    {props.posts.map((post) =>
    <li key={post.id}>
    {post.title}
    </li>
    )}
    </ul>
    );
    const content = props.posts.map((post) =>
    <div key={post.id}>
    <h3>{post.title}</h3>
    <p>{post.content}</p>
    </div>
    );
    return (
    <div>
    {sidebar}
    <hr />
    {content}
    </div>
    );
    }

    const posts = [
    {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
    {id: 2, title: 'Installation', content: 'You can install React from npm.'}
    ];
    ReactDOM.render(
    <Blog posts={posts} />,
    document.getElementById('root')
    );
  3. 在 JSX 中嵌入 map()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function NumberList(props) {
    const numbers = props.numbers;
    return (
    <ul>
    {numbers.map((number) =>
    <ListItem key={number.toString()} value={number} />
    )}
    </ul>
    );
    }

补充知识点

React 如何预防 XSS

1
2
3
const title = response.potentialyMaliciousInput // 此时只是字符串
// 直接使用是安全的
const element = <h1>{title}</h1>

React DOM 在渲染所有输入内容之前,默认会进行转义。

部分源码:

6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escape = '&quot;'
break
case 38: // &
escape = '&amp;'
break
case 39: // '
escape = '&#x27;'
break
case 60: // <
escape = '&;t;'
break
case 62: // >
escape = '&gt;'
break
default:
continue
}
}

关于 setState 的异步

异步目的:batch 处理、性能优化

  1. 合成事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class App extends Component {
    state = {val: 0}

    increment = () => {
    this.setState({val: this.state.val + 1})
    console.log(this.state.val) // 0
    }

    render() {
    return (
    <div onClick={this.increment}>
    {`Counter is: ${this.state.val}`}
    </div>
    )
    }
    }
  2. 生命周期
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class App extends Component {
    state = {val: 0}

    componentDidiMount() {
    this.setState({val: this.state.val + 1})
    console.log(this.state.val) // 0
    }

    render() {
    return (
    <div>
    {`Counter is: ${this.state.val}`}
    </div>
    )
    }
    }
  3. 原生事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class App extends Component {
    state = {val: 0}

    changeValue = () => {
    this.setState({val: this.state.val + 1})
    console.log(this.state.val) // 1
    }

    componentDidiMount() {
    document.body.addEventListener('click', this.changeValue, false)
    }

    render() {
    return (
    <div>
    {`Counter is: ${this.state.val}`}
    </div>
    )
    }
    }
  4. setTimeout
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class App extends Component {
    state = {val: 0}

    componentDidiMount() {
    setTimeout(_ => {
    this.setState({val: this.state.val + 1})
    console.log(this.state.val) // 1
    }, 0)
    }

    render() {
    return (
    <div>
    {`Counter is: ${this.state.val}`}
    </div>
    )
    }
    }
  5. 批处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class App extends Component {
    state = {val: 0}

    batchUpdates = () => {
    this.setState({val: this.state.val + 1})
    this.setState({val: this.state.val + 1})
    this.setState({val: this.state.val + 1})
    }

    render() {
    return (
    <div onClick={this.batchUpdates}>
    {`Counter is: ${this.state.val}`} // 1
    </div>
    )
    }
    }
    1. setState 只在合成事件和⽣命周期中是“异步”的,在原⽣事件和 setTimeout 中都是同步的;
    2. setState的“异步”并不是说内部由异步代码实现,其实本身执⾏的过程和代码都是同步的, 只是合成事件和钩⼦函数的调⽤顺序在更新之前,导致在合成事件和钩⼦函数中没法⽴⻢拿到更新后的值,形式了所谓的“异步”, 当然可以通过第⼆个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
    3. setState 的批量更新优化也是建⽴在“异步”(合成事件、钩⼦函数)之上的,在原⽣事件和 setTimeout 中不会批量更新,在“异步”中如果对同⼀个值进⾏多次 setState , setState 的批量更新策略会对其进⾏覆盖,取Y后⼀次的执⾏,如果是同时 setState 多个不同的值,在更新时会对其进⾏合并批量更新。

create-react-class

自动绑定 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var createReactClass = require('create-react-class');
var LoggingButton = createReactClass({
handleClick: function() {
console.log('this is:', this);
},

render: function() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
});