React(一):核心概念
# React 简介
# 背景
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在 2013 年 5 月开源了。
# react 是什么?
React 是一个用于构建用户界面的 JavaScript 库。用于构建快速、高效的用户界面;它是一个轻量级库,遵循组件化设计模式、声明式编程范式以及函数式编程概念。它使用虚拟 DOM 来有效地操作 DOM;遵循从高阶组件到低阶组件的单向数据流。
在整个 Web 应用的 MVC 架构中,你可以将 React 看作为视图层,并且是一个高效的视图。React 提供了和以往不一样的方式来看待视图,它以组件开发为基础。对 React 应用而言,你需要分割你的页面,使其成为一个个的组件。也就是说,你的应用是由这些组件组合而成的。你可以通过分割组件的方式去开发复杂的页面或某个功能区块,并且组件是可以被复用的。这个过程大概类似于用乐高积木去拼装不同的物体。
# 特点
- 采用组件化模式、声明式编码;提高开发效率和组件复用率
- 使用虚拟 DOM 和 diffing 算法,减少与真实 DOM 的交互
- 与已知的库或框架完好配合,相对灵活
- 使用 JSX 语法糖
- 单向数据流
# Virtual DOM
为了跟踪模型层的变化,并且将其应用到 DOM 中(也就是渲染),我们需要注意两个重要的事情:
- 数据是什么时候改变的 React 提供了一个观察者模型用于替代传统的脏检查(dirty checking),也就是持续的检查模型的变化。这也就是解释了为什么 React 不需要计算哪些发生了改变的原因,因为它会立即知道。这个过程减少了计算量,使应用程序变得更平滑。
- 哪一个(些)DOM 元素需要被更新 React 在内存中构建了 DOM 的树形表示,并且计算出哪个 DOM 元素应该被改变。对浏览器而言,DOM 操纵是比较耗费性能的,因此我们更倾向于让其变得最小化。幸运的是,React 视图尽可能少的触及到 DOM 元素。给予对象表示而言, 更少的 DOM 操纵意味着计算会更快,因此 DOM 改变也被尽可能的减少。 React 在底层实现了一个 diffing 算法,该算法使用 DOM 的树形表示法,当某个节点发生变化(标记为 dirty)时它会重新计算整个子树,你会注意到你的模型发生了改变,因为整个子树在之后会被重新渲染。
# 什么是虚拟 DOM?
在 react 中,简单来说虚拟 DOM 其实就是 JS 对象,将真实 DOM 抽象成一个 javascript 对象。
function el(name, props, child) {
{
this.name = name;
this.props = props;
this.child = child;
}
}
var dom = new el("div", { id: "list" }, [
new el("span", { id: "span_one" }, ["第一个元素"]),
new el("span", { id: "span_two" }, ["第二个元素"]),
new el("span", { id: "span_three" }, ["第三个元素"]),
new el("span", { id: "span_four" }, ["第四个元素"]),
]);
2
3
4
5
6
7
8
9
10
11
12
13
14
这里的 dom 就是一个对象,里面包含了 name,props,child。 将对象转换成真实的 DOM:
el.prototype.render = function () {
var d = document.createElement(this.name);
for (key in this.props) {
d.setAttribute(key, this.props[key]);
}
this.child.forEach(function (child) {
if (child instanceof el) {
tnode = child.render();
} else {
tnode = document.createTextNode(child);
}
d.appendChild(tnode);
});
return d;
};
let ele = dom.render();
document.body.appendChild(ele);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 原理
在 React 中,也有一个 render 函数来将虚拟 DOM 树转换成真实 DOM,并且,React 中有 state 转移的过程,所以每次 state 有变化之后,就会触发 render 函数,重新构造一个虚拟 DOM 树。对比新旧虚拟 DOM 树的差别,记录下差异,然后只针对差异部分对应的真实 DOM 进行操作。
- 如何进行新旧虚拟 DOM 树的对比? 这里采用的是 Diff 算法。Diff 算法比较复杂,主要的思路是这样的。 首先,每一次生成的虚拟 DOM 树上的各个节点都对应唯一的一个 id,当第二次生成了新的 DOM 树时,对原来树上的每一个节点对比新树上对应节点,如果不同,就记录下来这个差异。同时,差异也分为很多种:
- 替换节点
- 删除、移动、增加节点
- 修改属性
- 对文本内容做修改
对每一类记录下差异后,针对不同的差异做不同的操作。
比如:替换节点就需要调原生JS的repaceChild()接口;对于修改属性,则要调setAttribute()接口等等。
# JSX 语法
const element = <h1>Hello, Word!</h1>;
像这样的形式,被称为 JSX;是 javascript 的语法扩展。JSX 的基本语法和 XML 基本相同,都是使用成对标签的形式构成树状结构的数据。
# 为什么使用 JSX?
我们来看看 React 官方的回答:
简单来说,react 认为组件才是王道;而组件是和模板紧密关联的。组件模板和组件逻辑分离让问题复杂化了。 所以就有了 JSX 这种语法,就是为了把 HTML 模板直接嵌入到 JS 代码里面,这样就做到了模板和组件关联,但是 JS 不支持这种包含 HTML 的语法,所以需要通过工具将 JSX 编译输出成 JS 代码才能使用。
# JSX 是可选的
因为 JSX 最终是输出成 JS 代码来表达的,所以我们可以直接用 React 提供的这些 DOM 构建方法来写模板,比如一个 JSX 写的一个链接:
<a href="https://react.docschina.org/">Hello!</a>
用 js 来写的的话就是:
React.createElement('a', {href: 'https://react.docschina.org/'}, 'Hello!')
JSX 语法糖允许前端开发者使用我们最熟悉的类 HTML 标签语法来创建虚拟 DOM 在降低学习成本的同时,也提升了研发效率与研发体验。
# 使用
# 标签类型
- DOM 类型的标签(div,span 等), 使用时,标签首字母必须小写
- React 组件类型的标签,使用时,组件名称的首字母必须大写
React 通过首字母的大小写来判断渲染的是 DOM 类型标签还是 React 组件类型标签。
# JS 表达式
JSX 的本质是 javascript,在 JSX 中使用 JS 表达式需要用{}包裹起来。
- 使用场景
- 通过表达式给标签属性赋值
const dom = <Child data={ 1 + 1 } />
- 通过表达式定义子组件
const arr = ['li01','li02','li03']
const dom = {
<ul>
arr.map(item=>{<Item msg={item} key={item} />})
</ul>
}
2
3
4
5
6
7
# 标签属性
- 当 JSX 标签是 DOM 类型的标签时,对应的 DOM 标签支持的属性 JSX 也支持,部分属性名称有所变化,例如 class 写成 className,之所以改变是因为 React 对 DOM 标签支持的事件重新做了封装。事件属性名称采用驼峰格式。
- 当 JSX 标签是 React 组件类型时,可以自定义标签的属性名。
# 注释
JSX 的注释需要大括号”{}”将/**/包裹起来。
# 元素渲染
元素是构成 React 应用的最小砖块。
# 将一个元素渲染为 DOM
<div id="root"></div>
const dom = <h1>Hello, Word!</h1>;
ReactDOM.render(dom, document.getElementById("root"));
2
# 更新已渲染元素
React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。更新 UI 唯一的方式是创建一个全新的元素,并将其传入 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);
2
3
4
5
6
7
8
9
10
11
# React 只更新它需要更新的部分
ReactDOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。
尽管每一秒都会新建一个描述整个 UI 树的元素,ReactDOM 只会更新实际改变了的内容。
# 组件
# 组件的定义
定义组件的两种方式:
- 使用函数(函数组件)
- 使用 ES6 class(类组件)
# 函数组件
function Funcomponent(props) {
return <h1>我是函数组件,{props.name}</h1>;
}
2
3
该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。
# class 类组件
使用 class 定义组件满足的两个条件:
- class 继承自 React.Component。
- class 内部必须自定义 render 方法,render 方法返回代表该组件 UI 的 React 元素。
class Classcomponent extends React.Component {
render() {
return <h1>我是类组件,{this.props.name}</h1>;
}
}
2
3
4
5
# 组件渲染
const element = <Child name="child" />;
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
function Child(props) {
return <h1>Hello!{props.name}</h1>;
}
const element = <Child name="函数式组件" />;
ReactDOM.render(element, document.getElementById("root"));
2
3
4
5
6
来看看上述例子都做了什么?
- 调用 ReactDOM.render()函数,并传入< Child name="函数式组件" />作为参数。
- React 调用 Child 组件,并将{name: '函数式组件'}作为 props 传入。
- Child 组件将< h1 >Hello!函数式组件</ h1 >元素作为返回值。
- React DOM 将 DOM 高效地更新为< h1 >Hello!函数式组件</ h1 >。
# 组合、嵌套组件
将一个组件渲染到某一个节点里的时候,会将这个节点里原有内容覆盖。组件嵌套的方式就是将子组件写入到父组件的模板中去,且 React 没有 Vue 中的内容分发机制(slot),所以我们在一个组件的模板中只能看到父子关系
// 从 react 的包当中引入了 React 和 React.js 的组件父类 Component
// 还引入了一个React.js里的一种特殊的组件 Fragment
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
<h1>Hello,React</h1>
)
}
}
class Content extends Component {
render () {
return (
<p>React.js是一个用于构建用户界面的JavaScript库</p>
)
}
}
/** 由于每个React组件只能有一个根节点,所以要渲染多个组件的时候,需要在最外层包一个容器,如果使用div, 会生成多余的一层dom
class App extends Component {
render () {
return (
<div>
<Title />
<Content />
</div>
)
}
}
**/
// 如果不想生成多余的一层dom可以使用React提供的Fragment组件在最外层进行包裹
class App extends Component {
render () {
return (
<Fragment>
<Title />
<Content />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
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
# Props(组件的数据挂载方式)
# 属性 props(只读性)
props 是由外部传进来的,也可以由组件内部设置初始化的 props;组件无论是使用函数式组件还是 class 组件,都不能修改自身的 props。可以通过父组件主动重新渲染的方式来传入新的 props。
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component{
render(){
return (
<h1>Hello,{this.props.name}</h1>
)
}
}
const Conent = (props)=> {
return (
<p>{props.name}是一个用于构建用户界面的JavaScript库</p>
)
}
class App extends Component{
render(){
return(
<Fragment>
<Title name="React" />
<Conent name="React.js" />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
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
# 设置组件的默认 props
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component{
// 使用类创建的组件,直接在这里写static方法,创建defaultProps
static defaultProps = {
name: 'React'
}
render(){
return (
<h1>Hello,{this.props.name}</h1>
)
}
}
const Conent = (props)=> {
return (
<p>{props.name}是一个用于构建用户界面的JavaScript库</p>
)
}
// 使用箭头函数创建的组件,需要在这个组件上直接写defaultProps属性
Content.defaultProps = {
name: 'React.js'
}
class App extends Component{
render(){
return(
<Fragment>
{/* 由于设置了defaultProps, 不传props也能正常运行,如果传递了就会覆盖defaultProps的值 */}
<Title />
<Conent />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
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
# props.children
我们知道使用组件的时候,可以嵌套。要在自定义组件的使用嵌套结构,就需要使用 props.children。
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component{
render(){
return (
<h1>Hello,{this.props.children}</h1>
)
}
}
const Conent = (props)=> {
return (
<p>{props.children}</p>
)
}
class App extends Component{
render(){
return(
<Fragment>
<Title>React</Title>
<Conent><strong>React.js</strong>是一个用于构建用户界面的JavaScript库</Conent>
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
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
# prop-types
React 其实是为了构建大型应用程序而生, 在一个大型应用中,根本不知道别人使用你写的组件的时候会传入什么样的参数,有可能会造成应用程序运行不了,但是不报错。为了解决这个问题,React 提供了一种机制,让写组件的人可以给组件的 props 设定参数检查,需要安装和使用 prop-types:
npm i prop-types -S
# State(状态)
提示
组件的 state 是组件内部的状态,state 的变化最终将反映在组件 UI 的变化上。在构造方法 constructor 中通过 this.state 定义组件的初始状态,并调用 this.setState 方法改变组件的状态。
# 定义 state
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class App extends Component{
constructor(){
super()
this.state = {
name: 'React',
bool: true
}
}
render(){
return(
<div>
<h1>Welcome to {this.state.name} Word!</h1>
<button>{this.state.bool ? 'YES' : 'NO'}</button>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
提示
this.props 和 this.state 是纯 js 对象,在 vue 中,data 属性是利用 Object.defineProperty 处理过的,更改 data 的数据的时候会触发数据的 getter 和 setter,但是 React 中没有做这样的处理,如果直接更改的话,react 是无法得知的,所以,需要使用特殊的更改状态的方法 setState。
# setState
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class App extends Component{
constructor(){
super()
this.state = {
name: 'React',
bool: true
}
}
// 添加点击事件
handleBtn = ()=>{
this.setState = ({
bool: !this.state.bool
})
}
render(){
return(
<div>
<h1>Welcome to {this.state.name} Word!</h1>
<button onClick={this.handleBtn}>{this.state.bool ? 'YES' : 'NO'}</button>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
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
setState 有两个参数:
- 参数是对象
this.setState = {
bool: !this.state.bool,
};
2
3
- 参数是方法
注意
注意的是这个方法接收两个参数,第一个是上一次的 state, 第二个是 props setState 是异步的,所以想要获取到最新的 state,没有办法获取,就有了第二个参数,这是一个可选的回调函数
this.setState =
((preState, props) => {
return {
bool: !preState.bool,
};
},
() => {
console.log("callback", this.state.bool);
});
console.log("setState", this.state.bool);
2
3
4
5
6
7
8
9
10
# Props 和 State 对比
都是纯 js 对象,都会触发 render 更新,都具有确定性(状态/属性相同,结果相同)
Props | State |
---|---|
能从父组件获取 | 不能 |
可以由父组件修改 | 不可以 |
能在内部设置默认值 | 不能 |
不在组件内部修改 | 要改 |
能设置子组件初始值 | 不可以 |
可以修改子组件的值 | 不可以 |
主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props,否则组件的 props 永远保持不变。 | 主要作用是用于组件保存、控制、修改自己的可变状态。state 在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制的数据源。state 中状态可以通过 this.setState 方法进行更新,setState 会导致组件的重新渲染。 |
笔记
React 组件是通过 props 和 state 两种数据驱动渲染出组件 UI。Props 是组件对外的接口,它是只读的,组件通过 props 接受外部传入的数据;state 是组件对内的接口,它是可变的,组件内部状态通过 state 来反映。
如果搞不清 state 和 props 的使用场景,记住一个简单的规则:尽量少地用 state,多用 props。 没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
状态提升
- 如果有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理 具体操作可以查看 React 文档:状态提升 (opens new window)
# 生命周期
# React 旧版生命周期
- 第一次渲染 constructor → componentWillMount → render → componentDidMount
- props 更新 componentWillReceiveProps → shouldComponentUpdate(如果返回 true) → componentWillUpdate → render → componentDidUpdate
- state 更新 shouldComponentUpdate (如果返回 true)→ componentWillUpdate → render → componentDidUpdate
- 组件卸载 componentWillUnmount
React 旧版生命周期包含三个过程:
- 挂载过程 constructor() componentWillMount() componentDidMount()
- 更新过程 componentWillReceiveProps(nextProps) shouldComponentUpdate(nextProps,nextState) componentWillUpdate (nextProps,nextState) render() componentDidUpdate(prevProps,prevState)
- 卸载过程 componentWillUnmount()
其具体作用分别为:
- constructor() 完成了 React 数据的初始化。
- componentWillMount() 组件已经完成初始化数据,但是还未渲染 DOM 时执行的逻辑,主要用于服务端渲染。
- componentDidMount() 组件第一次渲染完成时执行的逻辑,此时 DOM 节点已经生成了。
- componentWillReceiveProps(nextProps) 接收父组件新的 props 时,重新渲染组件执行的逻辑。
- shouldComponentUpdate(nextProps, nextState) 在 setState 以后,state 发生变化,组件会进入重新渲染的流程时执行的逻辑。在这个生命周期中 return false 可以阻止组件的更新,主要用于性能优化。
- componentWillUpdate(nextProps, nextState) shouldComponentUpdate 返回 true 以后,组件进入重新渲染的流程时执行的逻辑。
- render() 页面渲染执行的逻辑,render 函数把 jsx 编译为函数并生成虚拟 dom,然后通过其 diff 算法比较更新前后的新旧 DOM 树,并渲染更改后的节点。
- componentDidUpdate(prevProps, prevState) 重新渲染后执行的逻辑。
- componentWillUnmount() 组件的卸载前执行的逻辑,比如进行“清除组件中所有的 setTimeout、setInterval 等计时器”或“移除所有组件中的监听器 removeEventListener”等操作。
# React 新版生命周期
所不同的是少了 componentWillMount, componentWillReceiveProps, componentWillUpdate 这三个鸡肋,其实这三个生命周期回调要直到 v17 才会被完全删除,不过现在都被冠以 UNSAFE 的前缀了。
多了两个是 getDerivedStateFromProps 和 getSnapshotBeforeUpdate,其中 getDerivedStateFromProps 在第一次渲染和更新的时候都会被调用,而 getSnapshotBeforeUpdate 只会在更新的时候被调用。
- 使用 getDerivedStateFromProps(nextProps, prevState)的原因:
笔记
旧的 React 中 componentWillReceiveProps 方法是用来判断前后两个 props 是否相同,如果不同,则将新的 props 更新到相应的 state 上去。在这个过程中我们实际上是可以访问到当前 props 的,这样我们可能会对 this.props 做一些奇奇怪怪的操作,很可能会破坏 state 数据的单一数据源,导致组件状态变得不可预测。
而在 getDerivedStateFromProps 中禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去访问 this.props 并做其他一些让组件自身状态变得更加不可预测的事情。 :::
- 使用 getSnapshotBeforeUpdate(prevProps, prevState)的原因:
笔记
在 React 开启异步渲染模式后,在执行函数时读到的 DOM 元素状态并不总是渲染时相同,这就导致在 componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。
而 getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。 :::
# 事件处理
# 绑定事件
React 元素的事件处理和 DOM 元素很相似,都是采用 on+事件名称的方式绑定事件。但不同的是原生事件采用的是纯小写形式,如:onclick;而 React 的事件使用小驼峰的形式,如:onClick;使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
提示
React 的事件不是原生事件,而是合成事件。
传统 HTML:
<button onclick="clickActive()">BUTTON</button>
React:
<button onClick="{clickActive}">BUTTON</button>
# 事件 handler 的写法
- 直接在 render 里写行内的箭头函数(不推荐)
- 在组件内使用箭头函数定义一个方法(推荐)
- 直接在组件内定义一个非箭头函数的方法,然后在 render 里直接使用 onClick={this.handleClick.bind(this)}(不推荐)
- 直接在组件内定义一个非箭头函数的方法,然后在 constructor 里 bind(this)(推荐)
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 (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "ON" : "OFF"}
</button>
);
}
}
ReactDOM.render(<Toggle />, document.getElementById("root"));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Event 对象
和普通浏览器一样,事件 handler 会被自动传入一个 event 对象,这个对象和普通的浏览器 event 对象所包含的方法和属性都基本一致。不同的是 React 中的 event 对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有 event.stopPropagation、event.preventDefault 这种常用的方法
因此,在 React 中你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault。
<a href="#" onclick="console.log('click'); return false"></a>
function Active() {
function clickLink(e) {
e.preventDefault();
console.log("click");
}
return <a href="#" onClick={clickLink}></a>;
}
2
3
4
5
6
7
8
# 向事件处理程序传递参数
- 在 render 里调用方法的地方包一层箭头函数
- 在 render 里通过 this.handleEvent.bind(this, 参数)这样的方式来传递
- 通过 event 传递
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
2
# 条件渲染
在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。
笔记
React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。
# 条件表达式渲染(适用于两个组件二选一的渲染)
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
# && 操作符渲染 (适用于一个组件有无的渲染)
function Mailbox(props) {
const messages = props.messages;
return (
<div>
<h1>Hello!</h1>
{messages.length > 0 && <h2>You have {messages.length} messages.</h2>}
</div>
);
}
2
3
4
5
6
7
8
9
# 利用变量输出组件渲染 (适用于有多个组件多种条件下的渲染)
render() {
const isLoggedIn = this.state.isLoggedIn;
const button = isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
);
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 利用函数方法输出组件或者利用函数式组件进行渲染 (适用于多个子组件需要根据复杂的条件输出的情况)
# 函数方式
renderButton(){
const isLoggedIn = this.state.isLoggedIn;
if(isLoggedIn)
{
return (<LogoutButton onClick={this.handleLogoutClick} />);
}
else
{
return (<LoginButton onClick={this.handleLoginClick} />);
}
}
render() {
return (
<div>
<Greeting />
{this.renderButton()}
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 函数式组件
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
<Greeting isLoggedIn={false} />,
document.getElementById("root")
);
2
3
4
5
6
7
8
9
10
11
12
# 列表和 key
当列表数据为多组时,调用数据运行时为提示警告:“Each child in an array or iterator should have a unique ‘ key ’ prop ”说明如果没有给每条数据添加一个“key” 的话,当数据组数过多时,不同组数有相同的数据,调用就可能会出现错误。因此这里的“key”就相当于数据库里的主键,给每组数据调取都有一个参照。
function ListItem(props) {
// 正确!这里不需要指定 key:
return <li>{props.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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
笔记
- key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
- 虽然列表元素的 key 不能重复,但这个唯一性仅限至于在当前列表中,而不是全局唯一。
- 不推荐使用索引作为 key,因为一旦列表中的数据发生重排,数据的索引也会发生变化,不利于 React 的渲染优化。
# 表单
# 受控组件
什么受控组件?简单来说就是如果一个表单元素的值是由 React 来管理的,那它就是一个受控组件。
贴一段 React 官网的解释:
import React, { Component } from "react";
class Inputs extends Component {
constructor(props) {
super(props);
this.state = {
name: "",
password: "",
};
}
handleChange(event) {
const e = event.target;
this.setState({
[e.name]: e.value,
});
}
handleSubmit(event) {
console.log("Login Success!");
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
用户名:
<input
type="text"
name="name"
value={this.state.name}
onChange={this.handleChange}
/>
</label>
<label>
密码:
<input
type="password"
name="password"
value={this.state.password}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="登录" />
</form>
);
}
}
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
笔记
用户名和密码两个表单元素的值是从组件的 state 中获取的,当用户更改表单元素的值时,onChange 事件会被触发,对应的 handleChange 处理函数会把变化同步到组件的 state,新的 state 又会触发表单元素重新渲染,从而实现表单元素状态的控制。
# 总结
提示
本章主要介绍了 React 的主要特性及其用法。React 通过 JSX 语法声明界面 UI,将界面 UI 和它的逻辑封装在同一个 JS 文件中。通过虚拟 DOM 和 diffing 算法来减少与真实 DOM 的交互。组件是 React 的核心,根据组件的外部接口 props 和内部接口 state 完成自身的渲染。使用组件需要理解它的生命周期,借助不同的生命周期方法,组件可以实现复杂逻辑。渲染时,注意 key 的使用,事件处理时,注意事件名称和事件处理函数的写法。最后介绍表单元素的用法,使用方式分受控组价和非受控组件。