React 的特点

  1. 声明式编码
  2. 组件化编码
  3. React Native 编写原生应用
  4. 高效(优秀的Diffing算法)

React 为什么高效?

  1. 使用了虚拟 DOM,不总是直接操作页面真是 DOM
  2. DOM Diffing 算法,最小化页面重绘

虚拟 DOM 与真实 DOM

虚拟 DOM 本质上是一个 Object 对象,最终会被 React 转换为真实 DOM,渲染到界面上。

虚拟 DOM 比较轻量。

两种方式创建虚拟 DOM

使用原生 JS(不推荐):

1
const VDOM = React.createElement('h1', {id: 'title'}, 'hello world')

使用 JSX(会通过 Babel 转化为 JS):

1
const VDOM = <h1 id='title'>hello world</h1>

渲染虚拟 DOM

将虚拟 DOM 渲染到页面中的真实容器 DOM 里

1
React.render(VDOM, containerDOM)

JSX 语法规则

  • 定义虚拟DOM时,不用写引号
  • 标签中混入JS表达式时要用{}
  • 样式的类名指定要用 className
  • 内联样式要用 style = {{key:value}} 的形式,使用驼峰命名法
  • 虚拟 DOM 只能有一个根标签
  • 标签必须闭合
  • 标签首字母
    • 小写字母开头,则将该标签转为 html 中的同名元素
    • 大写字母开头,React 渲染对应的组件

面向组件编程

函数式组件

1
2
3
4
5
function Demo() {
return <h2>这是函数式组件</h2>
}
// 注意 render 传的是组件标签
ReactDOM.render(<Demo/>, document.getElementById("test"));

类式组件

类式组件必须继承 React.Component ,且具有 render() 方法

1
2
3
4
5
6
class Demo extends React.Component {
render() {
return <h2>这是类式组件</h2>
}
}
ReactDOM.render(<Demo/>, document.getElementById("test"));

原理: 当执行 ReactDOM.render(<Demo/>.......) 之后,React 解析组件标签,找到了 Demo 组件。发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法。将 render 返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中。

组件实例的三大核心属性

state

state 属性用来表示组件的状态

state 属性只能通过 setState 函数更改,会与原先的 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
class Weather extends React.Component {
constructor(props) {
super(props);
//    初始化 state
this.state = { isHot: true };
this.change = this.change.bind(this);
}
render() {
const { isHot } = this.state;
return (
//  onClick 绑定事件,注意要使用驼峰命名法
<h2 onClick={this.change}>
今天天气:{this.state.isHot ? "hot" : "cold"}
</h2>
);
}
change() {
//  change 方法放在 Weather 的原型对象上
const isHot = this.state.isHot;
this.setState({ isHot: !isHot });
}
}
ReactDOM.render(<Weather />, document.getElementById("test"));

构造器中的 this 指向当前类的示例对象,render 方法通过类的实例调用,方法中的 this 也只想当前类的实例对象。

change 方法作为 onClick 的回调,并不是由类的实例调用,是直接调用。类中定义的方法自动开启了局部严格模式,故 thisundefined

可以通过 bind 函数将当前实例对象绑定到 change

state 的简写方式(推荐)

一般类组件中的自定义方法(除了 render)是作为回调函数调用的,可以将方法写成赋值语句+箭头函数的形式。箭头函数会找它外层函数的 this 作为它本身的 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Weather extends React.Component {
// 初始化状态
state = {isHot: true}

render() {
const { isHot } = this.state;
return (
<h2 onClick={this.change}>
今天天气:{this.state.isHot ? "hot" : "cold"}
</h2>
);
}

change = () => {
const isHot = this.state.isHot;
this.setState({ isHot: !isHot });
}
}
ReactDOM.render(<Weather />, document.getElementById("test"));

props

用于向组件传递数据,是只读的。

类式组件使用 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
class Person extends React.Component {
render() {
const { name, age, sex } = this.props;
return (
<ul>
<li>name: {name}</li>
<li>sex: {sex}</li>
<li>age: {age}</li>
</ul>
);
}
//  使用 static,给类添加属性
static propTypes = {
name: PropTypes.string.isRequired, //  isRequired: 必需
age: PropTypes.number,
sex: PropTypes.string,
speak: PropTypes.func, // func: 函数
};
//  设定默认值
static defaultProps = {
name: "ikun",
};
}

ReactDOM.render(<Person sex="0" age={20}/>,
document.getElementById("test"));

类式组件的构造器是否接收 props,是否传递给 super,取决于是否希望在构造器中通过 this 访问 props。一般情况下都不写构造器。

函数式组件使用 props

函数式组件通过接收参数来传递 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
function Person(props) {
const { name, sex, age } = props;
return (
<ul>
<li>name: {name}</li>
<li>sex: {sex}</li>
<li>age: {age}</li>
</ul>
);
}
//  给组件添加属性
Person.propTypes = {
name: PropTypes.string.isRequired, //  必需
age: PropTypes.number,
sex: PropTypes.string,
speak: PropTypes.func,
};
//  设定默认值
Person.defaultProps = {
name: "ikun",
};

ReactDOM.render(<Person sex="0" age={20} />,
document.getElementById("test"));

refs 与事件处理

ref 用来标识某个标签,相当于原生标签中的 id,可以通过 this.refs 来获取到对应的真实 DOM

字符串形式的 ref

存在效率问题,不推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Demo extends React.Component {
show = () => {
const {input} = this.refs;
alert(input.value);
};

render() {
return (
<div>
<input ref="input" type="text" />
&nbsp;
<button onClick={this.show}>click</button>
</div>
);
}
}
回调形式的 ref

ref 传入一个回调函数,回调函数的参数就是当前 DOM 节点,React 会自动调用该回调函数,将该节点挂载到实例对象上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Demo extends React.Component {
show = () => {
const {input} = this;
alert(input.value);
};

render() {
return (
<div>
<input ref={c => this.input = c } type="text" />
&nbsp;
<button onClick={this.show}>click</button>
</div>
);
}
}

关于回调 refs 的说明
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

使用 createRef

使用 React.createRef API 来创建一个容器,该容器可以存储被 ref 所标识的节点,一个容器只能放一个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Demo extends React.Component {
myRef = React.createRef();

show = () => {
alert(this.myRef.current.value);
};

render() {
return (
<div>
<input ref={this.myRef} type="text" />
&nbsp;
<button onClick={this.show}>click</button>
</div>
);
}
}
注意事项

避免过度使用 ref,建议使用受控组件,类似 Vue 中的双向绑定。

1
2
3
4
5
6
7
8
9
10
11
class Demo extends React.Component {
state = { info: "" };

saveInfo = (e) => {
this.setState({info: e.target.value})
}
// 通过onChange 将数据及时更新到 state 中
render() {
return <input onChange={this.saveInfo} type="text" />;
}
}

生命周期

React 组件中包含一系列生命周期回调函数,会在特定的时刻调用

image.png

static getDerivedStateFromProps

1
static getDerivedStateFromProps(props, state)

props 中获得派生的状态,适用于 state 值在任何时候都取决于 props一般不建议使用

getSnapshotBeforeUpdate

1
getSnapshotBeforeUpdate(prevProps, prevState)

在最近一次渲染输出之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息。其返回值将作为参数传递给 componentDidUpdate()

DOM 的 Diffing 算法

在虚拟 DOM 渲染到页面上成为真实 DOM 之前会与旧的虚拟 DOM 进行对比,如果没有差异,就不会更新原先的真实 DOM,反之将虚拟 DOM 重新渲染到页面上。

虚拟 DOM 中的 key 的作用

key 是虚拟 DOM 对象的标识

当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟 DOM】,随后React 进行【新虚拟 DOM】与【旧虚拟 DOM】的 diff 比较

  • 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key
    • 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
    • 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
  • 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key,根据数据创建新的真实 DOM,随后渲染到到页面

用 index 作为 key 可能会引发的问题:

  • 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 更新。虽然界面效果没问题, 但效率低。
  • 如果结构中还包含输入类的 DOM,会产生错误 DOM 更新,界面有问题。
  • 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。